feat: restore original YouTube titles with character transitions

This commit is contained in:
2026-10-19 18:42:19 +02:00
parent 78b0c92e9b
commit a44d95bb71
6 changed files with 474 additions and 2 deletions
+29 -2
View File
@@ -122,6 +122,7 @@ mod keys;
mod miruro_browser;
mod miruro_ui;
mod thumbnail;
mod titles;
mod ui;
mod video_state;
mod youtube;
@@ -939,6 +940,7 @@ struct App {
preview: Option<PreviewMode>,
help_popup: Option<HelpPopup>,
thumbnail: thumbnail::Thumbnail,
titles: titles::Titles,
description_job: Option<(String, Job)>,
description_error: Option<String>,
// ponytail: 64-entry linear lookup; use a keyed LRU if the buffer grows.
@@ -998,6 +1000,7 @@ impl App {
preview: None,
help_popup: None,
thumbnail: thumbnail::Thumbnail::new(),
titles: titles::Titles::default(),
description_job: None,
description_error: None,
descriptions: std::collections::VecDeque::with_capacity(DESCRIPTION_CACHE_SIZE),
@@ -2926,6 +2929,10 @@ impl App {
}
fn draw(&mut self, frame: &mut Frame, elapsed: Duration) {
self.draw_at(frame, elapsed, Instant::now());
}
fn draw_at(&mut self, frame: &mut Frame, elapsed: Duration, now: Instant) {
let spinner = ui::spinner(elapsed);
let area = frame.area();
let mut tab_lines = vec![Line::default()];
@@ -2979,6 +2986,25 @@ impl App {
self.mode == Mode::Insert && self.category_dialog.is_none(),
);
let view = &mut self.views[self.view_index];
let rows = usize::from(results.height.saturating_sub(2) / 2);
let mut offset = view
.selection
.offset()
.min(view.videos.len().saturating_sub(rows));
if let Some(selected) = view
.selection
.selected()
.filter(|&index| index < view.videos.len())
{
if selected < offset {
offset = selected;
} else if rows > 0 && selected >= offset + rows {
offset = selected + 1 - rows;
}
}
*view.selection.offset_mut() = offset;
self.titles
.update(&mut view.videos, offset..offset.saturating_add(rows), now);
let table = Table::new(
view.videos.iter().map(|video| {
let tagged = self
@@ -2986,7 +3012,7 @@ impl App {
.categories
.values()
.any(|channels| video_in_channels(video, channels, &self.config));
let title = Span::raw(video.title.as_str());
let title = Span::raw(self.titles.title(video));
let channel = Span::raw(video.channel.as_deref().unwrap_or("Unknown"));
Row::new([
Cell::from(if self.video_state.is_watched(&video.id) {
@@ -3555,7 +3581,8 @@ impl App {
loop {
self.update();
terminal.draw(|frame| self.draw(frame, started.elapsed()))?;
if event::poll(Duration::from_millis(100))? {
let refresh = Duration::from_millis(if self.titles.active() { 20 } else { 100 });
if event::poll(refresh)? {
match event::read()? {
Event::Key(key) if key.kind != KeyEventKind::Release => {
if self.key(key) {