diff --git a/changes/media-timing-review.md b/changes/media-timing-review.md index 3ee86c74..f4947b0b 100644 --- a/changes/media-timing-review.md +++ b/changes/media-timing-review.md @@ -1,4 +1,4 @@ type: added area: mining -- Added optional pre-generation timing review for word, sentence, and audio cards with a speech-weighted waveform you can drag to trim or slide the clip, keyboard nudging, audio preview with a sweeping playhead, exact screenshot and AVIF timing, and explicit cancellation choices. +- Added optional pre-generation timing review for word, sentence, and audio cards with a compact speech-weighted waveform, clearly labeled mined-line boundaries, drag and keyboard adjustments, audio preview with a sweeping playhead, exact screenshot and AVIF timing, and explicit cancellation choices. diff --git a/docs-site/anki-integration.md b/docs-site/anki-integration.md index 97eb7dfc..b86925f9 100644 --- a/docs-site/anki-integration.md +++ b/docs-site/anki-integration.md @@ -179,7 +179,7 @@ Output format: MP3 at 44100 Hz. If the video has multiple audio streams, SubMine The audio is uploaded to Anki's media folder and inserted as `[sound:audio_.mp3]`. -Set `media.reviewTiming` to `true` to pause playback and review each word, sentence, or audio card before its media is generated. The review opens with the subtitle range plus configured audio padding. Drag either edge of the clip to trim it, drag the middle to slide it without changing its length, or press anywhere else on the waveform to snap the nearer edge there. A focused edge also moves with the arrow keys, by 100 ms alone or 500 ms with Shift, and the 100 ms buttons do the same. Space previews the selection with a playhead that sweeps the clip, Enter confirms, and Escape cancels; buttons reveal another five seconds before or after the visible timeline. A speech-weighted waveform and markers for the original subtitle timing help locate sentence boundaries. SubMiner uses a center channel when one carries dialogue, then falls back to a speech-band mono mix. Waveform analysis failure leaves the timing controls available. The confirmed range is exact: SubMiner does not apply audio padding a second time. Static screenshots use its midpoint, and animated AVIF clips use the full confirmed range. +Set `media.reviewTiming` to `true` to pause playback and review each word, sentence, or audio card before its media is generated. The review opens with the subtitle range plus configured audio padding. Drag either edge of the clip to trim it, drag the middle to slide it without changing its length, or press anywhere else on the waveform to snap the nearer edge there. A focused edge also moves with the arrow keys, by 100 ms alone or 500 ms with Shift, and the 100 ms buttons do the same. Space previews the selection with a playhead that sweeps the clip, Enter confirms, and Escape cancels; buttons reveal another five seconds before or after the visible timeline. A speech-weighted waveform shows the mined subtitle as a tinted band with labeled line-start and line-end rails, making adjacent dialogue easier to distinguish. SubMiner uses a center channel when one carries dialogue, then falls back to a speech-band mono mix. Waveform analysis failure leaves the timing controls available. The confirmed range is exact: SubMiner does not apply audio padding a second time. Static screenshots use its midpoint, and animated AVIF clips use the full confirmed range. Canceling the review lets you keep editing, finish with the original timing, or discard the card. Discard deletes an existing Yomitan or audio card and skips creation for a direct sentence card. Clipboard updates and stats-dashboard mining do not open timing review. Audio preview failure does not block confirmation or card creation. The option is disabled by default and hot-reloads. diff --git a/src/renderer/index.html b/src/renderer/index.html index ff17628c..12b01948 100644 --- a/src/renderer/index.html +++ b/src/renderer/index.html @@ -265,9 +265,12 @@ + > + + +
div + div { @@ -1492,7 +1489,7 @@ body:focus-visible, } .media-timing-review-timeline-shell { - padding: 12px 14px 10px; + padding: 10px 12px 8px; border: 1px solid var(--ctp-surface1); border-radius: 14px; background: var(--ctp-mantle); @@ -1524,7 +1521,7 @@ body:focus-visible, --playhead-duration: 1s; position: relative; - height: 96px; + height: 76px; margin: 8px 0 9px; overflow: hidden; border: 1px solid var(--ctp-surface2); @@ -1566,35 +1563,63 @@ body:focus-visible, stop-color: var(--ctp-blue); } -/* Sits above the dimming scrim so the subtitle bounds stay readable outside the selection. */ +/* Sits above the selection scrim so adjacent dialogue reads as outside the mined subtitle. */ .media-timing-review-original-range { position: absolute; z-index: 4; - top: 3px; - bottom: 3px; + top: 0; + bottom: 0; left: var(--original-start); right: calc(100% - var(--original-end)); min-width: 1px; - border-inline: 1px dashed var(--ctp-peach); + background: color-mix(in srgb, var(--ctp-peach) 11%, transparent); + box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--ctp-peach) 28%, transparent); pointer-events: none; } -.media-timing-review-original-range::before { +.media-timing-review-original-boundary { position: absolute; top: 0; + bottom: 0; + width: 2px; + background: var(--ctp-peach); + box-shadow: 0 0 8px color-mix(in srgb, var(--ctp-peach) 45%, transparent); +} + +.media-timing-review-original-boundary.is-start { left: 0; - padding: 2px 4px; - border-radius: 0 0 5px; - background: color-mix(in srgb, var(--ctp-crust) 72%, transparent); - content: 'subtitle'; - color: var(--ctp-peach); +} + +.media-timing-review-original-boundary.is-end { + right: 0; +} + +.media-timing-review-original-boundary::after { + position: absolute; + padding: 3px 5px; + border-radius: 4px; + background: var(--ctp-peach); + color: var(--ctp-crust); font-size: 8px; - font-weight: 760; - letter-spacing: 0.08em; + font-weight: 800; + letter-spacing: 0.06em; line-height: 1; + white-space: nowrap; text-transform: uppercase; } +.media-timing-review-original-boundary.is-start::after { + top: 5px; + left: 5px; + content: 'line start'; +} + +.media-timing-review-original-boundary.is-end::after { + right: 5px; + bottom: 5px; + content: 'line end'; +} + .media-timing-review-track.is-loading::after { position: absolute; z-index: 2;