欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

如何用手机看电脑上的视频和字幕

程序员文章站 2022-06-04 19:50:01
...

需求背景

尽管现在用手机看片更方便,但无奈下片还是用的电脑,而且手机也不可能存个几百个G的片吧,所以有好几次都试图找一个简单的用手机看电脑上的视频的方法,试过共享文件夹,ES浏览器,windows自带的mediaplayer之类的方案。虽然最后勉强能看片了,但是一方面,我觉得设置起来很麻烦,另一方面,这些方案都不支持外接的ASS/SSA/SRT字幕播放。

因此,在无字幕强行啃生肉多次之后。。我终于决定自己实现一个能带字幕播放媒体文件的文件浏览器。

最终实现

一个本机运行的文件服务器(由python脚本打包而成的EXE可执行程序) + 通过手机浏览器查看。
支持自动加载视频文件同名SRT/SSA/ASS字幕。
支持扫二维码快速访问。

优势

简单(双击打开就能运行,不用任何设置) 自动加载字幕
另外这个文件浏览器也顺便让不同设备传文件变的简单了,以前我还得开个微信文件传输助手传来传去,速度慢还有100M的大小限制

实现流程

首先确定网络服务器框架代码,这里我使用python的flask框架。如果是视频文件,跳转播放页;如果是文件夹,则显示文件列表;如果是普通文件,则直接提供下载。

然后是字幕转换问题,由于原生的html字幕只支持vtt格式,所以我需要把ASS、SRT字幕都转成vtt格式,试验了一些三方依赖之后我选择了 webvtt和asstosrt 这两个库。后者支持ASS和SSA字幕转SRT。

最后,给出源代码及封装好的EXE文件地址:源码及EXE下载地址(github)
因为github可能打不开,所以也传了一份百度云
链接: https://pan.baidu.com/s/11EIkdlbJfu2k-aAj3XWv6g 提取码: n8tj

附python源码,写的很丑陋,仅供参考。如果有python环境的可以安装相关依赖后从脚本运行。
祝大家撸的开心。。。。

# -*- coding: utf-8 -*-

from flask import Flask, send_from_directory, send_file
from gevent import monkey
from gevent.pywsgi import WSGIServer
import os
import urllib
import sys
import html
import os
import webvtt
import asstosrt
import chardet
from tkinter import *
from tkinter import filedialog
import locale
import time

monkey.patch_all()
from flask_cors import *

app = Flask(__name__)
CORS(app, supports_credentials=True)

WORK_DIRECTORY = '/'
SEP = '/'
lang = locale.getdefaultlocale()[0]
IS_ZH = 'CN' in lang
BACK = "返回首页" if IS_ZH else 'back to index'
NAME = '仔仔文件传输助手' if IS_ZH else "ZZ file explorer"


@app.route('/')
def hello_world():
    return get_list_page(WORK_DIRECTORY)


@app.route("/static/<path:path>", methods=["GET"])
def static_dir(path):
    return send_from_directory('./static/', path)


@app.route("/file/<path:path>", methods=["GET"])
def get_file(path):
    print('getfile', path)
    suffix = path.split(".")
    suffix = suffix[-1] if suffix else ""
    path = urllib.parse.unquote(path)
    path = path.replace("@@", SEP)
    print(path)
    if path.endswith(SEP):  # 判断是否是文件夹
        return get_list_page(path)
    elif suffix not in ["exe", "bat"]:
        path = path.replace("@@", SEP)
        return send_file(path, conditional=True)


