Skip to content

feat(desktop): transcript editing - soft delete with ability to restore, deleted words buffers, pause deletion - #1880

Closed
ganganimaulik wants to merge 12 commits into
CapSoftware:mainfrom
ganganimaulik:feat/transcript-editing-buffers
Closed

ganganimaulik wants to merge 12 commits into
CapSoftware:mainfrom
ganganimaulik:feat/transcript-editing-buffers

Conversation

@ganganimaulik

@ganganimaulik ganganimaulik commented Jun 1, 2026 •

Copy link
Copy Markdown

Overview

Screenshot 2026-06-01 at 4 18 32 PM Screenshot 2026-06-01 at 4 18 58 PM

right click on deleted word:
Screenshot 2026-06-01 at 4 19 14 PM

This pull request implements comprehensive transcript editing tools for the Cap desktop editor, introducing word/pause deletion, adjustable speech buffers, timeline-wide ripple editing, and an automated "Auto Clean" feature with batch undo/redo.


Key Features

  1. Soft Word Deletion & Restoration

    • Soft-deletes words (deleted: true) rather than splicing them out.
    • Deleting words triggers a ripple cut across all timeline tracks (captions, zoom, mask, text, and keyboard events).
    • Restoring a word shifts the timeline rightward to cleanly re-insert it.
    • Deleted words are filtered out of the visual captions text and the Rust rendering pipeline.
  2. Adjustable Word Buffers (Buffer Popover)

    • Allows users to configure a custom bufferStart and bufferEnd (from -0.5s to 1.0s) around any deleted word by right-clicking it.
    • Modifying these buffers shifts the surrounding timeline tracks dynamically, ensuring word transitions do not cut off trailing/leading syllables.
  3. Pause Detection & Deletion

    • Automatically detects silence gaps between words and renders them as interactive inline pause indicators (e.g., ⏸ 1.2s).
    • Allows deleting pauses directly from the transcript UI, which performs a ripple deletion of that pause duration across the timeline.
  4. Auto-Clean with Batch Undo

    • Automated "Auto Clean" feature that scans the transcript for filler words (uh, um, ah, er, hmm, mhm) and pauses exceeding a configurable silence threshold.
    • Single-click cleans all fillers and long pauses.
    • Full support for batch undo to instantly restore all auto-cleaned items.

RetriggerView in GreptileConfidence Score: 4/5

Fix pause detection before merging so Auto Clean does not remove audible speech missed by transcription.

Findings

  1. P1 Auto Clean cuts missed speech ▶
Fix with agent prompt
### Issue 1
apps/desktop/src/routes/editor/transcript-cuts.ts:1605-1609
`buildTranscriptItems` treats the whole gap between words as silence, even when the loaded waveform contains speech there. If transcription misses speech between words ending at 1.4s and starting at 4s, Auto Clean cuts that entire gap and removes the spoken words from the video.

`audioSilence` only adjusts the neighboring word edges; it never checks the audio inside the gap. When waveforms are available, limit pause cuts to ranges that are actually quiet.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

Adds restorable transcript cuts, adjustable cut padding, pause controls, and Auto Clean.

  • The transcript editor cuts words and pauses and lets people restore them.
  • Parakeet word timings now stop closer to the end of speech.

The previous-review ancestry check passed, but the merge bases differed, so this review used the full PR diff. No supplied root threads had Comment numbers.

Reviews (3) · Last reviewed commit: "fix: keep transcript cut restores exact ..." · Reviewed by Greptile

@ganganimaulik
ganganimaulik marked this pull request as ready for review June 1, 2026 15:03
Comment thread apps/desktop/src/routes/editor/TranscriptPage.tsx Outdated
Comment thread apps/desktop/src/routes/editor/TranscriptPage.tsx Outdated
Comment thread apps/desktop/src/routes/editor/TranscriptPage.tsx Outdated
Comment thread apps/desktop/src-tauri/src/captions.rs
@didierlaket

Copy link
Copy Markdown

When will this feature be available?

@richiemcilroy

Copy link
Copy Markdown
Member

