Open-AutoTools 0.0.4rc2__py3-none-any.whl → 0.0.5__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.
Files changed (42) hide show
  1. autotools/autocaps/commands.py +21 -0
  2. autotools/autocolor/__init__.py +0 -0
  3. autotools/autocolor/commands.py +60 -0
  4. autotools/autocolor/core.py +99 -0
  5. autotools/autoconvert/__init__.py +0 -0
  6. autotools/autoconvert/commands.py +79 -0
  7. autotools/autoconvert/conversion/__init__.py +0 -0
  8. autotools/autoconvert/conversion/convert_audio.py +24 -0
  9. autotools/autoconvert/conversion/convert_image.py +29 -0
  10. autotools/autoconvert/conversion/convert_text.py +101 -0
  11. autotools/autoconvert/conversion/convert_video.py +25 -0
  12. autotools/autoconvert/core.py +54 -0
  13. autotools/autoip/commands.py +38 -1
  14. autotools/autoip/core.py +99 -42
  15. autotools/autolower/commands.py +21 -0
  16. autotools/autonote/__init__.py +0 -0
  17. autotools/autonote/commands.py +70 -0
  18. autotools/autonote/core.py +106 -0
  19. autotools/autopassword/commands.py +39 -1
  20. autotools/autotest/commands.py +36 -6
  21. autotools/autotodo/__init__.py +87 -0
  22. autotools/autotodo/commands.py +115 -0
  23. autotools/autotodo/core.py +567 -0
  24. autotools/autounit/__init__.py +0 -0
  25. autotools/autounit/commands.py +55 -0
  26. autotools/autounit/core.py +36 -0
  27. autotools/autozip/__init__.py +0 -0
  28. autotools/autozip/commands.py +88 -0
  29. autotools/autozip/core.py +107 -0
  30. autotools/cli.py +66 -62
  31. autotools/utils/commands.py +141 -10
  32. autotools/utils/smoke.py +246 -0
  33. autotools/utils/text.py +57 -0
  34. open_autotools-0.0.5.dist-info/METADATA +100 -0
  35. open_autotools-0.0.5.dist-info/RECORD +54 -0
  36. {open_autotools-0.0.4rc2.dist-info → open_autotools-0.0.5.dist-info}/WHEEL +1 -1
  37. open_autotools-0.0.5.dist-info/entry_points.txt +12 -0
  38. open_autotools-0.0.4rc2.dist-info/METADATA +0 -84
  39. open_autotools-0.0.4rc2.dist-info/RECORD +0 -30
  40. open_autotools-0.0.4rc2.dist-info/entry_points.txt +0 -6
  41. {open_autotools-0.0.4rc2.dist-info → open_autotools-0.0.5.dist-info}/licenses/LICENSE +0 -0
  42. {open_autotools-0.0.4rc2.dist-info → open_autotools-0.0.5.dist-info}/top_level.txt +0 -0
@@ -3,10 +3,31 @@ from .core import autocaps_transform
3
3
  from ..utils.loading import LoadingAnimation
4
4
  from ..utils.updates import check_for_updates
5
5
 
6
+ # TOOL CATEGORY (USED BY 'autotools smoke')
7
+ TOOL_CATEGORY = 'Text'
8
+
9
+ # SMOKE TEST CASES (USED BY 'autotools smoke')
10
+ SMOKE_TESTS = [
11
+ {'name': 'basic', 'args': ['test', 'with', 'multiple', 'words']},
12
+ {'name': 'special', 'args': ['special', 'chars:', '!@#$%^&*()']},
13
+ {'name': 'mixed', 'args': ['123', 'mixed', 'WITH', 'lowercase', '456']},
14
+ {'name': 'unicode', 'args': ['áccênts', 'ànd', 'émojis', '🚀', '⭐']},
15
+ ]
16
+
6
17
  # CLI COMMAND TO TRANSFORM TEXT TO UPPERCASE
7
18
  @click.command()
