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/install.sh b/install.sh index a7086b6..ebe0f4b 100755 --- a/install.sh +++ b/install.sh @@ -1,2 +1,74 @@ #!/bin/bash -echo alias tubestore=\"cd $PWD \; python3 main.py\" >> ~/.bashrc \ No newline at end of file +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 + +#!/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 + +# Install dependencies +pip install --upgrade pip +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\"" +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! Launching TubeStore...${RESET}" + +# Directly run the same command as the alias (expanded properly) +cd "$PWD" +source venv/bin/activate +python3 main.py 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() 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 diff --git a/videoConversion.py b/videoConversion.py index eb1a70f..05aeec9 100644 --- a/videoConversion.py +++ b/videoConversion.py @@ -1,55 +1,119 @@ 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.array(list(binary_data)).astype(np.uint8) - 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) + + # Ensure output folders exist + os.makedirs("output/videos", exist_ok=True) + + # Define output path + if USE_MJPG: + fourcc = cv2.VideoWriter_fourcc(*'MJPG') + out_name = os.path.join("output/videos", f"{filename_wo_ext}-Video-{difference}-{ext}.avi") + else: + fourcc = cv2.VideoWriter_fourcc(*'mp4v') + 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) 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)) - binary_data = binary_data[:0-int(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") + # 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]