Skip to content
Open
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*.egg-info
__pycache__
1 change: 0 additions & 1 deletion MANIFEST.in

This file was deleted.

14 changes: 8 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,24 @@ Xfer is a utility to allow out-of-band sending of arbitrary data, using animated
## Installation

1. Clone the repository
2. Run `pip install -r requirements.txt` (may be `pip3`, in place of `pip`, depending on your OS).
2. Install with `pip install .`
3. Run the CLI with `xfer --help`

## Workflow

The workflow looks something like this:

1. Alice runs Xfer on their local machine, as such: `cat 'This is a message to send' | python3 xfer.py write --outfile send.gif`
2. Alice opens `send.gif` and records the output on a mobile device.
2. Alice opens `send.gif` and records the output on a mobile device.
3. Alice send a message, via a third party service, such as Signal, to Bob.
4. Bob runs `python3 xfer.py read` on his laptop.
4. Bob runs `xfer read` on his laptop.
5. Bob plays the recording of the animated gif, and captures the video on their laptop's webcam.
6. Once Xfer has captured all the individual frames, it will output the original message on Bob's screen.

## How it works

1. `xfer write` breaks the source message down into chunks. By default these are 256 byte chunks. This is not supported as a flag, because the relationship between chunk size and QR code dimensions are not clear. The values can be seen at the top of the `xfer` script, but should not be changed unless you know what you are doing!
2. Each chunk is then encoded to a static QR code, with it's frame number for reassembly.
2. Each chunk is then encoded to a static QR code, with it's frame number for reassembly.
3. A keyframe is added to the start of the sequence, to instruct the receiving application on the number of frames it should expect.
4. The collection of static images is then compiled into an animated GIF.

Expand All @@ -31,7 +33,7 @@ The workflow looks something like this:
## Limitations

- Xfer was not written to be secure. It can be used as part of a secure workflow, if PGP keys are shared beforehand and the data transmitted is encrypted.
- Xfer does not compress data; repeated data will be encoded as is.
- Xfer does not compress data; repeated data will be encoded as is.

## Future work

Expand Down
133 changes: 133 additions & 0 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

130 changes: 130 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
{
description = "xfer - Utility to allow out-of-band sending of arbitrary data";

inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-25.11";
flake-utils.url = "github:numtide/flake-utils";

pyproject-nix = {
url = "github:pyproject-nix/pyproject.nix";
inputs.nixpkgs.follows = "nixpkgs";
};

uv2nix = {
url = "github:pyproject-nix/uv2nix";
inputs.pyproject-nix.follows = "pyproject-nix";
inputs.nixpkgs.follows = "nixpkgs";
};

pyproject-build-systems = {
url = "github:pyproject-nix/build-system-pkgs";
inputs.pyproject-nix.follows = "pyproject-nix";
inputs.uv2nix.follows = "uv2nix";
inputs.nixpkgs.follows = "nixpkgs";
};
};

outputs =
{
nixpkgs,
flake-utils,
pyproject-nix,
uv2nix,
pyproject-build-systems,
...
}:
let
workspace = uv2nix.lib.workspace.loadWorkspace { workspaceRoot = ./.; };

projectOverlay = workspace.mkPyprojectOverlay {
sourcePreference = "wheel";
};

