Files
xiaoli-cli/operit_py/file_converter.py
T
shuiyu1123 dc77dfd304 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 专属功能已降级处理(保留接口,返回不支持提示)
2026-06-23 12:27:18 +00:00

231 lines
10 KiB
Python

# 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