From 4e841e204ce665aff601f84409979e49a2957a37 Mon Sep 17 00:00:00 2001 From: WakelessSloth56 Date: Mon, 22 Jul 2024 14:15:09 +0800 Subject: [PATCH 1/8] feat: component saveVideoMetadata Co-authored-by: lainio24 --- .vscode/settings.json | 4 +- .../lib/components/video/metadata/Plugin.vue | 33 ++++++ .../video/metadata/SaveMetadata.vue | 45 ++++++++ .../lib/components/video/metadata/index.ts | 60 ++++++++++ .../lib/components/video/metadata/utils.ts | 105 ++++++++++++++++++ src/components/video/video-info.ts | 24 ++-- 6 files changed, 260 insertions(+), 11 deletions(-) create mode 100644 registry/lib/components/video/metadata/Plugin.vue create mode 100644 registry/lib/components/video/metadata/SaveMetadata.vue create mode 100644 registry/lib/components/video/metadata/index.ts create mode 100644 registry/lib/components/video/metadata/utils.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 586e80f5f1..2d7ebbf747 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -43,6 +43,7 @@ "durl", "epid", "esbuild", + "ffmetadata", "flac", "Fullscreen", "githubusercontent", @@ -135,5 +136,6 @@ "tsconfig.json": "tsconfig.*.json", "package.json": "package-lock.json, yarn.lock, pnpm-lock.yaml, .*", "index.ts": "index.*.ts, index.md, index.*.md" - } + }, + "typescript.tsdk": "node_modules\\typescript\\lib" } diff --git a/registry/lib/components/video/metadata/Plugin.vue b/registry/lib/components/video/metadata/Plugin.vue new file mode 100644 index 0000000000..b0e0df45f2 --- /dev/null +++ b/registry/lib/components/video/metadata/Plugin.vue @@ -0,0 +1,33 @@ + + diff --git a/registry/lib/components/video/metadata/SaveMetadata.vue b/registry/lib/components/video/metadata/SaveMetadata.vue new file mode 100644 index 0000000000..de27a843e4 --- /dev/null +++ b/registry/lib/components/video/metadata/SaveMetadata.vue @@ -0,0 +1,45 @@ + + + diff --git a/registry/lib/components/video/metadata/index.ts b/registry/lib/components/video/metadata/index.ts new file mode 100644 index 0000000000..3b4542e909 --- /dev/null +++ b/registry/lib/components/video/metadata/index.ts @@ -0,0 +1,60 @@ +import { defineComponentMetadata } from '@/components/define' +import { PackageEntry } from '@/core/download' +import { hasVideo } from '@/core/spin-query' +import { videoUrls } from '@/core/utils/urls' +import { DownloadVideoAssets } from '../download/types' +import { generateFFMetadataBlob } from './utils' + +const author = [ + { + name: 'WakelessSloth56', + link: 'https://github.com/WakelessSloth56', + }, + { + name: 'LainIO24', + link: 'https://github.com/LainIO24', + }, +] + +export const component = defineComponentMetadata({ + name: 'saveVideoMetadata', + displayName: '保存视频元数据', + description: + '保存视频元数据(标题、描述、UP、章节等)为 [FFMETADATA](https://ffmpeg.org/ffmpeg-formats.html#metadata) 格式', + author, + tags: [componentsTags.video], + entry: none, + urlInclude: videoUrls, + widget: { + condition: hasVideo, + component: () => import('./SaveMetadata.vue').then(m => m.default), + }, + plugin: { + displayName: '下载视频 - 保存元数据', + author, + setup: ({ addData }) => { + addData('downloadVideo.assets', async (assets: DownloadVideoAssets[]) => { + assets.push({ + name: 'saveMetadata', + displayName: '保存元数据', + getAssets: async (infos, instance: { saveMetadata: boolean }) => { + const { saveMetadata: enabled } = instance + if (enabled) { + const result: PackageEntry[] = [] + for (const info of infos) { + result.push({ + name: `${info.input.title}.ffmetadata.txt`, + data: await generateFFMetadataBlob(info.input.aid, info.input.cid), + options: {}, + }) + } + return result + } + return [] + }, + component: () => import('./Plugin.vue').then(m => m.default), + }) + }) + }, + }, +}) diff --git a/registry/lib/components/video/metadata/utils.ts b/registry/lib/components/video/metadata/utils.ts new file mode 100644 index 0000000000..1b8b339e2b --- /dev/null +++ b/registry/lib/components/video/metadata/utils.ts @@ -0,0 +1,105 @@ +import { VideoInfo, VideoPageInfo } from '@/components/video/video-info' +import { VideoQuality } from '@/components/video/video-quality' +import { bilibiliApi, getJsonWithCredentials } from '@/core/ajax' +import { meta } from '@/core/meta' + +function escape(s: string) { + return s.replace(/[=;#\\\n]/g, r => `\\${r}`) +} + +function ff(key: string, value: any, prefix = true) { + return `${prefix ? 'bilibili_' : ''}${key}=${escape(lodash.toString(value))}` +} + +export interface ViewPoint { + content: string + from: number + to: number + image: string +} + +export class VideoMetadata { + #aid: string + #cid: number | string + + basic: VideoInfo + + viewPoints: ViewPoint[] + page: VideoPageInfo + quality?: VideoQuality + + constructor(aid: string, cid: number | string) { + this.#aid = aid + this.#cid = cid + this.basic = new VideoInfo(aid) + } + + async fetch() { + await this.basic.fetchInfo() + this.page = this.basic.pages.filter(p => p.cid === parseInt(this.#cid))[0] + + const playInfo = await bilibiliApi( + getJsonWithCredentials( + `https://api.bilibili.com/x/player/wbi/v2?aid=${this.#aid}&cid=${this.#cid}`, + ), + ) + + this.viewPoints = lodash.get(playInfo, 'view_points', []) as ViewPoint[] + } +} + +export async function generateFFMetadata( + aid: string = unsafeWindow.aid, + cid: string = unsafeWindow.cid, +) { + const data = new VideoMetadata(aid, cid) + await data.fetch() + const info = data.basic + + const lines = [ + ';FFMETADATA1', + `;generated by Bilibili-Evolved v${meta.compilationInfo.version}`, + ff('title', `${info.title} - ${data.page.title}`, false), + ff('title', info.title), + ff('description', info.description), + ff('aid', info.aid), + ff('bvid', info.bvid), + ff('cid', data.page.cid), + ff('page_title', data.page.title), + ff('page', data.page.pageNumber), + ff('pages', info.pages.length), + ff('up_name', info.up.name), + ff('up_uid', info.up.uid), + ] + + if (data.quality) { + lines.push(ff('quality', data.quality.value)) + lines.push(ff('quality_label', data.quality.name)) + } + + if (data.viewPoints) { + for (const chapter of data.viewPoints) { + lines.push( + ...[ + '[CHAPTER]', + 'TIMEBASE=1/1', + ff('START', chapter.from, false), + ff('END', chapter.to, false), + ff('title', chapter.content, false), + ], + ) + } + } + + console.info(lines.join('\n')) + return lines.join('\n') +} + +export async function generateFFMetadataBlob( + aid: string = unsafeWindow.aid, + cid: string = unsafeWindow.cid, +) { + return new Blob([await generateFFMetadata(aid, cid)], { + type: 'text/plain', + }) +} diff --git a/src/components/video/video-info.ts b/src/components/video/video-info.ts index 43f5d3af78..9251d5689b 100644 --- a/src/components/video/video-info.ts +++ b/src/components/video/video-info.ts @@ -1,5 +1,17 @@ import { getJsonWithCredentials, getText } from '@/core/ajax' +export interface UpInfo { + uid: number + name: string + faceUrl: string +} + +export interface VideoPageInfo { + cid: number + title: string + pageNumber: number +} + export class VideoInfo { aid: string bvid: string @@ -13,16 +25,8 @@ export class VideoInfo { tagName: string title: string description: string - up: { - uid: number - name: string - faceUrl: string - } - pages: { - cid: number - title: string - pageNumber: number - }[] + up: UpInfo + pages: VideoPageInfo[] constructor(id: string, bvid = false) { if (bvid) { From 7d5286d30aafb8680acec71fc42ef7cd9bfcb0bb Mon Sep 17 00:00:00 2001 From: WakelessSloth56 Date: Mon, 22 Jul 2024 19:44:46 +0800 Subject: [PATCH 2/8] feat: component saveVideoMetadata more fields author, category_id, category_name, publish_date Co-authored-by: lainio24 --- .../lib/components/video/metadata/SaveMetadata.vue | 2 +- registry/lib/components/video/metadata/index.ts | 2 +- .../video/metadata/{utils.ts => metadata.ts} | 14 ++++++++++++-- 3 files changed, 14 insertions(+), 4 deletions(-) rename registry/lib/components/video/metadata/{utils.ts => metadata.ts} (86%) diff --git a/registry/lib/components/video/metadata/SaveMetadata.vue b/registry/lib/components/video/metadata/SaveMetadata.vue index de27a843e4..cb6049166a 100644 --- a/registry/lib/components/video/metadata/SaveMetadata.vue +++ b/registry/lib/components/video/metadata/SaveMetadata.vue @@ -15,7 +15,7 @@ import { DefaultWidget } from '@/ui' import { logError } from '@/core/utils/log' import { DownloadPackage } from '@/core/download' import { getFriendlyTitle } from '@/core/utils/title' -import { generateFFMetadataBlob } from './utils' +import { generateFFMetadataBlob } from './metadata' export default Vue.extend({ components: { diff --git a/registry/lib/components/video/metadata/index.ts b/registry/lib/components/video/metadata/index.ts index 3b4542e909..d29ae6034a 100644 --- a/registry/lib/components/video/metadata/index.ts +++ b/registry/lib/components/video/metadata/index.ts @@ -3,7 +3,7 @@ import { PackageEntry } from '@/core/download' import { hasVideo } from '@/core/spin-query' import { videoUrls } from '@/core/utils/urls' import { DownloadVideoAssets } from '../download/types' -import { generateFFMetadataBlob } from './utils' +import { generateFFMetadataBlob } from './metadata' const author = [ { diff --git a/registry/lib/components/video/metadata/utils.ts b/registry/lib/components/video/metadata/metadata.ts similarity index 86% rename from registry/lib/components/video/metadata/utils.ts rename to registry/lib/components/video/metadata/metadata.ts index 1b8b339e2b..304cac9261 100644 --- a/registry/lib/components/video/metadata/utils.ts +++ b/registry/lib/components/video/metadata/metadata.ts @@ -59,12 +59,20 @@ export async function generateFFMetadata( const lines = [ ';FFMETADATA1', `;generated by Bilibili-Evolved v${meta.compilationInfo.version}`, + `;generated on ${new Date().toLocaleString()}`, + // Standard fields ff('title', `${info.title} - ${data.page.title}`, false), + ff('description', info.description, false), + ff('author', info.up.name, false), + // Custom fields ff('title', info.title), ff('description', info.description), + ff('publish_date', new Date(info.pubdate * 1000).toLocaleString()), ff('aid', info.aid), ff('bvid', info.bvid), ff('cid', data.page.cid), + ff('category_id', info.tagId), + ff('category_name', info.tagName), ff('page_title', data.page.title), ff('page', data.page.pageNumber), ff('pages', info.pages.length), @@ -91,8 +99,10 @@ export async function generateFFMetadata( } } - console.info(lines.join('\n')) - return lines.join('\n') + const result = lines.join('\n') + + console.debug(result) + return result } export async function generateFFMetadataBlob( From da2c1f88518aa1b9e02873782522bb06677de3e1 Mon Sep 17 00:00:00 2001 From: WakelessSloth56 Date: Mon, 22 Jul 2024 20:38:28 +0800 Subject: [PATCH 3/8] feat: component saveVideoMetadata save ogm chapters Co-authored-by: lainio24 --- .../lib/components/video/metadata/Plugin.vue | 25 +++++-- .../video/metadata/SaveMetadata.vue | 17 +++-- .../lib/components/video/metadata/index.ts | 29 +++++--- .../lib/components/video/metadata/metadata.ts | 67 ++++++++++++++----- 4 files changed, 98 insertions(+), 40 deletions(-) diff --git a/registry/lib/components/video/metadata/Plugin.vue b/registry/lib/components/video/metadata/Plugin.vue index b0e0df45f2..a843f37a1b 100644 --- a/registry/lib/components/video/metadata/Plugin.vue +++ b/registry/lib/components/video/metadata/Plugin.vue @@ -2,31 +2,42 @@
保存元数据:
- + + +
diff --git a/registry/lib/plugins/video/download/wasm-output/handler.ts b/registry/lib/plugins/video/download/wasm-output/handler.ts index 1f97330e3e..3a22090ec5 100644 --- a/registry/lib/plugins/video/download/wasm-output/handler.ts +++ b/registry/lib/plugins/video/download/wasm-output/handler.ts @@ -87,7 +87,7 @@ async function single( ) } -export async function run(action: DownloadVideoAction) { +export async function run(action: DownloadVideoAction, muxWithMetadata: boolean) { if (!ffmpeg.loaded) { await loadFFmpeg() } @@ -95,15 +95,17 @@ export async function run(action: DownloadVideoAction) { const { infos: pages, extraAssets } = action let ffmetadata: PackageEntry[] - const extraAssetsForBrowser = [] - for (const { asset, instance } of extraAssets) { - if (!ffmetadata && asset.name === 'saveVideoMetadata' && instance.type === 'ffmetadata') { - ffmetadata = await asset.getAssets(pages, instance) - } else { - extraAssetsForBrowser.push({ asset, instance }) + if (muxWithMetadata) { + const extraAssetsForBrowser = [] + for (const { asset, instance } of extraAssets) { + if (!ffmetadata && asset.name === 'saveVideoMetadata' && instance.type === 'ffmetadata') { + ffmetadata = await asset.getAssets(pages, instance) + } else { + extraAssetsForBrowser.push({ asset, instance }) + } } + action.extraAssets = extraAssetsForBrowser } - action.extraAssets = extraAssetsForBrowser const { dashAudioExtension, dashFlacAudioExtension, dashVideoExtension } = getComponentSettings('downloadVideo').options diff --git a/registry/lib/plugins/video/download/wasm-output/index.ts b/registry/lib/plugins/video/download/wasm-output/index.ts index 3cde08f64c..d5039f2592 100644 --- a/registry/lib/plugins/video/download/wasm-output/index.ts +++ b/registry/lib/plugins/video/download/wasm-output/index.ts @@ -19,14 +19,15 @@ export const plugin: PluginMetadata = { outputs.push({ name: 'wasm', displayName: 'WASM', - description: `${desc},运行过程中请勿关闭页面,初次使用或清除缓存后需要加载约 30 MB 的 WASM 文件`, - runAction: async action => { + description: `${desc}。运行过程中请勿关闭页面,初次使用或清除缓存后需要加载约 30 MB 的 WASM 文件。`, + runAction: async (action, instance) => { try { - await run(action) + await run(action, instance.muxWithMetadata) } catch (error) { Toast.error(String(error), title) } }, + component: () => import('./Config.vue').then(m => m.default), }) }) },