@mixvideo/cli
Advanced tools
+164
| # @mixvideo/cli | ||
| 🎬 AI-powered video analysis and editing CLI tool for automatic video classification and Jianying draft generation. | ||
| ## Features | ||
| - 🤖 **AI Video Analysis**: Powered by Gemini AI for intelligent video content analysis | ||
| - 📁 **Automatic Classification**: Smart categorization of videos into folders | ||
| - 🎯 **Intelligent File Naming**: Content-based file naming with original name preservation | ||
| - 📋 **Jianying Integration**: Generate draft files for Jianying video editor | ||
| - 🔄 **Caching System**: Efficient caching to avoid duplicate processing | ||
| - 🛡️ **Safe Operations**: Copy mode by default to preserve original files | ||
| ## Installation | ||
| ```bash | ||
| npm install -g @mixvideo/cli | ||
| ``` | ||
| ## Quick Start | ||
| ### 1. Analyze Videos | ||
| Analyze videos in a directory and automatically classify them: | ||
| ```bash | ||
| mixvideo analyze ./videos | ||
| ``` | ||
| ### 2. Generate Jianying Drafts | ||
| Generate draft files for Jianying from video directory: | ||
| ```bash | ||
| mixvideo generate from-videos ./videos | ||
| ``` | ||
| ## Commands | ||
| ### `analyze` | ||
| Analyze video files and automatically classify them into folders. | ||
| ```bash | ||
| mixvideo analyze <source> [options] | ||
| ``` | ||
| **Options:** | ||
| - `-o, --output <path>`: Output directory (default: "outputs") | ||
| - `-m, --mode <mode>`: Analysis mode - gemini|gpt4 (default: "gemini") | ||
| - `--temperature <number>`: AI model temperature (0.0-1.0, default: "0.3") | ||
| - `--max-tokens <number>`: Maximum output tokens (default: "4096") | ||
| - `--move-files`: Move files instead of copying (default: false) | ||
| - `--create-backup`: Create backup files (default: false) | ||
| - `--confidence <number>`: Minimum confidence for file movement (0.0-1.0, default: "0.4") | ||
| **Example:** | ||
| ```bash | ||
| mixvideo analyze ./my-videos -o ./organized --mode gemini --confidence 0.6 | ||
| ``` | ||
| ### `generate` | ||
| Generate Jianying draft files from videos or templates. | ||
| #### `generate from-videos` | ||
| Generate draft from video directory: | ||
| ```bash | ||
| mixvideo generate from-videos <source> [options] | ||
| ``` | ||
| **Options:** | ||
| - `-o, --output <path>`: Output file path (default: "draft_content.json") | ||
| - `--title <title>`: Project title (default: "视频项目") | ||
| - `--fps <number>`: Frame rate (default: "30") | ||
| - `--resolution <resolution>`: Resolution - 1080p|720p|4k (default: "1080p") | ||
| #### `generate from-template` | ||
| Generate draft from template: | ||
| ```bash | ||
| mixvideo generate from-template <dir> [options] | ||
| ``` | ||
| **Options:** | ||
| - `-t, --template <template>`: Template file path (default: "draft_content.json") | ||
| ### `login` / `logout` | ||
| Authentication commands (coming soon): | ||
| ```bash | ||
| mixvideo login -u <username> -p <password> | ||
| mixvideo logout | ||
| ``` | ||
| ## Configuration | ||
| The CLI tool automatically creates the following folder structure for video classification: | ||
| - 📁 产品展示 (Product Display) | ||
| - 📁 产品使用 (Product Usage) | ||
| - 📁 生活场景 (Life Scenes) | ||
| - 📁 模特实拍 (Model Photography) | ||
| - 📁 服装配饰 (Fashion & Accessories) | ||
| - 📁 美妆护肤 (Beauty & Skincare) | ||
| - 📁 其他 (Others) | ||
| ## Environment Variables | ||
| Set up your AI API credentials: | ||
| ```bash | ||
| export GOOGLE_APPLICATION_CREDENTIALS="path/to/your/credentials.json" | ||
| ``` | ||
| ## Examples | ||
| ### Basic Video Analysis | ||
| ```bash | ||
| # Analyze videos in current directory | ||
| mixvideo analyze ./videos | ||
| # Analyze with custom output directory | ||
| mixvideo analyze ./raw-videos -o ./organized-videos | ||
| # Use GPT-4 mode with higher confidence | ||
| mixvideo analyze ./videos --mode gpt4 --confidence 0.8 | ||
| ``` | ||
| ### Generate Jianying Drafts | ||
| ```bash | ||
| # Generate draft from video directory | ||
| mixvideo generate from-videos ./organized-videos/产品展示 | ||
| # Generate with custom settings | ||
| mixvideo generate from-videos ./videos --title "我的产品视频" --fps 60 --resolution 4k | ||
| # Generate from template | ||
| mixvideo generate from-template ./materials -t ./templates/product-template.json | ||
| ``` | ||
| ## Supported Video Formats | ||
| - MP4, MOV, AVI, MKV | ||
| - WEBM, FLV, WMV, M4V | ||
| - 3GP, TS | ||
| ## License | ||
| MIT | ||
| ## Contributing | ||
| Contributions are welcome! Please feel free to submit a Pull Request. | ||
| ## Support | ||
| For issues and questions, please visit our [GitHub repository](https://github.com/imeepos/mixvideo). |
+262
-132
| #!/usr/bin/env node | ||
| "use strict"; | ||
| var __create = Object.create; | ||
| var __defProp = Object.defineProperty; | ||
| var __getOwnPropDesc = Object.getOwnPropertyDescriptor; | ||
| var __getOwnPropNames = Object.getOwnPropertyNames; | ||
| var __getProtoOf = Object.getPrototypeOf; | ||
| var __hasOwnProp = Object.prototype.hasOwnProperty; | ||
| var __copyProps = (to, from, except, desc) => { | ||
| if (from && typeof from === "object" || typeof from === "function") { | ||
| for (let key of __getOwnPropNames(from)) | ||
| if (!__hasOwnProp.call(to, key) && key !== except) | ||
| __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); | ||
| } | ||
| return to; | ||
| }; | ||
| var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( | ||
| // If the importer is in node compatibility mode or this is not an ESM | ||
| // file that has been converted to a CommonJS file using a Babel- | ||
| // compatible transform (i.e. "__esModule" has not been set), then set | ||
| // "default" to the CommonJS "module.exports" for node compatibility. | ||
| isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, | ||
| mod | ||
| )); | ||
| // src/index.ts | ||
| var import_jianying = require("@mixvideo/jianying"); | ||
| var import_promises = require("fs/promises"); | ||
| var import_path = __toESM(require("path")); | ||
| var import_commander4 = require("commander"); | ||
| // src/commands/analyze.ts | ||
| var import_commander = require("commander"); | ||
| var import_path2 = require("path"); | ||
| var import_promises2 = require("fs/promises"); | ||
| var import_video_analyzer = require("@mixvideo/video-analyzer"); | ||
| var import_fs = __toESM(require("fs")); | ||
| var root = process.cwd(); | ||
| async function main() { | ||
| const program = new import_commander.Command(); | ||
| program.name(`mixvideo`).version(`1.0.0`).description(`MixVideo CLI tool`); | ||
| program.command(`login`).description(`Login to MixVideo`).action(() => { | ||
| console.log(`Login`); | ||
| }); | ||
| program.command(`logout`).description(`Logout from MixVideo`).action(() => { | ||
| console.log(`Logout`); | ||
| }); | ||
| program.command(`analyze`).description(`Analyze a product video`).argument(`<dir>`, `Product video file`).action(async (dir) => { | ||
| // src/utils/folder-utils.ts | ||
| var import_promises = require("fs/promises"); | ||
| var import_path = require("path"); | ||
| var DEFAULT_FOLDERS = [ | ||
| "\u4EA7\u54C1\u5C55\u793A", | ||
| "\u4EA7\u54C1\u4F7F\u7528", | ||
| "\u751F\u6D3B\u573A\u666F", | ||
| "\u6A21\u7279\u5B9E\u62CD" | ||
| ]; | ||
| async function createDefaultFolders(targetDir) { | ||
| const folders = DEFAULT_FOLDERS.map((folder) => folder); | ||
| console.log(`\u{1F4C1} \u5DF2\u521B\u5EFA\u5206\u7C7B\u76EE\u5F55: ${folders.join(", ")}`); | ||
| for (const folder of folders) { | ||
| try { | ||
| await (0, import_promises.mkdir)((0, import_path.join)(targetDir, folder), { recursive: true }); | ||
| } catch (error) { | ||
| console.warn("\u26A0\uFE0F \u521B\u5EFA\u76EE\u5F55\u65F6\u51FA\u73B0\u8B66\u544A:", error); | ||
| } | ||
| } | ||
| } | ||
| // src/commands/analyze.ts | ||
| function createAnalyzeCommand() { | ||
| return new import_commander.Command("analyze").description("\u5206\u6790\u89C6\u9891\u6587\u4EF6\u5E76\u81EA\u52A8\u5206\u7C7B\u7EC4\u7EC7").argument("<source>", "\u6E90\u89C6\u9891\u76EE\u5F55\u8DEF\u5F84").option("-o, --output <path>", "\u8F93\u51FA\u76EE\u5F55\u8DEF\u5F84", "outputs").option("-m, --mode <mode>", "\u5206\u6790\u6A21\u5F0F (gemini|gpt4)", "gemini").option("--temperature <number>", "AI \u6A21\u578B\u6E29\u5EA6\u53C2\u6570 (0.0-1.0)", "0.3").option("--max-tokens <number>", "\u6700\u5927\u8F93\u51FA token \u6570", "4096").option("--move-files", "\u79FB\u52A8\u6587\u4EF6\u800C\u4E0D\u662F\u590D\u5236", false).option("--create-backup", "\u521B\u5EFA\u5907\u4EFD\u6587\u4EF6", false).option("--confidence <number>", "\u6587\u4EF6\u79FB\u52A8\u7684\u6700\u5C0F\u7F6E\u4FE1\u5EA6 (0.0-1.0)", "0.4").action(async (source, options) => { | ||
| try { | ||
| console.log("\u{1F3AC} \u5F00\u59CB\u89C6\u9891\u5206\u6790..."); | ||
| const root2 = process.cwd(); | ||
| const analyzer = (0, import_video_analyzer.createVideoAnalyzer)({ | ||
| upload: { | ||
| bucketName: "dy-media-storage", | ||
| filePrefix: "processed", | ||
| maxRetries: 3 | ||
| } | ||
| }); | ||
| const resourcesDir = import_path.default.join(root2, dir); | ||
| if (!import_fs.default.existsSync(resourcesDir)) { | ||
| console.log("\u{1F4C1} \u521B\u5EFA resources \u76EE\u5F55..."); | ||
| import_fs.default.mkdirSync(resourcesDir, { recursive: true }); | ||
| console.log("\u2705 resources \u76EE\u5F55\u5DF2\u521B\u5EFA\uFF0C\u8BF7\u5C06\u89C6\u9891\u6587\u4EF6\u653E\u5165\u8BE5\u76EE\u5F55"); | ||
| return; | ||
| const validModes = ["gemini", "gpt4"]; | ||
| if (!validModes.includes(options.mode)) { | ||
| throw new Error(`\u65E0\u6548\u7684\u5206\u6790\u6A21\u5F0F: ${options.mode}. \u652F\u6301\u7684\u6A21\u5F0F: ${validModes.join(", ")}`); | ||
| } | ||
| const analysisMode = { | ||
| type: "gemini", | ||
| model: "gemini-2.5-flash", | ||
| analysisType: "comprehensive" | ||
| type: options.mode, | ||
| model: options.mode === "gemini" ? "gemini-2.5-flash" : "gpt-4-vision-preview" | ||
| }; | ||
| const resourcesDir = (0, import_path2.join)(process.cwd(), source); | ||
| const targetDir = (0, import_path2.join)(process.cwd(), options.output); | ||
| await createDefaultFolders(targetDir); | ||
| const analyzer = new import_video_analyzer.VideoAnalyzer(); | ||
| const analysisOptions = { | ||
| enableProductAnalysis: true, | ||
| // 启用产品分析 | ||
| maxScenes: 20, | ||
| // 最大场景数 | ||
| confidenceThreshold: 0.7 | ||
| // 置信度阈值 | ||
| frameSamplingInterval: 2, | ||
| maxFrames: 10, | ||
| quality: "high", | ||
| language: "zh-CN" | ||
| }; | ||
| const onProgress = (progress) => { | ||
| console.log(`\u{1F4CA} ${progress.step}: ${progress.progress}% (${progress.currentFile || ""})`); | ||
| }; | ||
| console.log(`\u{1F50D} \u626B\u63CF\u76EE\u5F55: ${resourcesDir}`); | ||
| const result = await analyzer.analyzeDirectoryComplete( | ||
| const result = await analyzer.processDirectory( | ||
| resourcesDir, | ||
| targetDir, | ||
| analysisMode, | ||
| { | ||
| // 扫描选项 | ||
| scanOptions: { | ||
| recursive: true, | ||
| maxFileSize: 1024 * 1024 * 1024, | ||
| // 1GB | ||
| minFileSize: 1024 | ||
| // 1KB | ||
| }, | ||
| // 分析选项 | ||
| analysisOptions, | ||
| // 文件夹匹配配置 | ||
| folderConfig: { | ||
| baseDirectory: resourcesDir, | ||
| maxDepth: 2, | ||
| minConfidence: 0.4, | ||
| enableSemanticAnalysis: true | ||
| fileOrganizerConfig: { | ||
| moveFiles: options.moveFiles, | ||
| // 根据选项决定是否移动文件 | ||
| namingMode: "preserve-original", | ||
| // 保留原始文件名,只修复后缀 | ||
| createDirectories: true, | ||
| conflictResolution: "rename", | ||
| createBackup: options.createBackup | ||
| }, | ||
| // 报告生成选项 | ||
| reportOptions: { | ||
| format: "xml", | ||
| outputPath: import_path.default.join(__dirname, "../analysis-report.xml"), | ||
| includeFolderMatching: true, | ||
| includeDetailedAnalysis: true, | ||
| title: "MixVideo \u89C6\u9891\u5206\u6790\u62A5\u544A" | ||
| }, | ||
| // 进度跟踪 | ||
| onProgress | ||
| minConfidenceForMove: parseFloat(options.confidence) | ||
| }, | ||
| (progress) => { | ||
| console.log(`\u{1F4CA} [${progress.phase}] ${progress.step} (${progress.progress}%)`); | ||
| console.log(` \u5DF2\u5904\u7406: ${progress.processedVideos}/${progress.totalVideos}`); | ||
| } | ||
| ); | ||
| console.log("\n\u{1F389} \u5206\u6790\u5B8C\u6210\uFF01"); | ||
| console.log(`\u{1F4F9} \u5206\u6790\u89C6\u9891\u6570\u91CF: ${result.analysisResults.length}`); | ||
| console.log(`\u{1F4C2} \u6587\u4EF6\u5939\u5339\u914D\u6570\u91CF: ${Object.keys(result.folderMatches).length}`); | ||
| console.log(`\u{1F4C4} \u62A5\u544A\u4FDD\u5B58\u4F4D\u7F6E: ${result.reportPath}`); | ||
| const stats = analyzer.getAnalysisStatistics(result.analysisResults); | ||
| console.log("\n\u{1F4CA} \u8BE6\u7EC6\u7EDF\u8BA1:"); | ||
| console.log(`- \u603B\u5904\u7406\u65F6\u95F4: ${stats.totalProcessingTime}ms`); | ||
| console.log(`- \u603B\u573A\u666F\u6570: ${stats.totalScenes}`); | ||
| console.log(`- \u603B\u5BF9\u8C61\u6570: ${stats.totalObjects}`); | ||
| console.log(`- \u5E73\u5747\u8D28\u91CF\u5206\u6570: ${stats.averageQualityScore.toFixed(2)}`); | ||
| if (Object.keys(result.folderMatches).length > 0) { | ||
| console.log("\n\u{1F4C1} \u667A\u80FD\u6587\u4EF6\u5939\u5339\u914D\u5EFA\u8BAE:"); | ||
| for (const [videoPath, matches] of Object.entries(result.folderMatches)) { | ||
| const videoName = import_path.default.basename(videoPath); | ||
| console.log(` | ||
| \u{1F3AC} ${videoName}:`); | ||
| matches.slice(0, 3).forEach((match, index) => { | ||
| console.log(` ${index + 1}. ${match.folderPath} (\u7F6E\u4FE1\u5EA6: ${(match.confidence * 100).toFixed(1)}%)`); | ||
| if (match.reasons && match.reasons.length > 0) { | ||
| console.log(` \u7406\u7531: ${match.reasons.join(", ")}`); | ||
| } | ||
| }); | ||
| } | ||
| } | ||
| await (0, import_promises2.writeFile)( | ||
| (0, import_path2.join)(targetDir, "report.json"), | ||
| JSON.stringify(result, null, 2) | ||
| ); | ||
| console.log("\u2705 \u89C6\u9891\u5206\u6790\u5B8C\u6210\uFF01"); | ||
| console.log(`\u{1F4CA} \u603B\u8BA1: ${result.totalVideos} \u4E2A\u89C6\u9891`); | ||
| console.log(`\u2705 \u5DF2\u5206\u6790: ${result.analyzedVideos} \u4E2A`); | ||
| console.log(`\u{1F4C1} \u5DF2\u5206\u7C7B: ${result.matchedVideos} \u4E2A`); | ||
| console.log(`\u{1F4E6} \u5DF2\u7EC4\u7EC7: ${result.organizedVideos} \u4E2A`); | ||
| console.log(`\u{1F4C4} \u62A5\u544A\u5DF2\u4FDD\u5B58\u5230: ${(0, import_path2.join)(targetDir, "report.json")}`); | ||
| } catch (error) { | ||
@@ -140,24 +94,200 @@ console.error("\u274C \u5206\u6790\u8FC7\u7A0B\u4E2D\u51FA\u73B0\u9519\u8BEF:", error); | ||
| }); | ||
| program.command(`generate`).description(`Generate a video`).argument(`<dir>`, `Product video path`).option(`-t, --template [template]`, `Template to use`, `draft_content.json`).action(async (dir, options) => { | ||
| const draft = (0, import_jianying.parse)((0, import_path.join)(root, options.template)); | ||
| draft.materials.videos = await Promise.all(draft.materials.videos.map(async (video) => { | ||
| const material_name = video.material_name; | ||
| const videoPath = await getMaterialVideoByName(dir, material_name); | ||
| video.path = videoPath; | ||
| return video; | ||
| })); | ||
| await (0, import_promises.writeFile)((0, import_path.join)(root, options.template), JSON.stringify(draft, null, 2)); | ||
| console.log(`\u606D\u559C\u60A8\uFF0C\u751F\u6210\u6210\u529F`); | ||
| }); | ||
| program.parse(); | ||
| } | ||
| var set = /* @__PURE__ */ new Set(); | ||
| // src/commands/generate.ts | ||
| var import_commander2 = require("commander"); | ||
| var import_path4 = require("path"); | ||
| var import_promises4 = require("fs/promises"); | ||
| var import_jianying = require("@mixvideo/jianying"); | ||
| // src/utils/video-scanner.ts | ||
| var import_promises3 = require("fs/promises"); | ||
| var import_path3 = require("path"); | ||
| var SUPPORTED_VIDEO_FORMATS = [ | ||
| ".mp4", | ||
| ".mov", | ||
| ".avi", | ||
| ".mkv", | ||
| ".webm", | ||
| ".flv", | ||
| ".wmv", | ||
| ".m4v", | ||
| ".3gp", | ||
| ".ts" | ||
| ]; | ||
| async function scanVideoDirectory(dirPath) { | ||
| const videos = []; | ||
| try { | ||
| const files = await (0, import_promises3.readdir)(dirPath); | ||
| for (const file of files) { | ||
| const filePath = (0, import_path3.join)(dirPath, file); | ||
| const stats = await (0, import_promises3.stat)(filePath); | ||
| if (stats.isDirectory()) { | ||
| continue; | ||
| } | ||
| const ext = (0, import_path3.extname)(file).toLowerCase(); | ||
| if (!SUPPORTED_VIDEO_FORMATS.includes(ext)) { | ||
| continue; | ||
| } | ||
| const videoInfo = { | ||
| path: filePath, | ||
| name: file.replace(ext, ""), | ||
| // 移除扩展名 | ||
| size: stats.size, | ||
| format: ext.substring(1), | ||
| // 移除点号 | ||
| createdAt: stats.birthtime, | ||
| modifiedAt: stats.mtime | ||
| }; | ||
| videos.push(videoInfo); | ||
| } | ||
| } catch (error) { | ||
| throw new Error(`\u626B\u63CF\u89C6\u9891\u76EE\u5F55\u5931\u8D25: ${error}`); | ||
| } | ||
| return videos; | ||
| } | ||
| // src/commands/generate.ts | ||
| var usedFiles = /* @__PURE__ */ new Set(); | ||
| async function getMaterialVideoByName(dir, name) { | ||
| const files = await (0, import_promises.readdir)((0, import_path.join)(root, dir)); | ||
| const root = process.cwd(); | ||
| const files = await (0, import_promises4.readdir)((0, import_path4.join)(root, dir)); | ||
| const file = files[Math.floor(Math.random() * files.length)]; | ||
| const used = (0, import_path.join)(root, dir, file); | ||
| if (set.has(used)) return getMaterialVideoByName(dir, name); | ||
| set.add(used); | ||
| const used = (0, import_path4.join)(root, dir, file); | ||
| if (usedFiles.has(used)) { | ||
| return getMaterialVideoByName(dir, name); | ||
| } | ||
| usedFiles.add(used); | ||
| return used; | ||
| } | ||
| main(); | ||
| function createGenerateCommand() { | ||
| const command = new import_commander2.Command("generate").description("\u751F\u6210\u526A\u6620\u8349\u7A3F\u6587\u4EF6"); | ||
| command.command("from-videos").description("\u4ECE\u89C6\u9891\u76EE\u5F55\u751F\u6210\u526A\u6620\u8349\u7A3F\u6587\u4EF6").argument("<source>", "\u6E90\u89C6\u9891\u76EE\u5F55\u8DEF\u5F84").option("-o, --output <path>", "\u8F93\u51FA\u6587\u4EF6\u8DEF\u5F84", "draft_content.json").option("--title <title>", "\u9879\u76EE\u6807\u9898", "\u89C6\u9891\u9879\u76EE").option("--fps <number>", "\u5E27\u7387", "30").option("--resolution <resolution>", "\u5206\u8FA8\u7387 (1080p|720p|4k)", "1080p").action(async (source, options) => { | ||
| try { | ||
| console.log("\u{1F3AC} \u5F00\u59CB\u4ECE\u89C6\u9891\u76EE\u5F55\u751F\u6210\u526A\u6620\u8349\u7A3F..."); | ||
| const sourceDir = (0, import_path4.join)(process.cwd(), source); | ||
| const videos = await scanVideoDirectory(sourceDir); | ||
| if (videos.length === 0) { | ||
| console.log("\u26A0\uFE0F \u672A\u627E\u5230\u89C6\u9891\u6587\u4EF6"); | ||
| return; | ||
| } | ||
| console.log(`\u{1F4C1} \u627E\u5230 ${videos.length} \u4E2A\u89C6\u9891\u6587\u4EF6`); | ||
| const draftContent = { | ||
| version: "13.8.0", | ||
| materials: { | ||
| videos: videos.map((video, index) => ({ | ||
| id: `video_${index}`, | ||
| path: video.path, | ||
| name: video.name, | ||
| duration: 5e6, | ||
| // 默认5秒,微秒单位 | ||
| width: 1920, | ||
| height: 1080 | ||
| })) | ||
| }, | ||
| tracks: [ | ||
| { | ||
| type: "video", | ||
| segments: videos.map((_video, index) => ({ | ||
| id: `segment_${index}`, | ||
| material_id: `video_${index}`, | ||
| target_timerange: { | ||
| start: index * 5e6, | ||
| duration: 5e6 | ||
| } | ||
| })) | ||
| } | ||
| ] | ||
| }; | ||
| const outputPath = (0, import_path4.join)(process.cwd(), options.output); | ||
| await (0, import_promises4.writeFile)(outputPath, JSON.stringify(draftContent, null, 2)); | ||
| console.log("\u2705 \u526A\u6620\u8349\u7A3F\u751F\u6210\u5B8C\u6210\uFF01"); | ||
| console.log(`\u{1F4C4} \u8349\u7A3F\u6587\u4EF6\u5DF2\u4FDD\u5B58\u5230: ${outputPath}`); | ||
| console.log(`\u{1F3AC} \u5305\u542B ${videos.length} \u4E2A\u89C6\u9891\u7247\u6BB5`); | ||
| } catch (error) { | ||
| console.error("\u274C \u751F\u6210\u8349\u7A3F\u8FC7\u7A0B\u4E2D\u51FA\u73B0\u9519\u8BEF:", error); | ||
| process.exit(1); | ||
| } | ||
| }); | ||
| command.command("from-template").description("\u4ECE\u6A21\u677F\u751F\u6210\u526A\u6620\u8349\u7A3F\u6587\u4EF6").argument("<dir>", "\u7D20\u6750\u76EE\u5F55\u8DEF\u5F84").option("-t, --template <template>", "\u6A21\u677F\u6587\u4EF6\u8DEF\u5F84", "draft_content.json").action(async (dir, options) => { | ||
| try { | ||
| console.log("\u{1F3AC} \u5F00\u59CB\u4ECE\u6A21\u677F\u751F\u6210\u526A\u6620\u8349\u7A3F..."); | ||
| const root = process.cwd(); | ||
| const templatePath = (0, import_path4.join)(root, options.template); | ||
| const draft = (0, import_jianying.parse)(templatePath); | ||
| draft.materials.videos = await Promise.all( | ||
| draft.materials.videos.map(async (video) => { | ||
| const materialName = video.material_name; | ||
| const videoPath = await getMaterialVideoByName(dir, materialName); | ||
| video.path = videoPath; | ||
| return video; | ||
| }) | ||
| ); | ||
| await (0, import_promises4.writeFile)(templatePath, JSON.stringify(draft, null, 2)); | ||
| console.log("\u2705 \u8349\u7A3F\u751F\u6210\u6210\u529F\uFF01"); | ||
| console.log(`\u{1F4C4} \u5DF2\u66F4\u65B0\u6A21\u677F\u6587\u4EF6: ${templatePath}`); | ||
| } catch (error) { | ||
| console.error("\u274C \u751F\u6210\u8349\u7A3F\u8FC7\u7A0B\u4E2D\u51FA\u73B0\u9519\u8BEF:", error); | ||
| process.exit(1); | ||
| } | ||
| }); | ||
| return command; | ||
| } | ||
| // src/commands/auth.ts | ||
| var import_commander3 = require("commander"); | ||
| function createAuthCommands() { | ||
| const login = new import_commander3.Command("login").description("\u767B\u5F55\u5230 MixVideo").option("-u, --username <username>", "\u7528\u6237\u540D").option("-p, --password <password>", "\u5BC6\u7801").addHelpText("after", ` | ||
| \u793A\u4F8B: | ||
| $ mixvideo login -u user@example.com -p password123 | ||
| $ mixvideo login --username user@example.com --password password123 | ||
| \u6CE8\u610F: \u9009\u9879\u6807\u5FD7 -u \u548C -p \u4E4B\u95F4\u4E0D\u80FD\u6709\u7A7A\u683C`).action(async (options) => { | ||
| try { | ||
| console.log("\u{1F510} \u5F00\u59CB\u767B\u5F55..."); | ||
| if (!options.username || !options.password) { | ||
| console.error("\u274C \u7F3A\u5C11\u5FC5\u9700\u53C2\u6570"); | ||
| console.log("\uFFFD \u8BF7\u63D0\u4F9B\u7528\u6237\u540D\u548C\u5BC6\u7801"); | ||
| console.log(""); | ||
| console.log("\u6B63\u786E\u4F7F\u7528\u65B9\u5F0F:"); | ||
| console.log(" mixvideo login -u <username> -p <password>"); | ||
| console.log(""); | ||
| console.log("\u793A\u4F8B:"); | ||
| console.log(" mixvideo login -u user@bowongai.com -p bowong7777"); | ||
| console.log(""); | ||
| console.log("\u26A0\uFE0F \u6CE8\u610F: -u \u548C -p \u4E4B\u95F4\u4E0D\u80FD\u6709\u7A7A\u683C"); | ||
| process.exit(1); | ||
| } | ||
| console.log(`\u{1F464} \u7528\u6237\u540D: ${options.username}`); | ||
| console.log("\u2705 \u767B\u5F55\u6210\u529F\uFF01"); | ||
| } catch (error) { | ||
| console.error("\u274C \u767B\u5F55\u5931\u8D25:", error); | ||
| process.exit(1); | ||
| } | ||
| }); | ||
| const logout = new import_commander3.Command("logout").description("\u9000\u51FA\u767B\u5F55").action(async () => { | ||
| try { | ||
| console.log("\u{1F513} \u6B63\u5728\u9000\u51FA\u767B\u5F55..."); | ||
| console.log("\u2705 \u5DF2\u9000\u51FA\u767B\u5F55"); | ||
| } catch (error) { | ||
| console.error("\u274C \u9000\u51FA\u767B\u5F55\u5931\u8D25:", error); | ||
| process.exit(1); | ||
| } | ||
| }); | ||
| return { login, logout }; | ||
| } | ||
| // src/index.ts | ||
| async function main() { | ||
| const program = new import_commander4.Command(); | ||
| program.name("mixvideo").version("1.0.0").description("\u89C6\u9891\u5206\u6790\u548C\u6DF7\u526A\u5DE5\u5177"); | ||
| program.addCommand(createAuthCommands().login); | ||
| program.addCommand(createAuthCommands().logout); | ||
| program.addCommand(createAnalyzeCommand()); | ||
| program.addCommand(createGenerateCommand()); | ||
| program.parse(); | ||
| } | ||
| main().catch((error) => { | ||
| console.error("\u274C \u7A0B\u5E8F\u8FD0\u884C\u51FA\u9519:", error); | ||
| process.exit(1); | ||
| }); |
+263
-111
| #!/usr/bin/env node | ||
| // src/index.ts | ||
| import { parse } from "@mixvideo/jianying"; | ||
| import { readdir, writeFile } from "fs/promises"; | ||
| import path, { join } from "path"; | ||
| import { Command as Command4 } from "commander"; | ||
| // src/commands/analyze.ts | ||
| import { Command } from "commander"; | ||
| import { createVideoAnalyzer } from "@mixvideo/video-analyzer"; | ||
| import fs from "fs"; | ||
| var root = process.cwd(); | ||
| async function main() { | ||
| const program = new Command(); | ||
| program.name(`mixvideo`).version(`1.0.0`).description(`MixVideo CLI tool`); | ||
| program.command(`login`).description(`Login to MixVideo`).action(() => { | ||
| console.log(`Login`); | ||
| }); | ||
| program.command(`logout`).description(`Logout from MixVideo`).action(() => { | ||
| console.log(`Logout`); | ||
| }); | ||
| program.command(`analyze`).description(`Analyze a product video`).argument(`<dir>`, `Product video file`).action(async (dir) => { | ||
| import { join as join2 } from "path"; | ||
| import { writeFile } from "fs/promises"; | ||
| import { VideoAnalyzer } from "@mixvideo/video-analyzer"; | ||
| // src/utils/folder-utils.ts | ||
| import { mkdir } from "fs/promises"; | ||
| import { join } from "path"; | ||
| var DEFAULT_FOLDERS = [ | ||
| "\u4EA7\u54C1\u5C55\u793A", | ||
| "\u4EA7\u54C1\u4F7F\u7528", | ||
| "\u751F\u6D3B\u573A\u666F", | ||
| "\u6A21\u7279\u5B9E\u62CD" | ||
| ]; | ||
| async function createDefaultFolders(targetDir) { | ||
| const folders = DEFAULT_FOLDERS.map((folder) => folder); | ||
| console.log(`\u{1F4C1} \u5DF2\u521B\u5EFA\u5206\u7C7B\u76EE\u5F55: ${folders.join(", ")}`); | ||
| for (const folder of folders) { | ||
| try { | ||
| await mkdir(join(targetDir, folder), { recursive: true }); | ||
| } catch (error) { | ||
| console.warn("\u26A0\uFE0F \u521B\u5EFA\u76EE\u5F55\u65F6\u51FA\u73B0\u8B66\u544A:", error); | ||
| } | ||
| } | ||
| } | ||
| // src/commands/analyze.ts | ||
| function createAnalyzeCommand() { | ||
| return new Command("analyze").description("\u5206\u6790\u89C6\u9891\u6587\u4EF6\u5E76\u81EA\u52A8\u5206\u7C7B\u7EC4\u7EC7").argument("<source>", "\u6E90\u89C6\u9891\u76EE\u5F55\u8DEF\u5F84").option("-o, --output <path>", "\u8F93\u51FA\u76EE\u5F55\u8DEF\u5F84", "outputs").option("-m, --mode <mode>", "\u5206\u6790\u6A21\u5F0F (gemini|gpt4)", "gemini").option("--temperature <number>", "AI \u6A21\u578B\u6E29\u5EA6\u53C2\u6570 (0.0-1.0)", "0.3").option("--max-tokens <number>", "\u6700\u5927\u8F93\u51FA token \u6570", "4096").option("--move-files", "\u79FB\u52A8\u6587\u4EF6\u800C\u4E0D\u662F\u590D\u5236", false).option("--create-backup", "\u521B\u5EFA\u5907\u4EFD\u6587\u4EF6", false).option("--confidence <number>", "\u6587\u4EF6\u79FB\u52A8\u7684\u6700\u5C0F\u7F6E\u4FE1\u5EA6 (0.0-1.0)", "0.4").action(async (source, options) => { | ||
| try { | ||
| console.log("\u{1F3AC} \u5F00\u59CB\u89C6\u9891\u5206\u6790..."); | ||
| const root2 = process.cwd(); | ||
| const analyzer = createVideoAnalyzer({ | ||
| upload: { | ||
| bucketName: "dy-media-storage", | ||
| filePrefix: "processed", | ||
| maxRetries: 3 | ||
| } | ||
| }); | ||
| const resourcesDir = path.join(root2, dir); | ||
| if (!fs.existsSync(resourcesDir)) { | ||
| console.log("\u{1F4C1} \u521B\u5EFA resources \u76EE\u5F55..."); | ||
| fs.mkdirSync(resourcesDir, { recursive: true }); | ||
| console.log("\u2705 resources \u76EE\u5F55\u5DF2\u521B\u5EFA\uFF0C\u8BF7\u5C06\u89C6\u9891\u6587\u4EF6\u653E\u5165\u8BE5\u76EE\u5F55"); | ||
| return; | ||
| const validModes = ["gemini", "gpt4"]; | ||
| if (!validModes.includes(options.mode)) { | ||
| throw new Error(`\u65E0\u6548\u7684\u5206\u6790\u6A21\u5F0F: ${options.mode}. \u652F\u6301\u7684\u6A21\u5F0F: ${validModes.join(", ")}`); | ||
| } | ||
| const analysisMode = { | ||
| type: "gemini", | ||
| model: "gemini-2.5-flash", | ||
| analysisType: "comprehensive" | ||
| type: options.mode, | ||
| model: options.mode === "gemini" ? "gemini-2.5-flash" : "gpt-4-vision-preview" | ||
| }; | ||
| const resourcesDir = join2(process.cwd(), source); | ||
| const targetDir = join2(process.cwd(), options.output); | ||
| await createDefaultFolders(targetDir); | ||
| const analyzer = new VideoAnalyzer(); | ||
| const analysisOptions = { | ||
| enableProductAnalysis: true, | ||
| // 启用产品分析 | ||
| maxScenes: 20, | ||
| // 最大场景数 | ||
| confidenceThreshold: 0.7 | ||
| // 置信度阈值 | ||
| frameSamplingInterval: 2, | ||
| maxFrames: 10, | ||
| quality: "high", | ||
| language: "zh-CN" | ||
| }; | ||
| const onProgress = (progress) => { | ||
| console.log(`\u{1F4CA} ${progress.step}: ${progress.progress}% (${progress.currentFile || ""})`); | ||
| }; | ||
| console.log(`\u{1F50D} \u626B\u63CF\u76EE\u5F55: ${resourcesDir}`); | ||
| const result = await analyzer.analyzeDirectoryComplete( | ||
| const result = await analyzer.processDirectory( | ||
| resourcesDir, | ||
| targetDir, | ||
| analysisMode, | ||
| { | ||
| // 扫描选项 | ||
| scanOptions: { | ||
| recursive: true, | ||
| maxFileSize: 1024 * 1024 * 1024, | ||
| // 1GB | ||
| minFileSize: 1024 | ||
| // 1KB | ||
| }, | ||
| // 分析选项 | ||
| analysisOptions, | ||
| // 文件夹匹配配置 | ||
| folderConfig: { | ||
| baseDirectory: resourcesDir, | ||
| maxDepth: 2, | ||
| minConfidence: 0.4, | ||
| enableSemanticAnalysis: true | ||
| fileOrganizerConfig: { | ||
| moveFiles: options.moveFiles, | ||
| // 根据选项决定是否移动文件 | ||
| namingMode: "preserve-original", | ||
| // 保留原始文件名,只修复后缀 | ||
| createDirectories: true, | ||
| conflictResolution: "rename", | ||
| createBackup: options.createBackup | ||
| }, | ||
| // 报告生成选项 | ||
| reportOptions: { | ||
| format: "xml", | ||
| outputPath: path.join(__dirname, "../analysis-report.xml"), | ||
| includeFolderMatching: true, | ||
| includeDetailedAnalysis: true, | ||
| title: "MixVideo \u89C6\u9891\u5206\u6790\u62A5\u544A" | ||
| }, | ||
| // 进度跟踪 | ||
| onProgress | ||
| minConfidenceForMove: parseFloat(options.confidence) | ||
| }, | ||
| (progress) => { | ||
| console.log(`\u{1F4CA} [${progress.phase}] ${progress.step} (${progress.progress}%)`); | ||
| console.log(` \u5DF2\u5904\u7406: ${progress.processedVideos}/${progress.totalVideos}`); | ||
| } | ||
| ); | ||
| console.log("\n\u{1F389} \u5206\u6790\u5B8C\u6210\uFF01"); | ||
| console.log(`\u{1F4F9} \u5206\u6790\u89C6\u9891\u6570\u91CF: ${result.analysisResults.length}`); | ||
| console.log(`\u{1F4C2} \u6587\u4EF6\u5939\u5339\u914D\u6570\u91CF: ${Object.keys(result.folderMatches).length}`); | ||
| console.log(`\u{1F4C4} \u62A5\u544A\u4FDD\u5B58\u4F4D\u7F6E: ${result.reportPath}`); | ||
| const stats = analyzer.getAnalysisStatistics(result.analysisResults); | ||
| console.log("\n\u{1F4CA} \u8BE6\u7EC6\u7EDF\u8BA1:"); | ||
| console.log(`- \u603B\u5904\u7406\u65F6\u95F4: ${stats.totalProcessingTime}ms`); | ||
| console.log(`- \u603B\u573A\u666F\u6570: ${stats.totalScenes}`); | ||
| console.log(`- \u603B\u5BF9\u8C61\u6570: ${stats.totalObjects}`); | ||
| console.log(`- \u5E73\u5747\u8D28\u91CF\u5206\u6570: ${stats.averageQualityScore.toFixed(2)}`); | ||
| if (Object.keys(result.folderMatches).length > 0) { | ||
| console.log("\n\u{1F4C1} \u667A\u80FD\u6587\u4EF6\u5939\u5339\u914D\u5EFA\u8BAE:"); | ||
| for (const [videoPath, matches] of Object.entries(result.folderMatches)) { | ||
| const videoName = path.basename(videoPath); | ||
| console.log(` | ||
| \u{1F3AC} ${videoName}:`); | ||
| matches.slice(0, 3).forEach((match, index) => { | ||
| console.log(` ${index + 1}. ${match.folderPath} (\u7F6E\u4FE1\u5EA6: ${(match.confidence * 100).toFixed(1)}%)`); | ||
| if (match.reasons && match.reasons.length > 0) { | ||
| console.log(` \u7406\u7531: ${match.reasons.join(", ")}`); | ||
| } | ||
| }); | ||
| } | ||
| } | ||
| await writeFile( | ||
| join2(targetDir, "report.json"), | ||
| JSON.stringify(result, null, 2) | ||
| ); | ||
| console.log("\u2705 \u89C6\u9891\u5206\u6790\u5B8C\u6210\uFF01"); | ||
| console.log(`\u{1F4CA} \u603B\u8BA1: ${result.totalVideos} \u4E2A\u89C6\u9891`); | ||
| console.log(`\u2705 \u5DF2\u5206\u6790: ${result.analyzedVideos} \u4E2A`); | ||
| console.log(`\u{1F4C1} \u5DF2\u5206\u7C7B: ${result.matchedVideos} \u4E2A`); | ||
| console.log(`\u{1F4E6} \u5DF2\u7EC4\u7EC7: ${result.organizedVideos} \u4E2A`); | ||
| console.log(`\u{1F4C4} \u62A5\u544A\u5DF2\u4FDD\u5B58\u5230: ${join2(targetDir, "report.json")}`); | ||
| } catch (error) { | ||
@@ -117,24 +93,200 @@ console.error("\u274C \u5206\u6790\u8FC7\u7A0B\u4E2D\u51FA\u73B0\u9519\u8BEF:", error); | ||
| }); | ||
| program.command(`generate`).description(`Generate a video`).argument(`<dir>`, `Product video path`).option(`-t, --template [template]`, `Template to use`, `draft_content.json`).action(async (dir, options) => { | ||
| const draft = parse(join(root, options.template)); | ||
| draft.materials.videos = await Promise.all(draft.materials.videos.map(async (video) => { | ||
| const material_name = video.material_name; | ||
| const videoPath = await getMaterialVideoByName(dir, material_name); | ||
| video.path = videoPath; | ||
| return video; | ||
| })); | ||
| await writeFile(join(root, options.template), JSON.stringify(draft, null, 2)); | ||
| console.log(`\u606D\u559C\u60A8\uFF0C\u751F\u6210\u6210\u529F`); | ||
| }); | ||
| program.parse(); | ||
| } | ||
| var set = /* @__PURE__ */ new Set(); | ||
| // src/commands/generate.ts | ||
| import { Command as Command2 } from "commander"; | ||
| import { join as join4 } from "path"; | ||
| import { writeFile as writeFile2, readdir as readdir2 } from "fs/promises"; | ||
| import { parse } from "@mixvideo/jianying"; | ||
| // src/utils/video-scanner.ts | ||
| import { readdir, stat } from "fs/promises"; | ||
| import { join as join3, extname } from "path"; | ||
| var SUPPORTED_VIDEO_FORMATS = [ | ||
| ".mp4", | ||
| ".mov", | ||
| ".avi", | ||
| ".mkv", | ||
| ".webm", | ||
| ".flv", | ||
| ".wmv", | ||
| ".m4v", | ||
| ".3gp", | ||
| ".ts" | ||
| ]; | ||
| async function scanVideoDirectory(dirPath) { | ||
| const videos = []; | ||
| try { | ||
| const files = await readdir(dirPath); | ||
| for (const file of files) { | ||
| const filePath = join3(dirPath, file); | ||
| const stats = await stat(filePath); | ||
| if (stats.isDirectory()) { | ||
| continue; | ||
| } | ||
| const ext = extname(file).toLowerCase(); | ||
| if (!SUPPORTED_VIDEO_FORMATS.includes(ext)) { | ||
| continue; | ||
| } | ||
| const videoInfo = { | ||
| path: filePath, | ||
| name: file.replace(ext, ""), | ||
| // 移除扩展名 | ||
| size: stats.size, | ||
| format: ext.substring(1), | ||
| // 移除点号 | ||
| createdAt: stats.birthtime, | ||
| modifiedAt: stats.mtime | ||
| }; | ||
| videos.push(videoInfo); | ||
| } | ||
| } catch (error) { | ||
| throw new Error(`\u626B\u63CF\u89C6\u9891\u76EE\u5F55\u5931\u8D25: ${error}`); | ||
| } | ||
| return videos; | ||
| } | ||
| // src/commands/generate.ts | ||
| var usedFiles = /* @__PURE__ */ new Set(); | ||
| async function getMaterialVideoByName(dir, name) { | ||
| const files = await readdir(join(root, dir)); | ||
| const root = process.cwd(); | ||
| const files = await readdir2(join4(root, dir)); | ||
| const file = files[Math.floor(Math.random() * files.length)]; | ||
| const used = join(root, dir, file); | ||
| if (set.has(used)) return getMaterialVideoByName(dir, name); | ||
| set.add(used); | ||
| const used = join4(root, dir, file); | ||
| if (usedFiles.has(used)) { | ||
| return getMaterialVideoByName(dir, name); | ||
| } | ||
| usedFiles.add(used); | ||
| return used; | ||
| } | ||
| main(); | ||
| function createGenerateCommand() { | ||
| const command = new Command2("generate").description("\u751F\u6210\u526A\u6620\u8349\u7A3F\u6587\u4EF6"); | ||
| command.command("from-videos").description("\u4ECE\u89C6\u9891\u76EE\u5F55\u751F\u6210\u526A\u6620\u8349\u7A3F\u6587\u4EF6").argument("<source>", "\u6E90\u89C6\u9891\u76EE\u5F55\u8DEF\u5F84").option("-o, --output <path>", "\u8F93\u51FA\u6587\u4EF6\u8DEF\u5F84", "draft_content.json").option("--title <title>", "\u9879\u76EE\u6807\u9898", "\u89C6\u9891\u9879\u76EE").option("--fps <number>", "\u5E27\u7387", "30").option("--resolution <resolution>", "\u5206\u8FA8\u7387 (1080p|720p|4k)", "1080p").action(async (source, options) => { | ||
| try { | ||
| console.log("\u{1F3AC} \u5F00\u59CB\u4ECE\u89C6\u9891\u76EE\u5F55\u751F\u6210\u526A\u6620\u8349\u7A3F..."); | ||
| const sourceDir = join4(process.cwd(), source); | ||
| const videos = await scanVideoDirectory(sourceDir); | ||
| if (videos.length === 0) { | ||
| console.log("\u26A0\uFE0F \u672A\u627E\u5230\u89C6\u9891\u6587\u4EF6"); | ||
| return; | ||
| } | ||
| console.log(`\u{1F4C1} \u627E\u5230 ${videos.length} \u4E2A\u89C6\u9891\u6587\u4EF6`); | ||
| const draftContent = { | ||
| version: "13.8.0", | ||
| materials: { | ||
| videos: videos.map((video, index) => ({ | ||
| id: `video_${index}`, | ||
| path: video.path, | ||
| name: video.name, | ||
| duration: 5e6, | ||
| // 默认5秒,微秒单位 | ||
| width: 1920, | ||
| height: 1080 | ||
| })) | ||
| }, | ||
| tracks: [ | ||
| { | ||
| type: "video", | ||
| segments: videos.map((_video, index) => ({ | ||
| id: `segment_${index}`, | ||
| material_id: `video_${index}`, | ||
| target_timerange: { | ||
| start: index * 5e6, | ||
| duration: 5e6 | ||
| } | ||
| })) | ||
| } | ||
| ] | ||
| }; | ||
| const outputPath = join4(process.cwd(), options.output); | ||
| await writeFile2(outputPath, JSON.stringify(draftContent, null, 2)); | ||
| console.log("\u2705 \u526A\u6620\u8349\u7A3F\u751F\u6210\u5B8C\u6210\uFF01"); | ||
| console.log(`\u{1F4C4} \u8349\u7A3F\u6587\u4EF6\u5DF2\u4FDD\u5B58\u5230: ${outputPath}`); | ||
| console.log(`\u{1F3AC} \u5305\u542B ${videos.length} \u4E2A\u89C6\u9891\u7247\u6BB5`); | ||
| } catch (error) { | ||
| console.error("\u274C \u751F\u6210\u8349\u7A3F\u8FC7\u7A0B\u4E2D\u51FA\u73B0\u9519\u8BEF:", error); | ||
| process.exit(1); | ||
| } | ||
| }); | ||
| command.command("from-template").description("\u4ECE\u6A21\u677F\u751F\u6210\u526A\u6620\u8349\u7A3F\u6587\u4EF6").argument("<dir>", "\u7D20\u6750\u76EE\u5F55\u8DEF\u5F84").option("-t, --template <template>", "\u6A21\u677F\u6587\u4EF6\u8DEF\u5F84", "draft_content.json").action(async (dir, options) => { | ||
| try { | ||
| console.log("\u{1F3AC} \u5F00\u59CB\u4ECE\u6A21\u677F\u751F\u6210\u526A\u6620\u8349\u7A3F..."); | ||
| const root = process.cwd(); | ||
| const templatePath = join4(root, options.template); | ||
| const draft = parse(templatePath); | ||
| draft.materials.videos = await Promise.all( | ||
| draft.materials.videos.map(async (video) => { | ||
| const materialName = video.material_name; | ||
| const videoPath = await getMaterialVideoByName(dir, materialName); | ||
| video.path = videoPath; | ||
| return video; | ||
| }) | ||
| ); | ||
| await writeFile2(templatePath, JSON.stringify(draft, null, 2)); | ||
| console.log("\u2705 \u8349\u7A3F\u751F\u6210\u6210\u529F\uFF01"); | ||
| console.log(`\u{1F4C4} \u5DF2\u66F4\u65B0\u6A21\u677F\u6587\u4EF6: ${templatePath}`); | ||
| } catch (error) { | ||
| console.error("\u274C \u751F\u6210\u8349\u7A3F\u8FC7\u7A0B\u4E2D\u51FA\u73B0\u9519\u8BEF:", error); | ||
| process.exit(1); | ||
| } | ||
| }); | ||
| return command; | ||
| } | ||
| // src/commands/auth.ts | ||
| import { Command as Command3 } from "commander"; | ||
| function createAuthCommands() { | ||
| const login = new Command3("login").description("\u767B\u5F55\u5230 MixVideo").option("-u, --username <username>", "\u7528\u6237\u540D").option("-p, --password <password>", "\u5BC6\u7801").addHelpText("after", ` | ||
| \u793A\u4F8B: | ||
| $ mixvideo login -u user@example.com -p password123 | ||
| $ mixvideo login --username user@example.com --password password123 | ||
| \u6CE8\u610F: \u9009\u9879\u6807\u5FD7 -u \u548C -p \u4E4B\u95F4\u4E0D\u80FD\u6709\u7A7A\u683C`).action(async (options) => { | ||
| try { | ||
| console.log("\u{1F510} \u5F00\u59CB\u767B\u5F55..."); | ||
| if (!options.username || !options.password) { | ||
| console.error("\u274C \u7F3A\u5C11\u5FC5\u9700\u53C2\u6570"); | ||
| console.log("\uFFFD \u8BF7\u63D0\u4F9B\u7528\u6237\u540D\u548C\u5BC6\u7801"); | ||
| console.log(""); | ||
| console.log("\u6B63\u786E\u4F7F\u7528\u65B9\u5F0F:"); | ||
| console.log(" mixvideo login -u <username> -p <password>"); | ||
| console.log(""); | ||
| console.log("\u793A\u4F8B:"); | ||
| console.log(" mixvideo login -u user@bowongai.com -p bowong7777"); | ||
| console.log(""); | ||
| console.log("\u26A0\uFE0F \u6CE8\u610F: -u \u548C -p \u4E4B\u95F4\u4E0D\u80FD\u6709\u7A7A\u683C"); | ||
| process.exit(1); | ||
| } | ||
| console.log(`\u{1F464} \u7528\u6237\u540D: ${options.username}`); | ||
| console.log("\u2705 \u767B\u5F55\u6210\u529F\uFF01"); | ||
| } catch (error) { | ||
| console.error("\u274C \u767B\u5F55\u5931\u8D25:", error); | ||
| process.exit(1); | ||
| } | ||
| }); | ||
| const logout = new Command3("logout").description("\u9000\u51FA\u767B\u5F55").action(async () => { | ||
| try { | ||
| console.log("\u{1F513} \u6B63\u5728\u9000\u51FA\u767B\u5F55..."); | ||
| console.log("\u2705 \u5DF2\u9000\u51FA\u767B\u5F55"); | ||
| } catch (error) { | ||
| console.error("\u274C \u9000\u51FA\u767B\u5F55\u5931\u8D25:", error); | ||
| process.exit(1); | ||
| } | ||
| }); | ||
| return { login, logout }; | ||
| } | ||
| // src/index.ts | ||
| async function main() { | ||
| const program = new Command4(); | ||
| program.name("mixvideo").version("1.0.0").description("\u89C6\u9891\u5206\u6790\u548C\u6DF7\u526A\u5DE5\u5177"); | ||
| program.addCommand(createAuthCommands().login); | ||
| program.addCommand(createAuthCommands().logout); | ||
| program.addCommand(createAnalyzeCommand()); | ||
| program.addCommand(createGenerateCommand()); | ||
| program.parse(); | ||
| } | ||
| main().catch((error) => { | ||
| console.error("\u274C \u7A0B\u5E8F\u8FD0\u884C\u51FA\u9519:", error); | ||
| process.exit(1); | ||
| }); |
+19
-16
| { | ||
| "name": "@mixvideo/cli", | ||
| "version": "1.0.3", | ||
| "description": "Gemini AI API client library for MixVideo project", | ||
| "version": "1.0.4", | ||
| "description": "AI-powered video analysis and editing CLI tool for automatic video classification and Jianying draft generation", | ||
| "main": "dist/index.js", | ||
@@ -18,9 +18,19 @@ "module": "dist/index.mjs", | ||
| ], | ||
| "scripts": { | ||
| "build": "tsup", | ||
| "start": "tsx src/index.ts", | ||
| "dev": "tsup --watch", | ||
| "clean": "rm -rf dist", | ||
| "type-check": "tsc --noEmit" | ||
| }, | ||
| "keywords": [ | ||
| "video-analysis", | ||
| "ai", | ||
| "cli", | ||
| "video-editing", | ||
| "gemini", | ||
| "ai", | ||
| "google", | ||
| "vertex-ai", | ||
| "jianying", | ||
| "video-classification", | ||
| "typescript", | ||
| "api-client" | ||
| "automation" | ||
| ], | ||
@@ -30,5 +40,5 @@ "author": "MixVideo Team", | ||
| "dependencies": { | ||
| "commander": "^14.0.0", | ||
| "@mixvideo/jianying": "1.0.0", | ||
| "@mixvideo/video-analyzer": "1.0.0" | ||
| "@mixvideo/jianying": "workspace:*", | ||
| "@mixvideo/video-analyzer": "workspace:*", | ||
| "commander": "^14.0.0" | ||
| }, | ||
@@ -43,10 +53,3 @@ "devDependencies": { | ||
| "typescript": "^5.0.0" | ||
| }, | ||
| "scripts": { | ||
| "build": "tsup", | ||
| "start": "tsx src/index.ts", | ||
| "dev": "tsup --watch", | ||
| "clean": "rm -rf dist", | ||
| "type-check": "tsc --noEmit" | ||
| } | ||
| } |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No README
QualityPackage does not have a README. This may indicate a failed publish or a low quality package.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 2 instances in 1 package
30370
114.95%6
20%574
91.33%1
-50%165
Infinity%9
80%- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed