atmos/os_lib/web_engine/
youtube.rs1#![allow(dead_code)]
3
4extern crate alloc;
5use alloc::format;
6use alloc::string::{String, ToString};
7
8#[derive(Debug, Clone, PartialEq, Eq)]
10pub enum YouTubeUrlType {
11 Watch(String),
13 Shorts(String),
15 Embed(String),
17 Home,
19}
20
21pub fn is_youtube_host(host: &str) -> bool {
23 let h = host.to_ascii_lowercase();
24 let clean_h = h.split(':').next().unwrap_or(&h);
25 clean_h == "youtube.com"
26 || clean_h == "www.youtube.com"
27 || clean_h == "m.youtube.com"
28 || clean_h == "youtu.be"
29 || clean_h == "music.youtube.com"
30}
31
32pub fn extract_youtube_video_id(url: &str) -> Option<String> {
43 let clean = url.trim();
44
45 if !clean.contains("youtube.com") && !clean.contains("youtu.be") {
47 return None;
48 }
49
50 if let Some(pos) = clean.find("youtu.be/") {
52 let after = clean.get(pos + 9..).unwrap_or("");
53 let id_part = after.split(&['?', '&', '/', '#'][..]).next().unwrap_or("");
54 if is_valid_youtube_id(id_part) {
55 return Some(id_part.to_string());
56 }
57 }
58
59 if let Some(pos) = clean.find("/embed/") {
61 let after = clean.get(pos + 7..).unwrap_or("");
62 let id_part = after.split(&['?', '&', '/', '#'][..]).next().unwrap_or("");
63 if is_valid_youtube_id(id_part) {
64 return Some(id_part.to_string());
65 }
66 }
67
68 if let Some(pos) = clean.find("/shorts/") {
70 let after = clean.get(pos + 8..).unwrap_or("");
71 let id_part = after.split(&['?', '&', '/', '#'][..]).next().unwrap_or("");
72 if is_valid_youtube_id(id_part) {
73 return Some(id_part.to_string());
74 }
75 }
76
77 if let Some(pos) = clean.find("/v/") {
79 let after = clean.get(pos + 3..).unwrap_or("");
80 let id_part = after.split(&['?', '&', '/', '#'][..]).next().unwrap_or("");
81 if is_valid_youtube_id(id_part) {
82 return Some(id_part.to_string());
83 }
84 }
85
86 if clean.contains("/watch") || clean.contains("youtube.com") {
88 if let Some(q_idx) = clean.find('?') {
89 let query = clean.get(q_idx + 1..).unwrap_or("");
90 for pair in query.split('&') {
91 if let Some(val) = pair.strip_prefix("v=") {
92 let id_part = val.split('#').next().unwrap_or("");
93 if is_valid_youtube_id(id_part) {
94 return Some(id_part.to_string());
95 }
96 }
97 }
98 }
99 }
100
101 None
102}
103
104fn is_valid_youtube_id(id: &str) -> bool {
106 if id.len() != 11 {
107 return false;
108 }
109 id.chars()
110 .all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_')
111}
112
113pub fn parse_youtube_url(url: &str) -> Option<YouTubeUrlType> {
115 let clean = url.trim();
116
117 if let Some(id) = extract_youtube_video_id(clean) {
119 if clean.contains("/shorts/") {
120 return Some(YouTubeUrlType::Shorts(id));
121 }
122 if clean.contains("/embed/") {
123 return Some(YouTubeUrlType::Embed(id));
124 }
125 return Some(YouTubeUrlType::Watch(id));
126 }
127
128 if clean == "https://www.youtube.com"
130 || clean == "https://www.youtube.com/"
131 || clean == "http://www.youtube.com"
132 || clean == "http://www.youtube.com/"
133 || clean == "https://youtube.com"
134 || clean == "https://youtube.com/"
135 || clean == "https://m.youtube.com"
136 || clean == "https://m.youtube.com/"
137 {
138 return Some(YouTubeUrlType::Home);
139 }
140
141 None
142}
143
144pub fn get_youtube_thumbnail_url(video_id: &str) -> String {
146 format!("https://img.youtube.com/vi/{}/hqdefault.jpg", video_id)
147}
148
149pub fn get_youtube_stream_url(video_id: &str) -> String {
151 format!("https://www.youtube.com/watch?v={}.mp4", video_id)
152}
153
154pub fn generate_youtube_watch_html(video_id: &str, title: &str, desc: &str) -> String {
156 let thumb = get_youtube_thumbnail_url(video_id);
157 let stream_url = get_youtube_stream_url(video_id);
158
159 format!(
160 r#"<!DOCTYPE html>
161<html>
162<head>
163<meta charset="utf-8">
164<title>{title} - YouTube (AtmOS)</title>
165<style>
166body {{
167 background-color: #0f0f0f;
168 color: #f1f1f1;
169 font-family: sans-serif;
170 margin: 0;
171 padding: 0;
172}}
173.yt-header {{
174 background-color: #212121;
175 padding: 12px 20px;
176 display: flex;
177 align-items: center;
178 border-bottom: 1px solid #303030;
179}}
180.yt-logo {{
181 color: #ff0000;
182 font-size: 20px;
183 font-weight: bold;
184 text-decoration: none;
185}}
186.yt-logo span {{
187 color: #ffffff;
188 font-size: 14px;
189 background-color: #282828;
190 padding: 2px 6px;
191 border-radius: 4px;
192 margin-left: 6px;
193}}
194.yt-content {{
195 max-width: 960px;
196 margin: 0 auto;
197 padding: 20px;
198}}
199.video-container {{
200 background-color: #000000;
201 border-radius: 12px;
202 overflow: hidden;
203 margin-bottom: 16px;
204 border: 1px solid #303030;
205}}
206.video-title {{
207 font-size: 18px;
208 font-weight: bold;
209 margin: 12px 0 8px 0;
210 color: #f1f1f1;
211}}
212.channel-bar {{
213 display: flex;
214 align-items: center;
215 padding: 10px 0;
216 border-bottom: 1px solid #272727;
217 margin-bottom: 14px;
218}}
219.channel-avatar {{
220 width: 40px;
221 height: 40px;
222 border-radius: 50%;
223 background-color: #cc0000;
224 color: #ffffff;
225 font-weight: bold;
226 text-align: center;
227 line-height: 40px;
228 margin-right: 12px;
229}}
230.channel-name {{
231 font-weight: bold;
232 color: #ffffff;
233}}
234.desc-box {{
235 background-color: #272727;
236 padding: 14px;
237 border-radius: 8px;
238 font-size: 13px;
239 line-height: 1.5;
240 color: #e0e0e0;
241}}
242.back-link {{
243 display: inline-block;
244 margin-top: 14px;
245 color: #3ea6ff;
246 text-decoration: none;
247}}
248</style>
249</head>
250<body>
251 <div class="yt-header">
252 <a href="https://www.youtube.com/" class="yt-logo">▶ YouTube <span>AtmOS Player</span></a>
253 </div>
254 <div class="yt-content">
255 <div class="video-container">
256 <video src="{stream_url}" poster="{thumb}" width="720" height="405" controls></video>
257 </div>
258 <div class="video-title">{title}</div>
259 <div class="channel-bar">
260 <div class="channel-avatar">S</div>
261 <div>
262 <div class="channel-name">Sugimura Laboratory / AtmOS Official</div>
263 <div style="font-size: 11px; color: #aaaaaa;">動画ID: {video_id} • ARM64 ベアメタル再生</div>
264 </div>
265 </div>
266 <div class="desc-box">
267 {desc}
268 <br><br>
269 ※ AtmOS 独自実装の H.264 Baseline ソフトウェアデコーダおよび MP3 オーディオ同期出力により、ベアメタル環境で直接動画を再生します。
270 </div>
271 <a href="https://www.youtube.com/" class="back-link">← YouTube ホームへ戻る</a>
272 </div>
273</body>
274</html>"#,
275 title = title,
276 video_id = video_id,
277 thumb = thumb,
278 stream_url = stream_url,
279 desc = desc,
280 )
281}
282
283pub fn generate_youtube_home_html() -> String {
285 format!(
286 r#"<!DOCTYPE html>
287<html>
288<head>
289<meta charset="utf-8">
290<title>YouTube - AtmOS</title>
291<style>
292body {{
293 background-color: #0f0f0f;
294 color: #f1f1f1;
295 font-family: sans-serif;
296 margin: 0;
297 padding: 0;
298}}
299.yt-header {{
300 background-color: #212121;
301 padding: 12px 24px;
302 display: flex;
303 align-items: center;
304 justify-content: space-between;
305 border-bottom: 1px solid #303030;
306}}
307.yt-logo {{
308 color: #ff0000;
309 font-size: 22px;
310 font-weight: bold;
311 text-decoration: none;
312}}
313.yt-logo span {{
314 color: #ffffff;
315 font-size: 14px;
316 background-color: #282828;
317 padding: 2px 8px;
318 border-radius: 4px;
319 margin-left: 8px;
320}}
321.yt-content {{
322 max-width: 1080px;
323 margin: 0 auto;
324 padding: 24px;
325}}
326.section-title {{
327 font-size: 18px;
328 font-weight: bold;
329 margin-bottom: 16px;
330 color: #ffffff;
331 border-left: 4px solid #ff0000;
332 padding-left: 10px;
333}}
334.video-grid {{
335 display: grid;
336 grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
337 gap: 20px;
338 margin-bottom: 32px;
339}}
340.video-card {{
341 background-color: #1e1e1e;
342 border-radius: 8px;
343 overflow: hidden;
344 text-decoration: none;
345 color: inherit;
346 border: 1px solid #2a2a2a;
347}}
348.video-card:hover {{
349 border-color: #ff0000;
350}}
351.card-thumb {{
352 width: 100%;
353 height: 160px;
354 background-color: #2a2a2a;
355 position: relative;
356 display: flex;
357 align-items: center;
358 justify-content: center;
359}}
360.card-thumb img {{
361 width: 100%;
362 height: 100%;
363 object-fit: cover;
364}}
365.play-badge {{
366 position: absolute;
367 background-color: rgba(0,0,0,0.7);
368 color: #ff0000;
369 font-size: 24px;
370 padding: 6px 14px;
371 border-radius: 8px;
372}}
373.card-info {{
374 padding: 12px;
375}}
376.card-title {{
377 font-size: 14px;
378 font-weight: bold;
379 line-height: 1.3;
380 margin-bottom: 6px;
381 color: #f1f1f1;
382}}
383.card-channel {{
384 font-size: 12px;
385 color: #aaaaaa;
386}}
387</style>
388</head>
389<body>
390 <div class="yt-header">
391 <a href="https://www.youtube.com/" class="yt-logo">▶ YouTube <span>AtmOS Portal</span></a>
392 <div style="font-size: 12px; color: #aaaaaa;">AtmOS ベアメタル Web エンジン</div>
393 </div>
394 <div class="yt-content">
395 <div class="section-title">注目の動画・研究プロジェクト</div>
396 <div class="video-grid">
397 <a href="https://www.youtube.com/watch?v=dQw4w9WgXcQ" class="video-card">
398 <div class="card-thumb">
399 <img src="https://img.youtube.com/vi/dQw4w9WgXcQ/hqdefault.jpg" alt="Video">
400 <div class="play-badge">▶</div>
401 </div>
402 <div class="card-info">
403 <div class="card-title">AtmOS ベアメタル H.264 デコーダ再生デモ (Never Gonna Give You Up)</div>
404 <div class="card-channel">Sugimura Laboratory • 1080p / 60fps</div>
405 </div>
406 </a>
407 <a href="https://www.youtube.com/watch?v=jNQXAC9IVRw" class="video-card">
408 <div class="card-thumb">
409 <img src="https://img.youtube.com/vi/jNQXAC9IVRw/hqdefault.jpg" alt="Video">
410 <div class="play-badge">▶</div>
411 </div>
412 <div class="card-info">
413 <div class="card-title">YouTube 最初の動画: Me at the zoo (2005) - 歴代アーカイブ</div>
414 <div class="card-channel">jawed • 480p</div>
415 </div>
416 </a>
417 <a href="https://www.youtube.com/watch?v=aqz-KE-bpKQ" class="video-card">
418 <div class="card-thumb">
419 <img src="https://img.youtube.com/vi/aqz-KE-bpKQ/hqdefault.jpg" alt="Video">
420 <div class="play-badge">▶</div>
421 </div>
422 <div class="card-info">
423 <div class="card-title">Big Buck Bunny - 4K オープンソースアニメーション(H.264 Baseline)</div>
424 <div class="card-channel">Blender Foundation • 1080p</div>
425 </div>
426 </a>
427 </div>
428
429 <div class="section-title">AtmOS Web ブラウザ機能</div>
430 <div style="background-color: #1e1e1e; padding: 16px; border-radius: 8px; border: 1px solid #2a2a2a; font-size: 13px; line-height: 1.6; color: #cccccc;">
431 AtmOS では、外部プロキシに依存することなく、ベアメタル ARM64(Raspberry Pi 3B+)上の Rust 実装のみで直接 YouTube 動画ストリームを受信・デコードします。<br>
432 上部のアドレスバーに <code>https://www.youtube.com/watch?v=...</code> を入力するか、上のカードをクリックして視聴を開始してください。
433 </div>
434 </div>
435</body>
436</html>"#
437 )
438}