UNPKG

polyv-live-cli

Version:

CLI tool for managing PolyV live streaming services.

533 lines (518 loc) 27.8 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.validateOutputFormat = validateOutputFormat; exports.validateYN = validateYN; exports.validatePlaybackType = validatePlaybackType; exports.validatePlaybackOrigin = validatePlaybackOrigin; exports.validateListType = validateListType; exports.registerRecordCommands = registerRecordCommands; const manager_1 = require("../config/manager"); const auth_adapter_1 = require("../config/auth-adapter"); const errors_1 = require("../utils/errors"); const api_command_1 = require("../utils/api-command"); async function loadAuthAndServiceConfig(parentOptions) { const authResult = auth_adapter_1.authAdapter.tryGetAuthConfig(parentOptions); if (!authResult) { throw new Error(auth_adapter_1.authAdapter.getStatusMessage(parentOptions)); } let configResult; try { configResult = await manager_1.configManager.load({ cliOptions: parentOptions, }); } catch (error) { if (error instanceof Error && error.message.includes('Auth configuration is incomplete')) { configResult = { config: { baseUrl: 'https://api.polyv.net', timeout: 30000, debug: false } }; } else { throw error; } } const serviceConfig = { baseUrl: configResult.config.baseUrl, timeout: configResult.config.timeout, debug: configResult.config.debug }; const isVerbose = !!parentOptions.verbose; if (isVerbose) { console.log(`🔐 Authentication Source: ${authResult.source}`); if (authResult.accountName) { console.log(`👤 Account: ${authResult.accountName}`); } console.log(''); } const result = { authConfig: authResult.config, serviceConfig, isVerbose, }; if (authResult.source) { result.authSource = authResult.source; } if (authResult.accountName) { result.accountName = authResult.accountName; } return result; } function validateOutputFormat(value) { if (!['table', 'json'].includes(value)) { throw new Error('Output format must be either "table" or "json"'); } return value; } function validateYN(value) { const upper = value.toUpperCase(); if (!['Y', 'N'].includes(upper)) { throw new Error('Value must be either "Y" or "N"'); } return upper; } function validatePlaybackType(value) { if (!['single', 'list'].includes(value)) { throw new Error('Playback type must be either "single" or "list"'); } return value; } function validatePlaybackOrigin(value) { if (!['record', 'playback', 'vod', 'material'].includes(value)) { throw new Error('Playback origin must be one of: record, playback, vod, material'); } return value; } function validateListType(value) { if (!['playback', 'vod'].includes(value)) { throw new Error('List type must be either "playback" or "vod"'); } return value; } async function runRecordAction(program, action) { try { const { authConfig, serviceConfig } = await loadAuthAndServiceConfig(program.opts()); const { RecordHandler } = await Promise.resolve().then(() => __importStar(require('../handlers/record.handler'))); await action(new RecordHandler(authConfig, serviceConfig)); } catch (error) { (0, errors_1.logError)(error instanceof Error ? error : new Error(String(error))); process.exit(1); } } function registerRecordCommands(program) { const recordCmd = program.command('record'); recordCmd.description('录制设置管理命令'); const settingCmd = recordCmd.command('setting'); settingCmd.description('回放设置管理'); const settingGetCmd = settingCmd .command('get') .description(`获取频道回放设置 Examples: $ polyv-live-cli record setting get -c "2588188" $ polyv-live-cli record setting get -c "2588188" -o json`) .requiredOption('-c, --channel-id <channelId>', '频道ID') .option('-o, --output <format>', '输出格式 (table|json)', validateOutputFormat, 'table') .action(async function (options) { try { const parentOptions = this.optsWithGlobals(); const { authConfig, serviceConfig } = await loadAuthAndServiceConfig(parentOptions); const { RecordHandler } = await Promise.resolve().then(() => __importStar(require('../handlers/record.handler'))); const recordHandler = new RecordHandler(authConfig, serviceConfig); const getOptions = { channelId: options.channelId, output: options.output }; await recordHandler.getPlaybackSetting(getOptions); } catch (error) { (0, errors_1.logError)(error instanceof Error ? error : new Error(String(error))); process.exit(1); } }); settingGetCmd.addHelpText('after', ` Examples: # 获取频道回放设置 $ polyv-live-cli record setting get -c "2588188" # JSON格式输出 $ polyv-live-cli record setting get -c "2588188" -o json Options: -c, --channel-id 频道ID (必填) -o, --output 输出格式: table 或 json,默认为table `); const settingSetCmd = settingCmd .command('set') .description(`设置频道回放设置 Examples: $ polyv-live-cli record setting set -c "2588188" --playback-enabled Y $ polyv-live-cli record setting set -c "2588188" --playback-enabled Y --type single --origin playback --video-id "73801f70c8" $ polyv-live-cli record setting set -c "2588188" --playback-enabled Y --playback-multiplier-enabled Y`) .requiredOption('-c, --channel-id <channelId>', '频道ID') .option('--playback-enabled <value>', '回放开关 (Y|N)', validateYN) .option('--type <type>', '回放方式 (single|list)', validatePlaybackType) .option('--origin <origin>', '回放来源 (record|playback|vod|material)', validatePlaybackOrigin) .option('--video-id <videoId>', '回放的视频ID') .option('--playback-multiplier-enabled <value>', '倍数播放开关 (Y|N)', validateYN) .option('--playback-progress-bar-enabled <value>', '进度条开关 (Y|N)', validateYN) .option('--chat-playback-enabled <value>', '聊天互动重现开关 (Y|N)', validateYN) .option('--product-playback-enabled <value>', '商品库开关 (Y|N)', validateYN) .option('-o, --output <format>', '输出格式 (table|json)', validateOutputFormat, 'table') .action(async function (options) { try { const parentOptions = this.optsWithGlobals(); const { authConfig, serviceConfig } = await loadAuthAndServiceConfig(parentOptions); const { RecordHandler } = await Promise.resolve().then(() => __importStar(require('../handlers/record.handler'))); const recordHandler = new RecordHandler(authConfig, serviceConfig); const setOptions = { channelId: options.channelId, output: options.output }; if (options.playbackEnabled !== undefined) setOptions.playbackEnabled = options.playbackEnabled; if (options.type !== undefined) setOptions.type = options.type; if (options.origin !== undefined) setOptions.origin = options.origin; if (options.videoId !== undefined) setOptions.videoId = options.videoId; if (options.playbackMultiplierEnabled !== undefined) setOptions.playbackMultiplierEnabled = options.playbackMultiplierEnabled; if (options.playbackProgressBarEnabled !== undefined) setOptions.playbackProgressBarEnabled = options.playbackProgressBarEnabled; if (options.chatPlaybackEnabled !== undefined) setOptions.chatPlaybackEnabled = options.chatPlaybackEnabled; if (options.productPlaybackEnabled !== undefined) setOptions.productPlaybackEnabled = options.productPlaybackEnabled; await recordHandler.setPlaybackSetting(setOptions); } catch (error) { (0, errors_1.logError)(error instanceof Error ? error : new Error(String(error))); process.exit(1); } }); settingSetCmd.addHelpText('after', ` Examples: # 开启回放 $ polyv-live-cli record setting set -c "2588188" --playback-enabled Y # 设置单个回放方式,指定视频 $ polyv-live-cli record setting set -c "2588188" --playback-enabled Y --type single --origin playback --video-id "73801f70c8" # 开启倍数播放 $ polyv-live-cli record setting set -c "2588188" --playback-enabled Y --playback-multiplier-enabled Y Options: -c, --channel-id 频道ID (必填) --playback-enabled 回放开关: Y(开启) 或 N(关闭) --type 回放方式: single(单个回放) 或 list(列表回放) --origin 回放来源: record(录制文件), playback(回放列表), vod(点播列表), material(素材库) --video-id 回放的视频ID --playback-multiplier-enabled 倍数播放开关: Y 或 N --playback-progress-bar-enabled 进度条开关: Y 或 N --chat-playback-enabled 聊天互动重现开关: Y 或 N --product-playback-enabled 商品库开关: Y 或 N -o, --output 输出格式: table 或 json,默认为table `); const convertCmd = recordCmd .command('convert') .description(`转存录制文件到点播 Examples: $ polyv-live-cli record convert -c "2588188" --session-id "fvlyin8qz3" --file-name "测试转存" $ polyv-live-cli record convert -c "2588188" --session-id "fvlyin8qz3" --file-name "测试转存" --to-play-list Y --set-as-default Y $ polyv-live-cli record convert -c "2588188" --file-ids "fileId1,fileId2" --async`) .requiredOption('-c, --channel-id <channelId>', '频道ID') .option('--file-name <fileName>', '转存后的点播视频名称(同步模式必填,异步模式可选)') .option('--session-id <sessionId>', '直播场次ID(同步模式:转存该场次的录制)') .option('--file-url <fileUrl>', '录制文件地址(同步模式:与 --session-id 二选一)') .option('--file-ids <fileIds>', '录制文件ID,逗号分隔(异步模式必填,可通过 `record file list` 获取)') .option('--user-id <userId>', '账号userId(同步模式;默认使用当前账号userId)') .option('--to-play-list <value>', '是否存放到回放列表 (Y|N)', validateYN) .option('--set-as-default <value>', '是否设为默认回放视频 (Y|N)', validateYN) .option('--async', '使用异步转存模式(需 --file-ids)', false) .option('--callback-url <url>', '转存完成后的回调URL(异步模式)') .option('-f, --force', '跳过确认提示') .option('-o, --output <format>', '输出格式 (table|json)', validateOutputFormat, 'table') .action(async function (options) { try { const parentOptions = this.optsWithGlobals(); const { authConfig, serviceConfig } = await loadAuthAndServiceConfig(parentOptions); const { RecordHandler } = await Promise.resolve().then(() => __importStar(require('../handlers/record.handler'))); const recordHandler = new RecordHandler(authConfig, serviceConfig); const convertOptions = { channelId: options.channelId, fileName: options.fileName, async: options.async, force: options.force, output: options.output }; if (options.sessionId !== undefined) convertOptions.sessionId = options.sessionId; if (options.fileUrl !== undefined) convertOptions.fileUrl = options.fileUrl; if (options.fileIds !== undefined) convertOptions.fileIds = options.fileIds; if (options.userId !== undefined) convertOptions.userId = options.userId; if (options.toPlayList !== undefined) convertOptions.toPlayList = options.toPlayList; if (options.setAsDefault !== undefined) convertOptions.setAsDefault = options.setAsDefault; if (options.callbackUrl !== undefined) convertOptions.callbackUrl = options.callbackUrl; await recordHandler.recordConvert(convertOptions); } catch (error) { (0, errors_1.logError)(error instanceof Error ? error : new Error(String(error))); process.exit(1); } }); convertCmd.addHelpText('after', ` Examples: # 同步转存:通过场次ID转存该场次的录制到点播 $ polyv-live-cli record convert -c "2588188" --session-id "fvlyin8qz3" --file-name "测试转存" # 转存并添加到回放列表、设为默认回放 $ polyv-live-cli record convert -c "2588188" --session-id "fvlyin8qz3" --file-name "测试转存" --to-play-list Y --set-as-default Y # 异步转存:通过录制文件ID(fileIds)转存,可一次转存多个文件 $ polyv-live-cli record convert -c "2588188" --file-ids "fileId1,fileId2" --async Options: -c, --channel-id 频道ID (必填) --file-name 转存后的点播视频名称(同步模式必填,异步模式可选) --session-id 直播场次ID(同步模式) --file-url 录制文件地址(同步模式,与 --session-id 二选一) --file-ids 录制文件ID,逗号分隔(异步模式必填) --user-id 账号userId(同步模式;默认使用当前账号userId) --to-play-list 是否存放到回放列表: Y 或 N --set-as-default 是否设为默认回放视频: Y 或 N --async 使用异步转存模式(需 --file-ids) --callback-url 转存完成后的回调URL(异步模式) -o, --output 输出格式: table 或 json,默认为table Notes: - 同一个POLYV账号,调用该接口的间隔至少5分钟 - 同步模式基于场次ID转存,会等待转存完成后返回视频ID(vid) - 异步模式基于录制文件ID(fileIds)转存,立即返回,转存完成后可在点播后台查看 - fileIds 可通过 \`record file list -c <channelId> --user-id <userId> --session-ids <id>\` 获取 `); const setDefaultCmd = recordCmd .command('set-default') .description(`设置默认回放视频 Examples: $ polyv-live-cli record set-default -c "2588188" --video-id "73801f70c8" $ polyv-live-cli record set-default -c "2588188" --video-id "73801f70c8" --list-type playback -o json`) .requiredOption('-c, --channel-id <channelId>', '频道ID') .requiredOption('--video-id <videoId>', '视频ID') .option('--list-type <type>', '视频列表类型 (playback|vod)', validateListType) .option('-o, --output <format>', '输出格式 (table|json)', validateOutputFormat, 'table') .action(async function (options) { try { const parentOptions = this.optsWithGlobals(); const { authConfig, serviceConfig } = await loadAuthAndServiceConfig(parentOptions); const { RecordHandler } = await Promise.resolve().then(() => __importStar(require('../handlers/record.handler'))); const recordHandler = new RecordHandler(authConfig, serviceConfig); const setDefaultOptions = { channelId: options.channelId, videoId: options.videoId, output: options.output }; if (options.listType !== undefined) setDefaultOptions.listType = options.listType; await recordHandler.setRecordDefault(setDefaultOptions); } catch (error) { (0, errors_1.logError)(error instanceof Error ? error : new Error(String(error))); process.exit(1); } }); setDefaultCmd.addHelpText('after', ` Examples: # 设置默认回放视频 $ polyv-live-cli record set-default -c "2588188" --video-id "73801f70c8" # 指定列表类型,JSON格式输出 $ polyv-live-cli record set-default -c "2588188" --video-id "73801f70c8" --list-type playback -o json Options: -c, --channel-id 频道ID (必填) --video-id 视频ID (必填) --list-type 视频列表类型: playback(回放列表) 或 vod(点播列表) -o, --output 输出格式: table 或 json,默认为table `); recordCmd .command('temp-list') .description('查询频道单个直播暂存信息') .requiredOption('-c, --channel-id <channelId>', '频道ID') .requiredOption('--file-id <fileId>', '暂存文件ID') .option('-o, --output <format>', '输出格式 (table|json)', validateOutputFormat, 'table') .action((options) => runRecordAction(program, (handler) => handler.getRecordFile(options))); recordCmd .command('material-list') .description('分页查询素材库频道直播回放列表') .requiredOption('-c, --channel-id <channelId>', '频道ID') .option('--page <number>', '页码', api_command_1.parsePositiveInteger) .option('--page-size <number>', '每页数量', api_command_1.parsePositiveInteger) .option('-o, --output <format>', '输出格式 (table|json)', validateOutputFormat, 'table') .action((options) => runRecordAction(program, (handler) => handler.listMaterialRecordFiles(options))); recordCmd .command('clip') .description('裁剪录制文件') .requiredOption('-c, --channel-id <channelId>', '频道ID') .requiredOption('--file-id <fileId>', '暂存文件ID') .requiredOption('--start-time <seconds>', '裁剪开始时间,秒', api_command_1.parseNonNegativeNumber) .requiredOption('--end-time <seconds>', '裁剪结束时间,秒', api_command_1.parseNonNegativeNumber) .option('--file-name <fileName>', '裁剪后文件名') .option('--callback-url <url>', '回调地址') .option('-f, --force', '跳过确认提示') .option('-o, --output <format>', '输出格式 (table|json)', validateOutputFormat, 'table') .action((options) => runRecordAction(program, (handler) => handler.clipRecordFile(options))); recordCmd .command('merge-mp4') .description('按创建时间区间合并直播录制文件并回调 MP4 下载地址') .requiredOption('-c, --channel-id <channelId>', '频道ID') .requiredOption('--start-time <ms>', '录制文件创建时间最小值,13 位毫秒时间戳') .requiredOption('--end-time <ms>', '录制文件创建时间最大值,13 位毫秒时间戳(与 start-time 间隔不超过 8 小时)') .option('--file-name <fileName>', '合并后文件名') .option('--callback-url <url>', '回调地址') .option('-f, --force', '跳过确认提示') .option('-o, --output <format>', '输出格式 (table|json)', validateOutputFormat, 'table') .action((options) => runRecordAction(program, (handler) => handler.recordMergeMp4(options))); recordCmd .command('merge-mp4-start') .description('按创建时间区间提交异步 MP4 合并任务') .requiredOption('-c, --channel-id <channelId>', '频道ID') .requiredOption('--start-time <ms>', '录制文件直播开始时间最小值,13 位毫秒时间戳') .requiredOption('--end-time <ms>', '录制文件直播开始时间最大值,13 位毫秒时间戳(与 start-time 间隔不超过 8 小时)') .option('--file-name <fileName>', '合并后文件名') .option('--callback-url <url>', '回调地址') .option('-f, --force', '跳过确认提示') .option('-o, --output <format>', '输出格式 (table|json)', validateOutputFormat, 'table') .action((options) => runRecordAction(program, (handler) => handler.recordMergeMp4Start(options))); const fileCmd = recordCmd .command('file') .description('管理历史录制文件'); fileCmd .command('list') .description(`查询频道直播暂存列表(返回的 fileId 可用于 \`playback merge --file-ids\` / \`record convert --async --file-ids\`)`) .requiredOption('-c, --channel-id <channelId>', '频道ID') .option('--user-id <userId>', '用户ID(默认使用当前账号userId)') .option('--start-date <date>', '开始日期 (YYYY-MM-DD)') .option('--end-date <date>', '结束日期 (YYYY-MM-DD)') .option('--session-ids <ids>', '场次ID,逗号分隔') .option('-o, --output <format>', '输出格式 (table|json)', validateOutputFormat, 'table') .action((options) => runRecordAction(program, (handler) => handler.listRecordFiles(options))); fileCmd .command('merge') .description('合并录制文件') .requiredOption('-c, --channel-id <channelId>', '频道ID') .option('--urls <urls>', '录制文件 URL,逗号分隔') .option('--file-ids <fileIds>', '录制文件ID,逗号分隔') .option('--file-name <fileName>', '合并后文件名') .option('-f, --force', '跳过确认提示') .option('-o, --output <format>', '输出格式 (table|json)', validateOutputFormat, 'table') .action((options) => runRecordAction(program, (handler) => handler.mergeRecordFiles(options))); fileCmd .command('delete') .description('删除直播暂存中的录制视频') .requiredOption('-c, --channel-id <channelId>', '频道ID') .option('--session-id <sessionId>', '场次ID') .option('--start-time <time>', '录制开始时间 yyyyMMddHHmmss') .option('-f, --force', '跳过确认提示') .option('-o, --output <format>', '输出格式 (table|json)', validateOutputFormat, 'table') .action((options) => runRecordAction(program, (handler) => handler.deleteRecordFile(options))); fileCmd .command('convert') .description('同步转存历史录制文件到点播') .requiredOption('-c, --channel-id <channelId>', '频道ID') .requiredOption('--user-id <userId>', '用户ID') .requiredOption('--file-name <fileName>', '转存后视频名称') .option('--file-url <url>', '录制文件 URL') .option('--session-id <sessionId>', '场次ID') .option('--cataid <cataid>', '点播分类ID') .option('--cataname <cataname>', '点播分类名称') .option('--to-play-list <value>', '是否存入回放列表 (Y|N)', validateYN) .option('--set-as-default <value>', '是否设为默认回放 (Y|N)', validateYN) .option('-f, --force', '跳过确认提示') .option('-o, --output <format>', '输出格式 (table|json)', validateOutputFormat, 'table') .action((options) => runRecordAction(program, (handler) => handler.convertRecordFileToVod(options))); const breakpointCmd = recordCmd .command('breakpoint') .description('管理录制打点'); breakpointCmd .command('add') .description('频道直播录制打点(暂停/继续录制,频道需在直播中)') .requiredOption('-c, --channel-id <channelId>', '频道ID') .requiredOption('--type <type>', '打点类型:pause 暂停录制 / resume 继续录制', (v) => { if (v !== 'pause' && v !== 'resume') { throw new Error('--type 仅支持 pause 或 resume'); } return v; }) .option('--from-start-stop <value>', '是否从一开始就暂停录制 (Y|N),仅在 type=pause 时生效', validateYN) .option('-f, --force', '跳过确认提示') .option('-o, --output <format>', '输出格式 (table|json)', validateOutputFormat, 'table') .action((options) => runRecordAction(program, (handler) => handler.recordAddBreakpoint(options))); const outlineCmd = recordCmd .command('outline') .description('管理暂存视频大纲'); outlineCmd .command('create') .description('创建暂存视频大纲') .requiredOption('--file-id <fileId>', '暂存文件ID') .option('--ai-knowledge-quiz-enabled <value>', 'AI 知识问答开关 (Y|N)', validateYN) .option('--ai-summary-audit-enabled <value>', 'AI 总结审核开关 (Y|N)', validateYN) .option('--sync-to-playback-dot-enabled <value>', '同步到回放打点开关 (Y|N)', validateYN) .option('-f, --force', '跳过确认提示') .option('-o, --output <format>', '输出格式 (table|json)', validateOutputFormat, 'table') .action((options) => runRecordAction(program, (handler) => handler.createRecordFileOutline(options))); outlineCmd .command('get') .description('查询暂存视频大纲') .requiredOption('-c, --channel-id <channelId>', '频道ID') .requiredOption('--file-id <fileId>', '暂存文件ID') .option('-o, --output <format>', '输出格式 (table|json)', validateOutputFormat, 'table') .action((options) => runRecordAction(program, (handler) => handler.getRecordFileOutline(options))); const subtitleCmd = recordCmd .command('subtitle') .description('管理暂存视频字幕'); subtitleCmd .command('publish') .description('暂存视频字幕文件批量发布') .requiredOption('--subtitles-json <json>', '字幕数组 JSON,例如 [{"id":1,"status":"publish"}]', api_command_1.parseJsonArray) .option('-f, --force', '跳过确认提示') .option('-o, --output <format>', '输出格式 (table|json)', validateOutputFormat, 'table') .action((options) => runRecordAction(program, (handler) => handler.batchPublishRecordFileSubtitles({ subtitles: options.subtitlesJson, force: options.force, output: options.output, }))); } //# sourceMappingURL=record.commands.js.map