Skip to content

Commit

Permalink
update
Browse files Browse the repository at this point in the history
  • Loading branch information
jlvihv committed Jun 15, 2024
1 parent 716c108 commit d14e9d4
Show file tree
Hide file tree
Showing 15 changed files with 244 additions and 148 deletions.
1 change: 1 addition & 0 deletions src-tauri/capabilities/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"menu:default",
"tray:default",
"shell:allow-open",
"shell:allow-execute",
"dialog:default",
"dialog:allow-open",
{
Expand Down
1 change: 1 addition & 0 deletions src-tauri/src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ pub enum PlatformKind {
Kuaishou,
Douyu,
Twitch,
Youtube,
Unknown,
}

Expand Down
10 changes: 7 additions & 3 deletions src/lib/components/button.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,22 @@
onClick,
black,
white,
red
red,
autoFocus
}: {
children: any;
className: string;
children?: any;
className?: string;
onClick: any;
black?: boolean;
white?: boolean;
red?: boolean;
autoFocus?: boolean;
} = $props();
</script>

<!-- svelte-ignore a11y_autofocus -->
<button
autofocus={autoFocus || false}
class={`rounded-full px-6 py-3 text-lg shadow-lg transition duration-200 hover:shadow-xl focus:scale-95 ${className} ${
black ? 'bg-black1 text-white1 hover:bg-black' : ''
} ${white ? 'bg-white1 text-black1 hover:bg-white' : ''} ${red ? 'bg-red-800 text-white1 hover:bg-red-700' : ''}`}
Expand Down
2 changes: 1 addition & 1 deletion src/lib/components/dialog.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
let { children, text, id }: { children?: any; text: string; id: string } = $props();
</script>

<dialog {id} class="min-w-[500px] max-w-[600px] rounded-xl p-4 shadow-lg">
<dialog {id} class="min-w-[500px] max-w-[800px] rounded-xl p-4 shadow-lg">
<h2 class="p-4 text-lg">{text}</h2>
<menu class="mt-16 flex justify-end gap-4 p-4">
{#if children}
Expand Down
35 changes: 33 additions & 2 deletions src/lib/i18n/cn.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@
"startRecord": "开始录制",
"anchor": "主播",
"notInLive": "当前不在播",
"addPlan": "加入录制计划,主播开播后自动录制",
"addPlan": "加入录制计划",
"addPlanTips": "加入录制计划后,主播开播后会自动录制",
"forceIgnoreError": "强制忽略错误,加入录制计划并自动重试",
"getRecordHistoryFailed": "获取录制历史失败",
"deleteSuccess": "删除成功",
Expand Down Expand Up @@ -105,5 +106,35 @@
"Twitch": "Twitch",
"Huya": "虎牙",
"Youtube": "Youtube",
"Xiaohongshu": "小红书"
"Xiaohongshu": "小红书",
"source": "原画",
"full_hd1": "全高清",
"hd1": "高清",
"uhd": "超高清",
"hd": "高清",
"hd-60": "高清60帧",
"hd-30": "高清30帧",
"hd_60": "高清60帧",
"hd_30": "高清30帧",
"sd1": "标清",
"sd": "标清",
"sd2": "标清",
"ld": "流畅",
"ld1": "流畅",
"ld2": "流畅",
"default": "默认",
"ao": "仅音频",
"720p60": "720p 60fps",
"720p30": "720p 30fps",
"1080p60": "1080p 60fps",
"1080p30": "1080p 30fps",
"480p30": "480p 30fps",
"360p30": "360p 30fps",
"240p30": "240p 30fps",
"144p30": "144p 30fps",
"160p30": "160p 30fps",
"uhd_60": "超高清60帧",
"recentSearch": "最近查询",
"tryThese": "不知道如何开始?试试这些",
"gotoRecordHistory": "去录制历史中查看"
}
38 changes: 36 additions & 2 deletions src/lib/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
"startRecord": "Start Recording",
"anchor": "Anchor",
"notInLive": "Not currently live",
"addPlan": "Add to recording plan, automatically record when the anchor starts broadcasting",
"addPlan": "Add to record plan",
"addPlanTips": "Add to record plan, the program will automatically record it according to the plan",
"forceIgnoreError": "Force ignore error, add to recording plan and auto retry",
"getRecordHistoryFailed": "Failed to get record history",
"deleteSuccess": "Delete successful",
Expand Down Expand Up @@ -105,5 +106,38 @@
"Twitch": "Twitch",
"Huya": "HuYa",
"Youtube": "Youtube",
"Xiaohongshu": "XiaoHongShu"
"Xiaohongshu": "XiaoHongShu",
"origin": "Original",
"source": "Original",
"full_hd1": "Full HD",
"FULL_HD1": "Full HD",
"hd1": "HD",
"uhd": "Ultra HD",
"hd": "HD",
"hd-60": "HD 60fps",
"hd-30": "HD 30fps",
"hd_60": "HD 60fps",
"hd_30": "HD 30fps",
"sd": "SD",
"sd1": "SD",
"sd2": "SD",
"SD2": "SD",
"ld": "Smooth",
"ld1": "Smooth",
"ld2": "Smooth",
"default": "Default",
"ao": "Audio only",
"720p60": "720p 60fps",
"720p30": "720p 30fps",
"1080p60": "1080p 60fps",
"1080p30": "1080p 30fps",
"480p30": "480p 30fps",
"360p30": "360p 30fps",
"240p30": "240p 30fps",
"144p30": "144p 30fps",
"160p30": "160p 30fps",
"uhd_60": "Ultra HD 60fps",
"recentSearch": "Recent search",
"tryThese": "Don't know how to start? Try these",
"gotoRecordHistory": "Go to record history to view"
}
2 changes: 1 addition & 1 deletion src/lib/platform/bilibili.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { LiveStatus, PlatformKind, StreamingProtocol, type LiveInfo, type Stream } from '@/model';