@app.route("/play/<path:path>", methods=["GET"])
def get_video_player(path):
    # show the user profile for that user
    print(path)
    r = []
    try:
        displaypath = urllib.parse.unquote(path,
                                           errors='surrogatepass')
    except UnicodeDecodeError:
        displaypath = urllib.parse.unquote(path)

    displaypath = html.escape(displaypath, quote=False)
    enc = sys.getfilesystemencoding()
    title = 'Player for %s' % displaypath
    r.append('<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" '
             '"http://www.w3.org/TR/html4/strict.dtd">')
    r.append('<html>\n<head>')

    r.append('<meta http-equiv="Content-Type" '
             'content="text/html; charset=%s">' % enc)
    r.append('<title>%s</title>\n</head>' % title)
    # r.append('<script src="/static/player.js"></script>'
    #          # '<script src="/static/videosub-0.9.9.js"></script>'
    #          )
    r.append('<body>\n<h1>%s</h1>' % title)
    r.append('<li><a href="%s">%s</a></li>'
             % ('/',
                html.escape(BACK, quote=False)))
    r.append(
        '\n<p align="center"><video id="mainPlayer"   controls="controls" width="640" height="480">')
    caption_path = getCaption(path.replace('@@', SEP)).replace(SEP, '@@')
    r.append('''
    <source src="/file/%s" type="video/mp4">
     <track id="video-caption" src="/file/%s"  
     kind="captions" srclang="zh" label="Chinese" default/>
    ''' % (path, caption_path))
    r.append('</video></p>\n\n')
    r.append('</body>\n</html>\n')
    return '\n'.join(r)


def get_list_page(path):
    """Helper to produce a directory listing (absent index.html).

    Return value is either a file object, or None (indicating an
    error).  In either case, the headers are sent, making the
    interface the same as for send_head().

    """

    try:
        list = os.listdir(path)
    except OSError as e:
        print(e)
        return None
    list.sort(key=lambda a: a.lower())
    r = []
    displaypath = html.escape(path, quote=False)
    enc = sys.getfilesystemencoding()
    title = NAME + ' Directory listing for %s' % displaypath
    r.append('<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" '
             '"http://www.w3.org/TR/html4/strict.dtd">')
    r.append('<html>\n<head>')
    r.append('<meta http-equiv="Content-Type" '
             'content="text/html; charset=%s">' % enc)
    r.append('<title>%s</title>\n</head>' % title)
    r.append('<body>\n<h1>%s</h1>' % title)
    r.append('<hr>\n<ul>')
    if path != WORK_DIRECTORY:  # 'a/b/c/'  -> 'a/b/'
        r.append('<li><a href="%s">%s</a></li>'
                 % ('/',
                    html.escape(BACK, quote=False)))

    for name in list:
        fullname = os.path.join(path, name)
        displayname = linkname = name
        # print(name, fullname)
        # Append / for directories
        if os.path.isdir(fullname):
            fullname = fullname + "/"
            displayname = name + "/"
            linkname = name + "/"

        suffix = linkname.split('.')[-1]
        if suffix in ['mp4', 'avi', 'mkv']:
            r.append('<li><a href="%s">%s</a></li>'
                     % ('/play/' + urllib.parse.quote(fullname.replace(SEP, "@@")),
                        html.escape(displayname, quote=False)))
        else:
            r.append('<li><a href="%s">%s</a></li>'
                     % ('/file/' + urllib.parse.quote(fullname.replace(SEP, "@@")),
                        html.escape(displayname, quote=False)))
    r.append('</ul>\n<hr>\n</body>\n</html>\n')
    return '\n'.join(r)


def getCaption(videoPath):
    # 根据video名称寻找字幕
    print('get caption',videoPath)
    vtt_exist, srt_exist, ass_exist,ssa_exist = False, False, False,False
    dir_path = os.path.dirname(videoPath)
    videoPath = videoPath.split('/')[-1]
    videoName = videoPath.split('.')[0]
    print(videoName)
    vtt_name = videoName + '.vtt'
    srt_name = videoName + '.srt'
    ass_name = videoName + '.ass'
    ssa_name = videoName + '.ssa'
    file_list = os.listdir(dir_path)
    for file_name in file_list:
        # print(file_name)
        if file_name == vtt_name:
            vtt_exist = True
        elif file_name == srt_name:
            srt_exist = True
        elif file_name == ass_name:
            ass_exist = True
        elif file_name == ssa_name:
            ssa_exist = True
    if vtt_exist:
        return os.path.join(dir_path, vtt_name)
    elif srt_exist:
        return srt2vtt(srt_name, dir_path)
    elif ass_exist:
        return ass2vtt(videoName, dir_path,ass_name)
    elif ssa_exist:
        return ass2vtt(videoName, dir_path,ssa_name)
    else:
        return ''


