From e42b7cce746d0c2ebbf71b7c722a0f98a5f363a8 Mon Sep 17 00:00:00 2001 From: jlvihv Date: Sun, 16 Jun 2024 14:22:39 +0800 Subject: [PATCH] youtube --- README.md | 32 +- README.zh.md | 35 ++ bun.lockb | Bin 140957 -> 141718 bytes package.json | 2 +- src-tauri/Cargo.lock | 770 +++++++++++++++++++++++++++++---- src-tauri/Cargo.toml | 5 +- src-tauri/src/lib.rs | 1 + src-tauri/src/manager.rs | 16 + src-tauri/tauri.conf.json | 2 +- src/lib/i18n/cn.json | 5 +- src/lib/i18n/en.json | 5 +- src/lib/platform/douyin.ts | 1 + src/lib/platform/youtube.ts | 160 +------ src/lib/utils.ts | 22 +- src/routes/record/+page.svelte | 116 +++-- static/bilibili.ico | Bin 0 -> 4286 bytes static/douyin.ico | Bin 0 -> 4286 bytes static/douyu.ico | Bin 0 -> 1938 bytes static/huya.ico | Bin 0 -> 4286 bytes static/kuaishou.ico | Bin 0 -> 4286 bytes static/tiktok.ico | Bin 0 -> 6755 bytes static/twitch.ico | Bin 0 -> 4286 bytes static/xiaohongshu.ico | Bin 0 -> 4286 bytes static/youtube.ico | Bin 0 -> 1150 bytes static/youtube.png | Bin 0 -> 729 bytes 25 files changed, 849 insertions(+), 323 deletions(-) create mode 100644 README.zh.md create mode 100644 static/bilibili.ico create mode 100644 static/douyin.ico create mode 100644 static/douyu.ico create mode 100644 static/huya.ico create mode 100644 static/kuaishou.ico create mode 100644 static/tiktok.ico create mode 100644 static/twitch.ico create mode 100644 static/xiaohongshu.ico create mode 100644 static/youtube.ico create mode 100644 static/youtube.png diff --git a/README.md b/README.md index a138916..4090204 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,35 @@ -## 简介 +[中文](README.zh.md) -liveship 是一个小巧易用的直播录制工具,目前已支持抖音、虎牙、小红书、tiktok、twitch,未来计划添加更多平台支持。 +## Introduction -## 工作原理 +liveship is a compact and easy-to-use live streaming recording tool. It currently supports YouTube, TikTok, Twitch, Douyin, Huya, and Xiaohongshu, with plans to add support for more platforms in the future. -liveship 本质上只是 ffmpeg 套壳,通过模拟请求获取直播流地址,然后使用 ffmpeg 进行录制。因此要求您的电脑上必须安装有 ffmpeg,并在“程序设置”页面指定 ffmpeg 路径。 +## How It Works -## 技术栈 +liveship essentially acts as a wrapper for ffmpeg. It simulates requests to obtain live stream URLs and then uses ffmpeg to record them. Therefore, you must have ffmpeg installed on your computer and specify the path to ffmpeg on the "Program Settings" page. -自豪的使用 rust、tauri 和 svelte 5 构建。 +## Tech Stack -## 使用方法 +Proudly built with Rust, Tauri, and Svelte 5. -liveship 是一个基于 tauri 的桌面应用程序,您可以从 [release](https://github.com/jlvihv/liveship/releases/) 页面下载对应平台的二进制文件,然后安装运行。 +## Usage -## 常见问题 +liveship is a Tauri-based desktop application. You can download the binary files for your platform from the [release](https://github.com/jlvihv/liveship/releases/) page, then install and run it. -1. mac os 提示“文件已损坏,无法打开”:这是因为 mac os 限制了非 app store 的应用程序,您可以在终端中执行 `sudo xattr -d com.apple.quarantine /Applications/liveship.app` 命令解除限制。 +## FAQ -## 特别鸣谢 +1. macOS prompts "The file is damaged and cannot be opened": This is because macOS restricts applications not from the App Store. You can remove the restriction by running the command `sudo xattr -d com.apple.quarantine /Applications/liveship.app` in the terminal. -直播源解析的代码很大程度上参考了 [DouyinLiveRecorder](https://github.com/ihmily/DouyinLiveRecorder) 项目,在此致以诚挚的感谢。 +## Special Thanks -## 特别说明 +The code for live stream parsing largely references the [DouyinLiveRecorder](https://github.com/ihmily/DouyinLiveRecorder) project. We extend our sincere thanks for their work. -liveship 计划在 1.0 版本之后添加收费功能,作为我独立开发人生道路的探索。但在 1.0 之前,所有功能都是开源免费的,期待您的建议和反馈。 +## Special Note + +liveship plans to introduce paid features after version 1.0 as part of my journey as an independent developer. However, all features will be open-source and free before version 1.0. Your suggestions and feedback are highly appreciated. ## License CC BY-NC (Creative Commons Attribution-NonCommercial): -允许复制、发行、展示和表演作品及其衍生作品,但仅限于非商业用途。 +Allows copying, distribution, display, and performance of the work and its derivative works, but only for non-commercial purposes. diff --git a/README.zh.md b/README.zh.md new file mode 100644 index 0000000..8ff5b3c --- /dev/null +++ b/README.zh.md @@ -0,0 +1,35 @@ +[English](README.md) + +## 简介 + +liveship 是一个小巧易用的直播录制工具,目前已支持抖音、虎牙、小红书、youtube、tiktok、twitch,未来计划添加更多平台支持。 + +## 工作原理 + +liveship 本质上只是 ffmpeg 套壳,通过模拟请求获取直播流地址,然后使用 ffmpeg 进行录制。因此要求您的电脑上必须安装有 ffmpeg,并在“程序设置”页面指定 ffmpeg 路径。 + +## 技术栈 + +自豪的使用 rust、tauri 和 svelte 5 构建。 + +## 使用方法 + +liveship 是一个基于 tauri 的桌面应用程序,您可以从 [release](https://github.com/jlvihv/liveship/releases/) 页面下载对应平台的二进制文件,然后安装运行。 + +## 常见问题 + +1. mac os 提示“文件已损坏,无法打开”:这是因为 mac os 限制了非 app store 的应用程序,您可以在终端中执行 `sudo xattr -d com.apple.quarantine /Applications/liveship.app` 命令解除限制。 + +## 特别鸣谢 + +直播源解析的代码很大程度上参考了 [DouyinLiveRecorder](https://github.com/ihmily/DouyinLiveRecorder) 项目,在此致以诚挚的感谢。 + +## 特别说明 + +liveship 计划在 1.0 版本之后添加收费功能,作为我独立开发人生道路的探索。但在 1.0 之前,所有功能都是开源免费的,期待您的建议和反馈。 + +## License + +CC BY-NC (Creative Commons Attribution-NonCommercial): + +允许复制、发行、展示和表演作品及其衍生作品,但仅限于非商业用途。 diff --git a/bun.lockb b/bun.lockb index b783c008abec072825f7c7861524c8f1e72100c9..54ee432931bf98cd453975df5bd89d899f7c936b 100755 GIT binary patch delta 1345 zcmc&zUu;uV9KOHPi}bd(4xDaogso_GHB%VFwWMsSD&oYk6VAW z@FVw!N-Do|j*eKDXKU1O$uru`^jO)AZG3#NhF8b#=FwQA_9NOhpj6?rm2S-*Qw!a=_ZewP?Yuj+A=RC z_?1L6o#C!zGoz#)N#3;VmG`!;czFxh1pnzf{(oUYpBD7F2fP(*1$Tj^-_4Yz^P9jO zBPdxPPM-hY&t+eDE`4oAO|6@nc71#K{Vz+;w!OPy6Iuoi02*5Z~Wq5^(JwtnrULAgO!SNGgK+s V%b8uAcCZ!VBPTN(4>;L=`WvrmIv)T4 delta 1501 zcmeH{ZAep57{~8(x8?5E6*EY)7c0_CO0BHTEOil-Nv1~4S>{Wvx!k5kto26P8|w|K z<2)7Emmsz9L(O@IU~d!^5eR(_$|8)w4+T+Dv;W=fL!awIA3E^A&-wq(b6$A3=XAW( zcfQbH$XdAldh^}5M{jM%l$BMzgTs?0(~XXO_nz24RlPsG&UI;6P8S8W?YU@871!*( z?R#pN?se|7tL&^x5F|lpb-0@9_u73zgq0gpDa=}oreN6k+Mv(SIuq7OV?K6yT0<}) z>FXBeZ_bh0F)atvbnHQvndP|)CB0h^R)7eMW}2h|xK&ZtmSt9l)MSyhrI@6LaAr6S zYfCdpV{p0AF-xvV%<`}SPl43v5rhpdYo>xL_3tCRDkFX&moiCLE8v@L~nk{xI%6rVN+2U^AqM~x^%F>0iisU%jb-=YS* zbb|j$B&@Ru>!`78B9@E7q$sEeT8=#bXU=lPuk~`bhDz8-MCOmtAshP;v9inKGTS&_ zLfhDtacf{b$?OMQb)cB6fv+S7dXZTZ_(oRVO(gSbg0JH}1W+jg$RfZ_ZW96Y8h}9+ z*tw(u2&-V07Qn%sT7U*EzzY>PmFYUlp22J9QhQgcj))Z{X_-GQBWnJCi2h;?I12p9 z;NPwOsDhB(z>6JJq?~imd6F7p(BFZBQ(Yv`8bl zWBKpA(flb+Sak^pq^@sQdOUPXpSKJBolrgW9h=r6ZieE}-+}lUu@-SVWP?^f-@q#Q zoBfo%bQ^3HR0FB$)sPj!&Hqiw`vK8T1$m~AGI^PguF?|aqmQl;f@#T zRMd5$pU5DDVo;Z^Tvh0hi Result { + let video = Video::new(&url).map_err(|e| format!("Could not get video info: {}", e))?; + let video_info = video + .get_info() + .await + .map_err(|e| format!("Could not get video info: {}", e))?; + Ok(video_info) + } +} diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 2de11b1..9e027f5 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,6 +1,6 @@ { "productName": "liveship", - "version": "0.1.17", + "version": "0.1.18", "identifier": "app.happyship.liveship", "build": { "beforeDevCommand": "bun run dev", diff --git a/src/lib/i18n/cn.json b/src/lib/i18n/cn.json index 2745ef6..b336386 100644 --- a/src/lib/i18n/cn.json +++ b/src/lib/i18n/cn.json @@ -118,7 +118,7 @@ "hd_30": "高清30帧", "sd1": "标清", "sd": "标清", - "sd2": "标清", + "sd2": "标清 2", "ld": "流畅", "ld1": "流畅", "ld2": "流畅", @@ -136,5 +136,6 @@ "uhd_60": "超高清60帧", "recentSearch": "最近查询", "tryThese": "不知道如何开始?试试这些", - "gotoRecordHistory": "去录制历史中查看" + "gotoRecordHistory": "去录制历史中查看", + "parseError": "解析直播流地址错误,请刷新试试" } diff --git a/src/lib/i18n/en.json b/src/lib/i18n/en.json index ac4a733..7dee79a 100644 --- a/src/lib/i18n/en.json +++ b/src/lib/i18n/en.json @@ -110,7 +110,6 @@ "origin": "Original", "source": "Original", "full_hd1": "Full HD", - "FULL_HD1": "Full HD", "hd1": "HD", "uhd": "Ultra HD", "hd": "HD", @@ -121,7 +120,6 @@ "sd": "SD", "sd1": "SD", "sd2": "SD", - "SD2": "SD", "ld": "Smooth", "ld1": "Smooth", "ld2": "Smooth", @@ -139,5 +137,6 @@ "uhd_60": "Ultra HD 60fps", "recentSearch": "Recent search", "tryThese": "Don't know how to start? Try these", - "gotoRecordHistory": "Go to record history to view" + "gotoRecordHistory": "Go to record history to view", + "parseError": "Failed to parse live stream address, please refresh and try again" } diff --git a/src/lib/platform/douyin.ts b/src/lib/platform/douyin.ts index 456b55a..b76a285 100644 --- a/src/lib/platform/douyin.ts +++ b/src/lib/platform/douyin.ts @@ -21,6 +21,7 @@ export async function getLiveInfoForDouyin(url: string): Promise { headers: getHeaders() }); let html = await resp.text(); + console.log('douyin html', html); // 解析 html,填充 LiveInfo parseHtmlAndFillLiveInfo(html, info); } catch (e) { diff --git a/src/lib/platform/youtube.ts b/src/lib/platform/youtube.ts index c929828..1bc9de5 100644 --- a/src/lib/platform/youtube.ts +++ b/src/lib/platform/youtube.ts @@ -1,7 +1,5 @@ -import { LiveStatus, PlatformKind, StreamingProtocol, type LiveInfo, type Stream } from '@/model'; +import { LiveStatus, PlatformKind, StreamingProtocol, type LiveInfo } from '@/model'; import { invoke } from '@tauri-apps/api/core'; -import { JSONPath } from 'jsonpath-plus'; -import { fetch } from '@tauri-apps/plugin-http'; export async function getLiveInfoForYoutube(url: string): Promise { let info: LiveInfo = { @@ -16,155 +14,25 @@ export async function getLiveInfoForYoutube(url: string): Promise { status: LiveStatus.NotLive }; try { - let videoId = url.split('v=')[1]; - console.log('youtube video id', videoId); - let resp = await fetch(url, { - method: 'GET', - headers: { - 'User-Agent': - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' - } - }); - let html = await resp.text(); - console.log('youtube html', html); - - // _re_ytInitialPlayerResponse = re.compile(r"""var\s+ytInitialPlayerResponse\s*=\s*({.*?});\s*var\s+\w+\s*=""", re.DOTALL) - let data = html.match(/var\s+ytInitialPlayerResponse\s*=\s*({.*?});\s*var\s+\w+\s*=/); - console.log('youtube data', data); - if (!data || data.length < 2) { - return info; - } - const initialPlayerResponse = JSON.parse(data[1]); - const streamingData = initialPlayerResponse.streamingData; - console.log('youtube streaming data', streamingData); - if (!streamingData) { - return info; - } - info.status = LiveStatus.Live; - info.streams.push({ - url: streamingData.hlsManifestUrl, - protocol: StreamingProtocol.Hls, - resolution: 'default' - }); - // 遍历 streamingData.adaptiveFormats,全都放入 info.streams - for (let format of streamingData.adaptiveFormats) { - console.log('youtube format', format.signatureCipher); + let youtubeInfo: any = await invoke('get_youtube_info', { url }); + console.log('youtube info', youtubeInfo); + info.status = youtubeInfo.videoDetails.isLiveContent ? LiveStatus.Live : LiveStatus.NotLive; + info.viewerCount = youtubeInfo.videoDetails.viewCount; + info.title = youtubeInfo.videoDetails.title; + info.anchorName = youtubeInfo.videoDetails.author.name; + info.anchorAvatar = youtubeInfo.videoDetails.author.thumbnails[0].url; + info.roomCover = youtubeInfo.videoDetails.thumbnails[0].url || ''; + for (let format of youtubeInfo.formats) { info.streams.push({ - url: format.signatureCipher.split('url=')[1], - protocol: StreamingProtocol.Hls, - resolution: format.quality + url: format.url, + protocol: format.isHLS ? StreamingProtocol.Hls : StreamingProtocol.Flv, + resolution: format.qualityLabel }); } - - // const embedUrl = `https://www.youtube.com/embed/${videoId}`; - // let resp = await invoke('request', { url: embedUrl, headers: {} }); - // console.log('embed', resp); - - // let channelIdArray = (resp as string).match(/\\"channelId\\":\\"(.{24})\\"/); - // let channelId = channelIdArray ? channelIdArray[1] : ''; - - // const apiUrl = - // 'https://www.youtube.com/youtubei/v1/browse?key=AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8'; - // let response = await invoke('request_post', { - // url: apiUrl, - // headers: {}, - // body: { - // context: { - // client: { - // hl: 'zh-CN', - // clientName: 'MWEB', - // clientVersion: '2.20230101.00.00', - // timeZone: 'Asia/Shanghai' - // } - // }, - // browseId: channelId, - // params: 'EgdzdHJlYW1z8gYECgJ6AA%3D%3D' - // } - // }); - // // match $..videoWithContextRenderer - // console.log('youtube api response', response); - // let apiJson = JSON.parse(response as string); - // console.log('youtube api response', apiJson); - // let videoWithContextRederer = JSONPath({ path: '$..videoWithContextRenderer', json: apiJson }); - // console.log('videoWithContextRederer', videoWithContextRederer); - // // 遍历 videoWithContextRederer,找到直播视频 - // for (let video of videoWithContextRederer) { - // } - - // let channelUrl = `https://www.youtube.com/channel/${channelId}/videos`; - // console.log('channel url', channelUrl); - // let channelResp = await invoke('request', { url: channelUrl, headers: {} }); - // console.log('channel', channelResp); - // // r'"gridVideoRenderer"((.(?!"gridVideoRenderer"))(?!"style":"UPCOMING"))+"label":"(LIVE|LIVE NOW|PREMIERING NOW)"([\s\S](?!"style":"UPCOMING"))+?("gridVideoRenderer"|)', - // let liveVideoArray = (channelResp as string).match( - // /r'"gridVideoRenderer"((.(?!"gridVideoRenderer"))(?!"style":"UPCOMING"))+"label":"(LIVE|LIVE NOW|PREMIERING NOW)"([\s\S](?!"style":"UPCOMING"))+?("gridVideoRenderer"|<\/script>)/ - // ); - // console.log('liveVideoArray', liveVideoArray); - // let liveVideo = liveVideoArray ? liveVideoArray[1] : ''; } catch (e) { - console.error('get live info for tiktok failed: ', e); + console.error('get live info for youtube failed: ', e); // 抛出一个错误 throw e; } return info; } - -// 解析 html,填充 LiveInfo -function parseHtmlAndFillLiveInfo(html: string, info: LiveInfo) { - // match {#snippet icon(platformKind:string)} -
- {platformKind} -
+ {platformKind} {/snippet} @@ -329,12 +319,11 @@
{#if liveInfo?.status === LiveStatus.Live} -

+

{@render icon(liveInfo?.platformKind)} - {liveInfo?.title} - {#if recordStatus === RecordingStatus.Recording} - {$t('recording')} - {/if} +

+ {liveInfo?.title} +

{:else if liveInfo?.status === LiveStatus.NotLive}
@@ -386,6 +375,9 @@ {liveInfo.anchorName}

{$t('living')}

+ {#if recordStatus === RecordingStatus.Recording} + {$t('recording')} + {/if}

{liveInfo.viewerCount ? liveInfo.viewerCount + $t('watching') : ''}

@@ -401,7 +393,7 @@ > {#each liveInfo.streams as item} {item.protocol + ' ' + $t(item.resolution.toLowerCase())} {/each} @@ -490,60 +482,58 @@ {/if}
{:else} -
-
- {#if queryHistory.length <= 5} -

{$t('tryThese')}

-
- {#each tryLinks as link} -
- -
- {/each} -
- {:else} -

{$t('recentSearch')}

- {#each queryHistory as history} +
+ {#if queryHistory.length <= 5} +

{$t('tryThese')}

+
+ {#each tryLinks as link}
-
{/each} - {/if} -
+
+ {:else} +

{$t('recentSearch')}

+ {#each queryHistory as history} +
+ + +
+ {/each} + {/if}
{/if}
diff --git a/static/bilibili.ico b/static/bilibili.ico new file mode 100644 index 0000000000000000000000000000000000000000..628ecb4a389b87478bc487a6a6540e18cd136eda GIT binary patch literal 4286 zcmd^@u}T9$6h%iONg=p`Ac{p&`WLa)pAfWGe<7cuU4B68G-6?)Hr6RbL7N3FA_T^J z%|00~vu;)Qt<>-sCstvTnmAf((j#QetxW2;LOO%l#| z@Bg*#PMs^%M0*TfL9dWcEPB$L46=;Nb`!dWQZvi^ZVD#ZEr@Yh?X2>*1^eh1*tRFK zFNJ@GcpVDwPl`V8F};n=mh3mlZ6Hp;i+ONgA^$~Uj>YDs%!T||KP4;X6=R0E*qoHP zkQM8vWW~H<%rK{OcJWL+LiZ5+I<9e5eaMRS)$ajJtK&L!T;r;G^D1lAPwRACH*@uz~$7kIqy9v$n>}Px8nXd)$`Iz_lllvtzd^UL<>^v4b zm)N?0+R~Hvn+&o-cC5dQnhQvI%w>`Jb?akH%W-{m*f#IGHB7T-^Vu)iH~p=qtSwu^ z!4z}Nb5HoZ7Sph8eryf&wDGET5w$1K6V!+5u+5uuIQJQ%7d>t4v;6~qe`5*W51ecC y_^*QE(?F`_{V?-B|Dr6A@30W84^YN3CIdl;!Oazuj>gSpN=wm~24+ literal 0 HcmV?d00001 diff --git a/static/douyin.ico b/static/douyin.ico new file mode 100644 index 0000000000000000000000000000000000000000..637ab319ac5a13cd94963aa1ea2fd6be8d4a6a74 GIT binary patch literal 4286 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 a?Nq7R+~SsK3%d`@7LW-VYgl36eE)BQUTR+e literal 0 HcmV?d00001 diff --git a/static/huya.ico b/static/huya.ico new file mode 100644 index 0000000000000000000000000000000000000000..e5c13f060e1bb1a9fa57b96a3bdfef24edf36db2 GIT binary patch literal 4286 zcmdT|c~n%_9e%OQKEuqgi^x{2OED&%wkPK_QDe_(taz+8lr)GMja!UWtG36q8W2IG zh+x=d6G8SxK&VTorqLvrBJLZ-V5*{^yxEum0l)t48^;iwiD^&&NG|8&zIX3?@B8j= zyB9d_Ir{PU=h$zqRKsx|9LG%{&=5C+z{Z6}bKlpdNHK7-SY41TMmLwRRvM$*F5S*2 zNvwz$NAsHri$u}X|~B$|hK z8xK(wKR{@9isZLAMe%|EtrqdDe7R_P+Mgv6$GV9h3Wl9JsFL(>PBlQCN!V+II-8JV zg)`kNGmTKtyll4~k~kgH0jJ2GA*c6yY6!vKMu+It%$cHTsW(Nhq(Bl@4`<@k(zjjr zF__?*Z-UD(X!A_aFyxv@iwVvI73osYJMs(zB#C;6$R*SsZx=K$TgbAEkbFQsnVAXEw6uCrSj{M#9mF9;KT*c^^sC5c+GO(c z%0@_kl`g#ihBhp->QLTeG@!(w_|jEkMJDR=DFVp_slN1#9xQ=rjyVvEX6y?$FXSd8K@~P zSWGYtvK5DgH8IF7E?Y`!GLw?C*se#f&EA64|GS{6YCa8dOQQA)TE8>D(Mh|JY2v(6iiFMtPPY z@c$we+D#u>xk`KCTWW@HnHhcr-(mxN^19%YbC3FNj4jmByj7D;hfRamV+|~BwR`?8 z)a2^4SF!8!IS)d=HPv9Rqjq#>CDTFOf= zvInzsK)D%Xsm3UmG@|hMwI`fk_L#t%`(b%Fh=HLY3_W^;M+DXn4n4xd!6Ecfy!ZAG zKyT^CeN!Lq=*;-yfmQd1=WiiA?g$ji4^iFJLlJp_Vj)wAMVDQAi#O?F+<_Hlc%||P zUQzmlyW7|&aN&PwOe53G^mLGxr~{`dM;oEo^agsm$)(O`sv9CZ-8B%$+aW+W0x~SB;_i z#khY|P5e7rnBdWv_rj@nZZbyXUZMPZMzRhZClOwUuYl=6pwysJ*1%;k%i6R zd;9I$)e$EfXLieJyadgHa=4{+vvX7v?Ttdr**J&ysy7N}Osddf${`D0Z0N(3gH)5L zUb_7L1g34QM$@@l7@%7FZQALpcQNa|Iw+SPgGaUw9(lSJ+MAWb`9@wV!}cqEcb@Hk z{;>_FHuhmEX_-L#`r#7xa^I|=46HXCtpD@ z@i99vUrZq#Obfeax`HWBCRFqyh|VB^WSamwg9VVb0J>-U7~4zPdC4o~CKPWLXJ|H@ zut#NgOtqlcNfloF#l%X3pn>TKK4`+YoG&fo3p$hNe)Es(EEQBMTBu&OQ_XH4S83vd zDy{pcKk@sP(BBE{{5UT+Ub(39$vM|9p`AgpL4@jebaq>QuvfFBvPnKKcdcT6o|~OV zg(3BlszBF`$6GvO&cc1$8Mfv&>71o`u*!Erx0L5QM%A24v=&}`38FT8WY`o4s| Y!@lK5m`;eKZ%6h?ISfXA{G^2Z2RXT{#{d8T literal 0 HcmV?d00001 diff --git a/static/kuaishou.ico b/static/kuaishou.ico new file mode 100644 index 0000000000000000000000000000000000000000..ba7dac3d587cd176d3ff7ea0c05ac76893399ede GIT binary patch literal 4286 zcmcgw-ES0C6u)MZHY9$<2aPdmAN7I%Kok4}nwSt1pZEg^XyV5UflvZ!Tf{b?C?PSx zgU|;wHP#o2Ai;t(_JfbMHU)&V+wOMT?e6U7?93d0zdJL#GdtZcG@j<}%-p$ie&_zq zx#!$lrJlfFPmhB1s0+_3^*rug(@#q6z!h)t3&FdH42$Z1^^Q`*(uMXu+5}n-P5$FX zozzW?E@FDP#C{2F3at*)5%qaMaO_~&eg7%Ob1c8(_s1&kxTxJTz$Es28k@FrY=6PU z>syzeqbd3FT2`(u=H(jNhd;v(>eqb0PU@#^eH)_oCuQfkl$^VnmEZ5><;)*hd1Wvq z>Tnu5^X`0z5$2BA*L5GUp=#=(;888c-3enFNtN>ZxW z;lX&aY)9r3CHdf|P1u1(j4(d2%eoKz%_q!=jw19H$~D~;=-hP?9Ds+sX+_5S9mEdG zngMM3D2#7ZpD`bPVL6V>By1U&u;qo5^haKHUdRSjNh;@Zg`o}Q^;0?7aV9G>aZ_w> zpaMn}FKh>FD`{P`N$A!*!B?>N`&rnv1rB}HO!&T7vEJF(jb%;b(G5pFzKZYY7wBPKNMy^^+~V8b=*qmg zm@Lbi-=bd7uhCi{y`zqdgWJo9xvys&d3Vf_t`h;SzYxi1k0FA9)PwZ*`BF`_@?_!Z+*1**mtpeA#5+iqLyl1WK7$5O@<56_Yjm)85RQDE+j4niL;;mILrvv|X!maBassDDx@5R@LwsSBvYF&{*ze zV%Sf`uwJvs1NYM4zfm4&WE1e~Ws>s(UdUefX>!>V)3W7$y2jU1rXumIh4>J8`$z0A z8{stShu-=<&3qF0+ynY+*;^wEYo^}I#9;F;58b>%<_zQ!okObe65y>7uHTRNXY2-r z`>!YavA<+KArG+S#bcZBFECATT+fZK>v67_+tg}}Pv4G-zY$lc73xr0Vh7jc?uwfq z7S_RWdp>sepKaLG`$NTt4P93=Wtn)8my}VfA-gZp$C3SZz28fS19$)320r~fjlF3r zSwyX<#zb?y#NOLUEN(mQ$iqx{9R}@LHXM28tCsWjc_22=om-s!n#HCg-_6;QDLXpO zX$@=S$UO3FYtDUap82}bnw&qI+3>I4alZcf(?1832md@)JC`)~pFfv&uGC3?6Qg?m SzM@pDkN+b0pM{uLfc+13@*bT4 literal 0 HcmV?d00001 diff --git a/static/tiktok.ico b/static/tiktok.ico new file mode 100644 index 0000000000000000000000000000000000000000..ddcb96316e2c464c9ed975d600d86f7bb3b07bf2 GIT binary patch literal 6755 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 U@fbRSrZL*$`d8=;dWSxsPwNCMi2wiq literal 0 HcmV?d00001 diff --git a/static/xiaohongshu.ico b/static/xiaohongshu.ico new file mode 100644 index 0000000000000000000000000000000000000000..8b489246d942c821e4490cdfe97cf912cdcdfc65 GIT binary patch literal 4286 zcmeHLTT4_?6yDJv=&5I)%<82JQ3PEai%Kagw3IH;RfMS|3^8FUBBZNms8ncK7-b0t zSs7yE6l6C@!GxZow0eng#(Us=t#y{0GoEvXdhtvTXURJIvesVT+IxMoVXM?E`b$Ys z{8jhnC^c6pl}1WaYAq?x8$vcGNuP&Q(q_^KDP~w1gRxRM7j8+zq%)0cjn9~R-&UJj zW4F#c(SF>hcOqlOf8R{&$9z~^3I(;{d55Y>ij&s> z$BzyQk8_EeyeBPPL1%o;X`}wc|AOtZf;p^h);4P?aO)~?;iMg(`F1%VYu~<*q=EnOw RsMPG!{4WBRg)xJK{RjIpYQ0;4T(f-Z(wdPXz yBj3NeKMn}2p><5tRvVRTAm0&TL87g};xGTN)%cC2Ql2;;fYjmb(j3jTX*~g$3rXVu literal 0 HcmV?d00001 diff --git a/static/youtube.png b/static/youtube.png new file mode 100644 index 0000000000000000000000000000000000000000..e143c556e3bbbf795af8ec57fc976603d7258fc0 GIT binary patch literal 729 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=k^_7~T!Hj|QqlbR{{sU4@8AC) z2-dIvub}Y%{rmq74gb%d|L@@NUqIl0Lc)Ioga7yM|L^brUr_L4;#78^HG(BUe!&b5 z1_}iY`_KQsKR=-VeS*Mx&VP|_7#NsDJY5_^Dj4714o!Msz~dU|{6=Gr(cypLma4o{ zUmy5;A@U&ijMVfV!($T3J%%3toDZ&5sd~Tqi-P^ir@v?YS>nsD_5Z-%hWA19KPzZo zm{2QKTM}>3o2xu~(xN^~xz^^VR7e{^X zk1Gq`xxeD(eSy33y&A{dSFKJkX=3TrHijcRMdhsYvr!K+uI<*YGpCysA#FP`*HQGZ9z4-AaX4=!!Otw3A z#MoSj5qfd(;MGI6$$DI09=wqf_K>}sz_jwScBD(ZjK^I$J+m^8M1|6#Q(Q~t3aL#f z^IGBXbZ*e*f(4)aGA}yZ(rjePT+YG`lv4)E%>fEbWD+g$I(PIb^UB@Y2c)%LOj*&$ z^nbGM|6t`=fA^O5$n(m|*Xl~g^!qJksBU7LafmB-QvB!Xjt#9!d78bqa!zb{J7eCC z#MaxFoO7$f-sC18IJR@Stgg$Whu#7LcNpc21>Ol%i1l=Zc|6EmDkd1#V|3wEsnOa6 zniswH?MV(b;oY+7;Iyy^$sJ3V?|BsC8dma=*EOWh@5;F3FCTMM zG9}qrI&k__tM8(9KDp;M%~N|mW!Hzr)n&0&2LE5|o4xAuVoO_zz27hD>i_a*g+;Q< aeuktSC#+X)m2m?m9tKZWKbLh*2~7YKtVm%1 literal 0 HcmV?d00001