feat: 将 47 个 Operit TypeScript 脚本全面翻译为 Python
- 新增 operit_runtime.py: 跨平台 Python 运行时 - 实现 Tools.System/Files/Net/UI/Memory/FFmpeg/Workflow/Chat/SoftwareSettings/Tasker - 实现 OkHttp 链式 HTTP 客户端兼容层 - 实现 complete()/exports/console 全局对象 - 实现 OperitScriptLoader 脚本加载器 - 支持 Windows/macOS/Linux 三平台 - 新增 operit_py/ 目录: 47 个 Python 翻译脚本 - 搜索类: duckduckgo, google_search, tavily, zhipu_search, various_search, crossref - 绘图类: openai_draw, qwen_draw, xai_draw, minimax_draw, siliconflow_draw, zhipu_draw, nanobanana_draw - 媒体类: ffmpeg, file_converter, douyin_download, bilibili_tools, jmcomic - 文件类: extended_file_tools, hex_editor, reader, beeimg_image_uploader - 网络类: extended_http_tools, network_test - 自动化类: browser, automatic_ui_base, automatic_ui_subagent, automatic_bilibili_assistant, automatic_xiaohongshu_assistant, automatic_baidu_map_assistant - 系统类: system_tools, super_admin, daily_life, operit_editor - 对话类: ai_chat, extended_chat, qq_intelligent - 工作流类: workflow, vflow_trigger, tasker - 工具类: time, code_runner, quick_start, extended_memory_tools, baidu_map, 12306, pdf_vision_parser - 测试结果: 47/47 语法检查通过, 38 脚本加载成功, 280 个工具注册 - Android 专属功能已降级处理(保留接口,返回不支持提示)
This commit is contained in:
1 parent
6fa759665d
commit
dc77dfd304
48 files changed
+22273
No files matched your search
@@ -0,0 +1,230 @@
|
||||
# METADATA
|
||||
# {
|
||||
# "name": "file_converter",
|
||||
#
|
||||
# "display_name": {
|
||||
# "zh": "文件转换器",
|
||||
# "en": "File Converter"
|
||||
# },
|
||||
# "description": {
|
||||
# "zh": "提供全面的文件格式转换功能。支持常见的音频/视频(如 MP4、MOV、MP3、WAV)、图像(如 JPG、PNG、WEBP)以及文档(如 Markdown、HTML、DOCX、PDF)之间的相互转换。",
|
||||
# "en": "Comprehensive file format conversion. Supports converting between common audio/video (MP4, MOV, MP3, WAV), images (JPG, PNG, WEBP), and documents (Markdown, HTML, DOCX, PDF)."
|
||||
# },
|
||||
# "enabledByDefault": true,
|
||||
# "category": "File",
|
||||
# "tools": [
|
||||
# {
|
||||
# "name": "convert_file",
|
||||
# "description": {
|
||||
# "zh": "调用 FFmpeg、ImageMagick 和 Pandoc 等外部命令行工具,转换文件的格式。支持音视频、图像和文档等多种类型。如果检测到工具未安装,会尝试自动安装。",
|
||||
# "en": "Convert files using external CLI tools such as FFmpeg, ImageMagick, and Pandoc. Supports audio/video, images, and documents. If a required tool is missing, it will attempt to install it automatically."
|
||||
# },
|
||||
# "parameters": [
|
||||
# { "name": "input_path", "description": { "zh": "输入文件的路径。", "en": "Input file path." }, "type": "string", "required": true },
|
||||
# { "name": "output_path", "description": { "zh": "输出文件的路径。扩展名决定了目标格式。", "en": "Output file path. The file extension determines the target format." }, "type": "string", "required": true },
|
||||
# { "name": "options", "description": { "zh": "用于转换工具的可选命令行选项 (例如, 为 ImageMagick 设置 '-quality 80' )。", "en": "Optional CLI options for the conversion tool (e.g. set '-quality 80' for ImageMagick)." }, "type": "string", "required": false }
|
||||
# ]
|
||||
# }
|
||||
# ]
|
||||
# }
|
||||
|
||||
import traceback
|
||||
|
||||
terminalSessionId = None
|
||||
|
||||
|
||||
async def getTerminalSessionId():
|
||||
global terminalSessionId
|
||||
if terminalSessionId:
|
||||
return terminalSessionId
|
||||
session = await Tools.System.terminal.create("file_converter_session")
|
||||
terminalSessionId = session["sessionId"]
|
||||
return terminalSessionId
|
||||
|
||||
|
||||
async def executeTerminalCommand(command, timeoutMs=None):
|
||||
sessionId = await getTerminalSessionId()
|
||||
return await Tools.System.terminal.exec(sessionId, command)
|
||||
|
||||
|
||||
async def checkAndInstall(toolName, packageName):
|
||||
"""Checks if a command-line tool is installed, and attempts to install it if not found."""
|
||||
console.log(f"Checking for {toolName}...")
|
||||
checkCmd = f"command -v {toolName}"
|
||||
checkResult = await executeTerminalCommand(checkCmd)
|
||||
|
||||
if checkResult["exitCode"] == 0 and checkResult["output"].strip() != "":
|
||||
console.log(f"{toolName} is already installed at: {checkResult['output'].strip()}")
|
||||
return True
|
||||
|
||||
console.log(f"{toolName} not found. Attempting to install package: {packageName}...")
|
||||
|
||||
# Assuming an apt-based system (like Debian/Ubuntu).
|
||||
updateCmd = "apt-get update"
|
||||
console.log(f"Running: {updateCmd}")
|
||||
updateResult = await executeTerminalCommand(updateCmd)
|
||||
if updateResult["exitCode"] != 0:
|
||||
console.warn(f"'apt-get update' failed. This might be okay if caches are fresh, but installation may fail.\nOutput: {updateResult['output']}")
|
||||
|
||||
installCmd = f"apt-get install -y {packageName}"
|
||||
console.log(f"Running: {installCmd}")
|
||||
installResult = await executeTerminalCommand(installCmd)
|
||||
|
||||
if installResult["exitCode"] != 0:
|
||||
console.error(f"Failed to install {packageName}: {installResult['output']}")
|
||||
raise Exception(f"Failed to install required tool: {toolName} (package: {packageName}). Please try installing it manually.")
|
||||
|
||||
console.log(f"{packageName} installed successfully.")
|
||||
return True
|
||||
|
||||
|
||||
def getConverterInfo(inputPath, outputPath, options=None):
|
||||
"""Determines the appropriate conversion tool and command based on file extensions."""
|
||||
def getExt(path):
|
||||
return path.split(".")[-1].lower()
|
||||
|
||||
inputExt = getExt(inputPath)
|
||||
outputExt = getExt(outputPath)
|
||||
|
||||
def isAudioVideo(ext):
|
||||
return ext in ["mp4", "mkv", "avi", "mov", "flv", "webm", "mp3", "wav", "ogg", "flac", "aac", "m4a", "wma", "wmv"]
|
||||
|
||||
def isImage(ext):
|
||||
return ext in ["jpg", "jpeg", "png", "gif", "bmp", "webp", "tiff", "ico", "svg"]
|
||||
|
||||
def isDocument(ext):
|
||||
return ext in ["md", "html", "docx", "pdf", "txt", "epub", "odt", "rtf", "tex", "rst", "json", "csv"]
|
||||
|
||||
if isAudioVideo(inputExt) or isAudioVideo(outputExt):
|
||||
return {
|
||||
"tool": "ffmpeg",
|
||||
"pkg": "ffmpeg",
|
||||
"command": f'ffmpeg -y -i "{inputPath}" {options or ""} "{outputPath}"'
|
||||
}
|
||||
|
||||
if isImage(inputExt) or isImage(outputExt):
|
||||
return {
|
||||
"tool": "convert",
|
||||
"pkg": "imagemagick",
|
||||
"command": f'convert "{inputPath}" {options or ""} "{outputPath}"'
|
||||
}
|
||||
|
||||
if isDocument(inputExt) or isDocument(outputExt):
|
||||
return {
|
||||
"tool": "pandoc",
|
||||
"pkg": "pandoc",
|
||||
"command": f'pandoc "{inputPath}" -o "{outputPath}" {options or ""}'
|
||||
}
|
||||
|
||||
raise Exception(f"Unsupported or ambiguous file conversion from .{inputExt} to .{outputExt}.")
|
||||
|
||||
|
||||
async def convert_file(params):
|
||||
"""The core logic for the convert_file tool."""
|
||||
input_path = params["input_path"]
|
||||
output_path = params["output_path"]
|
||||
options = params.get("options")
|
||||
|
||||
fileExists = await Tools.Files.exists(input_path)
|
||||
if not fileExists["exists"]:
|
||||
raise Exception(f"Input file not found: {input_path}")
|
||||
|
||||
converter = getConverterInfo(input_path, output_path, options)
|
||||
|
||||
await checkAndInstall(converter["tool"], converter["pkg"])
|
||||
|
||||
console.log(f"Executing conversion command: {converter['command']}")
|
||||
result = await executeTerminalCommand(converter["command"])
|
||||
|
||||
if result["exitCode"] != 0:
|
||||
raise Exception(f"Conversion failed. Exit code: {result['exitCode']}\nOutput:\n{result['output']}")
|
||||
|
||||
outputExists = await Tools.Files.exists(output_path)
|
||||
if not outputExists["exists"]:
|
||||
# Sometimes a tool exits with 0 but fails, writing to stderr.
|
||||
raise Exception(f"Conversion process finished, but output file was not created at: {output_path}\nTerminal Output:\n{result['output']}")
|
||||
|
||||
return {
|
||||
"output_path": output_path,
|
||||
"details": f"File converted successfully and saved to {output_path}.",
|
||||
"terminal_output": result["output"]
|
||||
}
|
||||
|
||||
|
||||
async def wrap(func, params, successMessage, failMessage):
|
||||
"""A wrapper function for executing tools to provide standardized success/error handling."""
|
||||
try:
|
||||
result = await func(params)
|
||||
complete({"success": True, "message": successMessage, "data": result})
|
||||
except Exception as error:
|
||||
msg = str(getattr(error, "message", error))
|
||||
console.error(f"Function {func.__name__} failed! Error: {msg}")
|
||||
complete({"success": False, "message": f"{failMessage}: {msg}", "error_stack": traceback.format_exc()})
|
||||
|
||||
|
||||
async def main():
|
||||
"""A main function for self-testing the capabilities of this tool package."""
|
||||
console.log("--- Starting File Converter Tool Test ---")
|
||||
testDir = "/sdcard/Download/converter_test"
|
||||
await Tools.Files.mkdir(testDir, True)
|
||||
|
||||
# Test 1: Image conversion (PNG to JPG)
|
||||
try:
|
||||
console.log("\n[1/3] Testing Image Conversion (PNG -> JPG)")
|
||||
# A simple 1x1 red pixel PNG in base64
|
||||
pngBase64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/wcAAwAB/epv2AAAAABJRU5ErkJggg=="
|
||||
inputPng = f"{testDir}/test.png"
|
||||
outputJpg = f"{testDir}/test.jpg"
|
||||
await Tools.Files.writeBinary(inputPng, pngBase64)
|
||||
|
||||
imageResult = await convert_file({"input_path": inputPng, "output_path": outputJpg})
|
||||
console.log("Image conversion success:", imageResult)
|
||||
if not (await Tools.Files.exists(outputJpg))["exists"]:
|
||||
raise Exception("JPG file not created.")
|
||||
|
||||
# Verify that readBinary can read the converted image as Base64
|
||||
jpgBinary = await Tools.Files.readBinary(outputJpg)
|
||||
console.log("ReadBinary success: size=", jpgBinary["size"], "bytes, base64 length=", len(jpgBinary["contentBase64"]))
|
||||
except Exception as e:
|
||||
msg = str(getattr(e, "message", e))
|
||||
console.error("Image conversion test failed:", msg)
|
||||
|
||||
# Test 2: Document conversion (MD to HTML)
|
||||
try:
|
||||
console.log("\n[2/3] Testing Document Conversion (MD -> HTML)")
|
||||
inputMd = f"{testDir}/test.md"
|
||||
outputHtml = f"{testDir}/test.html"
|
||||
await Tools.Files.write(inputMd, "# Hello World")
|
||||
|
||||
docResult = await convert_file({"input_path": inputMd, "output_path": outputHtml})
|
||||
console.log("Document conversion success:", docResult)
|
||||
htmlContent = (await Tools.Files.read(outputHtml))["content"]
|
||||
if "<h1" not in htmlContent:
|
||||
raise Exception("HTML content is incorrect.")
|
||||
except Exception as e:
|
||||
msg = str(getattr(e, "message", e))
|
||||
console.error("Document conversion test failed:", msg)
|
||||
|
||||
# Test 3: Unsupported conversion
|
||||
try:
|
||||
console.log("\n[3/3] Testing Unsupported Conversion (zip -> tar)")
|
||||
inputZip = f"{testDir}/test.zip"
|
||||
await Tools.Files.write(inputZip, "dummy content")
|
||||
await convert_file({"input_path": inputZip, "output_path": f"{testDir}/test.tar"})
|
||||
console.error("Unsupported conversion test FAILED: It should have thrown an error but didn't.")
|
||||
except Exception as e:
|
||||
msg = str(getattr(e, "message", e))
|
||||
console.log("Unsupported conversion test PASSED as expected:", msg)
|
||||
|
||||
console.log("\n--- File Converter Tool Test Finished ---")
|
||||
await Tools.Files.deleteFile(testDir, True)
|
||||
console.log("Cleaned up test directory.")
|
||||
complete({"success": True, "message": "All tests finished."})
|
||||
|
||||
|
||||
async def convert_file_wrapper(p):
|
||||
await wrap(convert_file, p, "File conversion successful.", "File conversion failed.")
|
||||
|
||||
|
||||
exports.convert_file = convert_file_wrapper
|
||||
exports.main = main
|
||||
Reference in new issue
Block a user