def stream_ts(self, p=None, **kwargs): """ Stream a ts from original location """ import socket socket._fileobject.default_bufsize = 0 serving_request = cherrypy.serving.request if serving_request.protocol >= (1, 1): r = httputil.get_ranges(serving_request.headers.get('Range'), sys.maxint) # TODO : do something with range request if need be req = urllib2.Request(kwargs['url']) for header in serving_request.headers: if header not in ['Range','Accept','User-Agent']: continue req.headers[header] = serving_request.headers.get(header) ts = urllib2.urlopen(req) for h in ts.headers.keys(): cherrypy.response.headers[h] = ts.headers[h] buffer = '_' while len(buffer) > 0: buffer = ts.read(30*1024) yield buffer
def _serve_fileobj(fileobj, content_type, content_length, debug = False): response = cherrypy.serving.response request = cherrypy.serving.request if request.protocol >= (1, 1): response.headers['Accept-Ranges'] = 'bytes' r = httputil.get_ranges(request.headers.get('Range'), content_length) if r == []: response.headers['Content-Range'] = 'bytes */%s' % content_length message = 'Invalid Range (first-byte-pos greater than Content-Length)' if debug: cherrypy.log(message, 'TOOLS.STATIC') raise cherrypy.HTTPError(416, message) if r: if len(r) == 1: start, stop = r[0] if stop > content_length: stop = content_length r_len = stop - start if debug: cherrypy.log('Single part; start: %r, stop: %r' % (start, stop), 'TOOLS.STATIC') response.status = '206 Partial Content' response.headers['Content-Range'] = 'bytes %s-%s/%s' % (start, stop - 1, content_length) response.headers['Content-Length'] = r_len fileobj.seek(start) response.body = file_generator_limited(fileobj, r_len) else: response.status = '206 Partial Content' from mimetools import choose_boundary boundary = choose_boundary() ct = 'multipart/byteranges; boundary=%s' % boundary response.headers['Content-Type'] = ct if 'Content-Length' in response.headers: del response.headers['Content-Length'] def file_ranges(): yield ntob('\r\n') for start, stop in r: if debug: cherrypy.log('Multipart; start: %r, stop: %r' % (start, stop), 'TOOLS.STATIC') yield ntob('--' + boundary, 'ascii') yield ntob('\r\nContent-type: %s' % content_type, 'ascii') yield ntob('\r\nContent-range: bytes %s-%s/%s\r\n\r\n' % (start, stop - 1, content_length), 'ascii') fileobj.seek(start) for chunk in file_generator_limited(fileobj, stop - start): yield chunk yield ntob('\r\n') yield ntob('--' + boundary + '--', 'ascii') yield ntob('\r\n') response.body = file_ranges() return response.body if debug: cherrypy.log('No byteranges requested', 'TOOLS.STATIC') response.headers['Content-Length'] = content_length response.body = fileobj return response.body
def stream_ts(self, p=None, **kwargs): """ Stream a ts from original location """ serving_request = cherrypy.serving.request if serving_request.protocol >= (1, 1): r = httputil.get_ranges(serving_request.headers.get('Range'), sys.maxint) # TODO : do something with range request if need be for bytes in self._stream_url(serving_request, kwargs.get("url")): yield bytes
def _handle_progressive_rules(self, url, rules, request, mock_shape_segment=True): stream_url = url matcher = progressive.from_rules(rules) if 'Range' in request.headers: ranges = httputil.get_ranges(request.headers.get('Range'), sys.maxint) if ranges: # can return multiple ranges # assume 1 for now action = matcher.get_action(ranges[0][0], ranges[0][1] -1) # make range inclusive else: # this is fetching an entire file, so this should be the same as matching 0-* action = matcher.get_action(0, sys.maxint) if action: if action.startswith("e"): self.ostatus(action[1:]) else: (traffic_limit, traffic_loss, cache) = shaper.parse_net_rule_action(action) port = shaper.get_shape_port_for(traffic_limit, traffic_loss, {}, mock_shape_segment) stream_url=conf.common.get_final_url("/s/{0}/progressive?url={1}&from_dripls=1".format(port, urllib.quote_plus(url)),"") return stream_url
def _serve_fileobj(fileobj, content_type, content_length, debug=False): """Internal. Set response.body to the given file object, perhaps ranged.""" response = cherrypy.serving.response # HTTP/1.0 didn't have Range/Accept-Ranges headers, or the 206 code request = cherrypy.serving.request if request.protocol >= (1, 1): response.headers["Accept-Ranges"] = "bytes" r = httputil.get_ranges(request.headers.get('Range'), content_length) if r == []: response.headers['Content-Range'] = "bytes */%s" % content_length message = ("Invalid Range (first-byte-pos greater than " "Content-Length)") if debug: cherrypy.log(message, 'TOOLS.STATIC') raise cherrypy.HTTPError(416, message) if r: if len(r) == 1: # Return a single-part response. start, stop = r[0] if stop > content_length: stop = content_length r_len = stop - start if debug: cherrypy.log( 'Single part; start: %r, stop: %r' % (start, stop), 'TOOLS.STATIC') response.status = "206 Partial Content" response.headers['Content-Range'] = ( "bytes %s-%s/%s" % (start, stop - 1, content_length)) response.headers['Content-Length'] = r_len fileobj.seek(start) response.body = file_generator_limited(fileobj, r_len) else: # Return a multipart/byteranges response. response.status = "206 Partial Content" try: # Python 3 from email.generator import _make_boundary as make_boundary except ImportError: # Python 2 from mimetools import choose_boundary as make_boundary boundary = make_boundary() ct = "multipart/byteranges; boundary=%s" % boundary response.headers['Content-Type'] = ct if "Content-Length" in response.headers: # Delete Content-Length header so finalize() recalcs it. del response.headers["Content-Length"] def file_ranges(): # Apache compatibility: yield ntob("\r\n") for start, stop in r: if debug: cherrypy.log( 'Multipart; start: %r, stop: %r' % ( start, stop), 'TOOLS.STATIC') yield ntob("--" + boundary, 'ascii') yield ntob("\r\nContent-type: %s" % content_type, 'ascii') yield ntob( "\r\nContent-range: bytes %s-%s/%s\r\n\r\n" % ( start, stop - 1, content_length), 'ascii') fileobj.seek(start) gen = file_generator_limited(fileobj, stop - start) for chunk in gen: yield chunk yield ntob("\r\n") # Final boundary yield ntob("--" + boundary + "--", 'ascii') # Apache compatibility: yield ntob("\r\n") response.body = file_ranges() return response.body else: if debug: cherrypy.log('No byteranges requested', 'TOOLS.STATIC') # Set Content-Length and use an iterable (file object) # this way CP won't load the whole file in memory response.headers['Content-Length'] = content_length response.body = fileobj return response.body
def get_ranges(self, bytes): return repr(httputil.get_ranges('bytes=%s' % bytes, 8))
def do_GET(self): if self.request_version == 'HTTP/1.1': self.protocol_version = 'HTTP/1.1' self._logger.debug("VOD request %s %s", self.client_address, self.path) downloadhash, fileindex = self.path.strip('/').split('/') downloadhash = unhexlify(downloadhash) download = self.server.session.get_download(downloadhash) if not download or not fileindex.isdigit() or int(fileindex) > len(download.get_def().get_files()): self.send_error(404, "Not Found") return fileindex = int(fileindex) filename, length = download.get_def().get_files_as_unicode_with_length()[fileindex] requested_range = get_ranges(self.headers.getheader('range'), length) if requested_range is not None and len(requested_range) != 1: self.send_error(416, "Requested Range Not Satisfiable") return has_changed = self.videoplayer.get_vod_fileindex() != fileindex or\ self.videoplayer.get_vod_download() != download if has_changed: # Notify the videoplayer (which will put the old VOD download back in normal mode). self.videoplayer.set_vod_fileindex(fileindex) self.videoplayer.set_vod_download(download) # Put download in sequential mode + trigger initial buffering. if download.get_def().is_multifile_torrent(): download.set_selected_files([filename]) download.set_mode(DLMODE_VOD) download.restart() piecelen = download.get_def().get_piece_length() blocksize = piecelen if requested_range is not None: firstbyte, lastbyte = requested_range[0] nbytes2send = lastbyte - firstbyte self.send_response(206) self.send_header('Content-Range', 'bytes %d-%d/%d' % (firstbyte, lastbyte - 1, length)) else: firstbyte = 0 nbytes2send = length self.send_response(200) self._logger.debug("requested range %d - %d", firstbyte, firstbyte + nbytes2send) mimetype = mimetypes.guess_type(filename)[0] if mimetype: self.send_header('Content-Type', mimetype) self.send_header('Accept-Ranges', 'bytes') if length is not None: self.send_header('Content-Length', nbytes2send) else: self.send_header('Transfer-Encoding', 'chunked') if self.request_version == 'HTTP/1.1' and self.headers.get('Connection', '').lower() != 'close': self.send_header('Connection', 'Keep-Alive') self.send_header('Keep-Alive', 'timeout=300, max=1') self.end_headers() if has_changed: self.wait_for_buffer(download) stream, lock = self.videoplayer.get_vod_stream(downloadhash, wait=True) with lock: if stream.closed: return stream.seek(firstbyte) nbyteswritten = 0 while True: data = stream.read(blocksize) if len(data) == 0: break elif length is not None and nbyteswritten + len(data) > nbytes2send: endlen = nbytes2send - nbyteswritten if endlen != 0: self.wfile.write(data[:endlen]) nbyteswritten += endlen break else: self.wfile.write(data) nbyteswritten += len(data) if nbyteswritten != nbytes2send: self._logger.error("sent wrong amount, wanted %s got %s", nbytes2send, nbyteswritten) if not requested_range: stream.close()
def serve_fileobj(fileobj, headers, content_length): status_code = 200 headers["Accept-Ranges"] = "bytes" r = httputil.get_ranges(headers.get('Range'), content_length) if r == []: headers['Content-Range'] = "bytes */{0}".format(content_length) message = "Invalid Range (first-byte-pos greater than Content-Length)" raise RequestedRangeNotSatisfiable(message) if not r: headers['Content-Length'] = content_length return fileobj, headers, status_code # Return a multipart/byteranges response. status_code = 206 if len(r) == 1: # Return a single-part response. start, stop = r[0] if stop > content_length: stop = content_length r_len = stop - start headers['Content-Range'] = "bytes {0}-{1}/{2}".format( start, stop - 1, content_length ) headers['Content-Length'] = r_len fileobj.seek(start) body = file_generator_limited(fileobj, r_len) return body, headers, status_code try: # Python 3 from email.generator import _make_boundary as make_boundary except ImportError: # Python 2 from mimetools import choose_boundary as make_boundary boundary = make_boundary() content_type = "multipart/byteranges; boundary={0}".format(boundary) headers['Content-Type'] = content_type if "Content-Length" in headers: del headers["Content-Length"] def file_ranges(): for start, stop in r: yield to_unicode("--" + boundary) yield to_unicode("\r\nContent-type: {0}".format(content_type)) yield to_unicode( "\r\nContent-range: bytes {0}-{1}/{2}\r\n\r\n".format( start, stop - 1, content_length ) ) fileobj.seek(start) gen = file_generator_limited(fileobj, stop - start) for chunk in gen: yield chunk yield to_unicode("\r\n") yield to_unicode("--" + boundary + "--") body = file_ranges() return body, headers, status_code
def serve_fileobj(fileobj, headers, content_length): status_code = 200 headers["Accept-Ranges"] = "bytes" r = httputil.get_ranges(headers.get('Range'), content_length) if r == []: headers['Content-Range'] = "bytes */{0}".format(content_length) message = "Invalid Range (first-byte-pos greater than Content-Length)" raise RequestedRangeNotSatisfiable(message) if not r: headers['Content-Length'] = content_length return fileobj, headers, status_code # Return a multipart/byteranges response. status_code = 206 if len(r) == 1: # Return a single-part response. start, stop = r[0] if stop > content_length: stop = content_length r_len = stop - start headers['Content-Range'] = "bytes {0}-{1}/{2}".format( start, stop - 1, content_length) headers['Content-Length'] = r_len fileobj.seek(start) body = file_generator_limited(fileobj, r_len) return body, headers, status_code try: # Python 3 from email.generator import _make_boundary as make_boundary except ImportError: # Python 2 from mimetools import choose_boundary as make_boundary boundary = make_boundary() content_type = "multipart/byteranges; boundary={0}".format(boundary) headers['Content-Type'] = content_type if "Content-Length" in headers: del headers["Content-Length"] def file_ranges(): for start, stop in r: yield to_unicode("--" + boundary) yield to_unicode("\r\nContent-type: {0}".format(content_type)) yield to_unicode( "\r\nContent-range: bytes {0}-{1}/{2}\r\n\r\n".format( start, stop - 1, content_length)) fileobj.seek(start) gen = file_generator_limited(fileobj, stop - start) for chunk in gen: yield chunk yield to_unicode("\r\n") yield to_unicode("--" + boundary + "--") body = file_ranges() return body, headers, status_code
def serve_raw_file(self, file, content_type=None, disposition=None, name=None): # Adapted from CherryPy's serve_file(), modified to work with file-like # objects response = cherrypy.response st = None if isinstance(file, str): path = file if not os.path.isabs(path): raise ValueError("'%s' is not an absolute path." % path) try: st = os.stat(path) except OSError: raise cherrypy.NotFound() if stat.S_ISDIR(st.st_mode): raise cherrypy.NotFound() response.headers['Last-Modified'] = httputil.HTTPDate(st.st_mtime) cptools.validate_since() file = open(path, "rb") else: path = getattr(file, "name", None) if path: try: st = os.stat(path) except OSError: pass else: if not hasattr(file, "read"): raise ValueError( "Expected a file-like object, got %r instead " "(object has no read() method)" % file) if not hasattr(file, "seek"): raise ValueError("Can't serve file-like object %r " "(object has no seek() method)" % file) if not hasattr(file, "tell"): raise ValueError("Can't serve file-like object %r " "(object has no tell() method)" % file) # Set the content type if content_type is None: if path: content_type = mimetypes.guess_type(path)[0] if not content_type: content_type = "text/plain" response.headers["Content-Type"] = content_type # Set the content disposition if disposition is not None: cd = disposition if not name and path: name = os.path.basename(path) if name: cd = rfc6266.build_header(name, cd) response.headers["Content-Disposition"] = cd if self.use_xsendfile and path: response.headers["X-Sendfile"] = path return "" # Find the size of the file if st is None: start = file.tell() file.seek(0, 2) # Move to the end of the file c_len = file.tell() - start file.seek(start) else: c_len = st.st_size # HTTP/1.0 didn't have Range/Accept-Ranges headers, or the 206 code if cherrypy.request.protocol >= (1, 1): response.headers["Accept-Ranges"] = "bytes" r = httputil.get_ranges(cherrypy.request.headers.get('Range'), c_len) if r == []: response.headers['Content-Range'] = "bytes */%s" % c_len message = "Invalid Range (first-byte-pos greater than Content-Length)" raise cherrypy.HTTPError(416, message) if r: if len(r) == 1: # Return a single-part response. start, stop = r[0] if stop > c_len: stop = c_len r_len = stop - start response.status = "206 Partial Content" response.headers['Content-Range'] = ( "bytes %s-%s/%s" % (start, stop - 1, c_len)) response.headers['Content-Length'] = r_len file.seek(start) response.body = file_generator_limited(file, r_len) else: # Return a multipart/byteranges response. response.status = "206 Partial Content" import mimetools boundary = mimetools.choose_boundary() ct = "multipart/byteranges; boundary=%s" % boundary response.headers['Content-Type'] = ct if "Content-Length" in response.headers: # Delete Content-Length header so finalize() recalcs it. del response.headers["Content-Length"] def file_ranges(): # Apache compatibility: yield "\r\n" for start, stop in r: yield "--" + boundary yield "\r\nContent-type: %s" % content_type yield ( "\r\nContent-range: bytes %s-%s/%s\r\n\r\n" % (start, stop - 1, c_len)) file.seek(start) for chunk in file_generator_limited( file, stop - start): yield chunk yield "\r\n" # Final boundary yield "--" + boundary + "--" # Apache compatibility: yield "\r\n" response.body = file_ranges() else: response.headers['Content-Length'] = c_len response.body = file else: response.headers['Content-Length'] = c_len response.body = file return response.body
def _serve_fileobj(fileobj, content_type, content_length, debug=False): response = cherrypy.serving.response request = cherrypy.serving.request if request.protocol >= (1, 1): response.headers["Accept-Ranges"] = "bytes" r = httputil.get_ranges(request.headers.get("Range"), content_length) if r == []: response.headers["Content-Range"] = "bytes */%s" % content_length message = "Invalid Range (first-byte-pos greater than Content-Length)" if debug: cherrypy.log(message, "TOOLS.STATIC") raise cherrypy.HTTPError(416, message) if r: if len(r) == 1: start, stop = r[0] if stop > content_length: stop = content_length r_len = stop - start if debug: cherrypy.log("Single part; start: %r, stop: %r" % (start, stop), "TOOLS.STATIC") response.status = "206 Partial Content" response.headers["Content-Range"] = "bytes %s-%s/%s" % (start, stop - 1, content_length) response.headers["Content-Length"] = r_len fileobj.seek(start) response.body = file_generator_limited(fileobj, r_len) else: response.status = "206 Partial Content" from mimetools import choose_boundary boundary = choose_boundary() ct = "multipart/byteranges; boundary=%s" % boundary response.headers["Content-Type"] = ct if "Content-Length" in response.headers: del response.headers["Content-Length"] def file_ranges(): yield ntob("\r\n") for start, stop in r: if debug: cherrypy.log("Multipart; start: %r, stop: %r" % (start, stop), "TOOLS.STATIC") yield ntob("--" + boundary, "ascii") yield ntob("\r\nContent-type: %s" % content_type, "ascii") yield ntob( "\r\nContent-range: bytes %s-%s/%s\r\n\r\n" % (start, stop - 1, content_length), "ascii" ) fileobj.seek(start) for chunk in file_generator_limited(fileobj, stop - start): yield chunk yield ntob("\r\n") yield ntob("--" + boundary + "--", "ascii") yield ntob("\r\n") response.body = file_ranges() return response.body if debug: cherrypy.log("No byteranges requested", "TOOLS.STATIC") response.headers["Content-Length"] = content_length response.body = fileobj return response.body
def _serve_fileobj(fileobj, content_type, content_length, debug=False): """Internal. Set response.body to the given file object, perhaps ranged.""" response = cherrypy.serving.response # HTTP/1.0 didn't have Range/Accept-Ranges headers, or the 206 code request = cherrypy.serving.request if request.protocol >= (1, 1): response.headers["Accept-Ranges"] = "bytes" r = httputil.get_ranges(request.headers.get("Range"), content_length) if r == []: response.headers["Content-Range"] = "bytes */%s" % content_length message = "Invalid Range (first-byte-pos greater than Content-Length)" if debug: cherrypy.log(message, "TOOLS.STATIC") raise cherrypy.HTTPError(416, message) if r: if len(r) == 1: # Return a single-part response. start, stop = r[0] if stop > content_length: stop = content_length r_len = stop - start if debug: cherrypy.log("Single part; start: %r, stop: %r" % (start, stop), "TOOLS.STATIC") response.status = "206 Partial Content" response.headers["Content-Range"] = "bytes %s-%s/%s" % (start, stop - 1, content_length) response.headers["Content-Length"] = r_len fileobj.seek(start) response.body = file_generator_limited(fileobj, r_len) else: # Return a multipart/byteranges response. response.status = "206 Partial Content" try: # Python 3 from email.generator import _make_boundary as choose_boundary except ImportError: # Python 2 from mimetools import choose_boundary boundary = choose_boundary() ct = "multipart/byteranges; boundary=%s" % boundary response.headers["Content-Type"] = ct if "Content-Length" in response.headers: # Delete Content-Length header so finalize() recalcs it. del response.headers["Content-Length"] def file_ranges(): # Apache compatibility: yield ntob("\r\n") for start, stop in r: if debug: cherrypy.log("Multipart; start: %r, stop: %r" % (start, stop), "TOOLS.STATIC") yield ntob("--" + boundary, "ascii") yield ntob("\r\nContent-type: %s" % content_type, "ascii") yield ntob( "\r\nContent-range: bytes %s-%s/%s\r\n\r\n" % (start, stop - 1, content_length), "ascii" ) fileobj.seek(start) for chunk in file_generator_limited(fileobj, stop - start): yield chunk yield ntob("\r\n") # Final boundary yield ntob("--" + boundary + "--", "ascii") # Apache compatibility: yield ntob("\r\n") response.body = file_ranges() return response.body else: if debug: cherrypy.log("No byteranges requested", "TOOLS.STATIC") # Set Content-Length and use an iterable (file object) # this way CP won't load the whole file in memory response.headers["Content-Length"] = content_length response.body = fileobj return response.body
def _serve_fileobj(fileobj, content_type, content_length, debug=False): """Internal. Set response.body to the given file object, perhaps ranged.""" response = cherrypy.serving.response # HTTP/1.0 didn't have Range/Accept-Ranges headers, or the 206 code request = cherrypy.serving.request if request.protocol >= (1, 1): response.headers['Accept-Ranges'] = 'bytes' r = httputil.get_ranges(request.headers.get('Range'), content_length) if r == []: response.headers['Content-Range'] = 'bytes */%s' % content_length message = ('Invalid Range (first-byte-pos greater than ' 'Content-Length)') if debug: cherrypy.log(message, 'TOOLS.STATIC') raise cherrypy.HTTPError(416, message) if r: if len(r) == 1: # Return a single-part response. start, stop = r[0] if stop > content_length: stop = content_length r_len = stop - start if debug: cherrypy.log( 'Single part; start: %r, stop: %r' % (start, stop), 'TOOLS.STATIC') response.status = '206 Partial Content' response.headers['Content-Range'] = ( 'bytes %s-%s/%s' % (start, stop - 1, content_length)) response.headers['Content-Length'] = r_len fileobj.seek(start) response.body = file_generator_limited(fileobj, r_len) else: # Return a multipart/byteranges response. response.status = '206 Partial Content' boundary = make_boundary() ct = 'multipart/byteranges; boundary=%s' % boundary response.headers['Content-Type'] = ct if 'Content-Length' in response.headers: # Delete Content-Length header so finalize() recalcs it. del response.headers['Content-Length'] def file_ranges(): # Apache compatibility: yield ntob('\r\n') for start, stop in r: if debug: cherrypy.log( 'Multipart; start: %r, stop: %r' % ( start, stop), 'TOOLS.STATIC') yield ntob('--' + boundary, 'ascii') yield ntob('\r\nContent-type: %s' % content_type, 'ascii') yield ntob( '\r\nContent-range: bytes %s-%s/%s\r\n\r\n' % ( start, stop - 1, content_length), 'ascii') fileobj.seek(start) gen = file_generator_limited(fileobj, stop - start) for chunk in gen: yield chunk yield ntob('\r\n') # Final boundary yield ntob('--' + boundary + '--', 'ascii') # Apache compatibility: yield ntob('\r\n') response.body = file_ranges() return response.body else: if debug: cherrypy.log('No byteranges requested', 'TOOLS.STATIC') # Set Content-Length and use an iterable (file object) # this way CP won't load the whole file in memory response.headers['Content-Length'] = content_length response.body = fileobj return response.body
def do_GET(self): if self.request_version == 'HTTP/1.1': self.protocol_version = 'HTTP/1.1' self._logger.debug("VOD request %s %s", self.client_address, self.path) downloadhash, fileindex = self.path.strip('/').split('/') downloadhash = unhexlify(downloadhash) download = self.server.session.get_download(downloadhash) if not download or not fileindex.isdigit() or int(fileindex) > len( download.get_def().get_files()): self.send_error(404, "Not Found") return fileindex = int(fileindex) filename, length = download.get_def().get_files_as_unicode_with_length( )[fileindex] requested_range = get_ranges(self.headers.getheader('range'), length) if requested_range is not None and len(requested_range) != 1: self.send_error(416, "Requested Range Not Satisfiable") return has_changed = self.videoplayer.get_vod_fileindex() != fileindex or\ self.videoplayer.get_vod_download() != download if has_changed: # Notify the videoplayer (which will put the old VOD download back in normal mode). self.videoplayer.set_vod_fileindex(fileindex) self.videoplayer.set_vod_download(download) # Put download in sequential mode + trigger initial buffering. if download.get_def().is_multifile_torrent(): download.set_selected_files([filename]) download.set_mode(DLMODE_VOD) download.restart() piecelen = download.get_def().get_piece_length() blocksize = piecelen if requested_range is not None: firstbyte, lastbyte = requested_range[0] nbytes2send = lastbyte - firstbyte self.send_response(206) self.send_header( 'Content-Range', 'bytes %d-%d/%d' % (firstbyte, lastbyte - 1, length)) else: firstbyte = 0 nbytes2send = length self.send_response(200) self._logger.debug("requested range %d - %d", firstbyte, firstbyte + nbytes2send) mimetype = mimetypes.guess_type(filename)[0] if mimetype: self.send_header('Content-Type', mimetype) self.send_header('Accept-Ranges', 'bytes') if length is not None: self.send_header('Content-Length', nbytes2send) else: self.send_header('Transfer-Encoding', 'chunked') if self.request_version == 'HTTP/1.1' and self.headers.get( 'Connection', '').lower() != 'close': self.send_header('Connection', 'Keep-Alive') self.send_header('Keep-Alive', 'timeout=300, max=1') self.end_headers() if has_changed: self.wait_for_buffer(download) stream, lock = self.videoplayer.get_vod_stream(downloadhash, wait=True) with lock: if stream.closed: return stream.seek(firstbyte) nbyteswritten = 0 while True: data = stream.read(blocksize) if len(data) == 0: break elif length is not None and nbyteswritten + len( data) > nbytes2send: endlen = nbytes2send - nbyteswritten if endlen != 0: self.wfile.write(data[:endlen]) nbyteswritten += endlen break else: self.wfile.write(data) nbyteswritten += len(data) if nbyteswritten != nbytes2send: self._logger.error("sent wrong amount, wanted %s got %s", nbytes2send, nbyteswritten) if not requested_range: stream.close()
def getMedia(self, id): #https://github.com/happyworm/smartReadFile/blob/master/smartReadFile.php if not self.isValidId(id): raise cherrypy.HTTPError(404) itemService = ItemService() item = itemService.findOne(id) if item is None: raise cherrypy.HTTPError(404) if not os.path.isfile(item.mediaUri): raise cherrypy.HTTPError(404) from cherrypy.lib import httputil file = os.stat(item.mediaUri) contentLength = file.st_size begin = 0 end = contentLength - 1 r = httputil.get_ranges(cherrypy.request.headers.get("Range"), contentLength) if r is None: pass else: if r==[]: pass else: begin, end = r[0] if range in cherrypy.request.headers: cherrypy.response.status = "206 Partial Content" else: cherrypy.response.status = "200 OK" cherrypy.response.headers["Content-Type"] = mimetypes.guess_type(item.mediaUri)[0] cherrypy.response.headers["Cache-Control"] = "public, must-revalidate, max-age=0" cherrypy.response.headers["Pragma"] = "no-cache" cherrypy.response.headers["Accept-Ranges"] = "bytes" cherrypy.response.headers["Content-Length"] = str(end-begin+1) if range in cherrypy.request.headers: cherrypy.response.headers["Content-Range"] = "bytes {0}-{1}/{contentLength}".format(begin, end, contentLength) cherrypy.response.headers["Content-Disposition"] = "inline; filename=" + os.path.basename(item.mediaUri) cherrypy.response.headers["Content-Transfer-Encoding"] = "binary" cherrypy.response.headers["Last-Modified"] = file.st_mtime file = open (item.mediaUri, "rb") current = begin file.seek(current, 0) def stream(file, current, end): bufferSize = 1024*128 while current<=end: data = file.read(min(bufferSize, (end-current)+1)) if len(data)==0: if file is not None: file.close() return yield data current += bufferSize return stream(file, current, end)