editableOverlay = workspace.mkEditablePyprojectOverlay {
root = "$REPO_ROOT";
};
in
flake-utils.lib.eachDefaultSystem (
system:
let
pkgs = import nixpkgs { inherit system; };
python = pkgs.python313;
runtimeLibs = with pkgs; [
glib
libGL
stdenv.cc.cc.lib
xorg.libxcb
zbar
zlib

# Required for the uv package opencv wheel
fontconfig
dejavu_fonts
];

pythonSet =
(pkgs.callPackage pyproject-nix.build.packages {
inherit python;
}).overrideScope
(
pkgs.lib.composeManyExtensions [
pyproject-build-systems.overlays.wheel
projectOverlay
]
);

editablePythonSet = pythonSet.overrideScope editableOverlay;

appVenv = pythonSet.mkVirtualEnv "xfer-env" workspace.deps.default;
devVenv = editablePythonSet.mkVirtualEnv "xfer-dev-env" workspace.deps.all;
appWrapped = pkgs.symlinkJoin {
name = "xfer";
paths = [ appVenv ];
nativeBuildInputs = [ pkgs.makeWrapper ];
postBuild = ''
wrapProgram $out/bin/xfer \
--prefix LD_LIBRARY_PATH : ${pkgs.lib.makeLibraryPath runtimeLibs}
'';
};
in
{
packages.default = appWrapped;
apps.default = flake-utils.lib.mkApp {
drv = appWrapped;
exePath = "/bin/xfer";
};

devShells.default = pkgs.mkShell {
packages = [
devVenv
pkgs.uv
]
++ runtimeLibs;

UV_NO_SYNC = "1";
UV_PYTHON = editablePythonSet.python.interpreter;
UV_PYTHON_DOWNLOADS = "never";
LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath runtimeLibs;
NIX_LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath runtimeLibs;
NIX_LD = pkgs.lib.fileContents "${pkgs.stdenv.cc}/nix-support/dynamic-linker";
QT_QPA_FONTDIR = "${pkgs.dejavu_fonts}/share/fonts/truetype";
XFER_QT_FONTDIR = "${pkgs.dejavu_fonts}/share/fonts/truetype";
QT_QPA_PLATFORM = "xcb";
FONTCONFIG_FILE = "${pkgs.fontconfig.out}/etc/fonts/fonts.conf";
FONTCONFIG_PATH = "${pkgs.fontconfig.out}/etc/fonts";

shellHook = ''
unset PYTHONPATH
export REPO_ROOT=$(git rev-parse --show-toplevel)
export LD_LIBRARY_PATH="${pkgs.lib.makeLibraryPath runtimeLibs}:$LD_LIBRARY_PATH"
export QT_QPA_FONTDIR="${pkgs.dejavu_fonts}/share/fonts/truetype"
export XFER_QT_FONTDIR="${pkgs.dejavu_fonts}/share/fonts/truetype"
export QT_QPA_PLATFORM="xcb"
export FONTCONFIG_FILE="${pkgs.fontconfig.out}/etc/fonts/fonts.conf"
export FONTCONFIG_PATH="${pkgs.fontconfig.out}/etc/fonts"
unset QT_STYLE_OVERRIDE
'';
};
}
);
}
41 changes: 41 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
[build-system]
requires = ["setuptools>=69", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "xfer"
version = "0.0.5"
description = "Utility to allow out-of-band sending of arbitrary data"
readme = "README.md"
requires-python = ">=3.10"
license = { file = "LICENSE.md" }
authors = [{ name = "Chorus One", email = "tech@chorus.one" }]
classifiers = [
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Natural Language :: English",
"Intended Audience :: Developers",
"Intended Audience :: Financial and Insurance Industry",
]
dependencies = [
"click>=8.0",
"imageio>=2.9",
"numpy>=1.26",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly also <2.0 unless you have fixed that problem.

Copy link
Author

@patrickjeremic patrickjeremic Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In uv.lock you can see numpy being at 2.2.6 and i confirmed both flows (read and write) with it. Not sure what was causing the issue outside of nix before

You're correct, running uv sync + uv run python xfer.py read in the nix shell doesn't seem to work. Just running nix run .#default seems to run successfully. Seems to be something off with the python deps still.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I converted the project to uv2nix (https://pyproject-nix.github.io/uv2nix/templates.html) and it now covers all potential paths (Running python xfer.py, uv run python xfer.py and xfer in the dev shell, where xfer is just a wrapper to the xfer.py file). I also confirmed the stand alone binary to work. I didn't see any issues with the new numpy version interestingly.

"opencv-python>=4.10",
"pillow>=10.0",
"pyzbar>=0.1.9",
"qrcode>=7.4",
]

[project.urls]
Homepage = "https://chorus.one"

[project.scripts]
xfer = "xfer:main"

[dependency-groups]
dev = []

[tool.setuptools]
py-modules = ["xfer"]
10 changes: 0 additions & 10 deletions requirements.txt

This file was deleted.

Loading