Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Changelog

## 0.0.3
- Added test image generation to GH0STB1T imagestego

## 0.0.2 - 2026-02-05

### Added
Expand Down
19 changes: 18 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,9 +218,26 @@ ghostbit image analyze -i <image_filepath>

</details>

<details>
<summary><b>Create Test Files</b></summary>

<br>


```bash
# Audio Creation for Testing
ghostbit audio test -o test_audio

# Image Creation for Testing
ghostbit image test -o test_images

```

</details>

<br>

### 🔗 Python API
### Python API

GH0STB1T provides a Python API for seamless integration into existing applications and workflows.

Expand Down
4 changes: 3 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ dependencies = [
"PyWavelets>=1.4.0",
"argon2-cffi>=25.1.0",
"cryptography>=46.0.3",
"svgwrite>=1.4.3",
]

[project.urls]
Expand All @@ -86,7 +87,8 @@ image = [
"scipy>=1.11.0",
"PyWavelets>=1.4.0",
"argon2-cffi>=25.1.0",
"cryptography>=46.0.3"
"cryptography>=46.0.3",
"svgwrite>=1.4.3"
]

# video = [
Expand Down
139 changes: 65 additions & 74 deletions src/ghostbit/audiostego/cli/audiostego_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -430,11 +430,9 @@ def info_command(self) -> Optional[int]:
logger.debug("Info display complete")
return 0

def create_test_files_command(
self, output_dir: str, create_carrier: bool
) -> Optional[int]:
def create_test_files_command(self, output_dir: str) -> Optional[int]:
"""Create test files for demonstration"""
logger.info(f"Creating test files in {output_dir}, carrier={create_carrier}")
logger.info(f"Creating test files in {output_dir}")

self._print_header("Creating Test Files", "📄")

