diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..7620721 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +include quiet/* \ No newline at end of file diff --git a/README.md b/README.md index 7cf55b2..45d44da 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,102 @@ -# quiet.py -Python bindings for libquiet +quiet.py +======== + +[![](https://img.shields.io/pypi/v/quiet.py.svg)](https://pypi.org/project/quiet.py/) + + +Python ctypes bindings for libquiet to transmit data with sound. + +## Requirements ++ numpy + +## Install + ++ For ARM platform, binary package is available on pypi, just use `pip` to install it: + + ``` + sudo apt install python-numpy + pip install --no-deps quiet.py + ``` + + We install `numpy` separately, as installing `numpy` via pip requires compiling numpy from source. + ++ For x86/amd64 + + ``` + sudo apt install cmake + git clone https://github.com/xiongyihui/quiet.py && cd quiet.py + ./scripts/libs.sh + pip install . + ``` + + +## Usage +1. Encode a message, and then decode it +``` +from quiet import Encode, Decoder + +def test(): + encoder = Encoder() + decoder = Decoder() + + for chunk in encoder.encode('hello, world'): + message = decoder.decode(chunk) + if message is not None: + print(message) + + +test() +``` + +2. decode messages from recording in realtime + +``` +import sys +import numpy +import pyaudio +from quiet import Encode, Decoder + +def decode(): + if sys.version_info[0] < 3: + import Queue as queue + else: + import queue + + FORMAT = pyaudio.paFloat32 + CHANNELS = 1 + RATE = 44100 + CHUNK = 16384 # int(RATE / 100) + + p = pyaudio.PyAudio() + q = queue.Queue() + + def callback(in_data, frame_count, time_info, status): + q.put(in_data) + return (None, pyaudio.paContinue) + + stream = p.open(format=FORMAT, + channels=CHANNELS, + rate=RATE, + input=True, + frames_per_buffer=CHUNK, + stream_callback=callback) + + count = 0 + with Decoder(profile_name='ultrasonic-experimental') as decoder: + while True: + try: + audio = q.get() + audio = numpy.fromstring(audio, dtype='float32') + # audio = audio[::CHANNELS] + code = decoder.decode(audio) + if code is not None: + count += 1 + print(code.tostring().decode('utf-8', 'ignore')) + except KeyboardInterrupt: + break + + +decode() +``` + -This repo is not yet ready. It is a work in progress. diff --git a/quiet/__init__.py b/quiet/__init__.py new file mode 100644 index 0000000..6f361dc --- /dev/null +++ b/quiet/__init__.py @@ -0,0 +1,4 @@ +# -*- coding:utf-8 -*- + +from .quiet import Decoder, Encoder + diff --git a/quiet/quiet-profiles.json b/quiet/quiet-profiles.json new file mode 100644 index 0000000..03948c2 --- /dev/null +++ b/quiet/quiet-profiles.json @@ -0,0 +1,270 @@ +{ + "audible": { + "mod_scheme": "gmsk", + "checksum_scheme": "crc32", + "inner_fec_scheme": "v27", + "outer_fec_scheme": "none", + "frame_length": 100, + "modulation": { + "center_frequency": 4200, + "gain": 0.1 + }, + "interpolation": { + "shape": "kaiser", + "samples_per_symbol": 10, + "symbol_delay": 4, + "excess_bandwidth": 0.35 + }, + "encoder_filters": { + "dc_filter_alpha": 0.01 + }, + "resampler": { + "delay": 13, + "bandwidth": 0.45, + "attenuation": 60, + "filter_bank_size": 64 + } + }, + "audible-7k-channel-0": { + "mod_scheme": "arb16opt", + "checksum_scheme": "crc32", + "inner_fec_scheme": "rs8", + "outer_fec_scheme": "v29", + "frame_length": 600, + "modulation": { + "center_frequency": 9200, + "gain": 0.1 + }, + "interpolation": { + "shape": "kaiser", + "samples_per_symbol": 6, + "symbol_delay": 4, + "excess_bandwidth": 0.31 + }, + "encoder_filters": { + "dc_filter_alpha": 0.01 + }, + "resampler": { + "delay": 13, + "bandwidth": 0.45, + "attenuation": 60, + "filter_bank_size": 64 + }, + "ofdm": { + "num_subcarriers": 48, + "cyclic_prefix_length": 8, + "taper_length": 4, + "left_band": 0, + "right_band": 0 + } + }, + "audible-7k-channel-1": { + "mod_scheme": "arb16opt", + "checksum_scheme": "crc32", + "inner_fec_scheme": "rs8", + "outer_fec_scheme": "v29", + "frame_length": 600, + "modulation": { + "center_frequency": 15500, + "gain": 0.1 + }, + "interpolation": { + "shape": "kaiser", + "samples_per_symbol": 6, + "symbol_delay": 4, + "excess_bandwidth": 0.31 + }, + "encoder_filters": { + "dc_filter_alpha": 0.01 + }, + "resampler": { + "delay": 13, + "bandwidth": 0.45, + "attenuation": 60, + "filter_bank_size": 64 + }, + "ofdm": { + "num_subcarriers": 48, + "cyclic_prefix_length": 8, + "taper_length": 4, + "left_band": 0, + "right_band": 0 + } + }, + "cable-64k": { + "mod_scheme": "qam1024", + "checksum_scheme": "crc32", + "inner_fec_scheme": "v27p23", + "outer_fec_scheme": "rs8", + "frame_length": 7500, + "modulation": { + "center_frequency": 10200, + "gain": 0.09 + }, + "interpolation": { + "shape": "kaiser", + "samples_per_symbol": 2, + "symbol_delay": 4, + "excess_bandwidth": 0.35 + }, + "encoder_filters": { + "dc_filter_alpha": 0.03 + }, + "resampler": { + "delay": 13, + "bandwidth": 0.45, + "attenuation": 60, + "filter_bank_size": 64 + }, + "ofdm": { + "num_subcarriers": 128, + "cyclic_prefix_length": 16, + "taper_length": 8, + "left_band": 6, + "right_band": 12 + } + }, + "hello-world": { + "mod_scheme": "gmsk", + "checksum_scheme": "crc32", + "inner_fec_scheme": "v27", + "outer_fec_scheme": "none", + "frame_length": 25, + "modulation": { + "center_frequency": 4400, + "gain": 0.08 + }, + "interpolation": { + "shape": "kaiser", + "samples_per_symbol": 20, + "symbol_delay": 4, + "excess_bandwidth": 0.38 + }, + "encoder_filters": { + "dc_filter_alpha": 0.01 + }, + "resampler": { + "delay": 13, + "bandwidth": 0.45, + "attenuation": 60, + "filter_bank_size": 64 + } + }, + "ultrasonic": { + "mod_scheme": "gmsk", + "checksum_scheme": "crc32", + "inner_fec_scheme": "v27", + "outer_fec_scheme": "none", + "frame_length": 75, + "modulation": { + "center_frequency": 19000, + "gain": 0.1 + }, + "interpolation": { + "shape": "rrcos", + "samples_per_symbol": 14, + "symbol_delay": 4, + "excess_bandwidth": 0.35 + }, + "encoder_filters": { + "dc_filter_alpha": 0.01 + }, + "resampler": { + "delay": 13, + "bandwidth": 0.45, + "attenuation": 60, + "filter_bank_size": 64 + } + }, + "ultrasonic-3600": { + "ofdm": { + "num_subcarriers": 64, + "cyclic_prefix_length": 20, + "taper_length": 8, + "left_band": 4, + "right_band": 13 + }, + "mod_scheme": "V29", + "checksum_scheme": "crc8", + "inner_fec_scheme": "v27", + "outer_fec_scheme": "none", + "frame_length": 550, + "modulation": { + "center_frequency": 18500, + "gain": 0.1 + }, + "interpolation": { + "shape": "kaiser", + "samples_per_symbol": 7, + "symbol_delay": 4, + "excess_bandwidth": 0.33 + }, + "encoder_filters": { + "dc_filter_alpha": 0.01 + }, + "resampler": { + "delay": 13, + "bandwidth": 0.45, + "attenuation": 60, + "filter_bank_size": 64 + } + }, + "ultrasonic-whisper": { + "mod_scheme": "gmsk", + "checksum_scheme": "crc32", + "inner_fec_scheme": "v27", + "outer_fec_scheme": "none", + "frame_length": 16, + "modulation": { + "center_frequency": 19500, + "gain": 0.1 + }, + "interpolation": { + "shape": "rrcos", + "samples_per_symbol": 30, + "symbol_delay": 4, + "excess_bandwidth": 0.35 + }, + "encoder_filters": { + "dc_filter_alpha": 0.01 + }, + "resampler": { + "delay": 13, + "bandwidth": 0.45, + "attenuation": 60, + "filter_bank_size": 64 + } + }, + "ultrasonic-experimental": { + "mod_scheme": "bpsk", + "checksum_scheme": "crc32", + "inner_fec_scheme": "rs8", + "outer_fec_scheme": "v29", + "frame_length": 100, + "modulation": { + "center_frequency": 19000, + "gain": 0.2 + }, + "interpolation": { + "shape": "kaiser", + "samples_per_symbol": 10, + "symbol_delay": 4, + "excess_bandwidth": 0.31 + }, + "encoder_filters": { + "dc_filter_alpha": 0.01 + }, + "resampler": { + "delay": 13, + "bandwidth": 0.45, + "attenuation": 60, + "filter_bank_size": 64 + }, + "header": { + "checksum_scheme": "crc32", + "inner_fec_scheme": "secded7264", + "outer_fec_scheme": "v29", + "mod_scheme": "bpsk" + } + } +} diff --git a/quiet/quiet.py b/quiet/quiet.py new file mode 100755 index 0000000..dbba94e --- /dev/null +++ b/quiet/quiet.py @@ -0,0 +1,246 @@ + + +import time +import os +import json +from ctypes import c_char_p, c_void_p, c_float, c_size_t, c_ssize_t, c_uint, c_uint8, POINTER, cdll +from numpy.ctypeslib import ndpointer +import numpy + + +PROFILES = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'quiet-profiles.json') + +c_float_p = POINTER(c_float) + + +class Quiet(object): + # for lazy loading + lib = None + + @staticmethod + def load_lib(): + lib_name = 'libquiet.so' + lib_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), lib_name) + + if os.path.isfile(lib_path): + lib = cdll.LoadLibrary(lib_path) + else: + lib = cdll.LoadLibrary(lib_name) + + # quiet_encoder_options *quiet_encoder_profile_filename(const char *fname, const char *profilename) + lib.quiet_encoder_profile_filename.argtypes = [c_char_p, c_char_p] + lib.quiet_encoder_profile_filename.restype = c_void_p + + # quiet_decoder_options *quiet_decoder_profile_filename(const char *fname, const char *profilename) + lib.quiet_decoder_profile_filename.argtypes = [c_char_p, c_char_p] + lib.quiet_decoder_profile_filename.restype = c_void_p + + # quiet_encoder *quiet_encoder_create(const quiet_encoder_options *opt, float sample_rate) + lib.quiet_encoder_create.argtypes = [c_void_p, c_float] + lib.quiet_encoder_create.restype = c_void_p + + # ssize_t quiet_encoder_send(quiet_encoder *e, const void *buf, size_t len) + lib.quiet_encoder_send.argtypes = [c_void_p, c_char_p, c_size_t] + lib.quiet_encoder_send.restype = c_ssize_t + + # void quiet_encoder_set_blocking(quiet_encoder *e, time_t sec, long nano) + # lib.quiet_encoder_set_blocking.argtypes = [c_void_p, c_uint, c_long] + # lib.quiet_encoder_set_blocking.restype = None + + # void quiet_encoder_set_nonblocking(quiet_encoder *e) + + # size_t quiet_encoder_clamp_frame_len(quiet_encoder *e, size_t sample_len) + lib.quiet_encoder_clamp_frame_len.argtypes = [c_void_p, c_size_t] + lib.quiet_encoder_clamp_frame_len.restype = c_size_t + + # size_t quiet_encoder_get_frame_len(const quiet_encoder *e) + lib.quiet_encoder_get_frame_len.argtypes = [c_void_p] + lib.quiet_encoder_get_frame_len.restype = c_size_t + + # ssize_t quiet_encoder_emit(quiet_encoder *e, quiet_sample_t *samplebuf, size_t samplebuf_len) + lib.quiet_encoder_emit.argtypes = [ + c_void_p, ndpointer(c_float, flags="C_CONTIGUOUS"), c_size_t] + lib.quiet_encoder_emit.restype = c_ssize_t + + # void quiet_encoder_close(quiet_encoder *e) + lib.quiet_encoder_close.argtypes = [c_void_p] + lib.quiet_encoder_close.restype = None + + # void quiet_encoder_destroy(quiet_encoder *e) + lib.quiet_encoder_destroy.argtypes = [c_void_p] + lib.quiet_encoder_destroy.restype = None + + # quiet_decoder *quiet_decoder_create(const quiet_decoder_options *opt, float sample_rate) + lib.quiet_decoder_create.argtypes = [c_void_p, c_float] + lib.quiet_decoder_create.restype = c_void_p + + # ssize_t quiet_decoder_recv(quiet_decoder *d, uint8_t *data, size_t len) + lib.quiet_decoder_recv.argtypes = [ + c_void_p, ndpointer(c_uint8, flags="C_CONTIGUOUS"), c_size_t] + lib.quiet_decoder_recv.restype = c_ssize_t + + # void quiet_decoder_set_nonblocking(quiet_decoder *d) + # lib.quiet_decoder_set_nonblocking.argtypes = [c_void_p] + # lib.quiet_decoder_set_nonblocking.restype = None + + # void quiet_decoder_consume(quiet_decoder *d, const quiet_sample_t *samplebuf, size_t sample_len) + lib.quiet_decoder_consume.argtypes = [c_void_p, c_void_p, c_size_t] + lib.quiet_decoder_consume.restype = None + + # bool quiet_decoder_frame_in_progress(quiet_decoder *d) + + # void quiet_decoder_flush(quiet_decoder *d) + lib.quiet_decoder_flush.argtypes = [c_void_p] + lib.quiet_decoder_flush.restype = None + + # void quiet_decoder_close(quiet_decoder *d) + lib.quiet_decoder_close.argtypes = [c_void_p] + lib.quiet_decoder_close.restype = None + + # unsigned int quiet_decoder_checksum_fails(const quiet_decoder *d) + lib.quiet_decoder_checksum_fails.argtypes = [c_void_p] + lib.quiet_decoder_checksum_fails.restype = c_uint + + # void quiet_decoder_enable_stats(quiet_decoder *d) + + # void quiet_decoder_disable_stats(quiet_decoder *d) + + # void quiet_decoder_set_stats_blocking(quiet_decoder *d, time_t sec, long nano) + + # void quiet_decoder_set_stats_nonblocking(quiet_decoder *d) + + # void quiet_decoder_destroy(quiet_decoder *d) + lib.quiet_decoder_destroy.argtypes = [c_void_p] + lib.quiet_decoder_destroy.restype = None + + return lib + + +class Decoder(object): + def __init__(self, sample_rate=44100., profile_name='audible', profiles=PROFILES, max_frame=128): + if not Quiet.lib: + Quiet.lib = Quiet.load_lib() + + self._decoder_options = Quiet.lib.quiet_decoder_profile_filename( + profiles.encode('utf-8'), profile_name.encode('utf-8')) + self._decoder = Quiet.lib.quiet_decoder_create( + self._decoder_options, sample_rate) + + self.max_frame = max_frame + + def __del__(self): + Quiet.lib.quiet_decoder_destroy(self._decoder) + + def decode(self, data, flush=False): + Quiet.lib.quiet_decoder_consume( + self._decoder, data.ctypes.data_as(c_void_p), len(data)) + + if flush: + Quiet.lib.quiet_decoder_flush(self._decoder) + + buf = numpy.empty(self.max_frame, dtype='uint8') + got = Quiet.lib.quiet_decoder_recv(self._decoder, buf, len(buf)) + + if got > 0: + return buf[:got] + + def flush(self): + Quiet.lib.quiet_decoder_flush(self._decoder) + + def __enter__(self): + return self + + def __exit__(self, type, value, traceback): + pass + + +class Encoder(object): + def __init__(self, sample_rate=44100., profile_name='audible', profiles=PROFILES): + if not Quiet.lib: + Quiet.lib = Quiet.load_lib() + + self._encoder_options = Quiet.lib.quiet_encoder_profile_filename( + profiles.encode('utf-8'), profile_name.encode('utf-8')) + self._encoder = Quiet.lib.quiet_encoder_create( + self._encoder_options, sample_rate) + + def __del__(self): + Quiet.lib.quiet_encoder_destroy(self._encoder) + + def encode(self, data, chunk_size=1024): + Quiet.lib.quiet_encoder_send( + self._encoder, data.encode('utf-8'), len(data)) + + buf = numpy.empty(chunk_size, dtype='float32') + while True: + got = Quiet.lib.quiet_encoder_emit(self._encoder, buf, len(buf)) + + if got < 0: + return + elif got < chunk_size: + yield buf + return + else: + yield buf + + def __enter__(self): + return self + + def __exit__(self, type, value, traceback): + pass + + +def decode(): + import pyaudio + import sys + if sys.version_info[0] < 3: + import Queue as queue + else: + import queue + + FORMAT = pyaudio.paFloat32 + CHANNELS = 1 + RATE = 44100 + CHUNK = 16384 # int(RATE / 100) + + p = pyaudio.PyAudio() + q = queue.Queue() + + def callback(in_data, frame_count, time_info, status): + q.put(in_data) + return (None, pyaudio.paContinue) + + stream = p.open(format=FORMAT, + channels=CHANNELS, + rate=RATE, + input=True, + frames_per_buffer=CHUNK, + stream_callback=callback) + + count = 0 + with Decoder(profile_name='ultrasonic-experimental') as decoder: + while True: + try: + audio = q.get() + audio = numpy.fromstring(audio, dtype='float32') + # audio = audio[::CHANNELS] + code = decoder.decode(audio) + if code is not None: + count += 1 + print(code.tostring().decode('utf-8', 'ignore')) + except KeyboardInterrupt: + break + + +def test(): + encoder = Encoder() + decoder = Decoder() + + for chunk in encoder.encode('hello, world'): + message = decoder.decode(chunk) + if message is not None: + print(message) + + +if __name__ == '__main__': + decode() diff --git a/scripts/libs.sh b/scripts/libs.sh index 6f58f38..5af719f 100755 --- a/scripts/libs.sh +++ b/scripts/libs.sh @@ -23,19 +23,19 @@ rm -f "$SYSROOT/usr/lib/libfec.dylib" mkdir -p "$BUILDPATH/liquid-dsp" cd "$BUILDPATH/liquid-dsp" -cmake -DCMAKE_BUILD_TYPE=Release "$SRCPATH/liquid-dsp" -DCMAKE_INSTALL_PREFIX="$SYSROOT/usr" -DCMAKE_PREFIX_PATH="$SYSROOT" -DCMAKE_SHARED_LINKER_FLAGS="-L$SYSROOT/usr/lib" -DLIQUID_BUILD_EXAMPLES="off" -DLIQUID_BUILD_SANDBOX="off" && make liquid-static && make install +cmake -DCMAKE_BUILD_TYPE=Release "$SRCPATH/liquid-dsp" -DCMAKE_C_FLAGS="-fPIC" -DLIQUID_FFTOVERRIDE=ON -DCMAKE_INSTALL_PREFIX="$SYSROOT/usr" -DCMAKE_PREFIX_PATH="$SYSROOT" -DCMAKE_SHARED_LINKER_FLAGS="-L$SYSROOT/usr/lib" -DLIQUID_BUILD_EXAMPLES="off" -DLIQUID_BUILD_SANDBOX="off" && make liquid-static && make install mkdir -p "$BUILDPATH/jansson" cd "$BUILDPATH/jansson" cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="$SYSROOT/usr" -DJANSSON_BUILD_SHARED_LIBS=off -DJANSSON_WITHOUT_TESTS=on -DJANSSON_EXAMPLES=off -DJANSSON_BUILD_DOCS=off "$SRCPATH/jansson" && make && make install -mkdir -p "$BUILDPATH/portaudio" -cd "$BUILDPATH/portaudio" -cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="$SYSROOT/usr" -DCMAKE_PREFIX_PATH="$SYSROOT" "$SRCPATH/portaudio" && make && make install && cp libportaudio_static.a "$SYSROOT/usr/lib/libportaudio.a" +# mkdir -p "$BUILDPATH/portaudio" +# cd "$BUILDPATH/portaudio" +# cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS="-fPIC" -DCMAKE_INSTALL_PREFIX="$SYSROOT/usr" -DCMAKE_PREFIX_PATH="$SYSROOT" "$SRCPATH/portaudio" && make && make install && cp libportaudio_static.a "$SYSROOT/usr/lib/libportaudio.a" mkdir -p "$BUILDPATH/libquiet" cd "$BUILDPATH/libquiet" -cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="$SYSROOT/usr" -DCMAKE_PREFIX_PATH="$SYSROOT" "$SRCPATH/libquiet" && make +cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS="-fPIC" -DCMAKE_INSTALL_PREFIX="$SYSROOT/usr" -DCMAKE_PREFIX_PATH="$SYSROOT" "$SRCPATH/libquiet" && make CTEST_OUTPUT_ON_FAILURE=1 make check make install @@ -44,15 +44,26 @@ mkdir -p "$INCLUDEPATH" cp "$SYSROOTPATH/usr/lib/libfec.a" "$LIBPATH" cp "$SYSROOTPATH/usr/lib/libliquid.a" "$LIBPATH" cp "$SYSROOTPATH/usr/lib/libjansson.a" "$LIBPATH" -cp "$SYSROOTPATH/usr/lib/libportaudio.a" "$LIBPATH" +# cp "$SYSROOTPATH/usr/lib/libportaudio.a" "$LIBPATH" cp "$SYSROOTPATH/usr/lib/libquiet.a" "$LIBPATH" cp "$SYSROOTPATH/usr/include/fec.h" "$INCLUDEPATH" cp -R "$SYSROOTPATH/usr/include/liquid" "$INCLUDEPATH" cp "$SYSROOTPATH/usr/include/jansson.h" "$INCLUDEPATH" cp "$SYSROOTPATH/usr/include/jansson_config.h" "$INCLUDEPATH" -cp "$SYSROOTPATH/usr/include/portaudio.h" "$INCLUDEPATH" +# cp "$SYSROOTPATH/usr/include/portaudio.h" "$INCLUDEPATH" cp "$SYSROOTPATH/usr/include/quiet.h" "$INCLUDEPATH" +if [ "$(uname)" == "Darwin" ]; then +gcc -shared -o $ABSPATH/quiet/libquiet.so \ +-Wl,-all_load $LIBPATH/libquiet.a $LIBPATH/libliquid.a $LIBPATH/libfec.a \ +$LIBPATH/libjansson.a -Wl,-noall_load +else +gcc -shared -o $ABSPATH/quiet/libquiet.so \ +-Wl,--whole-archive $LIBPATH/libquiet.a -Wl,--no-whole-archive $LIBPATH/libliquid.a $LIBPATH/libfec.a \ +$LIBPATH/libjansson.a +fi + + echo echo "Build complete. Built libraries are in $LIBPATH" echo "and includes in $INCLUDEPATH." diff --git a/setup.py b/setup.py index aed2ff5..b16832a 100644 --- a/setup.py +++ b/setup.py @@ -1,21 +1,43 @@ -from distutils.core import setup, Extension -# if osx + +from setuptools import setup +from setuptools.command.build_py import build_py +import ctypes.util +import subprocess import os -os.environ['LDFLAGS'] = '-framework Carbon -framework AudioUnit -framework AudioToolbox -framework CoreAudio' -module = Extension('cquiet', - sources=['quietmodule.c'], - extra_compile_args=['-std=c99', '-Iinclude'], - extra_link_args=['-Llib', 'libquiet.a', 'libportaudio.a', 'libjansson.a', 'libliquid.a', 'libfec.a'], - ) -setup(name='quiet', +with open('README.md') as f: + long_description = f.read() + + +class BuildPyCommand(build_py): + """Custom build command.""" + + def run(self): + # check if libquiet.so is at system lib paths + if not ctypes.util.find_library('quiet'): + libquiet = os.path.join(os.path.dirname( + __file__), 'quiet', 'libquiet.so') + if not os.path.isfile(libquiet): + # build libquiet.so + subprocess.check_call(['bash', 'scripts/libs.sh']) + + build_py.run(self) + + +setup(name='quiet.py', version='0.1', - description='Quiet Modem', - author='Brian Armstrong', + description='Quiet Modem, to transmit data with sound', + long_description=long_description, + long_description_content_type='text/markdown', + author='Brian Armstrong, Yihui Xiong', author_email='brian.armstrong.ece+pypi@gmail.com', - url='https://github.com/quiet', - ext_modules=[module], + url='https://github.com/quiet/quiet.py', + cmdclass={ + 'build_py': BuildPyCommand, + }, packages=['quiet'], -) + include_package_data=True, + install_requires=['numpy'], + zip_safe=False)