diff --git a/.github/workflows/build-binaries.yml b/.github/workflows/build-binaries.yml new file mode 100644 index 0000000..9e9a2e7 --- /dev/null +++ b/.github/workflows/build-binaries.yml @@ -0,0 +1,95 @@ +name: Build Native Binaries + +on: + push: + tags: + - "v*" + workflow_dispatch: + +jobs: + build: + name: Build (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + artifact_name: passifypdf-linux-x86_64 + binary_path: dist/passifypdf + - os: macos-latest + artifact_name: passifypdf-macos-arm64 + binary_path: dist/passifypdf + - os: windows-latest + artifact_name: passifypdf-windows-x86_64 + binary_path: dist/passifypdf.exe + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install uv + uses: astral-sh/setup-uv@v4 + + - name: Install dependencies + run: uv sync + + - name: Install PyInstaller + run: uv pip install pyinstaller + + - name: Build binary + run: | + uv run pyinstaller \ + --onefile \ + --name passifypdf \ + --console \ + passifypdf/encryptpdf.py + + - name: Smoke test (Unix) + if: runner.os != 'Windows' + run: | + ./dist/passifypdf --help + + - name: Smoke test (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + .\dist\passifypdf.exe --help + + - name: Upload binary artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.artifact_name }} + path: ${{ matrix.binary_path }} + if-no-files-found: error + + release: + name: Attach Binaries to Release + needs: build + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/v') + permissions: + contents: write + + steps: + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: dist/ + + - name: List downloaded artifacts + run: ls -R dist/ + + - name: Create release and upload assets + uses: softprops/action-gh-release@v2 + with: + files: | + dist/passifypdf-linux-x86_64/passifypdf + dist/passifypdf-macos-arm64/passifypdf + dist/passifypdf-windows-x86_64/passifypdf.exe + generate_release_notes: true diff --git a/README.md b/README.md index 1c96951..8f108a7 100644 --- a/README.md +++ b/README.md @@ -45,9 +45,27 @@ passifypdf -i input.pdf -o protected.pdf -p mySecretPassword ``` ## Known Issues -If you have nay special chars(example: an emoji like Star 🌟) in the PDF file, it gives a minor complain during execution. -But it still does the job, so you can ingore that "char or object error" which you see in the output. +If you have any special chars (example: an emoji like Star 🌟) in the PDF file, it gives a minor complaint during execution. +But it still does the job, so you can ignore that "char or object error" which you see in the output. + +## Web UI (Streamlit) + +A local web interface is available for users who prefer a graphical workflow: + +```bash +# Install web UI dependencies +pip install -r requirements-webui.txt + +# Launch the app +streamlit run app/streamlit_app.py +``` + +Open the URL shown in your terminal (usually `http://localhost:8501`), upload a PDF, enter a password and download the protected file — no command line needed. + +## Download Pre-built Binaries + +Standalone executables for Linux, macOS, and Windows are built automatically on every tagged release via GitHub Actions. Visit the [Releases page](https://github.com/SUPAIDEAS/passifypdf/releases) to download the binary for your platform — no Python installation required. ## Note: -In general you can use passifypdf to protect your PDF files against chance attackers. -But you should not rely on this for mission-critical data or situation. +In general you can use passifypdf to protect your PDF files against chance attackers. +But you should not rely on this for mission-critical data or situations. diff --git a/UI-README.md b/UI-README.md new file mode 100644 index 0000000..05c5e63 --- /dev/null +++ b/UI-README.md @@ -0,0 +1,16 @@ +Use the following CLI commands to launch the UI. +- UI allows you to drag-drop PDF file, encrypt it(password protect it) and then, download it. + +Commands to run: +```shell +uv sync --all-groups +uv pip install -r requirements-webui.txt +uv pip install -e . <---------- Important, otherwise it gives import error. +uv run streamlit run app/streamlit_app.py +``` +Then, +Open: +http://localhost:8501/ + +Screenshot: +image diff --git a/app/streamlit_app.py b/app/streamlit_app.py new file mode 100644 index 0000000..2a83817 --- /dev/null +++ b/app/streamlit_app.py @@ -0,0 +1,118 @@ +"""Streamlit web UI wrapper for passifypdf. + +Run locally with: + streamlit run app/streamlit_app.py +""" + +import io +import tempfile +from pathlib import Path + +import streamlit as st + +from passifypdf.encryptpdf import encrypt_pdf + + +# ── Page config ──────────────────────────────────────────────────────────── + +st.set_page_config( + page_title="passifypdf — PDF Password Protector", + page_icon="🔒", + layout="centered", + initial_sidebar_state="collapsed", +) + +# ── Title ────────────────────────────────────────────────────────────────── + +st.title("🔒 passifypdf") +st.subheader("Protect your PDF files with a password — right in the browser.") +st.markdown("---") + +# ── File upload ──────────────────────────────────────────────────────────── + +uploaded_file = st.file_uploader( + "Upload a PDF file", + type=["pdf"], + help="Select the PDF you want to encrypt.", +) + +# ── Password ─────────────────────────────────────────────────────────────── + +col1, col2 = st.columns(2) +with col1: + password = st.text_input( + "Password", + type="password", + placeholder="Enter a strong password", + help="This password will be required to open the encrypted PDF.", + ) +with col2: + confirm_password = st.text_input( + "Confirm password", + type="password", + placeholder="Re-enter the password", + ) + +# ── Output filename ──────────────────────────────────────────────────────── + +output_name = st.text_input( + "Output filename", + value="protected.pdf", + help="Name of the encrypted file you will download.", +) +if not output_name.lower().endswith(".pdf"): + output_name += ".pdf" + +# ── Encrypt button ───────────────────────────────────────────────────────── + +if st.button("🔐 Encrypt PDF", type="primary", use_container_width=True): + if not uploaded_file: + st.error("Please upload a PDF file first.") + elif not password: + st.error("Please enter a password.") + elif password != confirm_password: + st.error("Passwords do not match. Please try again.") + else: + with st.spinner("Encrypting your PDF…"): + try: + # Write uploaded bytes to a temp input file + with tempfile.NamedTemporaryFile( + suffix=".pdf", delete=False + ) as tmp_in: + tmp_in.write(uploaded_file.read()) + tmp_in_path = Path(tmp_in.name) + + # Encrypt to a temp output file + with tempfile.NamedTemporaryFile( + suffix=".pdf", delete=False + ) as tmp_out: + tmp_out_path = Path(tmp_out.name) + + encrypt_pdf(tmp_in_path, tmp_out_path, password) + + # Read the encrypted bytes and offer download + encrypted_bytes = tmp_out_path.read_bytes() + + st.success("✅ PDF encrypted successfully!") + st.download_button( + label="⬇️ Download encrypted PDF", + data=encrypted_bytes, + file_name=output_name, + mime="application/pdf", + use_container_width=True, + ) + + except Exception as exc: + st.error(f"❌ Encryption failed: {exc}") + finally: + # Clean up temp files + tmp_in_path.unlink(missing_ok=True) + tmp_out_path.unlink(missing_ok=True) + +# ── Footer ───────────────────────────────────────────────────────────────── + +st.markdown("---") +st.caption( + "passifypdf — open source PDF encryption tool. " + "[View on GitHub](https://github.com/SUPAIDEAS/passifypdf)" +) diff --git a/requirements-webui.txt b/requirements-webui.txt new file mode 100644 index 0000000..bc9b4e6 --- /dev/null +++ b/requirements-webui.txt @@ -0,0 +1,4 @@ +# Requirements for the optional Streamlit web UI (app/streamlit_app.py) +# Install with: pip install -r requirements-webui.txt +streamlit>=1.35.0 +passifypdf diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..bda0207 --- /dev/null +++ b/uv.lock @@ -0,0 +1,3 @@ +version = 1 +revision = 3 +requires-python = ">=3.13"