#!/usr/bin/env python3
"""
Flask 挂载 /video0：将 /dev/video0 作为 MJPEG 流通过 HTTP 暴露。
- 流地址：/video0
- 单帧快照：/video0/snapshot

在 Raspberry Pi 上测试通过（v4l2 摄像头）。
依赖：Flask, opencv-python, numpy
    pip install flask opencv-python numpy

运行：
    python app_video0.py --host 0.0.0.0 --port 5000 
然后访问：
    http://<Pi的IP>:5000/video0  （浏览器可直接播放）
    http://<Pi的IP>:5000/video0/snapshot  （单张JPEG）

如果你已有一个现有的 Flask 应用，把下面的 blueprint 挂载进去即可（见文末整合说明）。
"""

import argparse
import io
import time
from typing import Generator, Optional

import cv2
import numpy as np
from flask import Flask, Blueprint, Response, abort, jsonify, make_response

# === 可调参数 ===
DEVICE = "/dev/video0"  # v4l2 设备
FRAME_WIDTH = 1920       # 调整为你的分辨率
FRAME_HEIGHT =  1080
FPS = 60                # 期望帧率（最终取决于设备能力）
JPEG_QUALITY = 80       # 0-100，越大越清晰，体积越大

# === Blueprint 实现 ===
video0_bp = Blueprint("video0", __name__)

class VideoCapture:
    def __init__(self, device: str):
        self.device = device
        self.cap: Optional[cv2.VideoCapture] = None
        self._open()

    def _open(self):
        cap = cv2.VideoCapture(self.device, cv2.CAP_V4L2)
        if not cap.isOpened():
            raise RuntimeError(f"无法打开摄像头: {self.device}")
        # 配置分辨率与帧率
        cap.set(cv2.CAP_PROP_FRAME_WIDTH, FRAME_WIDTH)
        cap.set(cv2.CAP_PROP_FRAME_HEIGHT, FRAME_HEIGHT)
        cap.set(cv2.CAP_PROP_FPS, FPS)
        # 某些摄像头需要设置编码四字符码（MJPG 可降低 CPU 压力）
        try:
            fourcc = cv2.VideoWriter_fourcc(*"MJPG")
            cap.set(cv2.CAP_PROP_FOURCC, fourcc)
        except Exception:
            pass
        self.cap = cap

    def read(self):
        if self.cap is None:
            self._open()
        ok, frame = self.cap.read()
        if not ok:
            # 轻量重连
            self.cap.release()
            time.sleep(0.1)
            self._open()
            ok, frame = self.cap.read()
            if not ok:
                raise RuntimeError("读取摄像头帧失败")
        return frame

vc = VideoCapture(DEVICE)


def mjpeg_generator() -> Generator[bytes, None, None]:
    """将 OpenCV 帧编码为 JPEG，并以 multipart/x-mixed-replace 持续输出。"""
    while True:
        frame = vc.read()
        # 这里可以插入画面叠加（时间戳等）
        # cv2.putText(frame, time.strftime('%H:%M:%S'), (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,255,0), 2)

        ok, buf = cv2.imencode(".jpg", frame, [int(cv2.IMWRITE_JPEG_QUALITY), JPEG_QUALITY])
        if not ok:
            continue
        jpg = buf.tobytes()
        yield (b"--frame\r\n"
               b"Content-Type: image/jpeg\r\n"
               b"Content-Length: " + str(len(jpg)).encode() + b"\r\n\r\n" +
               jpg + b"\r\n")


@video0_bp.route("/video0")
def video0_stream():
    return Response(mjpeg_generator(),
                    mimetype="multipart/x-mixed-replace; boundary=frame",
                    headers={"Cache-Control": "no-store, no-cache, must-revalidate, pre-check=0, post-check=0, max-age=0"})


@video0_bp.route("/video0/snapshot")
def video0_snapshot():
    try:
        frame = vc.read()
    except Exception as e:
        abort(make_response(jsonify({"error": str(e)}), 500))
    ok, buf = cv2.imencode(".jpg", frame, [int(cv2.IMWRITE_JPEG_QUALITY), JPEG_QUALITY])
    if not ok:
        abort(500)
    resp = make_response(buf.tobytes())
    resp.headers["Content-Type"] = "image/jpeg"
    resp.headers["Cache-Control"] = "no-store"
    return resp


# === 独立运行的 Flask App ===

def create_app():
    app = Flask(__name__)
    app.register_blueprint(video0_bp)

    @app.route("/")
    def index():
        return ("<h3>Raspberry Pi /dev/video0</h3>"
                "<ul>"
                "<li><a href='/video0' target='_blank'>/video0 (MJPEG 流)</a></li>"
                "<li><a href='/video0/snapshot' target='_blank'>/video0/snapshot (单帧)</a></li>"
                "</ul>")

    return app


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("--host", default="0.0.0.0")
    parser.add_argument("--port", type=int, default=5000)
    args = parser.parse_args()

    app = create_app()
    app.run(host=args.host, port=args.port, threaded=True)

"""
== 将 blueprint 集成到你现有的 Flask 项目 ==
1. 将本文件中的 video0_bp、VideoCapture、mjpeg_generator 复制到你的项目任意模块。
2. 在你的 Flask 应用初始化时：

    from yourmodule.video0 import video0_bp
    app.register_blueprint(video0_bp)

然后就可以通过 /video0 访问流。

== 常见问题 ==
- 权限：确保运行用户对 /dev/video0 有读权限：
    sudo usermod -aG video $USER  # 之后重新登录
- 摄像头分辨率/帧率不支持：降低 FRAME_WIDTH/HEIGHT 或移除 FOURCC 设置。
- 性能：MJPEG 对 CPU 占用较低。如果仍然卡顿，可改用 mjpg-streamer 或者启用硬件编码（GStreamer/FFmpeg）。
- Nginx 反向代理：请关闭 proxy_buffering，并传递分块：
    location /video0 {
        proxy_pass http://127.0.0.1:5000/video0;
        proxy_buffering off;
        chunked_transfer_encoding on;
    }
"""
