@xiongtao.xt/tau-plugins 0.21.0 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # 更新记录
2
2
 
3
+ ## 0.22.0
4
+
5
+ - 新增 `context.video.register`,接入视频来源(视频网站、自建服务器等):插件识别链接、解析标题与清晰度,返回播放源描述(单文件、音视频分离或交给宿主解析器),下载、缓存与播放由宿主完成。
6
+ - 视频账号与代理配置通过 `settings.register({ target: "video" })` 注册,接收每个插件一份的 `videoConfig`。
7
+ - 新增 `services.videoExtractor.probe`,查询视频页面的标题与可下载格式;新增插件分类 `video`(在线视频)。
8
+
3
9
  ## 0.21.0
4
10
 
5
11
  - 新增 context.publish 发布提供方、publishConfig 配置表单和文件式/快照式提交协议,本地导出仍由宿主提供。
package/SERVICES.md CHANGED
@@ -15,6 +15,7 @@
15
15
  | `markdown.render(source, options?)` | Markdown 字符串 | `{ html, css, warnings }` |
16
16
  | `ffmpeg.probe(file, options?)` | `{ name, data: Blob }` | `{ format, streams }` |
17
17
  | `ffmpeg.run(input, options?)` | `{ files, args, outputs }` | `{ files, log }` |
18
+ | `videoExtractor.probe(url, options?)` | 视频页面地址,可选 `proxy`(SDK 0.22.0) | `{ title, cover?, formats }` |
18
19
 
19
20
  ## 在静态组件中获取服务
20
21
 
package/VIDEO.md ADDED
@@ -0,0 +1,40 @@
1
+ # 视频提供方
2
+
3
+ 需要 SDK 0.22.0 和支持该能力的客户端。通过 `context.video.register({ id: context.pluginId, name, create })` 注册,每个插件一个提供方。视频来源可以是视频网站,也可以是自建的视频服务(如 NAS)。
4
+
5
+ 启用后,编辑器、白板、项目视频笔记和学习资料的「添加视频」中会出现以 `name` 命名的入口。用户从该入口粘贴视频链接,客户端先用 `matches` 确认链接属于本插件,再调用 `resolve`。
6
+
7
+ 插件只负责视频来源本身的知识:识别链接、查询标题与可选清晰度、给出真实的流地址和所需请求头。选择弹窗、下载进度、合并音视频、缓存和播放都由客户端完成,插件不提供这些界面,也不传输视频字节。
8
+
9
+ ## 实例
10
+
11
+ `create(config)` 按当前插件配置同步创建实例,不发网络请求。实例需要实现四个方法:
12
+
13
+ - `matches(url)`:同步判断链接是否属于本来源,不访问网络。链接属于其他已启用插件时,客户端会提示用户改用对应入口。
14
+ - `resolve(url, signal)`:返回 `{ title, cover?, variants, defaultVariant? }`。`variants` 是可选清晰度(至少一项),每项为 `{ label, hint?, ref }`。
15
+ - `cacheKey(ref)`:返回稳定的缓存身份,应包含清晰度。视频已缓存时客户端不再调用 `getPlayback`,可以离线播放。
16
+ - `getPlayback(ref, signal)`:每次缓存前现取播放源,可以返回会过期的直链。
17
+
18
+ ## 视频引用
19
+
20
+ 用户选中的 `ref` 原样写入文档和数据库,以后只能通过它找回视频。它必须是可长期保存的 JSON,例如视频 ID 加清晰度,不要放会过期的直链、Cookie 或令牌。`type`、`title`、`thumbnail` 三个键由客户端占用,引用中不要使用。
21
+
22
+ ## 播放源
23
+
24
+ `getPlayback` 返回以下三种之一:
25
+
26
+ - `{ kind: "file", stream }`:单个音视频合一的文件。
27
+ - `{ kind: "split", video, audio }`:音视频分离的流,客户端分别下载后无损合并。
28
+ - `{ kind: "extract", url, videoFormat?, audioFormat?, proxy? }`:交给客户端内置的视频解析器下载,格式 ID 来自 `context.services.videoExtractor.probe`。
29
+
30
+ `stream` 为 `{ url, headers? }`。需要 Referer、Cookie 等请求头时放在 `headers` 中,客户端只在下载时使用,不写入文档或缓存索引。网络请求使用 `context.fetch` 并传入方法的 signal,它可以设置这些请求头。
31
+
32
+ ## 视频解析服务
33
+
34
+ `context.services.videoExtractor.probe(url, { proxy?, signal? })` 返回 `{ title, cover?, duration?, formats }`,`formats` 每项包含 `id`、`videoOnly`、`audioOnly`、`ext`、`width`、`height`、`fps`、`bitrate`、`size`、`note`。插件根据格式列表组织清晰度,再返回 `kind: "extract"` 的播放源;插件不能自行启动外部程序。
35
+
36
+ ## 配置
37
+
38
+ 账号、Cookie 或代理通过 `context.settings.register({ id, title, target: "video", component })` 注册配置视图,挂载在「设置 · 账号与连接 · 在线视频」。组件接收 `PluginSettingsProps.videoConfig`:`value` 和 `save(value)`,每个插件一份配置,保存后下一次调用 `create` 即读到新值。保存失败时保留草稿,成功后才提示已保存。
39
+
40
+ 插件停用或调用方取消时,进行中的请求会收到取消信号,迟到的结果被丢弃。插件未启用时,已保存的视频显示需要启用插件的提示,原数据保留。移动端和发布站点不运行插件,插件提供的视频在那里只显示说明。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xiongtao.xt/tau-plugins",
3
- "version": "0.21.0",
3
+ "version": "0.22.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -46,6 +46,7 @@ var PLUGIN_CATEGORIES = [
46
46
  { id: "blog-publish", label: "\u535A\u5BA2\u53D1\u5E03" },