8
19
  @click.argument('text', nargs=-1)
9
20
  def autocaps(text):
21
+ """
22
+ TRANSFORMS TEXT TO UPPERCASE.
23
+
24
+ \b
25
+ EXAMPLES:
26
+ autocaps hello world
27
+ autocaps "this is a test"
28
+ echo "text" | autocaps
29
+ """
30
+
10
31
  with LoadingAnimation(): result = autocaps_transform(" ".join(text))
11
32
  click.echo(result)
12
33
  update_msg = check_for_updates()
File without changes
@@ -0,0 +1,60 @@
1
+ import click
2
+ from .core import autocolor_convert
3
+ from ..utils.loading import LoadingAnimation
4
+ from ..utils.updates import check_for_updates
5
+
6
+ # TOOL CATEGORY (USED BY 'autotools smoke')
7
+ TOOL_CATEGORY = 'Color'
8
+
9
+ # SMOKE TEST CASES (USED BY 'autotools smoke')
10
+ SMOKE_TESTS = [
11
+ {'name': 'hex-default', 'args': ['#FF5733']},
12
+ {'name': 'hex-rgb', 'args': ['#FF5733', '--format', 'rgb']},
13
+ {'name': 'rgb-hsl', 'args': ['rgb(255,87,51)', '--format', 'hsl']},
14
+ {'name': 'hsl-rgba', 'args': ['hsl(9,100%,60%)', '--format', 'rgba']},
15
+ ]
16
+
17
+ # CLI COMMAND TO CONVERT COLOR CODES BETWEEN FORMATS
18
+ @click.command()
19
+ @click.argument('color', nargs=-1)
20
+ @click.option('--format', '-f', 'output_format',
21
+ type=click.Choice(['hex', 'rgb', 'rgba', 'hsl', 'hsla'], case_sensitive=False),
22
+ default='hex', help='OUTPUT FORMAT (hex, rgb, rgba, hsl, hsla)')
23
+ def autocolor(color, output_format):
24
+ """
25
+ CONVERTS COLOR CODES BETWEEN DIFFERENT FORMATS.
26
+
27
+ \b
28
+ SUPPORTS INPUT FORMATS:
29
+ - HEX: #RRGGBB, #RRGGBBAA, #RGB
30
+ - RGB: rgb(r, g, b)
31
+ - RGBA: rgba(r, g, b, a)
32
+ - HSL: hsl(h, s%, l%)
33
+ - HSLA: hsla(h, s%, l%, a)
34
+
35
+ \b
36
+ EXAMPLES:
37
+ autocolor "#FF5733"
38
+ autocolor "rgb(255, 87, 51)" --format hsl
39
+ autocolor "hsl(9, 100%, 60%)" --format hex
40
+ autocolor "#F73" --format rgba
41
+ """
42
+
43
+ if not color:
44
+ click.echo(click.style("ERROR: COLOR ARGUMENT IS REQUIRED", fg='red'), err=True)
45
+ click.echo(click.get_current_context().get_help())
46
+ return
47
+
48
+ color_input = " ".join(color)
49
+
50
+ try:
51
+ with LoadingAnimation(): result = autocolor_convert(color_input, output_format)
52
+ click.echo(result)
53
+ update_msg = check_for_updates()
54
+ if update_msg: click.echo(update_msg)
55
+ except ValueError as e:
56
+ click.echo(click.style(f"ERROR: {str(e)}", fg='red'), err=True)
57
+ raise click.Abort()
58
+ except Exception as e:
59
+ click.echo(click.style(f"UNEXPECTED ERROR: {str(e)}", fg='red'), err=True)
60
+ raise click.Abort()
@@ -0,0 +1,99 @@
1
+ import re
2
+ import colorsys
3
+ import pyperclip
4
+
5
+ # CONVERTS HEX COLOR TO RGB TUPLE
6
+ def _hex_to_rgb(hex_color):
7
+ hex_color = hex_color.lstrip('#')
8
+ if len(hex_color) == 3:
9
+ hex_color = ''.join([c * 2 for c in hex_color])
10
+ if len(hex_color) == 6:
11
+ return tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
12
+ elif len(hex_color) == 8:
13
+ rgb = tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
14
+ alpha = int(hex_color[6:8], 16) / 255.0
15
+ return (*rgb, alpha)
16
+ raise ValueError(f"INVALID HEX COLOR: {hex_color}")
17
+
18
+ # CONVERTS RGB TUPLE TO HEX
19
+ def _rgb_to_hex(r, g, b, alpha=None):
20
+ hex_color = f"#{r:02x}{g:02x}{b:02x}"
21
+ if alpha is not None: hex_color += f"{int(alpha * 255):02x}"
22
+ return hex_color.upper()
23
+
24
+ # PARSES RGB/RGBA STRING
25
+ def _parse_rgb(rgb_str):
26
+ match = re.match(r'rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)', rgb_str.lower())
27
+ if not match:
28
+ raise ValueError(f"INVALID RGB/RGBA FORMAT: {rgb_str}")
29
+ r, g, b = int(match.group(1)), int(match.group(2)), int(match.group(3))
30
+ alpha = float(match.group(4)) if match.group(4) else None
31
+ if alpha is not None and alpha > 1:
32
+ alpha = alpha / 255.0
33
+ return (r, g, b, alpha) if alpha is not None else (r, g, b)
34
+
35
+ # PARSES HSL/HSLA STRING
36
+ def _parse_hsl(hsl_str):
37
+ match = re.match(r'hsla?\((\d+),\s*(\d+)%,\s*(\d+)%(?:,\s*([\d.]+))?\)', hsl_str.lower())
38
+ if not match:
39
+ raise ValueError(f"INVALID HSL/HSLA FORMAT: {hsl_str}")
40
+ h = int(match.group(1)) / 360.0
41
+ s = int(match.group(2)) / 100.0
42
+ l = int(match.group(3)) / 100.0
43
+ alpha = float(match.group(4)) if match.group(4) else None
44
+ return (h, s, l, alpha) if alpha is not None else (h, s, l)
45
+
46
+ # PARSES INPUT COLOR AND RETURNS RGB VALUES WITH ALPHA
47
+ def _parse_color_input(color_input):
48
+ if color_input.startswith('#'):
49
+ rgb_result = _hex_to_rgb(color_input)
50
+ return rgb_result if len(rgb_result) == 4 else (*rgb_result, None)
51
+ elif color_input.startswith('rgb'):
52
+ rgb_result = _parse_rgb(color_input)
53
+ return rgb_result if len(rgb_result) == 4 else (*rgb_result, None)
54
+ elif color_input.startswith('hsl'):
55
+ hsl_result = _parse_hsl(color_input)
56
+ if len(hsl_result) == 4:
57
+ h, s, l, alpha = hsl_result
58
+ else:
59
+ h, s, l = hsl_result
60
+ alpha = None
61
+ r, g, b = [int(c * 255) for c in colorsys.hls_to_rgb(h, l, s)]
62
+ return (r, g, b, alpha)
63
+ raise ValueError(f"UNSUPPORTED COLOR FORMAT: {color_input}")
64
+
65
+ # CONVERTS RGB TO HSL VALUES
66
+ def _rgb_to_hsl_values(r, g, b):
67
+ h, l, s = colorsys.rgb_to_hls(r / 255.0, g / 255.0, b / 255.0)
68
+ return (int(h * 360), int(s * 100), int(l * 100))
69
+
70
+ # FORMATS OUTPUT COLOR BASED ON FORMAT TYPE
71
+ def _format_output(r, g, b, alpha, output_format):
72
+ if output_format == 'hex':
73
+ return _rgb_to_hex(r, g, b, alpha)
74
+ elif output_format == 'rgb':
75
+ return f"rgb({r}, {g}, {b})"
76
+ elif output_format == 'rgba':
77
+ alpha = alpha if alpha is not None else 1.0
78
+ return f"rgba({r}, {g}, {b}, {alpha:.2f})"
79
+ elif output_format == 'hsl':
80
+ h, s, l = _rgb_to_hsl_values(r, g, b)
81
+ return f"hsl({h}, {s}%, {l}%)"
82
+ elif output_format == 'hsla':
83
+ alpha = alpha if alpha is not None else 1.0
84
+ h, s, l = _rgb_to_hsl_values(r, g, b)
85
+ return f"hsla({h}, {s}%, {l}%, {alpha:.2f})"
86
+ raise ValueError(f"UNSUPPORTED OUTPUT FORMAT: {output_format}")
87
+
88
+ # CONVERTS COLOR CODE TO DIFFERENT FORMATS
89
+ def autocolor_convert(color_input, output_format='hex'):
90
+ color_input = color_input.strip()
91
+ output_format = output_format.lower()
92
+
93
+ r, g, b, alpha = _parse_color_input(color_input)
94
+ result = _format_output(r, g, b, alpha, output_format)
95
+
96
+ try: pyperclip.copy(result)
97
+ except pyperclip.PyperclipException: pass
98
+
99
+ return result
File without changes
@@ -0,0 +1,79 @@
1
+ import os
2
+ import click
3
+ from pathlib import Path
4
+ from .core import convert_file, detect_file_type
5
+ from ..utils.loading import LoadingAnimation
6
+ from ..utils.updates import check_for_updates
7
+
8
+ # TOOL CATEGORY (USED BY 'autotools smoke')
9
+ TOOL_CATEGORY = 'Files'
10
+
11
+ # SMOKE TEST CASES (USED BY 'autotools smoke')
12
+ SMOKE_TESTS = [
13
+ {'name': 'md-json', 'args': ['README.md', '/tmp/autoconvert-smoke.json']},
14
+ ]
15
+
16
+ # CLI COMMAND TO CONVERT FILES BETWEEN DIFFERENT FORMATS
17
+ @click.command()
18
+ @click.argument('input_file', type=click.Path())
19
+ @click.argument('output_file', type=click.Path())
20
+ @click.option('--input-type', '-i', help='FORCE INPUT FILE TYPE (text/image/audio/video)')
21
+ @click.option('--output-type', '-o', help='FORCE OUTPUT FILE TYPE (text/image/audio/video)')
22
+ @click.option('--format', '-f', help='OUTPUT FORMAT (OVERRIDES OUTPUT FILE EXTENSION)')
23
+ def autoconvert(input_file, output_file, input_type, output_type, format):
24
+ """
25
+ CONVERTS FILES BETWEEN DIFFERENT FORMATS.
26
+
27
+ \b
28
+ SUPPORTS:
29
+ - TEXT: txt, md, markdown, json, xml, html, htm, csv
30
+ - IMAGES: jpg, jpeg, png, gif, webp, bmp, tiff, tif, ico, svg
31
+ - AUDIO: mp3, wav, ogg, flac, aac, m4a, wma, opus
32
+ - VIDEO: mp4, avi, mov, mkv, wmv, flv, webm, m4v
33
+
34
+ \b
35
+ EXAMPLES:
36
+ autoconvert input.txt output.json
37
+ autoconvert image.jpg image.png
38
+ autoconvert audio.mp3 audio.wav
39
+ autoconvert video.mp4 video.avi
40
+ """
41
+
42
+ # TRY TO CONVERT FILE
43
+ # - CHECK IF INPUT FILE EXISTS, OTHERWISE FAIL
44
+ # - HANDLE --FORMAT: UPDATE OUTPUT NAME/EXT, DETECT OUTPUT TYPE IF UNSET
45
+ # - MAKE OUTPUT DIRECTORY IF IT DOESN'T EXIST, RUN CONVERT WITH LOADING SPINNER
46
+ # - SHOW RESULT, PRINT UPDATE NOTICE
47
+ try:
48
+ if not os.path.exists(input_file):
49
+ raise FileNotFoundError(f"INPUT FILE NOT FOUND: {input_file}")
50
+
51
+ if format:
52
+ output_path = Path(output_file)
53
+ if not output_file.endswith(f'.{format}'): output_file = str(output_path.with_suffix(f'.{format}'))
54
+ if output_type is None: output_type = detect_file_type(output_file)
55
+
56
+ output_path = Path(output_file)
57
+ output_path.parent.mkdir(parents=True, exist_ok=True)
58
+
59
+ with LoadingAnimation(): success, message = convert_file(input_file, output_file, input_type, output_type)
60
+
61
+ if success:
62
+ click.echo(click.style(f"✓ {message}", fg='green'))
63
+ click.echo(click.style(f"OUTPUT: {output_file}", fg='blue'))
64
+ else:
65
+ click.echo(click.style(f"✗ {message}", fg='red'), err=True)
66
+ raise click.Abort()
67
+
68
+ update_msg = check_for_updates()
69
+ if update_msg: click.echo(update_msg)
70
+
71
+ except FileNotFoundError as e:
72
+ click.echo(click.style(f"✗ FILE NOT FOUND: {str(e)}", fg='red'), err=True)
73
+ raise click.Abort()
74
+ except ImportError as e:
75
+ click.echo(click.style(f"✗ {str(e)}", fg='red'), err=True)
76
+ raise click.Abort()
77
+ except Exception as e:
78
+ click.echo(click.style(f"✗ ERROR: {str(e)}", fg='red'), err=True)
79
+ raise click.Abort()
File without changes
@@ -0,0 +1,24 @@
1
+ import os
2
+ from pathlib import Path
3
+ from typing import Optional
4
+
5
+ # CONVERTS AUDIO BETWEEN FORMATS
6
+ def convert_audio(input_path: str, output_path: str, output_format: Optional[str] = None) -> bool:
7
+ try:
8
+ from pydub import AudioSegment
9
+
10
+ if not os.path.exists(input_path):
11
+ raise FileNotFoundError(f"INPUT FILE NOT FOUND: {input_path}")
12
+ if output_format is None: output_format = Path(output_path).suffix[1:].lower()
13
+
14
+ audio = AudioSegment.from_file(input_path)
15
+ audio.export(output_path, format=output_format)
16
+
17
+ return True
18
+
19
+ except ImportError:
20
+ raise ImportError("PYDUB is required for audio conversion. Install with: pip install pydub. Also install FFMPEG.")
21
+ except (OSError, ValueError, IOError) as e:
22
+ raise RuntimeError(f"AUDIO CONVERSION FAILED: {str(e)}")
23
+ except Exception as e:
24
+ raise RuntimeError(f"AUDIO CONVERSION FAILED: {str(e)}")
@@ -0,0 +1,29 @@
1
+ import os
2
+ from pathlib import Path
3
+ from typing import Optional
4
+
5
+ # CONVERTS IMAGE BETWEEN FORMATS
6
+ def convert_image(input_path: str, output_path: str, output_format: Optional[str] = None) -> bool:
7
+ try:
8
+ from PIL import Image
9
+
10
+ if not os.path.exists(input_path):
11
+ raise FileNotFoundError(f"INPUT FILE NOT FOUND: {input_path}")
12
+ if output_format is None: output_format = Path(output_path).suffix[1:].upper()
13
+
14
+ with Image.open(input_path) as img:
15
+ if output_format in ['JPG', 'JPEG'] and img.mode in ('RGBA', 'LA', 'P'):
16
+ rgb_img = Image.new('RGB', img.size, (255, 255, 255))
17
+ if img.mode == 'P': img = img.convert('RGBA')
18
+ rgb_img.paste(img, mask=img.split()[-1] if img.mode in ('RGBA', 'LA') else None)
19
+ img = rgb_img
20
+
21
+ img.save(output_path, format=output_format)
22
+ return True
23
+
24
+ except ImportError:
25
+ raise ImportError("PILLOW (PIL) is required for image conversion. Install with: pip install Pillow")
26
+ except (OSError, ValueError, IOError) as e:
27
+ raise RuntimeError(f"IMAGE CONVERSION FAILED: {str(e)}")
28
+ except Exception as e:
29
+ raise RuntimeError(f"IMAGE CONVERSION FAILED: {str(e)}")
@@ -0,0 +1,101 @@
1
+ import os
2
+ import json
3
+ import xml.etree.ElementTree as ET
4
+ from pathlib import Path
5
+ from typing import Tuple
6
+
7
+ # CONVERTS TEXT TO JSON FORMAT
8
+ def text_to_json(text: str, indent: int = 2) -> str:
9
+ data = {"text": text}
10
+ return json.dumps(data, indent=indent, ensure_ascii=False)
11
+
12
+ # CONVERTS TEXT TO XML FORMAT
13
+ def text_to_xml(text: str, root_tag: str = "text") -> str:
14
+ root = ET.Element(root_tag)
15
+ root.text = text
16
+ return ET.tostring(root, encoding='unicode')
17
+
18
+ # CONVERTS TEXT TO HTML FORMAT
19
+ def text_to_html(text: str, title: str = "Document") -> str:
20
+ escaped_text = text.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
21
+ return f"""<!DOCTYPE html>
22
+ <html>
23
+ <head>
24
+ <title>{title}</title>
25
+ </head>
26
+ <body>
27
+ <pre>{escaped_text}</pre>
28
+ </body>
29
+ </html>"""
30
+
31
+ # CONVERTS TEXT TO MARKDOWN FORMAT
32
+ def text_to_markdown(text: str) -> str:
33
+ return text
34
+
35
+ # CONVERTS JSON TO TEXT
36
+ def json_to_text(json_str: str) -> str:
37
+ try:
38
+ data = json.loads(json_str)
39
+ if isinstance(data, dict) and "text" in data: return str(data["text"])
40
+ return json.dumps(data, indent=2, ensure_ascii=False)
41
+ except json.JSONDecodeError:
42
+ return json_str
43
+
44
+ # CONVERTS XML TO TEXT
45
+ def xml_to_text(xml_str: str) -> str:
46
+ try:
47
+ root = ET.fromstring(xml_str)
48
+ text_parts = []
49
+
50
+ for elem in root.iter():
51
+ if elem.text and elem.text.strip(): text_parts.append(elem.text.strip())
52
+ if elem.tail and elem.tail.strip(): text_parts.append(elem.tail.strip())
53
+
54
+ result = " ".join(text_parts)
55
+ return result
56
+ except ET.ParseError:
57
+ return xml_str
58
+
59
+ # CONVERTS TEXT FILE FROM ONE FORMAT TO ANOTHER
60
+ def convert_text_file(input_path: str, output_path: str) -> Tuple[bool, str]:
61
+ if not os.path.exists(input_path):
62
+ raise FileNotFoundError(f"INPUT FILE NOT FOUND: {input_path}")
63
+ with open(input_path, 'r', encoding='utf-8') as f: content = f.read()
64
+
65
+ input_ext = Path(input_path).suffix[1:].lower()
66
+ output_ext = Path(output_path).suffix[1:].lower()
67
+
68
+ # CONVERT BASED ON INPUT FORMAT
69
+ if input_ext == 'json':
70
+ content = json_to_text(content)
71
+ elif input_ext == 'xml':
72
+ content = xml_to_text(content)
73
+ elif input_ext in ['html', 'htm']:
74
+ from html.parser import HTMLParser
75
+ class TextExtractor(HTMLParser):
76
+ def __init__(self):
77
+ super().__init__()
78
+ self.text = []
79
+ def handle_data(self, data):
80
+ self.text.append(data)
81
+ parser = TextExtractor()
82
+ parser.feed(content)
83
+ content = ' '.join(parser.text)
84
+
85
+ # CONVERT TO OUTPUT FORMAT
86
+ if output_ext == 'json': content = text_to_json(content)
87
+ elif output_ext == 'xml': content = text_to_xml(content)
88
+ elif output_ext in ['html', 'htm']: content = text_to_html(content)
89
+
90
+ # CREATE OUTPUT DIRECTORY IF IT DOESN'T EXIST
91
+ output_dir = os.path.dirname(output_path)
92
+ if output_dir and not os.path.exists(output_dir):
93
+ try: os.makedirs(output_dir, exist_ok=True)
94
+ except OSError as e: return False, f"CONVERSION FAILED: Cannot create output directory: {str(e)}"
95
+
96
+ try:
97
+ with open(output_path, 'w', encoding='utf-8') as f: f.write(content)
98
+ except OSError as e:
99
+ return False, f"CONVERSION FAILED: Cannot write output file: {str(e)}"
100
+
101
+ return True, f"TEXT CONVERTED FROM {input_ext.upper()} TO {output_ext.upper()}"
@@ -0,0 +1,25 @@
1
+ import os
2
+ from pathlib import Path
3
+ from typing import Optional
4
+
5
+ # CONVERTS VIDEO BETWEEN FORMATS
6
+ def convert_video(input_path: str, output_path: str, output_format: Optional[str] = None) -> bool:
7
+ try:
8
+ from moviepy.editor import VideoFileClip
9
+
10
+ if not os.path.exists(input_path):
11
+ raise FileNotFoundError(f"INPUT FILE NOT FOUND: {input_path}")
12
+ if output_format is None: output_format = Path(output_path).suffix[1:].lower()
13
+
14
+ with VideoFileClip(input_path) as video: video.write_videofile(output_path, codec='libx264', audio_codec='aac')
15
+
16
+ return True
17
+
18
+ except ImportError:
19
+ raise ImportError("MOVIEPY IS REQUIRED FOR VIDEO CONVERSION. INSTALL WITH: pip install moviepy. ALSO INSTALL FFMPEG.")
20
+
21
+ except (OSError, ValueError, IOError) as e:
22
+ raise RuntimeError(f"VIDEO CONVERSION FAILED: {str(e)}")
23
+
24
+ except Exception as e:
25
+ raise RuntimeError(f"VIDEO CONVERSION FAILED: {str(e)}")
@@ -0,0 +1,54 @@
1
+ from pathlib import Path
2
+ from typing import Optional, Tuple
3
+
4
+ from .conversion.convert_text import convert_text_file
5
+ from .conversion.convert_image import convert_image
6
+ from .conversion.convert_audio import convert_audio
7
+ from .conversion.convert_video import convert_video
8
+
9
+ # DETECTS FILE TYPE AND CONVERTS ACCORDINGLY
10
+ def detect_file_type(file_path: str) -> str:
11
+ ext = Path(file_path).suffix[1:].lower()
12
+
13
+ # TEXT FORMATS
14
+ text_formats = ['txt', 'md', 'markdown', 'json', 'xml', 'html', 'htm', 'csv']
15
+ if ext in text_formats: return 'text'
16
+
17
+ # IMAGE FORMATS
18
+ image_formats = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'tiff', 'tif', 'ico', 'svg']
19
+ if ext in image_formats: return 'image'
20
+
21
+ # AUDIO FORMATS
22
+ audio_formats = ['mp3', 'wav', 'ogg', 'flac', 'aac', 'm4a', 'wma', 'opus']
23
+ if ext in audio_formats: return 'audio'
24
+
25
+ # VIDEO FORMATS
26
+ video_formats = ['mp4', 'avi', 'mov', 'mkv', 'wmv', 'flv', 'webm', 'm4v']
27
+ if ext in video_formats: return 'video'
28
+
29
+ return 'unknown'
30
+
31
+ # CONVERTS FILE FROM ONE FORMAT TO ANOTHER
32
+ def convert_file(input_path: str, output_path: str, input_type: Optional[str] = None, output_type: Optional[str] = None) -> Tuple[bool, str]:
33
+ try:
34
+ if input_type is None: input_type = detect_file_type(input_path)
35
+ if output_type is None: output_type = detect_file_type(output_path)
36
+ if input_type == 'text' and output_type == 'text': return convert_text_file(input_path, output_path)
37
+
38
+ # HANDLE CONVERSIONS WITHIN THE SAME MEDIA TYPE
39
+ elif input_type == 'image' and output_type == 'image':
40
+ convert_image(input_path, output_path)
41
+ return True, "IMAGE CONVERTED SUCCESSFULLY"
42
+ elif input_type == 'audio' and output_type == 'audio':
43
+ convert_audio(input_path, output_path)
44
+ return True, "AUDIO CONVERTED SUCCESSFULLY"
45
+ elif input_type == 'video' and output_type == 'video':
46
+ convert_video(input_path, output_path)
47
+ return True, "VIDEO CONVERTED SUCCESSFULLY"
48
+ else:
49
+ return False, f"UNSUPPORTED CONVERSION: {input_type} TO {output_type}"
50
+
51
+ except FileNotFoundError:
52
+ raise
53
+ except Exception as e:
54
+ return False, f"CONVERSION FAILED: {str(e)}"
@@ -2,7 +2,18 @@ import click
2
2
  from .core import run
