3 # This script is based on the work done by gadgetguru
4 # <david@vuistbijl.nl> at
5 # https://github.com/gadgetguru/PHP-Streaming-Audio and released
6 # under the Public Domain.
13 from datetime import datetime
14 from urllib.parse import parse_qs
16 https = os.environ.get('HTTPS', None)
20 radio_url += 'https://'
22 radio_url += 'http://'
23 radio_url += '{}{}'.format(os.environ.get('HTTP_HOST', ''), os.environ.get('REQUEST_URI', ''))
25 query = parse_qs(os.environ.get('QUERY_STRING', ''), keep_blank_values=True)
27 name = query.get('name', [''])[0]
30 media_directory = os.path.abspath(os.path.dirname(name))
35 'chunkSize': int(query.get('chunkSize', [1024 * 256])[0]),
36 'databaseFile': 'metadata.db',
37 'httpStatus': '500 Internal Server Error',
38 'mediaDirectory': media_directory,
39 'mimeType': query.get('type', [''])[0],
41 'radioName': 'WebKit Test Radio',
42 'radioUrl': radio_url,
43 'setContentLength': query.get('content-length', ['yes'])[0],
44 'setIcyData': query.get('icy-data', ['no'])[0],
45 'supportRanges': query.get('ranges', ['yes'])[0],
46 'stallOffset': int(query.get('stallOffset', [0])[0]),
47 'stallDuration': int(query.get('stallDuration', [2])[0]),
54 'Connection: close\r\n'.format(settings['httpStatus'][0:3])
57 if settings['httpStatus'].startswith('500'):
59 'Content-Type: text/html\r\n\r\n'
60 '<html><body><h1>{}</h1><p/></body></html>'.format(settings['httpStatus'])
65 file_size = os.path.getsize(file_name)
66 last_modified = datetime.utcnow()
68 'Last-Modified: {} GMT\r\n'
69 'Cache-Control: no-cache\r\n'
70 'Etag: "{}-{}"\r\n'.format(last_modified.strftime('%a, %d %b %Y %H:%M:%S'), file_size, str(os.stat(file_name).st_mtime).split('.')[0])
73 if settings['setIcyData'] == 'yes':
74 bit_rate = math.ceil(play_files[len(play_files) - 1]['mimeType'] / 1000)
75 if settings['mimeType'] == '':
76 settings['mimeType'] = play_files[len(play_files) - 1]['mimeType']
79 'icy-notice1: <BR>This stream requires a shoutcast/icecast compatible player.<BR>\r\n'
80 'icy-notice2: WebKit Stream Test<BR>\r\n'
81 'icy-name: {name}\r\n'
82 'icy-genre: {genre}\r\n'
85 'icy-br: {rate}\r\n'.format(name=settings['radioName'], genre=settings['radioGenre'], url=settings['radioUrl'], rate=bit_rate)
88 sys.stdout.write('Content-Type: {}\r\n'.format(settings['mimeType']))
90 if settings['supportRanges'] != 'no':
91 sys.stdout.write('Accept-Ranges: bytes\r\n')
92 if settings['setContentLength'] != 'no':
93 sys.stdout.write('Content-Length: {}\r\n'.format(end - start + 1))
94 if content_range is not None:
95 sys.stdout.write('Content-Range: bytes {}-{}/{}\r\n'.format(start, end, file_size))
96 sys.stdout.write('\r\n')
99 open_file = open(file_name, 'rb')
100 content = open_file.read()
104 read_size = min(settings['chunkSize'], (end - offset) + 1)
106 if not stalled_once and settings['stallOffset'] >= offset and settings['stallOffset'] < offset + read_size:
107 read_size = min(settings['chunkSize'], settings['stallOffset'] - offset)
110 buff = content[offset:read_size]
111 read_length = len(buff)
113 sys.stdout.buffer.write(buff)
115 offset += read_length
118 time.sleep(settings['stallDuration'])
125 if query.get('name', [None])[0] is None:
126 sys.stderr.write('You have not specified a \'name\' parameter.\n')
129 if not os.path.isfile(file_name):
130 sys.stderr.write('The file \'{}\' doesn\'t exist.\n'.format(file_name))
132 settings['databaseFile'] = settings['mediaDirectory'] + '/' + settings['databaseFile']
134 if settings['setIcyData'] != 'yes' and settings['mimeType'] == '':
135 sys.stderr.write('You have not specified a \'type\' parameter.\n')
138 if settings['setIcyData'] == 'yes':
139 if not os.path.isfile(settings['databaseFile']):
140 # If the metadata database file doesn't exist it has to
141 # be create previously.
143 # Check the instructions about how to create it from the
144 # create-id3-db.php script file in this same directory.
146 sys.stderr.write('The metadata database doesn\'t exist. To create one, check the script \'create-id3-db.php\'.\n')
150 with open(settings['databaseFile'], 'r') as file:
151 play_files = json.loads(file.read())
152 sys.stderr.write('\n{}\n'.format(play_files))
155 for play_file in play_files:
156 if file_name.split('/')[-1] == play_file['fileName']:
161 sys.stderr.write('The requested file is not in the database.\n')
164 # We have everything that's needed to send the media file
165 file_size = os.path.getsize(file_name)
166 if settings['stallOffset'] > file_size:
167 sys.stderr.write('The \'stallOffset\' offset parameter is greater than file size ({}).\n'.format(file_size))
173 if settings['supportRanges'] != 'no' and os.environ.get('HTTP_RANGE', None) is not None:
174 content_range = os.environ.get('HTTP_RANGE')
175 if content_range is not None:
176 rng = content_range[len('bytes='):].split('-')
178 if len(rng) > 1 and rng[1] != '':
180 settings['httpStatus'] = '206 Partial Content'
182 settings['httpStatus'] = '200 OK'