Thanks @ganganimaulik! Greptile is at 4/5 here. We only merge once Greptile reaches 5/5, so please address its findings in the summary above (or reply to them if you think one is wrong), then comment @greptileai review to re-run it. If the branch is behind main, please merge or rebase on main first. Once it is 5/5 we will run the full verification suite on it.

@ganganimaulik
ganganimaulik force-pushed the feat/transcript-editing-buffers branch from f27285b to 570b8db Compare October 19, 2026 13:50
@vercel

vercel Bot commented Oct 19, 2026

Copy link
Copy Markdown
Contributor

@ganganimaulik is attempting to deploy a commit to the Cap Software Inc Team on Vercel.

A member of the Team first needs to authorize it.

@didierlaket

didierlaket commented Oct 19, 2026 •

Copy link
Copy Markdown

@richiemcilroy I realize the Cap team has plenty on its plate, but this feature could be a major differentiator for Cap compared with competing tools. I currently use Tella, but I’m not entirely happy with their implementation and think it could be better. If Cap releases this feature and it works smoothly, I’d become a subscriber without hesitation! I understand that @ganganimaulik is the one behind this PR, but I wanted to emphasize just how valuable this feature would be!

parakeet-rs 0.3.4 ends every token where the next one starts, so each
word's stored end ran through the pause after it and captions stayed on
screen up to MAX_CAPTION_WORD_DURATION into every pause.

Add cap_audio::SpeechPauses, which finds pauses in the 16 kHz
transcription audio relative to the clip's noise floor and speech level,
and use it in the Tauri and GPUI Parakeet paths to end each word 0.1 s
after its speech stops. Word starts never move, timings are kept when
speech isn't clearly above the noise, and the 2.5 s clamp stays as the
backstop. Whisper output is unchanged, and existing transcriptions keep
their timings until they are generated again.
@ganganimaulik

Copy link
Copy Markdown
Author

@greptileai review

Comment thread apps/desktop/src/routes/editor/transcript-cuts.ts
Comment thread apps/desktop/src/routes/editor/transcript-cuts.ts
@greptile-apps

greptile-apps Bot commented Oct 19, 2026

Copy link
Copy Markdown
Contributor

Comments Outside Diff

These findings could not be posted inline.

  • P2 Incorrect deletedDurationInGap formula for non-zero buffers apps/desktop/src/routes/editor/TranscriptPage.tsx:254 ▶

    protectedStart = Math.max(0, -bufStart) only contributes when bufStart is negative (i.e., the cut is smaller than the word). For the common positive-buffer case - where the actual cut extends beyond the word - nothing is added, so deletedDurationInGap undercounts by bufferStart + bufferEnd. This means silentGap = gap - deletedDurationInGap ends up larger than the real remaining silence, causing false pause badges to appear between deleted words whenever a user has adjusted buffers to positive values.

    The effective deleted duration is curr.storedEnd + bufEnd - Math.max(0, curr.start - bufStart), which should replace the current formula.

A saved cut piece could only be restored whole, so shrinking a cut (or
restoring one of two overlapping cuts) brought the returned footage back
plain and then dropped the piece. Overlays and fullscreen text holds in
the returned part never came back, and the ones still cut were lost on a
later restore.

When part of a piece returns, restore the whole piece and cut the rest
again, which saves a fresh piece for what stays cut.
A cut saves the overlays it changed as whole before/after snapshots, so
they only restore exactly in reverse removal order. Restoring a cut while
a later one that edited the same overlay stayed cut skipped the snapshot,
and the earlier cut's changes (mask and camera keyframes, a zoom's
trimmed edge) were lost.

Restoring a piece now also restores any later piece still cut that edited
the same overlay, then cuts it again so it saves a fresh snapshot.

Trims also leave overlay edges on a cut point through a different float
path than the boundary a later cut or restore computes, so an edge could
land a hair on the wrong side: a restore stretched the overlay instead of
moving it, and a cut left a zero-length sliver. Edges within 1e-9 s of
the boundary now snap onto it first.
…ilence

