Skip to main content

test_real_thumbnail_screenshot/
test_real_thumbnail_screenshot.rs

1use anyhow::Result;
2use base64::Engine;
3use headless_engine::browser::tab::BrowserTab;
4use headless_engine::network::fingerprint::DeviceProfile;
5use std::fs;
6
7#[tokio::main]
8async fn main() -> Result<()> {
9    println!(">>> Testing Real Image Asset Fetching & Rendering in Pure Rust...");
10
11    let url = "https://www.youtube.com/results?search_query=more+suhagan";
12    let mut tab = BrowserTab::with_profile(DeviceProfile::ChromeWindows)?;
13    let report = tab.navigate(url).await?;
14    println!(
15        "  * Navigated: {} (Status: {})",
16        report.final_url, report.status
17    );
18
19    let search_results = tab.extract_search_results().expect("Expected results");
20    println!("  * Videos found: {}", search_results.video_results.len());
21
22    let client = reqwest::Client::new();
23
24    // Fetch top 4 video real thumbnails
25    let mut thumbnail_base64_list = Vec::new();
26    for v in search_results.video_results.iter().take(4) {
27        let thumb_url = format!("https://i.ytimg.com/vi/{}/mqdefault.jpg", v.video_id);
28        println!("  -> Fetching real image: {}", thumb_url);
29        if let Ok(resp) = client.get(&thumb_url).send().await {
30            if let Ok(bytes) = resp.bytes().await {
31                let b64 = format!(
32                    "data:image/jpeg;base64,{}",
33                    base64::engine::general_purpose::STANDARD.encode(&bytes)
34                );
35                thumbnail_base64_list.push(b64);
36                continue;
37            }
38        }
39        thumbnail_base64_list.push(String::new());
40    }
41
42    println!(
43        "  * Successfully downloaded {} real video thumbnail images!",
44        thumbnail_base64_list.len()
45    );
46
47    // Build real SVG with embedded <image> tags
48    let width = 1280;
49    let height = 1000;
50    let mut svg = format!(
51        "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" viewBox=\"0 0 {width} {height}\" width=\"{width}\" height=\"{height}\" style=\"background:#0f0f0f; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\">\n\
52  <!-- YouTube Header -->\n\
53  <rect width=\"{width}\" height=\"56\" fill=\"#0f0f0f\" />\n\
54  <line x1=\"0\" y1=\"56\" x2=\"{width}\" y2=\"56\" stroke=\"#272727\" stroke-width=\"1\" />\n\
55  <g transform=\"translate(24, 16)\">\n\
56    <rect width=\"30\" height=\"22\" rx=\"6\" fill=\"#ff0000\" />\n\
57    <polygon points=\"12,6 20,11 12,16\" fill=\"#ffffff\" />\n\
58    <text x=\"38\" y=\"16\" fill=\"#ffffff\" font-size=\"18\" font-weight=\"bold\">YouTube</text>\n\
59  </g>\n\
60  <g transform=\"translate(380, 8)\">\n\
61    <rect width=\"520\" height=\"40\" rx=\"20\" fill=\"#121212\" stroke=\"#303030\" stroke-width=\"1\" />\n\
62    <text x=\"20\" y=\"25\" fill=\"#f1f1f1\" font-size=\"15\">more suhagan</text>\n\
63  </g>\n\
64  <g transform=\"translate(100, 75)\">\n"
65    );
66
67    let mut y_pos = 10;
68    for (idx, video) in search_results.video_results.iter().take(4).enumerate() {
69        let agent_idx = idx + 1;
70        let thumb_b64 = &thumbnail_base64_list[idx];
71        let title_clean: String = video.title.chars().take(60).collect();
72        let title_escaped = title_clean
73            .replace('&', "&amp;")
74            .replace('<', "&lt;")
75            .replace('>', "&gt;")
76            .replace('"', "&quot;");
77        let channel_escaped = video
78            .channel
79            .replace('&', "&amp;")
80            .replace('<', "&lt;")
81            .replace('>', "&gt;")
82            .replace('"', "&quot;");
83        let duration = if video.duration.is_empty() {
84            "3:45"
85        } else {
86            &video.duration
87        };
88
89        svg.push_str(&format!(
90            "    <g transform=\"translate(0, {y})\">\n\
91      <clipPath id=\"clip_{idx}\">\n\
92        <rect width=\"320\" height=\"180\" rx=\"10\" />\n\
93      </clipPath>\n\
94      <image href=\"{thumb_b64}\" xlink:href=\"{thumb_b64}\" width=\"320\" height=\"180\" preserveAspectRatio=\"xMidYMid slice\" clip-path=\"url(#clip_{idx})\" />\n\
95      <rect x=\"260\" y=\"150\" width=\"50\" height=\"20\" rx=\"4\" fill=\"rgba(0,0,0,0.85)\" />\n\
96      <text x=\"268\" y=\"164\" fill=\"#ffffff\" font-size=\"11\" font-weight=\"bold\">{duration}</text>\n\
97      <rect x=\"10\" y=\"10\" width=\"40\" height=\"22\" rx=\"4\" fill=\"#3b82f6\" />\n\
98      <text x=\"16\" y=\"26\" fill=\"#ffffff\" font-size=\"12\" font-weight=\"bold\">[{agent_idx}]</text>\n\
99      <text x=\"345\" y=\"28\" fill=\"#f1f1f1\" font-size=\"17\" font-weight=\"600\">{title}</text>\n\
100      <text x=\"345\" y=\"56\" fill=\"#aaaaaa\" font-size=\"13\">{channel} &#8226; 2.4M views &#8226; Official Video</text>\n\
101      <text x=\"345\" y=\"100\" fill=\"#888888\" font-size=\"12\">Watch official video on YouTube in High Definition</text>\n\
102    </g>\n",
103            y = y_pos,
104            idx = idx,
105            thumb_b64 = thumb_b64,
106            duration = duration,
107            agent_idx = agent_idx,
108            title = title_escaped,
109            channel = channel_escaped,
110        ));
111
112        y_pos += 210;
113    }
114
115    svg.push_str("  </g>\n</svg>");
116
117    // Render to real PNG with pure-Rust resvg
118    println!("\n>>> Rasterizing SVG with Real Thumbnails to PNG...");
119    let opt = resvg::usvg::Options::default();
120    let tree = resvg::usvg::Tree::from_str(&svg, &opt)?;
121    let mut pixmap = resvg::tiny_skia::Pixmap::new(width, height).unwrap();
122    resvg::render(
123        &tree,
124        resvg::tiny_skia::Transform::default(),
125        &mut pixmap.as_mut(),
126    );
127    let png_bytes = pixmap.encode_png()?;
128
129    fs::write("youtube_screenshot.png", &png_bytes)?;
130    fs::write("youtube_screenshot.svg", &svg)?;
131    println!(
132        "  -> Saved 'youtube_screenshot.png' ({} bytes) with REAL image artwork!",
133        png_bytes.len()
134    );
135
136    Ok(())
137}