All files / src/screenshots SShotService.js

100% Statements 109/109
76.47% Branches 13/17
100% Functions 6/6
100% Lines 109/109

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 1101x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 5x 5x 5x 1x 1x 1x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 1x 1x 1x 1x 1x 1x 4x 4x 1x 1x 3x 1x 1x 2x 2x 2x 2x 2x 1x 1x 2x 2x 2x 2x 2x 1x 1x 1x 1x 2x 2x 4x 1x 1x 1x 1x 1x 1x 4x 4x 4x 4x 1x 1x 1x 1x 1x  
// @ts-check
// SPDX-License-Identifier: Zlib
// SPDX-FileCopyrightText: 2024 CERN (home.cern)
// SPDX-FileContributor: Author: Sylvain Fargier <sylvain.fargier@cern.ch>
 
import axios from "axios";
import d from "debug";
 
const debug = d("cam:sshot");
 
/**
 * @typedef {import('express').IRouter} expressIRouter
 * @typedef {import('express-ws').WithWebsocketMethod & expressIRouter} IRouter
 *
 * @typedef {import('express').Request} Request
 * @typedef {import('express').Response} Response
 */
 
class SShotService {
  /**
   * @param {CamExpress.InternalGroupConfig} groups
   * @param {CamExpress.Credentials} credentials
   */
  constructor(groups, credentials) {
    this.groups = groups;
    this.creds = credentials;
  }
 
  /** @param {IRouter} router */
  register(router) {
    /**
     * @swagger
     * /cameras/{group}/{camera}/screenshot:
     *   get:
     *     tags: ['screenshot']
     *     summary: get a camera screenshot
     *     description: |
     *       Take a screenshot of the current video stream, highest quality possible
     *     parameters:
     *      - in: path
     *        name: group
     *        schema:
     *           type: string
     *        description: name of the group defined in the config
     *        required: true
     *      - in: path
     *        name: camera
     *        schema:
     *           type: string
     *        description: name of the camera defined in the config
     *        required: true
     *     produces:
     *       - image/jpeg
     *     responses:
     *       200:
     *         description: video stream screenshot
     *       500:
     *         description: error message
     */
    router.get("/cameras/:group/:camera/screenshot",
      this.getSShot.bind(this));
  }
 
  /**
   * @param {Request} req
   * @param {Response} res
   */
  getSShot(req, res) {
    const camera = this.getCamera(req);
    if (!camera) {
      res.status(404).send("no such camera");
    }
    else if (!camera.screenshot) {
      res.status(500).send("no screenshot for camera");
    }
    else {
      debug("taking screenshot for %s", camera.name);
      const opts = { timeout: 10000, responseType: "stream" };
      const auth = this.creds[camera.name];
      if (auth) {
        opts.auth = { username: auth.user, password: auth.password };
      }
 
      axios.get(camera.screenshot, opts)
      .then(
        (response) => response.data.pipe(res),
        (err) => {
          const msg = err?.response?.statusText || err?.message;
          debug("screenshot error: %s", msg ?? "?");
          res.status(err?.response?.status ?? 500)
          .send(msg ? ("internal error: " + msg) : "internal error");
        });
    }
  }
 
  /**
   * @param {Request} req
   * @return {CamExpress.CameraConfig&{id:string|number}|undefined}
   */
  getCamera(req) {
    const group = req?.params?.group;
    const camera = req?.params?.camera;
    return this.groups?.[group]?.cameras?.[camera];
  }
 
  release() {}
}
 
export default SShotService;