export function getLiveInfoForKuaishou(url: string, html: string): LiveInfo {
export function getLiveInfoForBilibili(url: string, html: string): LiveInfo {
let info: LiveInfo = {
url,
title: '',
Expand Down
10 changes: 1 addition & 9 deletions src/lib/platform/kuaishou.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { LiveStatus, PlatformKind, StreamingProtocol, type LiveInfo, type Stream } from '@/model';

export function getLiveInfoForKuaishou(url: string, html: string): LiveInfo {
export function getLiveInfoForKuaishou(url: string): LiveInfo {
let info: LiveInfo = {
url,
title: '',
Expand All @@ -12,14 +12,6 @@ export function getLiveInfoForKuaishou(url: string, html: string): LiveInfo {
platformKind: PlatformKind.Kuaishou,
status: LiveStatus.NotLive
};
// match <script>window.__INITIAL_STATE__=(.*?);\(function\(\)\{var s;
let jsonStr = html.match(/<script>window.__INITIAL_STATE__=(.*?);\(function\(\)\{var s;/);
if (!jsonStr || jsonStr.length < 2) {
return info;
}
let json = JSON.parse(jsonStr[1]);
console.log('kuaishou', json);

return info;
}

Expand Down
122 changes: 80 additions & 42 deletions src/lib/platform/youtube.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { LiveStatus, PlatformKind, StreamingProtocol, type LiveInfo, type Stream } 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<LiveInfo> {
let info: LiveInfo = {
Expand All @@ -17,52 +18,89 @@ export async function getLiveInfoForYoutube(url: string): Promise<LiveInfo> {
try {
let videoId = url.split('v=')[1];
console.log('youtube video id', videoId);

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'
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'
}
});
// 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 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);
info.streams.push({
url: format.signatureCipher.split('url=')[1],
protocol: StreamingProtocol.Hls,
resolution: format.quality
});
}

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"|</script>)',
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] : '';
// 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"|</script>)',
// 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);
// 抛出一个错误
Expand Down
33 changes: 5 additions & 28 deletions src/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { PlatformKind, type LiveInfo } from './model';
import { getLiveInfoForDouyin } from './platform/douyin';
import { getLiveInfoForHuya } from './platform/huya';
import { getLiveInfoForKuaishou } from './platform/kuaishou';
import { getLiveInfoForTiktok } from './platform/tiktok';
import { getLiveInfoForTwitch } from './platform/twitch';
import { getLiveInfoForXiaohongshu } from './platform/xiaohongshu';
Expand Down Expand Up @@ -41,34 +42,6 @@ export function throttle<F extends (...args: any[]) => any>(
};
}

// 获取分辨率的中文表示
export function getResolutionName(resolution: string) {
switch (resolution.toLowerCase()) {
case 'full_hd1':
case 'origin':
return '原画';
case 'hd1':
case 'uhd':
case 'hd':
return '超清';
case 'hd-60':
return '超清 60 帧';
case 'sd1':
return '高清';
case 'sd2':
case 'sd':
return '标清';
case 'ld':
return '流畅';
case 'default':
return '默认';
case 'ao':
return '仅音频';
default:
return resolution;
}
}

// 格式化显示文件尺寸
export function formatFileSize(bytes: number) {
if (bytes === 0) return '0 B';
Expand Down Expand Up @@ -131,6 +104,8 @@ export function getPlatformKind(url: string): PlatformKind {
return PlatformKind.Twitch;
case url.startsWith('https://www.youtube.com/watch?v='):
return PlatformKind.Youtube;
case url.startsWith(''):
return PlatformKind.Kuaishou;
default:
return PlatformKind.Unknown;
}
Expand All @@ -152,6 +127,8 @@ export async function getLiveInfoForPlatform(url: string): Promise<LiveInfo> {
return getLiveInfoForYoutube(url);
case PlatformKind.Xiaohongshu:
return getLiveInfoForXiaohongshu(url);
case PlatformKind.Kuaishou:
return getLiveInfoForKuaishou(url);
default:
throw new Error('unknown platform');
}
Expand Down
Loading

0 comments on commit d14e9d4

Please sign in to comment.