openconvert 0.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 +7 -0
- openconvert/cli.py +145 -0
- openconvert/converter.py +152 -0
- openconvert/converters/__init__.py +3 -0
- openconvert/converters/archive_converter.py +277 -0
- openconvert/converters/audio_converter.py +223 -0
- openconvert/converters/code_converter.py +412 -0
- openconvert/converters/document_converter.py +596 -0
- openconvert/converters/image_converter.py +214 -0
- openconvert/converters/model_converter.py +208 -0
- openconvert/converters/video_converter.py +259 -0
- openconvert/launcher.py +0 -0
- openconvert-0.1.0.dist-info/METADATA +232 -0
- openconvert-0.1.0.dist-info/RECORD +17 -0
- openconvert-0.1.0.dist-info/WHEEL +5 -0
- openconvert-0.1.0.dist-info/entry_points.txt +2 -0
- openconvert-0.1.0.dist-info/top_level.txt +1 -0
openconvert/__init__.py
ADDED
openconvert/cli.py
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
"""
|
2
|
+
Command-line interface for AGConvert.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import argparse
|
6
|
+
import json
|
7
|
+
import logging
|
8
|
+
import os
|
9
|
+
import sys
|
10
|
+
from pathlib import Path
|
11
|
+
from typing import Dict, Any, Optional
|
12
|
+
|
13
|
+
from . import __version__
|
14
|
+
from .converter import open_convert
|
15
|
+
|
16
|
+
# Configure logging
|
17
|
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
18
|
+
logger = logging.getLogger(__name__)
|
19
|
+
|
20
|
+
def parse_args() -> argparse.Namespace:
|
21
|
+
"""Parse command-line arguments."""
|
22
|
+
parser = argparse.ArgumentParser(
|
23
|
+
description="AGConvert - A versatile file and data conversion tool",
|
24
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
25
|
+
epilog="""
|
26
|
+
Examples:
|
27
|
+
agconvert image.png image.jpg # Convert PNG to JPG
|
28
|
+
agconvert document.docx document.pdf # Convert DOCX to PDF
|
29
|
+
agconvert audio.mp3 audio.wav # Convert MP3 to WAV
|
30
|
+
agconvert video.mp4 video.gif # Convert MP4 to GIF
|
31
|
+
agconvert data.json data.yaml # Convert JSON to YAML
|
32
|
+
agconvert --options '{"quality": 90}' image.png image.jpg # With options
|
33
|
+
"""
|
34
|
+
)
|
35
|
+
|
36
|
+
parser.add_argument(
|
37
|
+
"input_file",
|
38
|
+
help="Path to the input file"
|
39
|
+
)
|
40
|
+
|
41
|
+
parser.add_argument(
|
42
|
+
"output_file",
|
43
|
+
help="Path to the output file"
|
44
|
+
)
|
45
|
+
|
46
|
+
parser.add_argument(
|
47
|
+
"--source-format",
|
48
|
+
help="Source file format (if not specified, will be inferred from input file extension)"
|
49
|
+
)
|
50
|
+
|
51
|
+
parser.add_argument(
|
52
|
+
"--target-format",
|
53
|
+
help="Target file format (if not specified, will be inferred from output file extension)"
|
54
|
+
)
|
55
|
+
|
56
|
+
parser.add_argument(
|
57
|
+
"--options",
|
58
|
+
help="Additional conversion options as JSON string",
|
59
|
+
default="{}"
|
60
|
+
)
|
61
|
+
|
62
|
+
parser.add_argument(
|
63
|
+
"--verbose", "-v",
|
64
|
+
action="store_true",
|
65
|
+
help="Enable verbose output"
|
66
|
+
)
|
67
|
+
|
68
|
+
parser.add_argument(
|
69
|
+
"--version",
|
70
|
+
action="version",
|
71
|
+
version=f"AGConvert {__version__}"
|
72
|
+
)
|
73
|
+
|
74
|
+
return parser.parse_args()
|
75
|
+
|
76
|
+
def parse_options(options_str: str) -> Dict[str, Any]:
|
77
|
+
"""Parse options from JSON string."""
|
78
|
+
try:
|
79
|
+
return json.loads(options_str)
|
80
|
+
except json.JSONDecodeError as e:
|
81
|
+
logger.error(f"Error parsing options: {str(e)}")
|
82
|
+
logger.error("Options must be a valid JSON string")
|
83
|
+
sys.exit(1)
|
84
|
+
|
85
|
+
def infer_format(filepath: str) -> str:
|
86
|
+
"""Infer file format from file extension."""
|
87
|
+
ext = os.path.splitext(filepath)[1].lower().lstrip('.')
|
88
|
+
|
89
|
+
# Handle special cases
|
90
|
+
if ext == 'jpeg':
|
91
|
+
return 'jpg'
|
92
|
+
elif ext == 'tif':
|
93
|
+
return 'tiff'
|
94
|
+
elif ext == 'yml':
|
95
|
+
return 'yaml'
|
96
|
+
elif ext == 'htm':
|
97
|
+
return 'html'
|
98
|
+
elif ext == 'markdown':
|
99
|
+
return 'md'
|
100
|
+
elif ext == 'tex':
|
101
|
+
return 'latex'
|
102
|
+
|
103
|
+
return ext
|
104
|
+
|
105
|
+
def main() -> None:
|
106
|
+
"""Main entry point for the CLI."""
|
107
|
+
args = parse_args()
|
108
|
+
|
109
|
+
# Set logging level
|
110
|
+
if args.verbose:
|
111
|
+
logging.getLogger().setLevel(logging.DEBUG)
|
112
|
+
|
113
|
+
# Parse options
|
114
|
+
options = parse_options(args.options)
|
115
|
+
|
116
|
+
# Infer formats if not specified
|
117
|
+
source_format = args.source_format or infer_format(args.input_file)
|
118
|
+
target_format = args.target_format or infer_format(args.output_file)
|
119
|
+
|
120
|
+
if not source_format:
|
121
|
+
logger.error("Could not determine source format. Please specify --source-format")
|
122
|
+
sys.exit(1)
|
123
|
+
|
124
|
+
if not target_format:
|
125
|
+
logger.error("Could not determine target format. Please specify --target-format")
|
126
|
+
sys.exit(1)
|
127
|
+
|
128
|
+
try:
|
129
|
+
# Perform the conversion
|
130
|
+
output_path = open_convert(
|
131
|
+
filepath=args.input_file,
|
132
|
+
source_format=source_format,
|
133
|
+
target_format=target_format,
|
134
|
+
output_path=args.output_file,
|
135
|
+
options=options
|
136
|
+
)
|
137
|
+
|
138
|
+
logger.info(f"Conversion successful: {args.input_file} → {output_path}")
|
139
|
+
|
140
|
+
except Exception as e:
|
141
|
+
logger.error(f"Conversion failed: {str(e)}")
|
142
|
+
sys.exit(1)
|
143
|
+
|
144
|
+
if __name__ == "__main__":
|
145
|
+
main()
|
openconvert/converter.py
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
"""
|
2
|
+
Main converter module that provides the open_convert function.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import os
|
6
|
+
import logging
|
7
|
+
from pathlib import Path
|
8
|
+
from typing import Optional, Union, List
|
9
|
+
|
10
|
+
from .converters import (
|
11
|
+
image_converter,
|
12
|
+
document_converter,
|
13
|
+
audio_converter,
|
14
|
+
video_converter,
|
15
|
+
archive_converter,
|
16
|
+
model_converter,
|
17
|
+
code_converter
|
18
|
+
)
|
19
|
+
|
20
|
+
# Configure logging
|
21
|
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
22
|
+
logger = logging.getLogger(__name__)
|
23
|
+
|
24
|
+
# Define conversion categories
|
25
|
+
CONVERSION_CATEGORIES = {
|
26
|
+
# Image formats
|
27
|
+
'png': 'image', 'jpg': 'image', 'jpeg': 'image', 'bmp': 'image', 'tiff': 'image',
|
28
|
+
'gif': 'image', 'ico': 'image', 'svg': 'image', 'webp': 'image',
|
29
|
+
|
30
|
+
# Document formats
|
31
|
+
'txt': 'document', 'pdf': 'document', 'docx': 'document', 'rtf': 'document',
|
32
|
+
'md': 'document', 'html': 'document', 'csv': 'document', 'xlsx': 'document',
|
33
|
+
'epub': 'document',
|
34
|
+
|
35
|
+
# Audio formats
|
36
|
+
'mp3': 'audio', 'wav': 'audio', 'ogg': 'audio', 'flac': 'audio', 'aac': 'audio',
|
37
|
+
|
38
|
+
# Video formats
|
39
|
+
'mp4': 'video', 'avi': 'video', 'mkv': 'video', 'mov': 'video',
|
40
|
+
|
41
|
+
# Archive formats
|
42
|
+
'zip': 'archive', 'rar': 'archive', '7z': 'archive', 'tar': 'archive', 'gz': 'archive',
|
43
|
+
|
44
|
+
# 3D Model formats
|
45
|
+
'stl': 'model', 'obj': 'model', 'fbx': 'model', 'ply': 'model',
|
46
|
+
|
47
|
+
# Code and markup formats
|
48
|
+
'json': 'code', 'yaml': 'code', 'xml': 'code', 'latex': 'code'
|
49
|
+
}
|
50
|
+
|
51
|
+
def open_convert(
|
52
|
+
filepath: Union[str, Path],
|
53
|
+
source_format: Optional[str] = None,
|
54
|
+
target_format: str = None,
|
55
|
+
output_path: Optional[Union[str, Path]] = None,
|
56
|
+
options: Optional[dict] = None
|
57
|
+
) -> str:
|
58
|
+
"""
|
59
|
+
Convert a file from one format to another.
|
60
|
+
|
61
|
+
Args:
|
62
|
+
filepath: Path to the source file
|
63
|
+
source_format: Source file format (if None, will be inferred from file extension)
|
64
|
+
target_format: Target file format
|
65
|
+
output_path: Path to save the converted file (if None, will use the same directory as source)
|
66
|
+
options: Additional conversion options
|
67
|
+
|
68
|
+
Returns:
|
69
|
+
Path to the converted file
|
70
|
+
|
71
|
+
Raises:
|
72
|
+
ValueError: If the conversion is not supported or parameters are invalid
|
73
|
+
FileNotFoundError: If the source file doesn't exist
|
74
|
+
RuntimeError: If the conversion fails
|
75
|
+
"""
|
76
|
+
if options is None:
|
77
|
+
options = {}
|
78
|
+
|
79
|
+
# Convert filepath to Path object
|
80
|
+
filepath = Path(filepath)
|
81
|
+
|
82
|
+
# Check if file exists
|
83
|
+
if not filepath.exists():
|
84
|
+
raise FileNotFoundError(f"Source file not found: {filepath}")
|
85
|
+
|
86
|
+
# Infer source format from file extension if not provided
|
87
|
+
if source_format is None:
|
88
|
+
source_format = filepath.suffix.lower().lstrip('.')
|
89
|
+
if not source_format:
|
90
|
+
raise ValueError("Could not determine source format from file extension. Please specify source_format.")
|
91
|
+
else:
|
92
|
+
source_format = source_format.lower()
|
93
|
+
|
94
|
+
# Ensure target format is provided and lowercase
|
95
|
+
if target_format is None:
|
96
|
+
raise ValueError("Target format must be specified")
|
97
|
+
target_format = target_format.lower()
|
98
|
+
|
99
|
+
# Check if formats are supported
|
100
|
+
if source_format not in CONVERSION_CATEGORIES:
|
101
|
+
raise ValueError(f"Unsupported source format: {source_format}")
|
102
|
+
if target_format not in CONVERSION_CATEGORIES:
|
103
|
+
raise ValueError(f"Unsupported target format: {target_format}")
|
104
|
+
|
105
|
+
# Determine output path if not provided
|
106
|
+
if output_path is None:
|
107
|
+
output_path = filepath.with_suffix(f".{target_format}")
|
108
|
+
else:
|
109
|
+
output_path = Path(output_path)
|
110
|
+
# If output_path is a directory, use the original filename with new extension
|
111
|
+
if output_path.is_dir():
|
112
|
+
output_path = output_path / filepath.with_suffix(f".{target_format}").name
|
113
|
+
|
114
|
+
logger.info(f"Converting {filepath} from {source_format} to {target_format}")
|
115
|
+
|
116
|
+
# Determine conversion category and route to appropriate converter
|
117
|
+
source_category = CONVERSION_CATEGORIES[source_format]
|
118
|
+
target_category = CONVERSION_CATEGORIES[target_format]
|
119
|
+
|
120
|
+
# Handle conversion based on categories
|
121
|
+
if source_category == 'image':
|
122
|
+
if target_category == 'image' or target_format == 'pdf':
|
123
|
+
return image_converter.convert(filepath, source_format, target_format, output_path, options)
|
124
|
+
|
125
|
+
elif source_category == 'document':
|
126
|
+
return document_converter.convert(filepath, source_format, target_format, output_path, options)
|
127
|
+
|
128
|
+
elif source_category == 'audio':
|
129
|
+
if target_category == 'audio':
|
130
|
+
return audio_converter.convert(filepath, source_format, target_format, output_path, options)
|
131
|
+
elif target_format == 'txt': # Speech-to-text
|
132
|
+
return audio_converter.speech_to_text(filepath, output_path, options)
|
133
|
+
|
134
|
+
elif source_category == 'video':
|
135
|
+
if target_category == 'video' or target_format == 'gif':
|
136
|
+
return video_converter.convert(filepath, source_format, target_format, output_path, options)
|
137
|
+
elif target_format == 'mp3': # Audio extraction
|
138
|
+
return video_converter.extract_audio(filepath, output_path, options)
|
139
|
+
elif target_format in ['png', 'jpg']: # Frame extraction
|
140
|
+
return video_converter.extract_frames(filepath, target_format, output_path, options)
|
141
|
+
|
142
|
+
elif source_category == 'archive':
|
143
|
+
return archive_converter.convert(filepath, source_format, target_format, output_path, options)
|
144
|
+
|
145
|
+
elif source_category == 'model':
|
146
|
+
return model_converter.convert(filepath, source_format, target_format, output_path, options)
|
147
|
+
|
148
|
+
elif source_category == 'code':
|
149
|
+
return code_converter.convert(filepath, source_format, target_format, output_path, options)
|
150
|
+
|
151
|
+
# If we get here, the specific conversion is not supported
|
152
|
+
raise ValueError(f"Conversion from {source_format} to {target_format} is not supported")
|
@@ -0,0 +1,277 @@
|
|
1
|
+
"""
|
2
|
+
Archive converter module for handling archive format conversions.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import logging
|
6
|
+
import os
|
7
|
+
import shutil
|
8
|
+
import tempfile
|
9
|
+
from pathlib import Path
|
10
|
+
from typing import Union, Optional, Dict, Any
|
11
|
+
|
12
|
+
logger = logging.getLogger(__name__)
|
13
|
+
|
14
|
+
# Define supported conversions
|
15
|
+
SUPPORTED_CONVERSIONS = {
|
16
|
+
'zip': ['rar', '7z', 'tar', 'gz'],
|
17
|
+
'rar': ['zip', '7z', 'tar', 'gz'],
|
18
|
+
'7z': ['zip', 'rar', 'tar', 'gz'],
|
19
|
+
'tar': ['zip', 'rar', '7z', 'gz'],
|
20
|
+
'gz': ['zip', 'rar', '7z', 'tar']
|
21
|
+
}
|
22
|
+
|
23
|
+
def convert(
|
24
|
+
filepath: Union[str, Path],
|
25
|
+
source_format: str,
|
26
|
+
target_format: str,
|
27
|
+
output_path: Union[str, Path],
|
28
|
+
options: Optional[Dict[str, Any]] = None
|
29
|
+
) -> str:
|
30
|
+
"""
|
31
|
+
Convert an archive from one format to another.
|
32
|
+
|
33
|
+
Args:
|
34
|
+
filepath: Path to the source archive file
|
35
|
+
source_format: Source archive format
|
36
|
+
target_format: Target archive format
|
37
|
+
output_path: Path to save the converted archive
|
38
|
+
options: Additional conversion options
|
39
|
+
|
40
|
+
Returns:
|
41
|
+
Path to the converted archive
|
42
|
+
|
43
|
+
Raises:
|
44
|
+
ValueError: If the conversion is not supported
|
45
|
+
RuntimeError: If the conversion fails
|
46
|
+
"""
|
47
|
+
if options is None:
|
48
|
+
options = {}
|
49
|
+
|
50
|
+
# Check if conversion is supported
|
51
|
+
if target_format not in SUPPORTED_CONVERSIONS.get(source_format, []):
|
52
|
+
raise ValueError(f"Conversion from {source_format} to {target_format} is not supported")
|
53
|
+
|
54
|
+
filepath = Path(filepath)
|
55
|
+
output_path = Path(output_path)
|
56
|
+
|
57
|
+
try:
|
58
|
+
# Create a temporary directory for extraction
|
59
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
60
|
+
temp_dir_path = Path(temp_dir)
|
61
|
+
|
62
|
+
# Extract the source archive
|
63
|
+
extract_dir = _extract_archive(filepath, source_format, temp_dir_path)
|
64
|
+
|
65
|
+
# Create the target archive
|
66
|
+
return _create_archive(extract_dir, target_format, output_path, options)
|
67
|
+
|
68
|
+
except Exception as e:
|
69
|
+
logger.error(f"Error converting {filepath} to {target_format}: {str(e)}")
|
70
|
+
raise RuntimeError(f"Failed to convert {filepath} to {target_format}: {str(e)}")
|
71
|
+
|
72
|
+
def _extract_archive(
|
73
|
+
filepath: Path,
|
74
|
+
format: str,
|
75
|
+
output_dir: Path
|
76
|
+
) -> Path:
|
77
|
+
"""Extract an archive to a directory."""
|
78
|
+
if format == 'zip':
|
79
|
+
return _extract_zip(filepath, output_dir)
|
80
|
+
elif format == 'rar':
|
81
|
+
return _extract_rar(filepath, output_dir)
|
82
|
+
elif format == '7z':
|
83
|
+
return _extract_7z(filepath, output_dir)
|
84
|
+
elif format == 'tar':
|
85
|
+
return _extract_tar(filepath, output_dir)
|
86
|
+
elif format == 'gz':
|
87
|
+
return _extract_gz(filepath, output_dir)
|
88
|
+
else:
|
89
|
+
raise ValueError(f"Unsupported archive format for extraction: {format}")
|
90
|
+
|
91
|
+
def _create_archive(
|
92
|
+
source_dir: Path,
|
93
|
+
format: str,
|
94
|
+
output_path: Path,
|
95
|
+
options: Dict[str, Any]
|
96
|
+
) -> str:
|
97
|
+
"""Create an archive from a directory."""
|
98
|
+
if format == 'zip':
|
99
|
+
return _create_zip(source_dir, output_path, options)
|
100
|
+
elif format == 'rar':
|
101
|
+
return _create_rar(source_dir, output_path, options)
|
102
|
+
elif format == '7z':
|
103
|
+
return _create_7z(source_dir, output_path, options)
|
104
|
+
elif format == 'tar':
|
105
|
+
return _create_tar(source_dir, output_path, options)
|
106
|
+
elif format == 'gz':
|
107
|
+
return _create_gz(source_dir, output_path, options)
|
108
|
+
else:
|
109
|
+
raise ValueError(f"Unsupported archive format for creation: {format}")
|
110
|
+
|
111
|
+
def _extract_zip(filepath: Path, output_dir: Path) -> Path:
|
112
|
+
"""Extract a ZIP archive."""
|
113
|
+
import zipfile
|
114
|
+
|
115
|
+
with zipfile.ZipFile(filepath, 'r') as zip_ref:
|
116
|
+
zip_ref.extractall(output_dir)
|
117
|
+
|
118
|
+
return output_dir
|
119
|
+
|
120
|
+
def _extract_rar(filepath: Path, output_dir: Path) -> Path:
|
121
|
+
"""Extract a RAR archive."""
|
122
|
+
try:
|
123
|
+
import rarfile
|
124
|
+
|
125
|
+
with rarfile.RarFile(filepath) as rf:
|
126
|
+
rf.extractall(output_dir)
|
127
|
+
|
128
|
+
return output_dir
|
129
|
+
|
130
|
+
except ImportError:
|
131
|
+
# Try using unrar command-line tool
|
132
|
+
import subprocess
|
133
|
+
|
134
|
+
try:
|
135
|
+
cmd = ['unrar', 'x', str(filepath), str(output_dir)]
|
136
|
+
subprocess.run(cmd, check=True, capture_output=True)
|
137
|
+
return output_dir
|
138
|
+
|
139
|
+
except subprocess.SubprocessError as e:
|
140
|
+
raise RuntimeError(f"Failed to extract RAR archive: {str(e)}. Please install rarfile or unrar.")
|
141
|
+
|
142
|
+
def _extract_7z(filepath: Path, output_dir: Path) -> Path:
|
143
|
+
"""Extract a 7Z archive."""
|
144
|
+
try:
|
145
|
+
import py7zr
|
146
|
+
|
147
|
+
with py7zr.SevenZipFile(filepath, mode='r') as z:
|
148
|
+
z.extractall(output_dir)
|
149
|
+
|
150
|
+
return output_dir
|
151
|
+
|
152
|
+
except ImportError:
|
153
|
+
# Try using 7z command-line tool
|
154
|
+
import subprocess
|
155
|
+
|
156
|
+
try:
|
157
|
+
cmd = ['7z', 'x', str(filepath), f'-o{output_dir}']
|
158
|
+
subprocess.run(cmd, check=True, capture_output=True)
|
159
|
+
return output_dir
|
160
|
+
|
161
|
+
except subprocess.SubprocessError as e:
|
162
|
+
raise RuntimeError(f"Failed to extract 7Z archive: {str(e)}. Please install py7zr or 7z.")
|
163
|
+
|
164
|
+
def _extract_tar(filepath: Path, output_dir: Path) -> Path:
|
165
|
+
"""Extract a TAR archive."""
|
166
|
+
import tarfile
|
167
|
+
|
168
|
+
with tarfile.open(filepath, 'r') as tar:
|
169
|
+
tar.extractall(output_dir)
|
170
|
+
|
171
|
+
return output_dir
|
172
|
+
|
173
|
+
def _extract_gz(filepath: Path, output_dir: Path) -> Path:
|
174
|
+
"""Extract a GZ archive."""
|
175
|
+
import gzip
|
176
|
+
import tarfile
|
177
|
+
|
178
|
+
# Check if it's a tar.gz file
|
179
|
+
if filepath.name.endswith('.tar.gz') or filepath.name.endswith('.tgz'):
|
180
|
+
with tarfile.open(filepath, 'r:gz') as tar:
|
181
|
+
tar.extractall(output_dir)
|
182
|
+
return output_dir
|
183
|
+
|
184
|
+
# Otherwise, it's a single gzipped file
|
185
|
+
with gzip.open(filepath, 'rb') as f_in:
|
186
|
+
output_file = output_dir / filepath.with_suffix('').name
|
187
|
+
with open(output_file, 'wb') as f_out:
|
188
|
+
shutil.copyfileobj(f_in, f_out)
|
189
|
+
|
190
|
+
return output_dir
|
191
|
+
|
192
|
+
def _create_zip(source_dir: Path, output_path: Path, options: Dict[str, Any]) -> str:
|
193
|
+
"""Create a ZIP archive."""
|
194
|
+
import zipfile
|
195
|
+
|
196
|
+
compression = options.get('compression', zipfile.ZIP_DEFLATED)
|
197
|
+
compression_level = options.get('compression_level', 9) # 0-9, 9 is highest
|
198
|
+
|
199
|
+
with zipfile.ZipFile(output_path, 'w', compression=compression, compresslevel=compression_level) as zipf:
|
200
|
+
for root, _, files in os.walk(source_dir):
|
201
|
+
for file in files:
|
202
|
+
file_path = Path(root) / file
|
203
|
+
arcname = file_path.relative_to(source_dir)
|
204
|
+
zipf.write(file_path, arcname)
|
205
|
+
|
206
|
+
return str(output_path)
|
207
|
+
|
208
|
+
def _create_rar(source_dir: Path, output_path: Path, options: Dict[str, Any]) -> str:
|
209
|
+
"""Create a RAR archive."""
|
210
|
+
# RAR creation requires command-line tool
|
211
|
+
import subprocess
|
212
|
+
|
213
|
+
try:
|
214
|
+
# Determine compression level
|
215
|
+
compression_level = options.get('compression_level', 5) # 0-5, 5 is highest
|
216
|
+
|
217
|
+
cmd = ['rar', f'a{compression_level}', str(output_path), str(source_dir)]
|
218
|
+
subprocess.run(cmd, check=True, capture_output=True)
|
219
|
+
return str(output_path)
|
220
|
+
|
221
|
+
except subprocess.SubprocessError as e:
|
222
|
+
raise RuntimeError(f"Failed to create RAR archive: {str(e)}. Please install rar command-line tool.")
|
223
|
+
|
224
|
+
def _create_7z(source_dir: Path, output_path: Path, options: Dict[str, Any]) -> str:
|
225
|
+
"""Create a 7Z archive."""
|
226
|
+
try:
|
227
|
+
import py7zr
|
228
|
+
|
229
|
+
compression_level = options.get('compression_level', 9) # 0-9, 9 is highest
|
230
|
+
|
231
|
+
with py7zr.SevenZipFile(output_path, 'w', compression_level=compression_level) as z:
|
232
|
+
z.writeall(source_dir)
|
233
|
+
|
234
|
+
return str(output_path)
|
235
|
+
|
236
|
+
except ImportError:
|
237
|
+
# Try using 7z command-line tool
|
238
|
+
import subprocess
|
239
|
+
|
240
|
+
try:
|
241
|
+
compression_level = options.get('compression_level', 9) # 0-9, 9 is highest
|
242
|
+
|
243
|
+
cmd = ['7z', 'a', f'-mx={compression_level}', str(output_path), f'{source_dir}/*']
|
244
|
+
subprocess.run(cmd, check=True, capture_output=True, shell=True)
|
245
|
+
return str(output_path)
|
246
|
+
|
247
|
+
except subprocess.SubprocessError as e:
|
248
|
+
raise RuntimeError(f"Failed to create 7Z archive: {str(e)}. Please install py7zr or 7z.")
|
249
|
+
|
250
|
+
def _create_tar(source_dir: Path, output_path: Path, options: Dict[str, Any]) -> str:
|
251
|
+
"""Create a TAR archive."""
|
252
|
+
import tarfile
|
253
|
+
|
254
|
+
with tarfile.open(output_path, 'w') as tar:
|
255
|
+
tar.add(source_dir, arcname=os.path.basename(source_dir))
|
256
|
+
|
257
|
+
return str(output_path)
|
258
|
+
|
259
|
+
def _create_gz(source_dir: Path, output_path: Path, options: Dict[str, Any]) -> str:
|
260
|
+
"""Create a GZ archive."""
|
261
|
+
import tarfile
|
262
|
+
|
263
|
+
# If there are multiple files, create a tar.gz
|
264
|
+
files = list(source_dir.glob('*'))
|
265
|
+
if len(files) > 1 or any(f.is_dir() for f in files):
|
266
|
+
with tarfile.open(output_path, 'w:gz') as tar:
|
267
|
+
tar.add(source_dir, arcname='')
|
268
|
+
else:
|
269
|
+
# If there's only one file, create a simple .gz
|
270
|
+
import gzip
|
271
|
+
|
272
|
+
input_file = files[0]
|
273
|
+
with open(input_file, 'rb') as f_in:
|
274
|
+
with gzip.open(output_path, 'wb') as f_out:
|
275
|
+
shutil.copyfileobj(f_in, f_out)
|
276
|
+
|
277
|
+
return str(output_path)
|