long-wav.py (4222B)
1 """ 2 This generates a 30 minute silent wav, and is capable of 3 responding to Range requests. 4 """ 5 import time 6 import re 7 import struct 8 9 from wptserve.utils import isomorphic_decode 10 11 def create_wav_header(sample_rate, bit_depth, channels, duration): 12 bytes_per_sample = int(bit_depth / 8) 13 block_align = bytes_per_sample * channels 14 byte_rate = sample_rate * block_align 15 sub_chunk_2_size = duration * byte_rate 16 17 data = b'' 18 # ChunkID 19 data += b'RIFF' 20 # ChunkSize 21 data += struct.pack('<L', 36 + sub_chunk_2_size) 22 # Format 23 data += b'WAVE' 24 # Subchunk1ID 25 data += b'fmt ' 26 # Subchunk1Size 27 data += struct.pack('<L', 16) 28 # AudioFormat 29 data += struct.pack('<H', 1) 30 # NumChannels 31 data += struct.pack('<H', channels) 32 # SampleRate 33 data += struct.pack('<L', sample_rate) 34 # ByteRate 35 data += struct.pack('<L', byte_rate) 36 # BlockAlign 37 data += struct.pack('<H', block_align) 38 # BitsPerSample 39 data += struct.pack('<H', bit_depth) 40 # Subchunk2ID 41 data += b'data' 42 # Subchunk2Size 43 data += struct.pack('<L', sub_chunk_2_size) 44 45 return data 46 47 48 def main(request, response): 49 if request.method == u"OPTIONS": 50 response.status = (404, b"Not Found") 51 response.headers.set(b"Content-Type", b"text/plain") 52 return b"Preflight not accepted" 53 54 response.headers.set(b"Content-Type", b"audio/wav") 55 response.headers.set(b"Accept-Ranges", b"bytes") 56 response.headers.set(b"Cache-Control", b"no-cache") 57 response.headers.set(b"Access-Control-Allow-Origin", request.headers.get(b'Origin', b'')) 58 59 range_header = request.headers.get(b'Range', b'') 60 range_header_match = range_header and re.search(r'^bytes=(\d*)-(\d*)$', isomorphic_decode(range_header)) 61 range_received_key = request.GET.first(b'range-received-key', b'') 62 accept_encoding_key = request.GET.first(b'accept-encoding-key', b'') 63 64 if range_received_key and range_header: 65 # Remove any current value 66 request.server.stash.take(range_received_key, b'/fetch/range/') 67 # This is later collected using stash-take.py 68 request.server.stash.put(range_received_key, u'range-header-received', b'/fetch/range/') 69 70 if accept_encoding_key: 71 # Remove any current value 72 request.server.stash.take( 73 accept_encoding_key, 74 b'/fetch/range/' 75 ) 76 # This is later collected using stash-take.py 77 request.server.stash.put( 78 accept_encoding_key, 79 isomorphic_decode(request.headers.get(b'Accept-Encoding', b'')), 80 b'/fetch/range/' 81 ) 82 83 # Audio details 84 sample_rate = 8000 85 bit_depth = 8 86 channels = 1 87 duration = 60 * 5 88 89 total_length = int((sample_rate * bit_depth * channels * duration) / 8) 90 bytes_remaining_to_send = total_length 91 initial_write = b'' 92 93 if range_header_match: 94 response.status = 206 95 start, end = range_header_match.groups() 96 97 start = int(start) 98 end = int(end) if end else 0 99 100 if end: 101 bytes_remaining_to_send = (end + 1) - start 102 else: 103 bytes_remaining_to_send = total_length - start 104 105 wav_header = create_wav_header(sample_rate, bit_depth, channels, duration) 106 107 if start < len(wav_header): 108 initial_write = wav_header[start:] 109 110 if bytes_remaining_to_send < len(initial_write): 111 initial_write = initial_write[0:bytes_remaining_to_send] 112 113 content_range = b"bytes %d-%d/%d" % (start, end or total_length - 1, total_length) 114 115 response.headers.set(b"Content-Range", content_range) 116 else: 117 initial_write = create_wav_header(sample_rate, bit_depth, channels, duration) 118 119 response.headers.set(b"Content-Length", bytes_remaining_to_send) 120 121 response.write_status_headers() 122 response.writer.write(initial_write) 123 124 bytes_remaining_to_send -= len(initial_write) 125 126 while bytes_remaining_to_send > 0: 127 to_send = b'\x00' * min(bytes_remaining_to_send, sample_rate) 128 bytes_remaining_to_send -= len(to_send) 129 130 if not response.writer.write(to_send): 131 break 132 133 # Throttle the stream 134 time.sleep(0.5)