Two cut words whose padding grows into the silence between them leave no
pause behind: the transcript only offers silence that is still in the
timeline, so the padded claims remove it from the pause list.
Restoring a saved piece puts the whole piece back and cuts the rest of it
again. On a sped-up clip a sliver of that rest can be too short on the
timeline for the cut to remove (under 1 ms there) while still counting as
more than 1 ms of footage, so the check that compared it in source time
refused the restore with "That cut would remove the whole video."

Measure what a cut leaves behind on the timeline, where the cut itself
skips it, and stop recording a sliver that stays in the timeline as
removed.
A fullscreen text hold that sits right where a cut ends, like a closing
title after a cut that reaches the end of the clips, stays with what
follows the cut. Restoring the cut put the footage back after the hold
instead of ahead of it, so the title played before the restored words,
and the cut's saved overlays no longer lined up and stayed trimmed.

Cut pieces now record that held time (heldAfter), and restoring puts the
footage back ahead of it.

Holds are stored in output time but sit at a point in recording time, so
one meant to sit at a cut point can land a hair before or after it, and a
closing title a hair before the clips' end was swallowed by the cut.
Holds within 1e-9 s of a cut point are now snapped onto it before a cut
or a restore.
Restore keyboard segments a cut deleted one cut at a time, take the
sub-millisecond leftover a cut drops at a clip edge, give restored words
their footage and trimmed length back, stop padding at kept words that
overlap a cut, end pauses where audio resumes, find pauses after stretched
words between waveform blocks, keep "err" and acronyms out of fillers, and
reuse the clip transition geometry check.
@ganganimaulik

Copy link
Copy Markdown
Author

@greptileai review

Comment on lines +1605 to +1609
const pauses =
gap.end - gap.start >= threshold
? clipToRange(silence, gap).filter(
(piece) => piece.end - piece.start >= threshold,
)

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.

P1 Auto Clean cuts missed speech

buildTranscriptItems treats the whole gap between words as silence, even when the loaded waveform contains speech there. If transcription misses speech between words ending at 1.4s and starting at 4s, Auto Clean cuts that entire gap and removes the spoken words from the video.

audioSilence only adjusts the neighboring word edges; it never checks the audio inside the gap. When waveforms are available, limit pause cuts to ranges that are actually quiet.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/routes/editor/transcript-cuts.ts
Line: 1605-1609

Comment:
**Auto Clean cuts missed speech**

`buildTranscriptItems` treats the whole gap between words as silence, even when the loaded waveform contains speech there. If transcription misses speech between words ending at 1.4s and starting at 4s, Auto Clean cuts that entire gap and removes the spoken words from the video.

`audioSilence` only adjusts the neighboring word edges; it never checks the audio inside the gap. When waveforms are available, limit pause cuts to ranges that are actually quiet.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

fixed 8bb8407

Pauses came from the gaps between transcript words, so when transcription
missed speech, the whole gap still read as silence even with the waveform
loaded. Auto Clean cut it as a long pause, and a filler cut took the gap
beside it, removing the speech either way.

The recording's waveform analysis now reports where it is audible as well
as where it is quiet. Pauses leave out audible audio, and a cut word only
takes the quiet on its own side of audio heard in a gap, unless that audio
runs into the word itself. Time without a waveform is neither, so pauses
there still follow the transcript.
@ganganimaulik

Copy link
Copy Markdown
Author

@richiemcilroy should integrate CrisperWhisper for this later. dont know if it can run locally like parakeet. this pr is only tested with parakeet. it also doesn't generate captions if video is longer than 7-8 mins. will need to split and generate captions for that. some changes in pr are related to how parakeet works, might not work same with other models.

@richiemcilroy

Copy link
Copy Markdown
Member

Thanks so much for this contribution, @ganganimaulik! Cap's desktop app has now fully moved from Tauri to our new native GPUI app (apps/desktop-gpui), which ships as Cap from 0.6.1. Thanks for this. The transcript editor is built in the retired Tauri/Solid app. GPUI (apps/desktop-gpui) has no transcript page yet, so this can't land as is, but the SpeechPauses word-end trimming would be welcome as a small GPUI-only PR. If you'd like to bring this to the GPUI app, we'd love a PR against apps/desktop-gpui. Really appreciate the time you put into this.

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.

3 participants