openconvert 0.1.0__py3-none-any.whl → 1.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- openconvert/__init__.py +96 -3
- openconvert/__main__.py +13 -0
- openconvert/client.py +392 -0
- openconvert/openconvert_cli.py +540 -0
- openconvert-1.1.0.dist-info/METADATA +504 -0
- openconvert-1.1.0.dist-info/RECORD +10 -0
- openconvert-1.1.0.dist-info/entry_points.txt +2 -0
- openconvert-1.1.0.dist-info/licenses/LICENSE +21 -0
- openconvert/cli.py +0 -145
- openconvert/converter.py +0 -152
- openconvert/converters/__init__.py +0 -3
- openconvert/converters/archive_converter.py +0 -277
- openconvert/converters/audio_converter.py +0 -223
- openconvert/converters/code_converter.py +0 -412
- openconvert/converters/document_converter.py +0 -596
- openconvert/converters/image_converter.py +0 -214
- openconvert/converters/model_converter.py +0 -208
- openconvert/converters/video_converter.py +0 -259
- openconvert/launcher.py +0 -0
- openconvert-0.1.0.dist-info/METADATA +0 -232
- openconvert-0.1.0.dist-info/RECORD +0 -17
- openconvert-0.1.0.dist-info/entry_points.txt +0 -2
- {openconvert-0.1.0.dist-info → openconvert-1.1.0.dist-info}/WHEEL +0 -0
- {openconvert-0.1.0.dist-info → openconvert-1.1.0.dist-info}/top_level.txt +0 -0
openconvert/__init__.py
CHANGED
@@ -1,7 +1,100 @@
|
|
1
1
|
"""
|
2
|
-
OpenConvert
|
2
|
+
OpenConvert CLI Tool
|
3
|
+
|
4
|
+
A command-line interface for connecting to the OpenConvert OpenAgents network
|
5
|
+
to discover file conversion services and perform file conversions.
|
3
6
|
"""
|
4
7
|
|
5
|
-
|
8
|
+
import asyncio
|
9
|
+
from pathlib import Path
|
10
|
+
from typing import Optional, List
|
11
|
+
|
12
|
+
__version__ = "1.1.0"
|
13
|
+
__author__ = "OpenAgents Team"
|
14
|
+
__email__ = "team@openagents.com"
|
15
|
+
|
16
|
+
# Import the async convert function
|
17
|
+
try:
|
18
|
+
from openconvert.openconvert_cli import convert as _async_convert
|
19
|
+
except ImportError:
|
20
|
+
from openconvert_cli import convert as _async_convert
|
21
|
+
|
22
|
+
|
23
|
+
def convert(
|
24
|
+
input_files: List[Path],
|
25
|
+
output_path: Path,
|
26
|
+
host: str = "network.openconvert.ai",
|
27
|
+
port: int = 8765,
|
28
|
+
) -> bool:
|
29
|
+
"""
|
30
|
+
Convert files using the OpenConvert network.
|
31
|
+
|
32
|
+
Args:
|
33
|
+
input_files: List of input file paths to convert
|
34
|
+
output_path: Output file path
|
35
|
+
host: OpenConvert network host (default: network.openconvert.ai)
|
36
|
+
port: OpenConvert network port (default: 8765)
|
37
|
+
|
38
|
+
Returns:
|
39
|
+
bool: True if conversion successful, False otherwise
|
40
|
+
"""
|
41
|
+
try:
|
42
|
+
result = asyncio.run(_async_convert(
|
43
|
+
input_files=input_files,
|
44
|
+
output_path=output_path,
|
45
|
+
host=host,
|
46
|
+
port=port
|
47
|
+
))
|
48
|
+
return result
|
49
|
+
except Exception as e:
|
50
|
+
print(f"Conversion failed: {e}")
|
51
|
+
return False
|
52
|
+
|
53
|
+
|
54
|
+
def convert_file(
|
55
|
+
input_path: str,
|
56
|
+
output_path: str,
|
57
|
+
host: str = "network.openconvert.ai",
|
58
|
+
port: int = 8765
|
59
|
+
) -> bool:
|
60
|
+
"""
|
61
|
+
Convert a single file using the OpenConvert network.
|
62
|
+
|
63
|
+
Args:
|
64
|
+
input_path: Path to input file
|
65
|
+
output_path: Path for output file
|
66
|
+
host: OpenConvert network host (default: network.openconvert.ai)
|
67
|
+
port: OpenConvert network port (default: 8765)
|
68
|
+
|
69
|
+
Returns:
|
70
|
+
bool: True if conversion successful, False otherwise
|
71
|
+
|
72
|
+
Example:
|
73
|
+
>>> from openconvert import convert_file
|
74
|
+
>>> success = convert_file("document.txt", "document.pdf")
|
75
|
+
>>> if success:
|
76
|
+
... print("Conversion completed!")
|
77
|
+
"""
|
78
|
+
try:
|
79
|
+
input_files = [Path(input_path)]
|
80
|
+
output_file = Path(output_path)
|
81
|
+
|
82
|
+
return convert(
|
83
|
+
input_files=input_files,
|
84
|
+
output_path=output_file,
|
85
|
+
host=host,
|
86
|
+
port=port
|
87
|
+
)
|
88
|
+
except Exception as e:
|
89
|
+
print(f"File conversion failed: {e}")
|
90
|
+
return False
|
91
|
+
|
6
92
|
|
7
|
-
|
93
|
+
# Export main functions
|
94
|
+
__all__ = [
|
95
|
+
"convert",
|
96
|
+
"convert_file",
|
97
|
+
"__version__",
|
98
|
+
"__author__",
|
99
|
+
"__email__",
|
100
|
+
]
|
openconvert/__main__.py
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
OpenConvert CLI Module Entry Point
|
4
|
+
|
5
|
+
Allows running the OpenConvert CLI as a module:
|
6
|
+
python -m openconvert -i input.txt -o output.pdf
|
7
|
+
"""
|
8
|
+
|
9
|
+
import sys
|
10
|
+
from openconvert.openconvert_cli import main
|
11
|
+
|
12
|
+
if __name__ == "__main__":
|
13
|
+
sys.exit(main())
|
openconvert/client.py
ADDED
@@ -0,0 +1,392 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
OpenConvert Client
|
4
|
+
|
5
|
+
Client class for connecting to the OpenConvert OpenAgents network,
|
6
|
+
discovering conversion agents, and performing file conversions.
|
7
|
+
"""
|
8
|
+
|
9
|
+
import asyncio
|
10
|
+
import logging
|
11
|
+
import base64
|
12
|
+
import tempfile
|
13
|
+
import uuid
|
14
|
+
from pathlib import Path
|
15
|
+
from typing import Optional, List, Dict, Any
|
16
|
+
|
17
|
+
# Import OpenAgents modules
|
18
|
+
import sys
|
19
|
+
import os
|
20
|
+
|
21
|
+
# Add openagents src to path
|
22
|
+
current_dir = Path(__file__).resolve().parent
|
23
|
+
openagents_root = current_dir.parent.parent
|
24
|
+
sys.path.insert(0, str(openagents_root / "src"))
|
25
|
+
|
26
|
+
from openagents.core.client import AgentClient
|
27
|
+
from openagents.protocols.discovery.openconvert_discovery.adapter import OpenConvertDiscoveryAdapter
|
28
|
+
from openagents.protocols.communication.simple_messaging.adapter import SimpleMessagingAgentAdapter
|
29
|
+
from openagents.models.messages import DirectMessage, BaseMessage
|
30
|
+
|
31
|
+
logger = logging.getLogger(__name__)
|
32
|
+
|
33
|
+
|
34
|
+
class OpenConvertClient:
|
35
|
+
"""Client for interacting with the OpenConvert OpenAgents network."""
|
36
|
+
|
37
|
+
def __init__(self, agent_id: Optional[str] = None):
|
38
|
+
"""Initialize the OpenConvert client.
|
39
|
+
|
40
|
+
Args:
|
41
|
+
agent_id: Optional agent ID. If not provided, a random one will be generated.
|
42
|
+
"""
|
43
|
+
self.agent_id = agent_id or f"openconvert-client-{uuid.uuid4().hex[:8]}"
|
44
|
+
self.client = AgentClient(self.agent_id)
|
45
|
+
self.discovery_adapter = OpenConvertDiscoveryAdapter()
|
46
|
+
self.messaging_adapter = SimpleMessagingAgentAdapter()
|
47
|
+
self.conversion_responses = {}
|
48
|
+
self.connected = False
|
49
|
+
|
50
|
+
logger.info(f"Initialized OpenConvert client with ID: {self.agent_id}")
|
51
|
+
|
52
|
+
async def connect(self, host: str = "network.openconvert.ai", port: int = 8765) -> bool:
|
53
|
+
"""Connect to the OpenConvert network.
|
54
|
+
|
55
|
+
Args:
|
56
|
+
host: Network host to connect to
|
57
|
+
port: Network port to connect to
|
58
|
+
|
59
|
+
Returns:
|
60
|
+
bool: True if connection successful
|
61
|
+
"""
|
62
|
+
try:
|
63
|
+
logger.info(f"Connecting to OpenConvert network at {host}:{port}")
|
64
|
+
|
65
|
+
# Connect to the network
|
66
|
+
success = await self.client.connect_to_server(
|
67
|
+
host=host,
|
68
|
+
port=port,
|
69
|
+
metadata={
|
70
|
+
"name": "OpenConvert CLI Client",
|
71
|
+
"type": "conversion_client",
|
72
|
+
"capabilities": ["file_conversion_requests"],
|
73
|
+
"version": "1.0.0"
|
74
|
+
}
|
75
|
+
)
|
76
|
+
|
77
|
+
if not success:
|
78
|
+
logger.error("Failed to connect to OpenConvert network")
|
79
|
+
return False
|
80
|
+
|
81
|
+
# Register protocol adapters
|
82
|
+
self.client.register_protocol_adapter(self.discovery_adapter)
|
83
|
+
self.client.register_protocol_adapter(self.messaging_adapter)
|
84
|
+
|
85
|
+
# Set up message handler for conversion responses
|
86
|
+
self.messaging_adapter.register_message_handler("conversion_response", self._handle_conversion_response)
|
87
|
+
|
88
|
+
self.connected = True
|
89
|
+
logger.info("✅ Successfully connected to OpenConvert network")
|
90
|
+
|
91
|
+
# Give some time for protocol registration
|
92
|
+
await asyncio.sleep(1)
|
93
|
+
|
94
|
+
return True
|
95
|
+
|
96
|
+
except Exception as e:
|
97
|
+
logger.error(f"Error connecting to network: {e}")
|
98
|
+
return False
|
99
|
+
|
100
|
+
def _handle_conversion_response(self, content: Dict[str, Any], sender_id: str) -> None:
|
101
|
+
"""Handle conversion response messages.
|
102
|
+
|
103
|
+
Args:
|
104
|
+
content: Message content
|
105
|
+
sender_id: ID of the agent that sent the response
|
106
|
+
"""
|
107
|
+
if content and (content.get("conversion_status") or content.get("action") == "conversion_result"):
|
108
|
+
self.conversion_responses[sender_id] = content
|
109
|
+
logger.debug(f"Received conversion response from {sender_id}")
|
110
|
+
|
111
|
+
async def discover_agents(self, source_format: str, target_format: str) -> List[Dict[str, Any]]:
|
112
|
+
"""Discover agents capable of performing a specific conversion.
|
113
|
+
|
114
|
+
Args:
|
115
|
+
source_format: Source MIME type
|
116
|
+
target_format: Target MIME type
|
117
|
+
|
118
|
+
Returns:
|
119
|
+
List of agent information dictionaries
|
120
|
+
"""
|
121
|
+
if not self.connected:
|
122
|
+
raise RuntimeError("Client is not connected to network")
|
123
|
+
|
124
|
+
logger.info(f"🔍 Discovering agents for {source_format} -> {target_format}")
|
125
|
+
|
126
|
+
try:
|
127
|
+
# Use the discovery adapter to find suitable agents
|
128
|
+
agents = await self.discovery_adapter.discover_conversion_agents(source_format, target_format)
|
129
|
+
|
130
|
+
logger.info(f"📋 Found {len(agents)} capable agents:")
|
131
|
+
for agent in agents:
|
132
|
+
agent_id = agent.get('agent_id', 'Unknown')
|
133
|
+
description = agent.get('description', 'No description')
|
134
|
+
logger.info(f" - {agent_id}: {description}")
|
135
|
+
|
136
|
+
return agents
|
137
|
+
|
138
|
+
except Exception as e:
|
139
|
+
logger.error(f"Error during agent discovery: {e}")
|
140
|
+
return []
|
141
|
+
|
142
|
+
async def convert_file(
|
143
|
+
self,
|
144
|
+
input_file: Path,
|
145
|
+
output_file: Path,
|
146
|
+
source_format: str,
|
147
|
+
target_format: str,
|
148
|
+
prompt: Optional[str] = None,
|
149
|
+
timeout: int = 60
|
150
|
+
) -> bool:
|
151
|
+
"""Convert a single file using the OpenConvert network.
|
152
|
+
|
153
|
+
Args:
|
154
|
+
input_file: Path to input file
|
155
|
+
output_file: Path to output file
|
156
|
+
source_format: Source MIME type
|
157
|
+
target_format: Target MIME type
|
158
|
+
prompt: Optional conversion instructions
|
159
|
+
timeout: Timeout in seconds for conversion
|
160
|
+
|
161
|
+
Returns:
|
162
|
+
bool: True if conversion successful
|
163
|
+
"""
|
164
|
+
if not self.connected:
|
165
|
+
raise RuntimeError("Client is not connected to network")
|
166
|
+
|
167
|
+
if not input_file.exists():
|
168
|
+
raise FileNotFoundError(f"Input file not found: {input_file}")
|
169
|
+
|
170
|
+
logger.info(f"🔄 Converting {input_file.name}: {source_format} -> {target_format}")
|
171
|
+
|
172
|
+
try:
|
173
|
+
# Discover agents capable of this conversion
|
174
|
+
agents = await self.discover_agents(source_format, target_format)
|
175
|
+
|
176
|
+
if not agents:
|
177
|
+
logger.error(f"❌ No agents found for {source_format} -> {target_format} conversion")
|
178
|
+
return False
|
179
|
+
|
180
|
+
# Use the first available agent
|
181
|
+
target_agent = agents[0]
|
182
|
+
agent_id = target_agent['agent_id']
|
183
|
+
|
184
|
+
logger.info(f"🎯 Using agent: {agent_id}")
|
185
|
+
|
186
|
+
# Read and encode the input file
|
187
|
+
file_data = input_file.read_bytes()
|
188
|
+
file_data_b64 = base64.b64encode(file_data).decode('ascii')
|
189
|
+
|
190
|
+
# Prepare conversion request
|
191
|
+
request_content = {
|
192
|
+
"file_data": file_data_b64,
|
193
|
+
"filename": input_file.name,
|
194
|
+
"source_format": source_format,
|
195
|
+
"target_format": target_format
|
196
|
+
}
|
197
|
+
|
198
|
+
# Add prompt if provided
|
199
|
+
if prompt:
|
200
|
+
request_content["prompt"] = prompt
|
201
|
+
logger.info(f"💬 Using prompt: {prompt}")
|
202
|
+
|
203
|
+
# Clear any previous responses
|
204
|
+
if agent_id in self.conversion_responses:
|
205
|
+
del self.conversion_responses[agent_id]
|
206
|
+
|
207
|
+
# Send conversion request
|
208
|
+
logger.info(f"📤 Sending conversion request to {agent_id}")
|
209
|
+
await self.messaging_adapter.send_direct_message(agent_id, request_content)
|
210
|
+
|
211
|
+
# Wait for response with timeout
|
212
|
+
for _ in range(timeout):
|
213
|
+
await asyncio.sleep(1)
|
214
|
+
|
215
|
+
if agent_id in self.conversion_responses:
|
216
|
+
response = self.conversion_responses[agent_id]
|
217
|
+
|
218
|
+
# Check if conversion was successful
|
219
|
+
if response.get("conversion_status") == "success" or response.get("success") == True:
|
220
|
+
# Extract converted file data
|
221
|
+
converted_data = response.get("file_data") or response.get("output_data")
|
222
|
+
|
223
|
+
if not converted_data:
|
224
|
+
logger.error("❌ No converted data in response")
|
225
|
+
return False
|
226
|
+
|
227
|
+
# Decode and save converted file
|
228
|
+
try:
|
229
|
+
converted_bytes = base64.b64decode(converted_data)
|
230
|
+
|
231
|
+
# Ensure output directory exists
|
232
|
+
output_file.parent.mkdir(parents=True, exist_ok=True)
|
233
|
+
|
234
|
+
# Write converted file
|
235
|
+
output_file.write_bytes(converted_bytes)
|
236
|
+
|
237
|
+
logger.info(f"✅ Conversion successful: {output_file}")
|
238
|
+
return True
|
239
|
+
|
240
|
+
except Exception as e:
|
241
|
+
logger.error(f"❌ Error saving converted file: {e}")
|
242
|
+
return False
|
243
|
+
|
244
|
+
elif response.get("conversion_status") == "error" or response.get("success") == False:
|
245
|
+
error_msg = response.get("error", "Unknown error")
|
246
|
+
logger.error(f"❌ Conversion failed: {error_msg}")
|
247
|
+
return False
|
248
|
+
|
249
|
+
# Timeout reached
|
250
|
+
logger.error(f"❌ Conversion timeout after {timeout} seconds")
|
251
|
+
return False
|
252
|
+
|
253
|
+
except Exception as e:
|
254
|
+
logger.error(f"❌ Error during file conversion: {e}")
|
255
|
+
return False
|
256
|
+
|
257
|
+
async def convert_files_batch(
|
258
|
+
self,
|
259
|
+
files: List[Dict[str, Any]],
|
260
|
+
timeout: int = 60
|
261
|
+
) -> List[bool]:
|
262
|
+
"""Convert multiple files in batch.
|
263
|
+
|
264
|
+
Args:
|
265
|
+
files: List of file conversion specifications, each containing:
|
266
|
+
- input_file: Path to input file
|
267
|
+
- output_file: Path to output file
|
268
|
+
- source_format: Source MIME type
|
269
|
+
- target_format: Target MIME type
|
270
|
+
- prompt: Optional conversion prompt
|
271
|
+
timeout: Timeout per file in seconds
|
272
|
+
|
273
|
+
Returns:
|
274
|
+
List of success flags for each conversion
|
275
|
+
"""
|
276
|
+
results = []
|
277
|
+
|
278
|
+
for i, file_spec in enumerate(files, 1):
|
279
|
+
logger.info(f"Processing file {i}/{len(files)}")
|
280
|
+
|
281
|
+
try:
|
282
|
+
success = await self.convert_file(
|
283
|
+
input_file=file_spec['input_file'],
|
284
|
+
output_file=file_spec['output_file'],
|
285
|
+
source_format=file_spec['source_format'],
|
286
|
+
target_format=file_spec['target_format'],
|
287
|
+
prompt=file_spec.get('prompt'),
|
288
|
+
timeout=timeout
|
289
|
+
)
|
290
|
+
results.append(success)
|
291
|
+
|
292
|
+
except Exception as e:
|
293
|
+
logger.error(f"❌ Error processing file {i}: {e}")
|
294
|
+
results.append(False)
|
295
|
+
|
296
|
+
return results
|
297
|
+
|
298
|
+
async def list_available_conversions(self) -> Dict[str, List[str]]:
|
299
|
+
"""Get a list of all available conversions in the network.
|
300
|
+
|
301
|
+
Returns:
|
302
|
+
Dictionary mapping source formats to lists of available target formats
|
303
|
+
"""
|
304
|
+
if not self.connected:
|
305
|
+
raise RuntimeError("Client is not connected to network")
|
306
|
+
|
307
|
+
logger.info("📋 Querying available conversions...")
|
308
|
+
|
309
|
+
# This would require a more sophisticated discovery mechanism
|
310
|
+
# For now, return a basic set based on known conversion categories
|
311
|
+
conversions = {}
|
312
|
+
|
313
|
+
# Common conversions to check for
|
314
|
+
common_formats = [
|
315
|
+
'text/plain', 'text/markdown', 'text/html', 'application/pdf',
|
316
|
+
'image/png', 'image/jpeg', 'image/gif', 'image/bmp',
|
317
|
+
'audio/mp3', 'audio/wav', 'video/mp4', 'application/zip'
|
318
|
+
]
|
319
|
+
|
320
|
+
for source_format in common_formats:
|
321
|
+
conversions[source_format] = []
|
322
|
+
for target_format in common_formats:
|
323
|
+
if source_format != target_format:
|
324
|
+
agents = await self.discover_agents(source_format, target_format)
|
325
|
+
if agents:
|
326
|
+
conversions[source_format].append(target_format)
|
327
|
+
|
328
|
+
return conversions
|
329
|
+
|
330
|
+
async def disconnect(self) -> None:
|
331
|
+
"""Disconnect from the OpenConvert network."""
|
332
|
+
if self.connected:
|
333
|
+
logger.info("🔌 Disconnecting from OpenConvert network")
|
334
|
+
await self.client.disconnect()
|
335
|
+
self.connected = False
|
336
|
+
logger.info("✅ Disconnected successfully")
|
337
|
+
|
338
|
+
|
339
|
+
# Convenience functions for direct usage
|
340
|
+
async def convert_file(
|
341
|
+
input_file: Path,
|
342
|
+
output_file: Path,
|
343
|
+
source_format: Optional[str] = None,
|
344
|
+
target_format: Optional[str] = None,
|
345
|
+
prompt: Optional[str] = None,
|
346
|
+
host: str = "network.openconvert.ai",
|
347
|
+
port: int = 8765
|
348
|
+
) -> bool:
|
349
|
+
"""Convenience function to convert a single file.
|
350
|
+
|
351
|
+
Args:
|
352
|
+
input_file: Path to input file
|
353
|
+
output_file: Path to output file
|
354
|
+
source_format: Source MIME type (auto-detected if None)
|
355
|
+
target_format: Target MIME type (auto-detected if None)
|
356
|
+
prompt: Optional conversion instructions
|
357
|
+
host: Network host
|
358
|
+
port: Network port
|
359
|
+
|
360
|
+
Returns:
|
361
|
+
bool: True if conversion successful
|
362
|
+
"""
|
363
|
+
client = OpenConvertClient()
|
364
|
+
|
365
|
+
try:
|
366
|
+
# Auto-detect formats if not provided
|
367
|
+
if source_format is None:
|
368
|
+
import mimetypes
|
369
|
+
source_format, _ = mimetypes.guess_type(str(input_file))
|
370
|
+
if source_format is None:
|
371
|
+
source_format = 'application/octet-stream'
|
372
|
+
|
373
|
+
if target_format is None:
|
374
|
+
import mimetypes
|
375
|
+
target_format, _ = mimetypes.guess_type(str(output_file))
|
376
|
+
if target_format is None:
|
377
|
+
target_format = 'application/octet-stream'
|
378
|
+
|
379
|
+
# Connect and convert
|
380
|
+
await client.connect(host=host, port=port)
|
381
|
+
success = await client.convert_file(
|
382
|
+
input_file=input_file,
|
383
|
+
output_file=output_file,
|
384
|
+
source_format=source_format,
|
385
|
+
target_format=target_format,
|
386
|
+
prompt=prompt
|
387
|
+
)
|
388
|
+
|
389
|
+
return success
|
390
|
+
|
391
|
+
finally:
|
392
|
+
await client.disconnect()
|