3
3
  from ..utils.loading import LoadingAnimation
4
4
  from ..utils.updates import check_for_updates
5
- from ..utils.text import safe_text
5
+ from ..utils.text import safe_text, is_ci_environment, mask_sensitive_info
6
+
7
+ # TOOL CATEGORY (USED BY 'autotools smoke')
8
+ TOOL_CATEGORY = 'Network'
9
+
10
+ # SMOKE TEST CASES (USED BY 'autotools smoke')
11
+ SMOKE_TESTS = [
12
+ {'name': 'basic', 'args': ['--no-ip']},
13
+ {'name': 'connectivity', 'args': ['--test']},
14
+ {'name': 'dns', 'args': ['--dns']},
15
+ {'name': 'ports', 'args': ['--ports']},
16
+ ]
6
17
 
7
18
  # CLI COMMAND TO DISPLAY NETWORK INFORMATION AND RUN DIAGNOSTICS
8
19
  @click.command()
@@ -15,11 +26,37 @@ from ..utils.text import safe_text
15
26
  @click.option('--location', '-l', is_flag=True, help='Show IP location info')
16
27
  @click.option('--no-ip', '-n', is_flag=True, help='Hide IP addresses')
17
28
  def autoip(test, speed, monitor, interval, ports, dns, location, no_ip):
29
+ """
30
+ DISPLAYS NETWORK INFORMATION AND RUNS DIAGNOSTICS.
31
+
32
+ \b
33
+ FEATURES:
34
+ - Show IP addresses (public and private)
35
+ - RUN CONNECTIVITY TESTS
36
+ - Measure internet speed
37
+ - Monitor network traffic
38
+ - Check port status
39
+ - Display DNS servers
40
+ - Show IP location information
41
+
42
+ \b
43
+ EXAMPLES:
44
+ autoip
45
+ autoip --test
46
+ autoip --speed
47
+ autoip --monitor --interval 5
48
+ autoip --ports --dns --location
49
+ autoip --no-ip
50
+ """
51
+
18
52
  with LoadingAnimation():
19
53
  output = run(
20
54
  test=test, speed=speed, monitor=monitor, interval=interval,
21
55
  ports=ports, dns=dns, location=location, no_ip=no_ip
22
56
  )
57
+
58
+ if is_ci_environment(): output = mask_sensitive_info(output, mask_ips=True)
59
+
23
60
  click.echo(safe_text(output))
24
61
  update_msg = check_for_updates()
25
62
  if update_msg: click.echo(update_msg)