如何用内置Python HTTP服务器提供mp3文件

3 投票
2 回答
6510 浏览
提问于 2025-04-16 15:45

我现在正在尝试用Python来播放MP3文件。问题是我只能播放一次MP3,之后媒体控制就不再响应了,我必须完全重新加载页面才能再次听到MP3。(在Chrome浏览器中测试过)

问题:运行下面的代码,然后在浏览器中输入 http://127.0.0.1/test.mp3,会返回一个MP3文件,但只能在刷新页面后才能重新播放。

注意:

  • 将页面保存为HTML文件并直接用Chrome打开(不通过Python服务器)就不会出现这个问题。

  • 用Apache来提供文件可以解决这个问题,但这太复杂了:我想让这个脚本非常简单,不需要安装Apache

这是我使用的代码:

import string
import os
import urllib
import socket

# Setup web server import string,cgi,time
import string,cgi,time
from os import curdir, sep
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
import hashlib

class MyHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        try:
            # serve mp3 files
            if self.path.endswith(".mp3"):
                print curdir + sep + self.path
                f = open(curdir + sep + self.path, 'rb')
                st = os.fstat( f.fileno() )
                length = st.st_size
                data = f.read()
                md5 = hashlib.md5()
                md5.update(data)
                md5_key = self.headers.getheader('If-None-Match')
                if md5_key:
                  if md5_key[1:-1] == md5.hexdigest():
                    self.send_response(304)
                    self.send_header('ETag', '"{0}"'.format(md5.hexdigest()))
                    self.send_header('Keep-Alive', 'timeout=5, max=100')
                    self.end_headers()
                    return

                self.send_response(200)
                self.send_header('Content-type',    'audio/mpeg')
                self.send_header('Content-Length', length )
                self.send_header('ETag', '"{0}"'.format(md5.hexdigest()))
                self.send_header('Accept-Ranges', 'bytes')
                self.send_header('Last-Modified', time.strftime("%a %d %b %Y %H:%M:%S GMT",time.localtime(os.path.getmtime('test.mp3'))))
                self.end_headers()
                self.wfile.write(data)
                f.close()
            return
        except IOError:
           self.send_error(404,'File Not Found: %s' % self.path)

from SocketServer import ThreadingMixIn
class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
    pass

if __name__ == "__main__":
    try:
       server = ThreadedHTTPServer(('', 80), MyHandler)
       print 'started httpserver...'
       server.serve_forever()
    except KeyboardInterrupt:
       print '^C received, shutting down server'
       server.socket.close()

2 个回答

2

编辑:我写这段内容的时候没意识到Mapadd只是打算在实验室里使用这个。对于他的使用场景,WSGI可能并不是必须的。

如果你愿意把这个当作一个wsgi应用来运行(我建议这样做,因为相比普通的CGI,这样更适合扩展),你可以使用我下面提供的脚本。

我对你的源代码做了一些修改……这个是基于之前的假设来工作的。顺便说一下,你应该花点时间检查一下你的HTML是否符合标准……这样可以帮助你在不同浏览器之间获得更好的兼容性……原来的代码里没有<head><body>标签……我下面的代码是严格的原型HTML,可以进一步改进。

要运行这个,你只需要在你的命令行中运行Python可执行文件,然后访问机器的IP地址,端口是8080。如果你是为了一个生产网站在做这个,我们应该使用lighttpd或apache来提供文件,但因为这只是为了实验室使用,内嵌的WSGI参考服务器应该就可以了。如果你想在apache或lighttpd上运行,可以替换文件底部的WSGIServer那一行。

保存为 mp3.py

from webob import Request
import re
import os
import sys

####
#### Run with:
#### twistd -n web --port 8080 --wsgi mp3.mp3_app

_MP3DIV = """<div id="musicHere"></div>"""

_MP3EMBED = """<embed src="mp3/" loop="true" autoplay="false" width="145" height="60"></embed>"""

_HTML = '''<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"><html><head></head><body> Hello %s %s</body></html> ''' % (_MP3DIV, _MP3EMBED)

def mp3_html(environ, start_response):
    """This function will be mounted on "/" and refer the browser to the mp3 serving URL."""

    start_response('200 OK', [('Content-Type', 'text/html')])
    return [_HTML]

def mp3_serve(environ, start_response):
    """Serve the MP3, one chunk at a time with a generator"""
    file_path = "/file/path/to/test.mp3"
    mimetype = "application/x-mplayer2"
    size = os.path.getsize(file_path)
    headers = [
        ("Content-type", mimetype),
        ("Content-length", str(size)),
    ]
    start_response("200 OK", headers)
    return send_file(file_path, size)

def send_file(file_path, size):
    BLOCK_SIZE = 4096
    fh = open(file_path, 'r')
    while True:
        block = fh.read(BLOCK_SIZE)
        if not block:
            fh.close()
            break
        yield block

def _not_found(environ,start_response):
    """Called if no URL matches."""
    start_response('404 NOT FOUND', [('Content-Type', 'text/plain')])
    return ['Not Found']

def mp3_app(environ,start_response):
    """
    The main WSGI application. Dispatch the current request to
    the functions andd store the regular expression
    captures in the WSGI environment as  `mp3app.url_args` so that
    the functions from above can access the url placeholders.

    If nothing matches call the `not_found` function.
    """
    # map urls to functions
    urls = [
        (r'^$', mp3_html),
        (r'mp3/?$', mp3_serve),
    ]
    path = environ.get('PATH_INFO', '').lstrip('/')
    for regex, callback in urls:
        match = re.search(regex, path)
        if match is not None:
            # assign http environment variables...
            environ['mp3app.url_args'] = match.groups()
            return callback(environ, start_response)
    return _not_found(environ, start_response)

在bash命令行中运行:twistd -n web --port 8080 --wsgi mp3.mp3_app,记得在你保存mp3.py的目录下运行(或者把mp3.py放在$PYTHONPATH的某个地方)。

现在访问外部IP(比如说http://some.ip.local:8080/),它会直接提供mp3文件。

我尝试直接运行你原来的应用,结果没法找到mp3文件,Linux给我报了错……

3

BaseServer 是单线程的,这意味着它一次只能处理一个连接。如果你想让它能够同时处理多个连接,你需要使用 ForkingMixIn 或者 ThreadingMixIn

举个例子,你可以把这一行替换为:

server = HTTPServer(('', 80), MyHandler)

from SocketServer import ThreadingMixIn

class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
    pass

server = ThreadedHTTPServer(('', 80), MyHandler)

撰写回答