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 CHANGED
@@ -1,7 +1,100 @@
1
1
  """
2
- OpenConvert - A versatile file conversion library
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
- __version__ = '0.1.0'
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
- from .converter import open_convert
93
+ # Export main functions
94
+ __all__ = [
95
+ "convert",
96
+ "convert_file",
97
+ "__version__",
98
+ "__author__",
99
+ "__email__",
100
+ ]
@@ -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()