From 2bd2c6264df78ed91019a0a3a727d88e12bd12fa Mon Sep 17 00:00:00 2001 From: sssamuelll Date: Sun, 5 Oct 2025 00:45:11 +0200 Subject: [PATCH 01/10] fix: handle zero-difference case in videoToBinary slicing --- videoConversion.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/videoConversion.py b/videoConversion.py index eb1a70f..840bcb8 100644 --- a/videoConversion.py +++ b/videoConversion.py @@ -48,7 +48,9 @@ def videoToBinary(video_path): for i in data: binary_data.extend(i) print("Length of binary data recovered: ",len(binary_data)) - binary_data = binary_data[:0-int(difference)] + difference = int(difference) + if difference > 0: + binary_data = binary_data[:-difference] #print(array[:10],binary_data[:10]) res = ''.join(str(i) for i in binary_data) cap.release() From 10d50bfa3595d2aca078fef9dbedf9086b2292d0 Mon Sep 17 00:00:00 2001 From: sssamuelll Date: Sun, 5 Oct 2025 00:54:49 +0200 Subject: [PATCH 02/10] chore: add requirements.txt and improve install.sh to set up venv automatically --- install.sh | 5 ++++- requirements.txt | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 requirements.txt diff --git a/install.sh b/install.sh index a7086b6..287bb45 100755 --- a/install.sh +++ b/install.sh @@ -1,2 +1,5 @@ #!/bin/bash -echo alias tubestore=\"cd $PWD \; python3 main.py\" >> ~/.bashrc \ No newline at end of file +python3 -m venv venv +source venv/bin/activate +pip install -r requirements.txt +echo alias tubestore=\"cd $PWD \; source venv/bin/activate \; python3 main.py\" >> ~/.bashrc diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..f5fdbf0 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +opencv-python>=4.8.0 +numpy>=1.25.0 From ab8d109b6b58cf714de71e00c6ad520c9432bb69 Mon Sep 17 00:00:00 2001 From: sssamuelll Date: Sun, 5 Oct 2025 01:00:23 +0200 Subject: [PATCH 03/10] chore: finalize production-ready install.sh with error handling and zsh support --- install.sh | 46 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/install.sh b/install.sh index 287bb45..051666b 100755 --- a/install.sh +++ b/install.sh @@ -1,5 +1,47 @@ #!/bin/bash -python3 -m venv venv +set -e # stop on first error + +# Colors for better UX +GREEN="\033[0;32m" +YELLOW="\033[1;33m" +RESET="\033[0m" + +echo -e "${GREEN}→ Setting up TubeStore environment...${RESET}" + +# Check for python3 +if ! command -v python3 &>/dev/null; then + echo -e "${YELLOW}⚠️ Python3 not found. Please install Python 3.8+ first.${RESET}" + exit 1 +fi + +# Create venv if not exists +if [ ! -d "venv" ]; then + python3 -m venv venv + echo -e "${GREEN}✓ Virtual environment created.${RESET}" +fi + +# Activate venv source venv/bin/activate + +# Install dependencies +pip install --upgrade pip pip install -r requirements.txt -echo alias tubestore=\"cd $PWD \; source venv/bin/activate \; python3 main.py\" >> ~/.bashrc +echo -e "${GREEN}✓ Dependencies installed.${RESET}" + +# Add alias to shell profile +ALIAS_CMD="alias tubestore='cd $PWD && source venv/bin/activate && python3 main.py'" +PROFILE="$HOME/.bashrc" + +# Detect zsh users +if [[ "$SHELL" == *"zsh"* ]]; then + PROFILE="$HOME/.zshrc" +fi + +if ! grep -Fxq "$ALIAS_CMD" "$PROFILE"; then + echo "$ALIAS_CMD" >> "$PROFILE" + echo -e "${GREEN}✓ Alias 'tubestore' added to $PROFILE.${RESET}" +else + echo -e "${YELLOW}↺ Alias 'tubestore' already exists in $PROFILE.${RESET}" +fi + +echo -e "${GREEN}🎉 Installation complete! Run 'tubestore' to start.${RESET}" From ad1bfb4ab4e3bc74a8de3d5d518f32444a20163b Mon Sep 17 00:00:00 2001 From: sssamuelll Date: Sun, 5 Oct 2025 01:03:16 +0200 Subject: [PATCH 04/10] chore: auto-reload shell profile in install.sh for immediate alias activation --- install.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/install.sh b/install.sh index 051666b..0c2bc6c 100755 --- a/install.sh +++ b/install.sh @@ -45,3 +45,10 @@ else fi echo -e "${GREEN}🎉 Installation complete! Run 'tubestore' to start.${RESET}" + +# Auto-reload the shell profile so alias is available immediately +if [[ "$SHELL" == *"zsh"* ]]; then + source ~/.zshrc +elif [[ "$SHELL" == *"bash"* ]]; then + source ~/.bashrc +fi From c55f9df8f25a85186c99cd2d2d8e47114498f855 Mon Sep 17 00:00:00 2001 From: sssamuelll Date: Sun, 5 Oct 2025 01:09:40 +0200 Subject: [PATCH 05/10] chore: finalize install.sh with auto-launch after setup --- install.sh | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/install.sh b/install.sh index 0c2bc6c..dc5bcff 100755 --- a/install.sh +++ b/install.sh @@ -44,11 +44,7 @@ else echo -e "${YELLOW}↺ Alias 'tubestore' already exists in $PROFILE.${RESET}" fi -echo -e "${GREEN}🎉 Installation complete! Run 'tubestore' to start.${RESET}" +echo -e "${GREEN}🎉 Installation complete! Launching TubeStore...${RESET}" -# Auto-reload the shell profile so alias is available immediately -if [[ "$SHELL" == *"zsh"* ]]; then - source ~/.zshrc -elif [[ "$SHELL" == *"bash"* ]]; then - source ~/.bashrc -fi +# Launch TubeStore immediately (no need to run 'source' manually) +eval "$ALIAS_CMD" From 4d519509145ceffeaa75da68aad224c2558ee8fc Mon Sep 17 00:00:00 2001 From: sssamuelll Date: Sun, 5 Oct 2025 01:34:55 +0200 Subject: [PATCH 06/10] fix: correct binary data conversion to ensure 0/1 values in video frames --- videoConversion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/videoConversion.py b/videoConversion.py index 840bcb8..8bd8c2f 100644 --- a/videoConversion.py +++ b/videoConversion.py @@ -4,7 +4,7 @@ def binaryToVideo(cache,path): binary_data = cache[0] ext = cache[1] filename = path.split("/")[-1].split(".")[-2] - array = np.array(list(binary_data)).astype(np.uint8) + array = np.frombuffer(binary_data.encode("ascii"), dtype=np.uint8) - ord("0") res_array = array size = len(binary_data) print("Initial size :",size) From ad3e843704f069abc3365b23e5efb81bce4fbdf7 Mon Sep 17 00:00:00 2001 From: sssamuelll Date: Sun, 5 Oct 2025 01:35:09 +0200 Subject: [PATCH 07/10] chore: finalize install.sh to auto-launch TubeStore after setup --- install.sh | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/install.sh b/install.sh index dc5bcff..ebe0f4b 100755 --- a/install.sh +++ b/install.sh @@ -20,6 +20,28 @@ if [ ! -d "venv" ]; then echo -e "${GREEN}✓ Virtual environment created.${RESET}" fi +#!/bin/bash +set -e # stop on first error + +# Colors for better UX +GREEN="\033[0;32m" +YELLOW="\033[1;33m" +RESET="\033[0m" + +echo -e "${GREEN}→ Setting up TubeStore environment...${RESET}" + +# Check for python3 +if ! command -v python3 &>/dev/null; then + echo -e "${YELLOW}⚠️ Python3 not found. Please install Python 3.8+ first.${RESET}" + exit 1 +fi + +# Create venv if not exists +if [ ! -d "venv" ]; then + python3 -m venv venv + echo -e "${GREEN}✓ Virtual environment created.${RESET}" +fi + # Activate venv source venv/bin/activate @@ -29,7 +51,7 @@ pip install -r requirements.txt echo -e "${GREEN}✓ Dependencies installed.${RESET}" # Add alias to shell profile -ALIAS_CMD="alias tubestore='cd $PWD && source venv/bin/activate && python3 main.py'" +ALIAS_CMD="alias tubestore=\"cd $PWD && source venv/bin/activate && python3 main.py\"" PROFILE="$HOME/.bashrc" # Detect zsh users @@ -46,5 +68,7 @@ fi echo -e "${GREEN}🎉 Installation complete! Launching TubeStore...${RESET}" -# Launch TubeStore immediately (no need to run 'source' manually) -eval "$ALIAS_CMD" +# Directly run the same command as the alias (expanded properly) +cd "$PWD" +source venv/bin/activate +python3 main.py From e2f77e791f3b7280c79d567bb22b98cc43556429 Mon Sep 17 00:00:00 2001 From: sssamuelll Date: Sun, 5 Oct 2025 01:42:55 +0200 Subject: [PATCH 08/10] fix: ensure bit-accurate conversions and switch to MJPG (.avi) for lossless tests --- videoConversion.py | 140 +++++++++++++++++++++++++++++++-------------- 1 file changed, 97 insertions(+), 43 deletions(-) diff --git a/videoConversion.py b/videoConversion.py index 8bd8c2f..7f3868e 100644 --- a/videoConversion.py +++ b/videoConversion.py @@ -1,57 +1,111 @@ import cv2 import numpy as np -def binaryToVideo(cache,path): - binary_data = cache[0] - ext = cache[1] - filename = path.split("/")[-1].split(".")[-2] - array = np.frombuffer(binary_data.encode("ascii"), dtype=np.uint8) - ord("0") - res_array = array - size = len(binary_data) - print("Initial size :",size) - frame_size = (128,128) - frames = int(np.ceil(size/(frame_size[0]*frame_size[1]))) - difference = int(frames*128*128 - size) - print("Difference: ",difference) - additional = np.zeros(difference,) - array = np.append(array,additional) - array = array.reshape(frames,frame_size[0],frame_size[1]) - print("Array shape: ",array.shape) - array = array.astype(np.uint8) - fourcc = cv2.VideoWriter_fourcc(*'mp4v') - fps = 30 - width = frame_size[0] - height = frame_size[1] - print(str(filename)+"-Video-"+str(difference)+"-"+str(ext)+".mp4") - out = cv2.VideoWriter(str(filename)+"-Video-"+str(difference)+"-"+str(ext)+".mp4",fourcc,fps,(width,height)) +import os + +# ========================== +# CONFIG +# ========================== +USE_MJPG = True # Use MJPG (visually lossless) for local tests. Set to False to use mp4v. +FRAME_SIZE = (128, 128) +FPS = 30 + +# ========================== +# HELPERS +# ========================== +def _bits_str_to_array(bits: str) -> np.ndarray: + """ + Convert a binary string ('0101...') into a NumPy array of 0/1 (uint8). + Avoids converting ASCII characters ('0'->48, '1'->49). + """ + return np.frombuffer(bits.encode("ascii"), dtype=np.uint8) - ord("0") + + +def _parse_meta_from_name(video_path: str): + """ + Extract metadata (difference and extension) from the video filename. + Example: name-Video-1234-pdf.mp4 -> (name, pdf, 1234) + """ + base = os.path.splitext(os.path.basename(video_path))[0] # strip .mp4/.avi + parts = base.split("-") + # Expected format: [..., "Video", difference, ext] + if len(parts) < 4 or parts[-3] != "Video": + raise ValueError(f"Filename does not contain expected metadata: {video_path}") + difference = int(parts[-2]) + ext = parts[-1] + filename_wo_ext = "-".join(parts[:-3]) # supports names with hyphens + return filename_wo_ext, ext, difference + +# ========================== +# MAIN FUNCTIONS +# ========================== +def binaryToVideo(cache, path): + """ + Convert a binary string into a video where each bit is represented by one pixel. + cache: [binary_string, file_extension] + path: original file path + """ + binary_data, ext = cache + filename_wo_ext = os.path.splitext(os.path.basename(path))[0] + + # Convert bits to a 0/1 array + arr = _bits_str_to_array(binary_data) + size = len(arr) + + width, height = FRAME_SIZE + bits_per_frame = width * height + frames = int(np.ceil(size / bits_per_frame)) + difference = frames * bits_per_frame - size + + if difference > 0: + arr = np.pad(arr, (0, difference), mode="constant", constant_values=0) + + arr = arr.reshape(frames, height, width).astype(np.uint8) + + # Configure codec and output file + if USE_MJPG: + fourcc = cv2.VideoWriter_fourcc(*'MJPG') + out_name = f"{filename_wo_ext}-Video-{difference}-{ext}.avi" + else: + fourcc = cv2.VideoWriter_fourcc(*'mp4v') + out_name = f"{filename_wo_ext}-Video-{difference}-{ext}.mp4" + + out = cv2.VideoWriter(out_name, fourcc, FPS, (width, height)) + white = np.array([255, 255, 255], dtype=np.uint8) for i in range(frames): - img = np.zeros((frame_size[0], frame_size[1], 3), dtype=np.uint8) - img[array[i] == 1] = [255, 255, 255] + img = np.zeros((height, width, 3), dtype=np.uint8) + mask = arr[i] == 1 + img[mask] = white out.write(img) + out.release() - return [difference,ext] + print(f"[✅] Video generated: {out_name}") + return [difference, ext] def videoToBinary(video_path): - [ext,difference] = [video_path.split(".")[0].split("-")[-1],video_path.split(".")[0].split("-")[-2]] - binary_data = [] - filename = video_path.split(".")[0] + """ + Convert a binary video back into a binary string. + Returns [filename.ext, bits] + """ + fname, ext, difference = _parse_meta_from_name(video_path) cap = cv2.VideoCapture(video_path) + bits = [] + while True: - ret,frame = cap.read() - #print(ret,frame) + ret, frame = cap.read() if not ret: break - frame = cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY) - ret,frame = cv2.threshold(frame, 127, 1, cv2.THRESH_BINARY) - data = frame.astype(np.uint8).tolist() - for i in data: - binary_data.extend(i) - print("Length of binary data recovered: ",len(binary_data)) - difference = int(difference) - if difference > 0: - binary_data = binary_data[:-difference] - #print(array[:10],binary_data[:10]) - res = ''.join(str(i) for i in binary_data) + gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) + _, bw = cv2.threshold(gray, 127, 1, cv2.THRESH_BINARY) + bits.extend(bw.ravel().tolist()) + cap.release() - return [filename+"."+ext,res] \ No newline at end of file + print(f"[ℹ️] Frames read: {len(bits) // (FRAME_SIZE[0] * FRAME_SIZE[1])}") + + if difference > 0: + bits = bits[:-difference] + + bitstr = ''.join('1' if b else '0' for b in bits) + print(f"[✅] Binary data recovered. Length: {len(bitstr)} bits") + return [f"{fname}.{ext}", bitstr] From e0253582b6710ffc682c7d243d27fe4ec122d184 Mon Sep 17 00:00:00 2001 From: sssamuelll Date: Sun, 5 Oct 2025 01:48:12 +0200 Subject: [PATCH 09/10] chore: organize generated files under /output and add clean .gitignore --- .gitignore | 19 +++++++++++++++++++ videoConversion.py | 16 ++++++++++++---- 2 files changed, 31 insertions(+), 4 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..087d4db --- /dev/null +++ b/.gitignore @@ -0,0 +1,19 @@ +# Python +__pycache__/ +*.py[cod] +*.pyo +*.pyd +*.so +*.egg-info/ +*.egg +venv/ +.env +.DS_Store + +# IDEs +.vscode/ +.idea/ +*.swp + +# Output folders +output/ diff --git a/videoConversion.py b/videoConversion.py index 7f3868e..05aeec9 100644 --- a/videoConversion.py +++ b/videoConversion.py @@ -61,13 +61,16 @@ def binaryToVideo(cache, path): arr = arr.reshape(frames, height, width).astype(np.uint8) - # Configure codec and output file + # Ensure output folders exist + os.makedirs("output/videos", exist_ok=True) + + # Define output path if USE_MJPG: fourcc = cv2.VideoWriter_fourcc(*'MJPG') - out_name = f"{filename_wo_ext}-Video-{difference}-{ext}.avi" + out_name = os.path.join("output/videos", f"{filename_wo_ext}-Video-{difference}-{ext}.avi") else: fourcc = cv2.VideoWriter_fourcc(*'mp4v') - out_name = f"{filename_wo_ext}-Video-{difference}-{ext}.mp4" + out_name = os.path.join("output/videos", f"{filename_wo_ext}-Video-{difference}-{ext}.mp4") out = cv2.VideoWriter(out_name, fourcc, FPS, (width, height)) white = np.array([255, 255, 255], dtype=np.uint8) @@ -108,4 +111,9 @@ def videoToBinary(video_path): bitstr = ''.join('1' if b else '0' for b in bits) print(f"[✅] Binary data recovered. Length: {len(bitstr)} bits") - return [f"{fname}.{ext}", bitstr] + # Ensure output folder for recovered files exists + os.makedirs("output/recovered", exist_ok=True) + + # Recovered file path (will be written later by binaryToFile) + recovered_path = os.path.join("output/recovered", f"{fname}.{ext}") + return [recovered_path, bitstr] From 0b3066c9200240f686f681bf1c2c6d179066def9 Mon Sep 17 00:00:00 2001 From: sssamuelll Date: Sun, 5 Oct 2025 01:57:08 +0200 Subject: [PATCH 10/10] feat: add cross-platform native file dialog using tkinter for file selection --- main.py | 125 ++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 103 insertions(+), 22 deletions(-) diff --git a/main.py b/main.py index 08d2823..c169030 100644 --- a/main.py +++ b/main.py @@ -1,32 +1,113 @@ +import os import videoConversion as vc import fileConversion as fc -import os +from tkinter import Tk, filedialog + +# ========================== +# HELPERS +# ========================== + +def select_file(title="Select a file", filetypes=(("All files", "*.*"),)): + """ + Open a native file dialog (cross-platform: Windows, macOS, Linux). + Returns the selected path or None if canceled. + """ + root = Tk() + root.withdraw() # hide the main Tkinter window + root.update() + filename = filedialog.askopenfilename(title=title, filetypes=filetypes) + root.destroy() + return filename if filename else None + + +def clear(): + """Clear console screen in a cross-platform way.""" + os.system("cls" if os.name == "nt" else "clear") + +# ========================== +# MAIN LOOP +# ========================== if __name__ == "__main__": - os.system("clear") + clear() while True: - print("TubeStore - Convert your data to youtube uploadable video format\n\n") - print("1. Create video from data\n") - print("2. Retrieve data from video\n") + print("TubeStore - Convert your data to youtube-uploadable video format\n") + print("1. Create video from data") + print("2. Retrieve data from video") print("3. Exit Program\n") - option = int(input()) - os.system("clear") + + try: + option = int(input("Select an option: ").strip()) + except ValueError: + clear() + continue + + clear() + + # ------------------ OPTION 1 ------------------ if option == 1: - path = input("\nEnter file path:") - print("\nProcessing . .") - [difference,ext] = vc.binaryToVideo(fc.fileToBinary(path),path) - os.system("clear") - print("Data to Video conversion successfull!\n\n") - elif option==2: - path = input("\nEnter video path:") - print("\nProcessing . .") + print("\n👉 Drag & drop your file here or press [Enter] to browse:") + path = input("> ").strip() + + # If user just pressed Enter, open file dialog + if not path: + path = select_file("Select a file to convert") + + if not path: + clear() + print("[ℹ️] No file selected. Returning to menu.\n") + continue + + path = path.strip('"').strip("'") + + if not os.path.isfile(path): + print(f"[❌] File not found: {path}") + input("\nPress Enter to continue...") + clear() + continue + + print("\nProcessing...") + [difference, ext] = vc.binaryToVideo(fc.fileToBinary(path), path) + clear() + print(f"[✅] Data-to-Video conversion successful!") + print(f"→ File saved under ./output/videos/\n") + input("Press Enter to return to menu...") + clear() + + # ------------------ OPTION 2 ------------------ + elif option == 2: + print("\n👉 Drag & drop your video here or press [Enter] to browse:") + path = input("> ").strip() + + if not path: + path = select_file("Select a video to decode", (("Video files", "*.avi *.mp4"), ("All files", "*.*"))) + + if not path: + clear() + print("[ℹ️] No video selected. Returning to menu.\n") + continue + + path = path.strip('"').strip("'") + + if not os.path.isfile(path): + print(f"[❌] Video not found: {path}") + input("\nPress Enter to continue...") + clear() + continue + + print("\nProcessing...") fc.binaryToFile(vc.videoToBinary(path)) - os.system("clear") - print("Video to Data conversion successfull!\n\n") - elif option==3: - os.system("clear") - exit() + clear() + print(f"[✅] Video-to-Data conversion successful!") + print(f"→ File saved under ./output/recovered/\n") + input("Press Enter to return to menu...") + clear() + + # ------------------ EXIT ------------------ + elif option == 3: + clear() + print("Bye 👋") + break else: - pass - \ No newline at end of file + clear()