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
@@ -0,0 +1,540 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
OpenConvert CLI Tool
|
4
|
+
|
5
|
+
A command-line interface for connecting to the OpenConvert OpenAgents network
|
6
|
+
to discover file conversion services and perform file conversions.
|
7
|
+
|
8
|
+
Usage:
|
9
|
+
openconvert -i input.png -o output.pdf
|
10
|
+
openconvert -i input_dir/ -o output.pdf --from image/png --to application/pdf
|
11
|
+
openconvert -i input.txt -o output.md --prompt "Add a title and format nicely"
|
12
|
+
"""
|
13
|
+
|
14
|
+
import argparse
|
15
|
+
import asyncio
|
16
|
+
import logging
|
17
|
+
import sys
|
18
|
+
import os
|
19
|
+
import mimetypes
|
20
|
+
from pathlib import Path
|
21
|
+
from typing import Optional, List, Dict, Any
|
22
|
+
|
23
|
+
# Add the openagents src to Python path
|
24
|
+
current_dir = Path(__file__).resolve().parent
|
25
|
+
openagents_root = current_dir.parent.parent
|
26
|
+
sys.path.insert(0, str(openagents_root / "src"))
|
27
|
+
|
28
|
+
from openconvert.client import OpenConvertClient
|
29
|
+
|
30
|
+
# Set up logging
|
31
|
+
logging.basicConfig(
|
32
|
+
level=logging.INFO,
|
33
|
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
34
|
+
)
|
35
|
+
logger = logging.getLogger(__name__)
|
36
|
+
|
37
|
+
|
38
|
+
def detect_mime_type(file_path: Path) -> str:
|
39
|
+
"""Detect MIME type of a file.
|
40
|
+
|
41
|
+
Args:
|
42
|
+
file_path: Path to the file
|
43
|
+
|
44
|
+
Returns:
|
45
|
+
str: MIME type string
|
46
|
+
"""
|
47
|
+
mime_type, _ = mimetypes.guess_type(str(file_path))
|
48
|
+
if mime_type is None:
|
49
|
+
# Fall back to common extensions
|
50
|
+
ext = file_path.suffix.lower()
|
51
|
+
common_types = {
|
52
|
+
'.txt': 'text/plain',
|
53
|
+
'.md': 'text/markdown',
|
54
|
+
'.html': 'text/html',
|
55
|
+
'.json': 'application/json',
|
56
|
+
'.xml': 'application/xml',
|
57
|
+
'.pdf': 'application/pdf',
|
58
|
+
'.doc': 'application/msword',
|
59
|
+
'.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
60
|
+
'.png': 'image/png',
|
61
|
+
'.jpg': 'image/jpeg',
|
62
|
+
'.jpeg': 'image/jpeg',
|
63
|
+
'.gif': 'image/gif',
|
64
|
+
'.bmp': 'image/bmp',
|
65
|
+
'.svg': 'image/svg+xml',
|
66
|
+
'.mp3': 'audio/mpeg',
|
67
|
+
'.wav': 'audio/wav',
|
68
|
+
'.mp4': 'video/mp4',
|
69
|
+
'.avi': 'video/x-msvideo',
|
70
|
+
'.zip': 'application/zip',
|
71
|
+
'.rar': 'application/x-rar-compressed',
|
72
|
+
}
|
73
|
+
mime_type = common_types.get(ext, 'application/octet-stream')
|
74
|
+
|
75
|
+
return mime_type
|
76
|
+
|
77
|
+
|
78
|
+
def validate_args(args: argparse.Namespace) -> bool:
|
79
|
+
"""Validate command-line arguments.
|
80
|
+
|
81
|
+
Args:
|
82
|
+
args: Parsed command-line arguments
|
83
|
+
|
84
|
+
Returns:
|
85
|
+
bool: True if arguments are valid
|
86
|
+
"""
|
87
|
+
# For conversion operations, input and output are required
|
88
|
+
if not args.input or not args.output:
|
89
|
+
logger.error("Input (-i) and output (-o) arguments are required for conversion")
|
90
|
+
return False
|
91
|
+
|
92
|
+
# Check input exists
|
93
|
+
input_path = Path(args.input)
|
94
|
+
if not input_path.exists():
|
95
|
+
logger.error(f"Input path does not exist: {input_path}")
|
96
|
+
return False
|
97
|
+
|
98
|
+
# Validate output path
|
99
|
+
output_path = Path(args.output)
|
100
|
+
output_dir = output_path.parent
|
101
|
+
if not output_dir.exists():
|
102
|
+
logger.error(f"Output directory does not exist: {output_dir}")
|
103
|
+
return False
|
104
|
+
|
105
|
+
# If input is a directory, require --from and --to
|
106
|
+
if input_path.is_dir():
|
107
|
+
if not args.from_format or not args.to_format:
|
108
|
+
logger.error("When input is a directory, --from and --to formats must be specified")
|
109
|
+
return False
|
110
|
+
|
111
|
+
return True
|
112
|
+
|
113
|
+
|
114
|
+
def get_input_files(input_path: Path, from_format: Optional[str] = None) -> List[Path]:
|
115
|
+
"""Get list of input files to process.
|
116
|
+
|
117
|
+
Args:
|
118
|
+
input_path: Input file or directory path
|
119
|
+
from_format: Optional MIME type filter for directory scanning
|
120
|
+
|
121
|
+
Returns:
|
122
|
+
List of file paths to process
|
123
|
+
"""
|
124
|
+
if input_path.is_file():
|
125
|
+
return [input_path]
|
126
|
+
|
127
|
+
elif input_path.is_dir():
|
128
|
+
files = []
|
129
|
+
for file_path in input_path.rglob('*'):
|
130
|
+
if file_path.is_file():
|
131
|
+
# If from_format is specified, filter by MIME type
|
132
|
+
if from_format:
|
133
|
+
file_mime = detect_mime_type(file_path)
|
134
|
+
if file_mime == from_format:
|
135
|
+
files.append(file_path)
|
136
|
+
else:
|
137
|
+
files.append(file_path)
|
138
|
+
return files
|
139
|
+
|
140
|
+
return []
|
141
|
+
|
142
|
+
|
143
|
+
async def convert(
|
144
|
+
input_files: List[Path],
|
145
|
+
output_path: Path,
|
146
|
+
from_format: Optional[str] = None,
|
147
|
+
to_format: Optional[str] = None,
|
148
|
+
prompt: Optional[str] = None,
|
149
|
+
host: str = "network.openconvert.ai",
|
150
|
+
port: int = 8765
|
151
|
+
) -> bool:
|
152
|
+
"""Convert files using the OpenConvert network.
|
153
|
+
|
154
|
+
Args:
|
155
|
+
input_files: List of input file paths
|
156
|
+
output_path: Output file or directory path
|
157
|
+
from_format: Source MIME type (optional, will be detected)
|
158
|
+
to_format: Target MIME type (optional, will be detected from output extension)
|
159
|
+
prompt: Optional conversion prompt
|
160
|
+
host: Network host to connect to
|
161
|
+
port: Network port to connect to
|
162
|
+
|
163
|
+
Returns:
|
164
|
+
bool: True if conversion succeeded
|
165
|
+
"""
|
166
|
+
client = OpenConvertClient()
|
167
|
+
|
168
|
+
try:
|
169
|
+
# Connect to the network
|
170
|
+
logger.info(f"Connecting to OpenConvert network at {host}:{port}")
|
171
|
+
await client.connect(host=host, port=port)
|
172
|
+
|
173
|
+
# Process each input file
|
174
|
+
success_count = 0
|
175
|
+
total_files = len(input_files)
|
176
|
+
|
177
|
+
for i, input_file in enumerate(input_files, 1):
|
178
|
+
logger.info(f"Processing file {i}/{total_files}: {input_file.name}")
|
179
|
+
|
180
|
+
# Detect source format if not specified
|
181
|
+
source_format = from_format or detect_mime_type(input_file)
|
182
|
+
|
183
|
+
# Detect target format if not specified
|
184
|
+
if to_format:
|
185
|
+
target_format = to_format
|
186
|
+
else:
|
187
|
+
target_format = detect_mime_type(output_path)
|
188
|
+
|
189
|
+
logger.info(f"Converting {source_format} -> {target_format}")
|
190
|
+
|
191
|
+
# Determine output file path
|
192
|
+
if len(input_files) == 1:
|
193
|
+
# Single file conversion
|
194
|
+
output_file = output_path
|
195
|
+
else:
|
196
|
+
# Multiple files - create output directory if needed
|
197
|
+
if output_path.suffix:
|
198
|
+
# Output path has extension, treat as directory name
|
199
|
+
output_dir = output_path.parent / output_path.stem
|
200
|
+
else:
|
201
|
+
# Output path is a directory
|
202
|
+
output_dir = output_path
|
203
|
+
|
204
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
205
|
+
|
206
|
+
# Generate output filename with target extension
|
207
|
+
target_ext = None
|
208
|
+
for ext, mime in mimetypes.types_map.items():
|
209
|
+
if mime == target_format:
|
210
|
+
target_ext = ext
|
211
|
+
break
|
212
|
+
|
213
|
+
if not target_ext:
|
214
|
+
# Fall back to common extensions
|
215
|
+
ext_map = {
|
216
|
+
'text/plain': '.txt',
|
217
|
+
'text/markdown': '.md',
|
218
|
+
'text/html': '.html',
|
219
|
+
'application/pdf': '.pdf',
|
220
|
+
'image/png': '.png',
|
221
|
+
'image/jpeg': '.jpg',
|
222
|
+
'application/json': '.json',
|
223
|
+
}
|
224
|
+
target_ext = ext_map.get(target_format, '.out')
|
225
|
+
|
226
|
+
output_file = output_dir / f"{input_file.stem}{target_ext}"
|
227
|
+
|
228
|
+
# Perform conversion
|
229
|
+
try:
|
230
|
+
success = await client.convert_file(
|
231
|
+
input_file=input_file,
|
232
|
+
output_file=output_file,
|
233
|
+
source_format=source_format,
|
234
|
+
target_format=target_format,
|
235
|
+
prompt=prompt
|
236
|
+
)
|
237
|
+
|
238
|
+
if success:
|
239
|
+
logger.info(f"✅ Successfully converted to {output_file}")
|
240
|
+
success_count += 1
|
241
|
+
else:
|
242
|
+
logger.error(f"❌ Failed to convert {input_file.name}")
|
243
|
+
|
244
|
+
except Exception as e:
|
245
|
+
logger.error(f"❌ Error converting {input_file.name}: {e}")
|
246
|
+
|
247
|
+
# Report results
|
248
|
+
if success_count == total_files:
|
249
|
+
logger.info(f"🎉 All {total_files} files converted successfully!")
|
250
|
+
return True
|
251
|
+
elif success_count > 0:
|
252
|
+
logger.info(f"⚠️ {success_count}/{total_files} files converted successfully")
|
253
|
+
return True
|
254
|
+
else:
|
255
|
+
logger.error(f"❌ No files were converted successfully")
|
256
|
+
return False
|
257
|
+
|
258
|
+
except Exception as e:
|
259
|
+
logger.error(f"Error during conversion: {e}")
|
260
|
+
return False
|
261
|
+
|
262
|
+
finally:
|
263
|
+
await client.disconnect()
|
264
|
+
|
265
|
+
|
266
|
+
async def list_available_formats(host: str = "network.openconvert.ai", port: int = 8765) -> bool:
|
267
|
+
"""List all available conversion formats from connected agents.
|
268
|
+
|
269
|
+
Args:
|
270
|
+
host: Network host to connect to
|
271
|
+
port: Network port to connect to
|
272
|
+
|
273
|
+
Returns:
|
274
|
+
bool: True if discovery succeeded
|
275
|
+
"""
|
276
|
+
client = OpenConvertClient()
|
277
|
+
|
278
|
+
try:
|
279
|
+
# Connect to the network
|
280
|
+
logger.info(f"🌐 Connecting to OpenConvert network at {host}:{port}")
|
281
|
+
print(f"🔍 Discovering available conversion formats...")
|
282
|
+
|
283
|
+
await client.connect(host=host, port=port)
|
284
|
+
|
285
|
+
# Common format combinations to test
|
286
|
+
test_formats = [
|
287
|
+
'text/plain', 'text/markdown', 'text/html', 'application/pdf',
|
288
|
+
'image/png', 'image/jpeg', 'image/gif', 'image/bmp',
|
289
|
+
'audio/mp3', 'audio/wav', 'video/mp4', 'application/zip',
|
290
|
+
'application/json', 'application/xml', 'text/csv',
|
291
|
+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
|
292
|
+
]
|
293
|
+
|
294
|
+
# Dictionary to store discovered conversions
|
295
|
+
available_conversions = {}
|
296
|
+
|
297
|
+
print("📡 Querying agents for conversion capabilities...")
|
298
|
+
|
299
|
+
# Test various format combinations
|
300
|
+
for source_format in test_formats:
|
301
|
+
available_conversions[source_format] = []
|
302
|
+
for target_format in test_formats:
|
303
|
+
if source_format != target_format:
|
304
|
+
agents = await client.discover_agents(source_format, target_format)
|
305
|
+
if agents:
|
306
|
+
available_conversions[source_format].append(target_format)
|
307
|
+
|
308
|
+
# Display results
|
309
|
+
print("\n🎯 Available Conversions:")
|
310
|
+
print("=" * 60)
|
311
|
+
|
312
|
+
total_conversions = 0
|
313
|
+
total_agents = set()
|
314
|
+
|
315
|
+
for source_format, target_formats in available_conversions.items():
|
316
|
+
if target_formats:
|
317
|
+
# Get a friendly name for the format
|
318
|
+
source_name = get_format_name(source_format)
|
319
|
+
print(f"\n📄 {source_name} ({source_format}):")
|
320
|
+
|
321
|
+
for target_format in target_formats:
|
322
|
+
target_name = get_format_name(target_format)
|
323
|
+
# Get agents for this conversion
|
324
|
+
agents = await client.discover_agents(source_format, target_format)
|
325
|
+
agent_names = [agent.get('agent_id', 'Unknown') for agent in agents]
|
326
|
+
total_agents.update(agent_names)
|
327
|
+
|
328
|
+
print(f" ➜ {target_name} ({target_format})")
|
329
|
+
print(f" Agents: {', '.join(agent_names)}")
|
330
|
+
total_conversions += 1
|
331
|
+
|
332
|
+
# Summary
|
333
|
+
print(f"\n📊 Summary:")
|
334
|
+
print(f" 🔄 Total conversions available: {total_conversions}")
|
335
|
+
print(f" 🤖 Active agents: {len(total_agents)}")
|
336
|
+
|
337
|
+
if total_conversions == 0:
|
338
|
+
print("\n⚠️ No conversion agents found!")
|
339
|
+
print(" Make sure conversion agents are running:")
|
340
|
+
print(" • python demos/openconvert/run_agent.py doc")
|
341
|
+
print(" • python demos/openconvert/run_agent.py image")
|
342
|
+
print(" • python demos/openconvert/run_agent.py audio")
|
343
|
+
|
344
|
+
return True
|
345
|
+
|
346
|
+
except Exception as e:
|
347
|
+
logger.error(f"❌ Error during format discovery: {e}")
|
348
|
+
print(f"\n❌ Failed to discover formats: {e}")
|
349
|
+
print(" Make sure the OpenConvert network is running:")
|
350
|
+
print(" openagents launch-network demos/openconvert/network_config.yaml")
|
351
|
+
return False
|
352
|
+
|
353
|
+
finally:
|
354
|
+
await client.disconnect()
|
355
|
+
|
356
|
+
|
357
|
+
def get_format_name(mime_type: str) -> str:
|
358
|
+
"""Get a friendly name for a MIME type.
|
359
|
+
|
360
|
+
Args:
|
361
|
+
mime_type: MIME type string
|
362
|
+
|
363
|
+
Returns:
|
364
|
+
str: Friendly format name
|
365
|
+
"""
|
366
|
+
format_names = {
|
367
|
+
'text/plain': 'Plain Text',
|
368
|
+
'text/markdown': 'Markdown',
|
369
|
+
'text/html': 'HTML',
|
370
|
+
'text/csv': 'CSV',
|
371
|
+
'application/pdf': 'PDF',
|
372
|
+
'application/json': 'JSON',
|
373
|
+
'application/xml': 'XML',
|
374
|
+
'application/zip': 'ZIP Archive',
|
375
|
+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'Word Document',
|
376
|
+
'image/png': 'PNG Image',
|
377
|
+
'image/jpeg': 'JPEG Image',
|
378
|
+
'image/gif': 'GIF Image',
|
379
|
+
'image/bmp': 'BMP Image',
|
380
|
+
'audio/mp3': 'MP3 Audio',
|
381
|
+
'audio/wav': 'WAV Audio',
|
382
|
+
'video/mp4': 'MP4 Video'
|
383
|
+
}
|
384
|
+
return format_names.get(mime_type, mime_type)
|
385
|
+
|
386
|
+
|
387
|
+
def main() -> int:
|
388
|
+
"""Main CLI entry point.
|
389
|
+
|
390
|
+
Returns:
|
391
|
+
int: Exit code (0 = success, 1 = error)
|
392
|
+
"""
|
393
|
+
parser = argparse.ArgumentParser(
|
394
|
+
description="OpenConvert CLI - Connect to OpenAgents network for file conversion",
|
395
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
396
|
+
epilog="""
|
397
|
+
Examples:
|
398
|
+
# Convert single file (formats auto-detected)
|
399
|
+
openconvert -i document.txt -o document.pdf
|
400
|
+
|
401
|
+
# Convert directory with specific formats
|
402
|
+
openconvert -i images/ -o converted/ --from image/png --to application/pdf
|
403
|
+
|
404
|
+
# Convert with custom prompt
|
405
|
+
openconvert -i data.csv -o report.pdf --prompt "Create a formatted report with charts"
|
406
|
+
|
407
|
+
# Specify network connection
|
408
|
+
openconvert -i file.doc -o file.md --host remote.server.com --port 8765
|
409
|
+
|
410
|
+
Supported formats include:
|
411
|
+
Documents: txt, pdf, docx, html, md, rtf, csv, xlsx
|
412
|
+
Images: png, jpg, jpeg, bmp, gif, tiff, svg, webp
|
413
|
+
Audio: mp3, wav, ogg, flac, aac
|
414
|
+
Video: mp4, avi, mkv, mov, webm
|
415
|
+
Archives: zip, rar, 7z, tar, gz
|
416
|
+
Code: json, xml, yaml, html, latex
|
417
|
+
Models: stl, obj, fbx, ply
|
418
|
+
"""
|
419
|
+
)
|
420
|
+
|
421
|
+
# Arguments (required for conversion, optional for discovery)
|
422
|
+
parser.add_argument(
|
423
|
+
"-i", "--input",
|
424
|
+
help="Input file or directory path"
|
425
|
+
)
|
426
|
+
parser.add_argument(
|
427
|
+
"-o", "--output",
|
428
|
+
help="Output file or directory path"
|
429
|
+
)
|
430
|
+
|
431
|
+
# Optional format specification
|
432
|
+
parser.add_argument(
|
433
|
+
"--from", dest="from_format",
|
434
|
+
help="Source MIME type (e.g., 'text/plain', 'image/png'). Auto-detected if not specified."
|
435
|
+
)
|
436
|
+
parser.add_argument(
|
437
|
+
"--to", dest="to_format",
|
438
|
+
help="Target MIME type (e.g., 'application/pdf', 'text/markdown'). Auto-detected from output extension if not specified."
|
439
|
+
)
|
440
|
+
|
441
|
+
# Optional conversion prompt
|
442
|
+
parser.add_argument(
|
443
|
+
"--prompt",
|
444
|
+
help="Additional instructions for the conversion (e.g., 'compress by 50%%', 'add a title')"
|
445
|
+
)
|
446
|
+
|
447
|
+
# Network connection options
|
448
|
+
parser.add_argument(
|
449
|
+
"--host",
|
450
|
+
default="network.openconvert.ai",
|
451
|
+
help="OpenConvert network host (default: network.openconvert.ai)"
|
452
|
+
)
|
453
|
+
parser.add_argument(
|
454
|
+
"--port",
|
455
|
+
type=int,
|
456
|
+
default=8765,
|
457
|
+
help="OpenConvert network port (default: 8765)"
|
458
|
+
)
|
459
|
+
|
460
|
+
# Logging options
|
461
|
+
parser.add_argument(
|
462
|
+
"--verbose", "-v",
|
463
|
+
action="store_true",
|
464
|
+
help="Enable verbose logging"
|
465
|
+
)
|
466
|
+
parser.add_argument(
|
467
|
+
"--quiet", "-q",
|
468
|
+
action="store_true",
|
469
|
+
help="Suppress all output except errors"
|
470
|
+
)
|
471
|
+
|
472
|
+
# Discovery options
|
473
|
+
parser.add_argument(
|
474
|
+
"--list-formats",
|
475
|
+
action="store_true",
|
476
|
+
help="List all available conversion formats from connected agents"
|
477
|
+
)
|
478
|
+
|
479
|
+
# Parse arguments
|
480
|
+
args = parser.parse_args()
|
481
|
+
|
482
|
+
# Configure logging level
|
483
|
+
if args.quiet:
|
484
|
+
logging.getLogger().setLevel(logging.ERROR)
|
485
|
+
elif args.verbose:
|
486
|
+
logging.getLogger().setLevel(logging.DEBUG)
|
487
|
+
|
488
|
+
# Handle list-formats option
|
489
|
+
if args.list_formats:
|
490
|
+
try:
|
491
|
+
success = asyncio.run(list_available_formats(
|
492
|
+
host=args.host,
|
493
|
+
port=args.port
|
494
|
+
))
|
495
|
+
return 0 if success else 1
|
496
|
+
except KeyboardInterrupt:
|
497
|
+
logger.info("Discovery cancelled by user")
|
498
|
+
return 1
|
499
|
+
except Exception as e:
|
500
|
+
logger.error(f"Error discovering formats: {e}")
|
501
|
+
return 1
|
502
|
+
|
503
|
+
# Validate arguments for conversion operations
|
504
|
+
if not validate_args(args):
|
505
|
+
return 1
|
506
|
+
|
507
|
+
# Get input files
|
508
|
+
input_path = Path(args.input)
|
509
|
+
input_files = get_input_files(input_path, args.from_format)
|
510
|
+
|
511
|
+
if not input_files:
|
512
|
+
logger.error("No input files found to process")
|
513
|
+
return 1
|
514
|
+
|
515
|
+
logger.info(f"Found {len(input_files)} files to process")
|
516
|
+
|
517
|
+
# Run conversion
|
518
|
+
try:
|
519
|
+
success = asyncio.run(convert(
|
520
|
+
input_files=input_files,
|
521
|
+
output_path=Path(args.output),
|
522
|
+
from_format=args.from_format,
|
523
|
+
to_format=args.to_format,
|
524
|
+
prompt=args.prompt,
|
525
|
+
host=args.host,
|
526
|
+
port=args.port
|
527
|
+
))
|
528
|
+
|
529
|
+
return 0 if success else 1
|
530
|
+
|
531
|
+
except KeyboardInterrupt:
|
532
|
+
logger.info("Conversion cancelled by user")
|
533
|
+
return 1
|
534
|
+
except Exception as e:
|
535
|
+
logger.error(f"Unexpected error: {e}")
|
536
|
+
return 1
|
537
|
+
|
538
|
+
|
539
|
+
if __name__ == "__main__":
|
540
|
+
sys.exit(main())
|