Expand Down Expand Up @@ -465,68 +463,63 @@ def create_test_files_command(
print(f"❌ Error creating text files: {e}")
return 1

if create_carrier:
try:
logger.info("Creating carrier audio files")
print("\n🎵 Creating test carrier WAV file...")

wav_path = f"{output_dir}/test_carrier.wav"
logger.debug(f"Creating WAV file: {wav_path}")

with wave.open(wav_path, "w") as wav:
wav.setnchannels(1) # Mono
wav.setsampwidth(2) # 16-bit
wav.setframerate(44100) # 44.1kHz

duration = 5
logger.debug(f"Generating {duration} seconds of audio")
for i in range(44100 * duration):
value = int(32767 * 0.3 * math.sin(2 * math.pi * 440 * i / 44100))
wav.writeframes(struct.pack("<h", value))

size_mb = os.path.getsize(wav_path) / 1024 / 1024
print(f" ✓ Created test_carrier.wav ({size_mb:.2f} MB)")
logger.info(f"Created test_carrier.wav ({size_mb:.2f} MB)")

logger.info("Converting WAV to other formats")
print("\n🎵 Converting WAV to other formats...")

try:
logger.info("Creating carrier audio files")
print("\n🎵 Creating test carrier WAV file...")

wav_path = f"{output_dir}/test_carrier.wav"
logger.debug(f"Creating WAV file: {wav_path}")

with wave.open(wav_path, "w") as wav:
wav.setnchannels(1) # Mono
wav.setsampwidth(2) # 16-bit
wav.setframerate(44100) # 44.1kHz

duration = 5
logger.debug(f"Generating {duration} seconds of audio")
for i in range(44100 * duration):
value = int(
32767 * 0.3 * math.sin(2 * math.pi * 440 * i / 44100)
)
wav.writeframes(struct.pack("<h", value))

size_mb = os.path.getsize(wav_path) / 1024 / 1024
print(f" ✓ Created test_carrier.wav ({size_mb:.2f} MB)")
logger.info(f"Created test_carrier.wav ({size_mb:.2f} MB)")

logger.info("Converting WAV to other formats")
print("\n🎵 Converting WAV to other formats...")

try:
sound = AudioSegment.from_wav(wav_path)

logger.debug("Exporting to MP3")
sound.export(f"{output_dir}/test_carrier.mp3", format="mp3")
print(" ✓ Created test_carrier.mp3")
logger.info("Created test_carrier.mp3")

logger.debug("Exporting to M4A")
sound.export(f"{output_dir}/test_carrier.m4a", format="mp4")
print(" ✓ Created test_carrier.m4a")
logger.info("Created test_carrier.m4a")

logger.debug("Exporting to FLAC")
sound.export(f"{output_dir}/test_carrier.flac", format="flac")
print(" ✓ Created test_carrier.flac")
logger.info("Created test_carrier.flac")

logger.debug("Exporting to AIFF")
sound.export(f"{output_dir}/test_carrier.aiff", format="aiff")
print(" ✓ Created test_carrier.aiff")
logger.info("Created test_carrier.aiff")

print("\n✅ Success!")
except Exception as e:
logger.warning(f"Audio format conversion failed: {e}")
print(f"⚠️ Some format conversions failed: {e}")
print(" WAV file created successfully")
sound = AudioSegment.from_wav(wav_path)

logger.debug("Exporting to MP3")
sound.export(f"{output_dir}/test_carrier.mp3", format="mp3")
print(" ✓ Created test_carrier.mp3")
logger.info("Created test_carrier.mp3")

logger.debug("Exporting to M4A")
sound.export(f"{output_dir}/test_carrier.m4a", format="mp4")
print(" ✓ Created test_carrier.m4a")
logger.info("Created test_carrier.m4a")

logger.debug("Exporting to FLAC")
sound.export(f"{output_dir}/test_carrier.flac", format="flac")
print(" ✓ Created test_carrier.flac")
logger.info("Created test_carrier.flac")

logger.debug("Exporting to AIFF")
sound.export(f"{output_dir}/test_carrier.aiff", format="aiff")
print(" ✓ Created test_carrier.aiff")
logger.info("Created test_carrier.aiff")

print("\n✅ Success!")
except Exception as e:
logger.error(f"Failed to create carrier audio files: {e}")
print(f"⚠️ Could not create WAV file: {e}")
else:
logger.debug("Carrier creation skipped (not requested)")
logger.warning(f"Audio format conversion failed: {e}")
print(f"⚠️ Some format conversions failed: {e}")
print(" WAV file created successfully")

except Exception as e:
logger.error(f"Failed to create carrier audio files: {e}")
print(f"⚠️ Could not create WAV file: {e}")

abs_path = os.path.abspath(output_dir)
logger.info(f"Test files created in: {abs_path}")
Expand Down Expand Up @@ -571,7 +564,9 @@ def main() -> Optional[int]:

{C.BOLD}{C.BLUE}Analyze:{C.RESET}
{C.BOLD}{C.PINK}ghostbit audio{C.RESET} {C.GREEN}analyze{C.RESET} {C.GREEN}-i{C.RESET} {C.CYAN}audio.wav{C.RESET} {C.GREEN}-v{C.RESET}


{C.BOLD}{C.BLUE}Test Audio Creation:{C.RESET}
{C.BOLD}{C.PINK}ghostbit audio{C.RESET} {C.GREEN}test{C.RESET} {C.GREEN}-o{C.RESET} {C.CYAN}test_audio{C.RESET}
""",
)

Expand Down Expand Up @@ -769,7 +764,7 @@ def main() -> Optional[int]:
"test",
formatter_class=ColorHelpFormatter,
add_help=False,
help=f"{C.CYAN}Create test secret files{C.RESET}",
help=f"{C.CYAN}Create test audio files{C.RESET}",
)
test_parser.add_argument(
"-h", "--help", action="help", help=f"{C.CYAN}Show help message{C.RESET}"
Expand All @@ -778,14 +773,9 @@ def main() -> Optional[int]:
"-o",
"--output_dir",
required=False,
default="testcases",
default="test_audio",
help=f"{C.CYAN}Output folder for test files{C.RESET}",
)
test_parser.add_argument(
"--create-carrier",
action="store_true",
help=f"{C.CYAN}Create a test carrier audio files (AIFF|WAV|MP3|FLAC|M4A){C.RESET}",
)
test_parser.add_argument(
"-v",
"--verbose",
Expand Down Expand Up @@ -826,12 +816,13 @@ def main() -> Optional[int]:
)
elif args.subparser_command == "capacity":
return cli.capacity_command(args.input_file, args.quality)

elif args.subparser_command == "info":
return cli.info_command()

elif args.subparser_command == "test":
return cli.create_test_files_command(
args.output_dir, create_carrier=args.create_carrier
)
return cli.create_test_files_command(args.output_dir)

else:
parser.print_help()
return 1
Expand Down
72 changes: 60 additions & 12 deletions src/ghostbit/imagestego/cli/imagestego_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
Colors as C,
)
from ghostbit.imagestego.core.image_multiformat_coder import (
ImageGenerator,
ImageMultiFormatCoder,
ImageTestCreationException,
ImageMultiFormatCoderException,
)

Expand All @@ -32,7 +34,7 @@ def _print_header(self, title: str, emoji: Optional[str] = None) -> None:
print(f"\n{emoji} {C.BOLD}{C.BRIGHT_BLUE}{title}{C.RESET}")
else:
print(f"\n{C.BOLD}{C.BRIGHT_BLUE}{title}{C.RESET}")
print(f"{C.WHITE}{'─' * 70}{C.RESET}\n")
print(f"{C.BRIGHT_BLUE}{'─' * 70}{C.RESET}\n")

def encode_command(
self,
Expand Down Expand Up @@ -81,7 +83,7 @@ def encode_command(
password=password,
show_stats=show_stats,
)
print(f"{C.WHITE}{'─' * 70}{C.RESET}\n")
print(f"{C.BRIGHT_BLUE}{'─' * 70}{C.RESET}\n")
return 0

except ImageMultiFormatCoderException as e:
Expand Down Expand Up @@ -121,7 +123,7 @@ def decode_command(
self._print_header("Decoding Files", "🔓")
stego = ImageMultiFormatCoder()
stego.decode(input_file, output_filepath, password)
print(f"{C.WHITE}{'─' * 70}{C.RESET}\n")
print(f"{C.BRIGHT_BLUE}{'─' * 70}{C.RESET}\n")
return 0

except ImageMultiFormatCoderException as e:
Expand Down Expand Up @@ -151,7 +153,7 @@ def capacity_command(self, input_file: str) -> Optional[int]:
print(f" • {result['capacity_bytes']:,} bytes")
print(f" • {result['capacity_kb']:.2f} KB")
print(f" • {result['capacity_mb']:.2f} MB")
print(f"\n{C.WHITE}{'─' * 70}{C.RESET}\n")
print(f"\n{C.BRIGHT_BLUE}{'─' * 70}{C.RESET}\n")
return 0

except ImageMultiFormatCoderException as e:
Expand Down Expand Up @@ -183,26 +185,40 @@ def analyze_command(

print("\n🔍 Steganography Details:")
print(
f" • Hidden Data: {'✓ YES' if result['has_hidden_data'] else '✗ NO'}"
f" • Hidden Data: {'✓ YES' if result['has_hidden_data'] else '✗ NO'}"
)
if result["has_hidden_data"]:
print(
f" • Algorithm: {result['algorithm'].name if result['algorithm'] else 'Unknown'}"
f" • Algorithm: {result['algorithm'].name if result['algorithm'] else 'Unknown'}"
)
print(f" • Encrypted: {'Yes' if result['encrypted'] else 'No'}")
print(f" • Encrypted: {'Yes' if result['encrypted'] else 'No'}")
print("\n💡 Next Steps:")
if result["encrypted"]:
print(f" • ghostbit image decode -i {input_file} -p")
print(f" • ghostbit image decode -i {input_file} -p")
else:
print(f" • ghostbit image decode -i {input_file}")
print(f"\n{C.WHITE}{'─' * 70}{C.RESET}\n")
print(f" • ghostbit image decode -i {input_file}")
print(f"\n{C.BRIGHT_BLUE}{'─' * 70}{C.RESET}\n")
return 0

except ImageMultiFormatCoderException as e:
logger.error(
f"Image Analysis failed with ImageMultiFormatCoderException: {e}"
)
print(f"\n❌ Image Analysis failed: {e}")
print(f"\n Image Analysis failed: {e}")
return 1

def create_test_files_command(self, output_dir: str):
self._print_header("Creating Test Images", "📁")
try:
outputdir = os.path.join("output", output_dir)
logger.debug(f"Output directory: {outputdir}")
print(f"📁 Output Directory: '{outputdir}'\n")
image_gen = ImageGenerator(out_dir=outputdir)
image_gen.generate_all()
print(f"\n{C.BRIGHT_BLUE}{'─' * 70}{C.RESET}\n")
except ImageTestCreationException as e:
logger.error(f"Image Test File Creation failed: {e}")
print(f"\n Image Test File Creation failed: {e}")
return 1


Expand All @@ -229,7 +245,9 @@ def main():

{C.BOLD}{C.BLUE}Analyze:{C.RESET}
{C.BOLD}{C.PINK}ghostbit image{C.RESET} {C.GREEN}analyze{C.RESET} {C.GREEN}-i{C.RESET} {C.CYAN}suspicious.webp{C.RESET} {C.GREEN}-v{C.RESET}


{C.BOLD}{C.BLUE}Test Image Creation:{C.RESET}
{C.BOLD}{C.PINK}ghostbit image{C.RESET} {C.GREEN}test{C.RESET} {C.GREEN}-o{C.RESET} {C.CYAN}test_images{C.RESET}
""",
)

