Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@mixvideo/cli

Package Overview
Dependencies
Maintainers
1
Versions
6
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@mixvideo/cli - npm Package Compare versions

Comparing version
1.0.3
to
1.0.4
+164
README.md
# @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);
});
#!/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);
});
{
"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"
}
}