def srt2vtt(srt_name, dir_path):
    print('srt2vtt',srt_name)
    srt_path = os.path.join(dir_path, srt_name)
    with  open(file=srt_path, mode='rb')  as f3:  # 以二进制模式读取文件
        data = f3.read()  # 获取文件内容
        # print(data)
        f3.close()  # 关闭文件
        origin_charset = chardet.detect(data)['encoding']  # 检测文件内容
        print(origin_charset)

    convert_webvtt = webvtt.from_srt(srt_path)
    convert_webvtt.save()
    return convert_webvtt.file


def removeIfExists(file_path):
    if (os.path.exists(file_path)):
        os.remove(file_path)


def ass2vtt(video_name, dir_path,ass_name):
    # 编码转换 然后用pysub
    ass_path = os.path.join(dir_path, ass_name)
    print(ass_path)
    with  open(file=ass_path, mode='rb')  as f3:  # 以二进制模式读取文件
        data = f3.read()  # 获取文件内容
        # print(data)
        f3.close()  # 关闭文件
        origin_charset = chardet.detect(data)['encoding']  # 检测文件内容
        print(origin_charset)
    tmp_path = ass_path
    tmp_ass_path = os.path.join(dir_path, 'tmp.ass')
    if origin_charset != 'utf8':
        tmp_path = tmp_ass_path
        with open(tmp_path, 'wb') as tmp_file:
            data = data.decode(origin_charset).encode('utf8')
            tmp_file.write(data)
    with open(tmp_path,encoding='utf8') as f:
        srt_str = asstosrt.convert(f)
        print(srt_str)
    removeIfExists(tmp_ass_path)
    tmp_srt_path = os.path.join(dir_path, video_name + '.srt')
    with open(tmp_srt_path, 'w',newline="",encoding="utf8") as f:
        f.write(srt_str)
    return srt2vtt(tmp_srt_path, dir_path)


def get_ip():
    import socket
    ip = '本机ip'
    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        s.connect(('8.8.8.8', 80))
        ip = s.getsockname()[0]
    finally:
        s.close()
    print(ip)
    return ip


def show_qcode(url):
    import qrcode
    qr = qrcode.QRCode(version=2, error_correction=qrcode.constants.ERROR_CORRECT_L, box_size=10, border=10, )
    qr.add_data(url)
    qr.make(fit=True)
    img = qr.make_image()
    img.show()


if __name__ == '__main__':
    # 或者app.debug = True #代码修改了就自动运行
    # app.run(host='0.0.0.0',port=30006,debug=True)
    try:
        if IS_ZH:
            print("请在弹出框里选择需要共享的目录")
        else:
            print('please select directory to share')
        window = Tk()
        window.withdraw()  # 主窗口隐藏
        WORK_DIRECTORY = filedialog.askdirectory(parent=window, initialdir="/",
                                                 title='choose the share directory')
        if not WORK_DIRECTORY:
            print('cancel')
            sys.exit(1)
        url = "http://%s:30007" % get_ip()
        print("共享目录 " if IS_ZH else 'share directory: ', WORK_DIRECTORY)
        print("服务器启动" if IS_ZH else 'server started  ')
        print(
            '用手机或电脑浏览器访问 %s (或扫描生成的二维码)即可访问共享目录内容' % url if IS_ZH else
            'open link %s with browser on PC/mobile (or just scan the QR code),to xisit your content' % url)
        show_qcode(url)
        WSGIServer(('0.0.0.0', 30007), app).serve_forever()
    except Exception as e:
        print(e)
        time.sleep(10)