Expand Down Expand Up @@ -363,6 +381,29 @@ def main():
help=f"{C.CYAN}Enable verbose output{C.RESET}",
)

test_parser = subparsers.add_parser(
"test",
formatter_class=ColorHelpFormatter,
add_help=False,
help=f"{C.CYAN}Create test image files{C.RESET}",
)
test_parser.add_argument(
"-h", "--help", action="help", help=f"{C.CYAN}Show help message{C.RESET}"
)
test_parser.add_argument(
"-o",
"--output_dir",
required=False,
default="test_images",
help=f"{C.CYAN}(Optional) Output folder for test files{C.RESET}",
)
test_parser.add_argument(
"-v",
"--verbose",
action="store_true",
help=f"{C.CYAN}Enable verbose output{C.RESET}",
)

args = parser.parse_args()

if not getattr(args, "subparser_command", None):
Expand Down Expand Up @@ -391,6 +432,13 @@ def main():
elif args.subparser_command == "analyze":
return cli.analyze_command(args.input_file)

elif args.subparser_command == "test":
return cli.create_test_files_command(args.output_dir)

else:
parser.print_help()
return 1

except ImageMultiFormatCoderException as e:
print(f"❌ ImageMultiFormatCoder Error: {e}")
return 1
Expand Down
Loading