commit 78448f953bae662872e00c13f99d7befa8c7dd92 Author: Hsiaoming Yang Date: Tue Nov 25 18:05:00 2014 +0800 Init with audio captcha diff --git a/captcha/__init__.py b/captcha/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/captcha/audio.py b/captcha/audio.py new file mode 100644 index 0000000..1d3fe1f --- /dev/null +++ b/captcha/audio.py @@ -0,0 +1,216 @@ +# coding: utf-8 + +import os +import copy +import wave +import struct +import random + + +WAVE_SAMPLE_RATE = 8000 # HZ +WAVE_HEADER = bytearray( + b'RIFF\x00\x00\x00\x00WAVEfmt \x10\x00\x00\x00\x01\x00\x01\x00' + b'@\x1f\x00\x00@\x1f\x00\x00\x01\x00\x08\x00data' +) +WAVE_HEADER_LENGTH = len(WAVE_HEADER) - 4 +DATA_DIR = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'data') + + +def _read_wave_file(filepath): + w = wave.open(filepath) + data = w.readframes(-1) + w.close() + return data + + +def change_speed(body, speed=1): + """Change the voice speed of the wave body.""" + if speed == 1: + return body + + length = int(len(body) * speed) + rv = bytearray(length) + + step = 0 + for v in body: + i = int(step) + while i < int(step + speed) and i < length: + rv[i] = v + i += 1 + step += speed + return rv + + +def patch_wave_header(body): + """Patch header to the given wave body. + + :param body: the wave content body, it should be bytearray. + """ + assert isinstance(body, bytearray) + + length = len(body) + + padded = length + length % 2 + total = WAVE_HEADER_LENGTH + padded + + header = copy.copy(WAVE_HEADER) + # fill the total length position + header[4:8] = bytearray(struct.pack(' 128: + v = (v - 128) * level + 128 + v = max(int(v), 128) + v = min(v, 256) + elif v < 128: + v = 128 - (128 - v) * level + v = min(int(v), 128) + v = max(v, 0) + body[i] = v + return body + + +def mix_wave(src, dst): + """Mix two wave body into one.""" + if len(src) > len(dst): + # output should be longer + dst, src = src, dst + + for i, sv in enumerate(src): + dv = dst[i] + if sv < 128 and dv < 128: + dst[i] = sv * dv / 128 + else: + dst[i] = 2 * (sv + dv) - sv * dv / 128 - 256 + return dst + + +BEEP = bytearray(_read_wave_file(os.path.join(DATA_DIR, 'beep.wav'))) +END_BEEP = change_speed(BEEP, 1.4) +SILENCE = create_silence(WAVE_SAMPLE_RATE / 5) + + +class AudioCaptcha(object): + def __init__(self, voicedir=None): + if voicedir is None: + voicedir = DATA_DIR + self._voicedir = voicedir + self._cache = {} + + def load(self): + for n in os.listdir(self._voicedir): + if len(n) == 1 and os.path.isdir(os.path.join(self._voicedir, n)): + self._load_data(n) + + def _load_data(self, name): + dirname = os.path.join(self._voicedir, name) + data = [] + for f in os.listdir(dirname): + filepath = os.path.join(dirname, f) + if f.endswith('.wav') and os.path.isfile(filepath): + data.append(bytearray(_read_wave_file(filepath))) + self._cache[name] = data + + def twist_pick(self, key): + voice = random.choice(self._cache[key]) + + # random change speed + speed = random.randrange(90, 120) / 100.0 + voice = change_speed(voice, speed) + + # random change sound + level = random.randrange(80, 120) / 100.0 + voice = change_sound(voice, level) + return voice + + def pick_for_background(self): + key = random.choice(self._cache.keys()) + voice = random.choice(self._cache[key]) + voice = copy.copy(voice) + voice.reverse() + + speed = random.randrange(8, 16) / 10.0 + voice = change_speed(voice, speed) + + level = random.randrange(2, 6) / 10.0 + voice = change_sound(voice, level) + return voice + + def create_background_noise(self, length, chars): + noise = create_noise(length, 4) + pos = 0 + while pos < length: + sound = self.pick_for_background() + end = pos + len(sound) + 1 + noise[pos:end] = mix_wave(sound, noise[pos:end]) + pos = end + random.randint(0, WAVE_SAMPLE_RATE / 10) + return noise + + def create_wave_body(self, chars): + voices = [] + inters = [] + for key in chars: + voices.append(self.twist_pick(key)) + v = random.randint(WAVE_SAMPLE_RATE, WAVE_SAMPLE_RATE * 3) + inters.append(v) + + durations = map(lambda a: len(a), voices) + l = max(durations) * len(chars) + reduce(lambda a, b: a + b, inters) + bg = self.create_background_noise(l, chars) + + body = BEEP + SILENCE + BEEP + SILENCE + BEEP + + # begin + pos = inters[0] + for i, v in enumerate(voices): + end = pos + len(v) + 1 + bg[pos:end] = mix_wave(v, bg[pos:end]) + pos = end + inters[i] + + body += bg + END_BEEP + return body + + def generate(self, chars): + if not self._cache: + self.load() + body = self.create_wave_body(chars) + return patch_wave_header(body) diff --git a/captcha/data/0/default.wav b/captcha/data/0/default.wav new file mode 100644 index 0000000..8579958 Binary files /dev/null and b/captcha/data/0/default.wav differ diff --git a/captcha/data/1/default.wav b/captcha/data/1/default.wav new file mode 100644 index 0000000..2c6c74f Binary files /dev/null and b/captcha/data/1/default.wav differ diff --git a/captcha/data/2/default.wav b/captcha/data/2/default.wav new file mode 100644 index 0000000..95b9fac Binary files /dev/null and b/captcha/data/2/default.wav differ diff --git a/captcha/data/3/default.wav b/captcha/data/3/default.wav new file mode 100644 index 0000000..a0d9acd Binary files /dev/null and b/captcha/data/3/default.wav differ diff --git a/captcha/data/4/default.wav b/captcha/data/4/default.wav new file mode 100644 index 0000000..ac73980 Binary files /dev/null and b/captcha/data/4/default.wav differ diff --git a/captcha/data/5/default.wav b/captcha/data/5/default.wav new file mode 100644 index 0000000..6f4b9a7 Binary files /dev/null and b/captcha/data/5/default.wav differ diff --git a/captcha/data/6/default.wav b/captcha/data/6/default.wav new file mode 100644 index 0000000..43b404b Binary files /dev/null and b/captcha/data/6/default.wav differ diff --git a/captcha/data/7/default.wav b/captcha/data/7/default.wav new file mode 100644 index 0000000..96ea53b Binary files /dev/null and b/captcha/data/7/default.wav differ diff --git a/captcha/data/8/default.wav b/captcha/data/8/default.wav new file mode 100644 index 0000000..4e6afde Binary files /dev/null and b/captcha/data/8/default.wav differ diff --git a/captcha/data/9/default.wav b/captcha/data/9/default.wav new file mode 100644 index 0000000..2ce6ea8 Binary files /dev/null and b/captcha/data/9/default.wav differ diff --git a/captcha/data/beep.wav b/captcha/data/beep.wav new file mode 100644 index 0000000..0e71375 Binary files /dev/null and b/captcha/data/beep.wav differ