47
47
  { id: "ai", label: "AI" },
48
48
  { id: "voice", label: "\u8BED\u97F3" },
49
+ { id: "video", label: "\u5728\u7EBF\u89C6\u9891" },
49
50
  { id: "page", label: "\u9875\u9762" }
50
51
  ];
51
52
  var PLUGIN_KEYWORD_LIMIT = 5;
@@ -23,6 +23,9 @@ export declare const PLUGIN_CATEGORIES: readonly [{
23
23
  }, {
24
24
  readonly id: "voice";
25
25
  readonly label: "语音";
26
+ }, {
27
+ readonly id: "video";
28
+ readonly label: "在线视频";
26
29
  }, {
27
30
  readonly id: "page";
28
31
  readonly label: "页面";
@@ -11,6 +11,7 @@ import type { PluginSync } from "./sync";
11
11
  import type { PluginUpload } from "./upload";
12
12
  import type { PluginTTS } from "./tts";
13
13
  import type { PluginImages } from "./images";
14
+ import type { PluginVideo } from "./video";
14
15
  import type { PluginPublish } from "./publish";
15
16
  import type { PluginSidebar } from "./sidebar";
16
17
  import type { PluginExcerpts, PluginMaterials } from "./excerpts";
@@ -47,6 +48,7 @@ export interface PluginContext {
47
48
  readonly upload: PluginUpload;
48
49
  readonly tts: PluginTTS;
49
50
  readonly images: PluginImages;
51
+ readonly video: PluginVideo;
50
52
  readonly publish: PluginPublish;
51
53
  readonly sidebar: PluginSidebar;
52
54
  readonly excerpts: PluginExcerpts;
@@ -2,6 +2,7 @@ import type { PluginDefinition } from "./contract";
2
2
  export type * from "./upload";
3
3
  export type * from "./tts";
4
4
  export type * from "./images";
5
+ export type * from "./video";
5
6
  export type * from "./publish";
6
7
  export type { JsonValue, Disposable, PluginStorage, PluginCommand, PluginContext, PluginInstance, PluginDefinition, } from "./contract";
7
8
  export type { PluginSettings, PluginSettingsDefinition, PluginSettingsProps, PluginSettingsHandle, } from "./settings";
@@ -39,6 +39,26 @@ export interface MediaInfo {
39
39
  channels?: number;
40
40
  }[];
41
41
  }
42
+ export interface VideoFormat {
43
+ id: string;
44
+ /** 只含视频或只含音频的流;两者都为 false 时是音视频合一的流。 */
45
+ videoOnly: boolean;
46
+ audioOnly: boolean;
47
+ ext?: string;
48
+ width?: number;
49
+ height?: number;
50
+ fps?: number;
51
+ /** 码率,单位 kbps。 */
52
+ bitrate?: number;
53
+ size?: number;
54
+ note?: string;
55
+ }
56
+ export interface VideoProbeResult {
57
+ title: string;
58
+ cover?: string;
59
+ duration?: number;
60
+ formats: VideoFormat[];
61
+ }
42
62
  export interface PluginServices {
43
63
  git: {
44
64
  publishSnapshot(input: GitSnapshotRequest, options?: ServiceOptions): Promise<void>;
@@ -96,6 +116,12 @@ export interface PluginServices {
96
116
  log: string;
97
117
  }>;
98
118
  };
119
+ /** 宿主视频解析器(yt-dlp),只查询页面的标题与可下载格式,下载经视频播放源交给宿主。 */
120
+ videoExtractor: {
121
+ probe(url: string, options?: ServiceOptions & {
122
+ proxy?: string;
123
+ }): Promise<VideoProbeResult>;
124
+ };
99
125
  }
100
126
  /** 每个调用同时受调用方与插件实例生命周期约束。 */
101
127
  export declare function bindPluginServices(adapter: PluginServices | undefined, lifetime: AbortSignal, assertActive: () => void): PluginServices;
@@ -29,6 +29,10 @@ export interface PluginSettingsProps {
29
29
  readonly value: JsonValue;
30
30
  save(value: JsonValue): Promise<void>;
31
31
  };
32
+ readonly videoConfig?: {
33
+ readonly value: JsonValue;
34
+ save(value: JsonValue): Promise<void>;
35
+ };
32
36
  readonly syncConfig?: {
33
37
  readonly value: JsonValue;
34
38
  save(value: JsonValue): Promise<void>;
@@ -39,7 +43,7 @@ export interface PluginSettingsDefinition {
39
43
  title: string;
40
44
  description?: string;
41
45
  /** 提供方配置表单挂载到对应宿主设置;普通插件设置省略。 */
42
- target?: "sync" | "upload" | "tts" | "images" | "publish";
46
+ target?: "sync" | "upload" | "tts" | "images" | "video" | "publish";
43
47
  component: ComponentType<PluginSettingsProps>;
44
48
  }
45
49
  export interface PluginSettingsHandle extends Disposable {
@@ -0,0 +1,58 @@
1
+ import type { Disposable, JsonValue } from "./contract";
2
+ /**
3
+ * 插件私有的视频引用,宿主原样写入文档与数据库;必须可长期保存,不放会过期的直链。
4
+ * 宿主在元数据中占用 type / title / thumbnail 三个键,引用不得使用。
5
+ */
6
+ export type VideoRef = {
7
+ readonly [key: string]: JsonValue;
8
+ };
9
+ export interface VideoStream {
10
+ url: string;
11
+ /** 宿主下载时附带的请求头(如 Referer、Cookie),不写入文档或缓存索引。 */
12
+ headers?: Record<string, string>;
13
+ }
14
+ /** 播放源描述:插件只说明流在哪里,下载、合并、缓存与播放由宿主完成。 */
15
+ export type VideoPlaybackSource = {
16
+ kind: "file";
17
+ stream: VideoStream;
18
+ } | {
19
+ kind: "split";
20
+ video: VideoStream;
21
+ audio: VideoStream;
22
+ } | {
23
+ kind: "extract";
24
+ /** 交给宿主视频解析服务的页面地址。 */
25
+ url: string;
26
+ videoFormat?: string;
27
+ audioFormat?: string;
28
+ proxy?: string;
29
+ };
30
+ export interface VideoVariant {
31
+ label: string;
32
+ hint?: string;
33
+ ref: VideoRef;
34
+ }
35
+ export interface ResolvedVideo {
36
+ title: string;
37
+ cover?: string;
38
+ /** 可选清晰度等变体,至少一项;用户选中项的 ref 被保存。 */
39
+ variants: readonly VideoVariant[];
40
+ defaultVariant?: number;
41
+ }
42
+ export interface VideoInstance {
43
+ /** 同步识别链接是否属于本提供方,不发网络请求。 */
44
+ matches(url: string): boolean;
45
+ resolve(url: string, signal: AbortSignal): Promise<ResolvedVideo>;
46
+ /** 稳定的缓存身份(应包含清晰度);已缓存时宿主不再调用 getPlayback。 */
47
+ cacheKey(ref: VideoRef): string;
48
+ getPlayback(ref: VideoRef, signal: AbortSignal): Promise<VideoPlaybackSource>;
49
+ }
50
+ export interface VideoProviderDefinition {
51
+ id: string;
52
+ name: string;
53
+ /** 按当前插件配置同步创建实例,不发网络请求。 */
54
+ create(config: JsonValue): VideoInstance;
55
+ }
56
+ export interface PluginVideo {
57
+ register(definition: VideoProviderDefinition): Disposable;
58
+ }