angle-uparrow-clockwisearrow-counterclockwisearrow-down-uparrow-leftatcalendarcard-listchatcheckenvelopefolderhouseinfo-circlepencilpeoplepersonperson-fillperson-plusphoneplusquestion-circlesearchtagtrashx

Einfache Videogalerie mit Flask, Jinja, Bootstrap und JQuery

Anzeigen von Videos nach Datum und Uhrzeit mit minimalen Klicks mit Flask, Jinja, Bootstrap und JQuery

28 Oktober 2024
In Flask
post main image
https://www.pexels.com/nl-nl/@mo3ath-photos-110226063

Wir haben eine Reihe von Kameras, die kurze Clips erzeugen, wenn sich etwas vor der Kamera bewegt. Alle Clips, Videos, landen auf einem System. Wir verwenden Standardprogramme zur Anzeige der Videos.

Nun möchten wir diese Videos aber mit anderen in unserem lokalen Netzwerk teilen, und wir möchten die Videodateien nicht kopieren. Eine offensichtliche Lösung wäre die Auswahl und Installation eines Videogalerie-Servers. Aber, hey, dies ist ein Blog über Python, und dies ist ein kleines Projekt, also erstellen wir das hier selbst. Wir implementieren weder HTTPS noch ein Login, d.h. jeder im lokalen Netzwerk kann die Videos sehen. Wenn Sie möchten, können Sie den Zugriff durch IP address einschränken.

Wie immer verwende ich Ubuntu 22.04, und auf den anderen Rechnern im lokalen Netzwerk läuft ebenfalls Ubuntu.

Verfügbare Videogalerie-Software vs. maßgeschneidertes Projekt

Können wir etwas wie die open source -Videogalerie-Software wählen? Das Problem ist in diesem Fall die erforderliche Funktionalität.
Wir wollen die Anzahl der Klicks minimieren, um ein Video zu sehen, um das nächste Video zu sehen, usw. Dies erfordert eine immer sichtbare Liste von Videos mit der Zeit der Videos, und die Liste kann lang sein. Die meisten Bilder- und Videogalerien zeigen eine Seite mit Miniaturbildern, und wenn man darauf klickt, öffnet sich eine neue Seite, und man muss zurückgehen, um ein anderes Video auszuwählen. Das ist nicht das, was wir wollen!

Zusammengefasst sollte unser Bildschirm wie folgt aussehen:

  +-------------------+-------------------------------+
  |   <<              |                               |
  | > 2024-10-27      |                               |
  | v 2024-10-26      |                               |
  |   CAM01  18:54:48 |                               |
  |   CAM01  18:53:17 |            VIDEO              |
  |   ...             |                               |
  |   ...             |                               |
  |   ...             |                               |
  | > 2024-10-25      |                               |
  | > 2024-10-24      |                               |
  |   ...             |                               |
  |                   |                               |
  |                   |                               |
  |                   |                               |
  +-------------------+-------------------------------+

Klicken Sie auf eine Zeit und das Video wird angezeigt und ausgeführt. Oh, und das muss nicht responsiv sein, wir schauen uns das immer auf einem Monitor an.

Das Projekt

Dies ist eine perfekte Aufgabe für Flask und Jinja. Ich habe bereits Nginx auf meinem Entwicklungsrechner laufen. Die Videos sind 'MP4' und werden in Ordnern gespeichert, die Namen dieser Ordner sind Daten. Flask wird nur verwendet, um ein Datum und Videos auszuwählen. Wenn Sie auf einen Videolink klicken, wird keine neue Seite geöffnet, sondern das ausgewählte Video im Videofenster der Seite abgespielt.

Hier ist die Verzeichnisstruktur des Projekts:

├── app
│   ├── main.py
│   ├── static
│   │   └── videos
│   │       ├── 2024-10-23
│   │       │   └── ...
│   │       ├── 2024-10-24
│   │       │   └── ...
│   │       ├── 2024-10-25
│   │       │   ├── CAM01_101-01-20241025031350.mp4
│   │       │   ├── CAM01_101-01-20241025161248.mp4
│   │       │   └── ...
│   │       ├── 2024-10-26
│   │       │   ├── CAM01_101-01-20241026185317.mp4
│   │       │   ├── CAM01_101-02-20241026185448.mp4
│   │       │   └── ...
│   │       ├── CAM01_101-04-20241025231354.mp4
│   │       ├── CAM02_102-05-20241025231454.mp4
│   │       └── ...
│   └── templates
│       └── index.html
├── run.py

Erstellen Sie ein virtual environment, ein Projektverzeichnis, und installieren Sie Flask.

Nginx

Wir verwenden Nginx , um unsere Anwendung zu bedienen:

  • Die Flask -Anwendung, die einen reverse proxy -Server verwendet
  • Die Videos, aus einem Verzeichnis
  • Der statische Inhalt, aus einem Verzeichnis

Da nicht alle Daten von unserer Flask -Anwendung stammen, weisen wir unserer Anwendung einen Hostnamen (Server) zu:

motion-videos

Wir bearbeiten:

/etc/hosts

und fügen die Zeile hinzu:

127.0.0.1 motion-videos

Auf anderen Rechnern in unserem lokalen Netzwerk fügen wir diese Zeile ebenfalls hinzu, aber jetzt mit dem IP address unseres Entwicklungsrechners, dem Rechner, auf dem die Videos laufen und auf dem die Anwendung läuft.

Um unsere Anwendung in unserem Browser aufzurufen, geben wir sie in die Adressleiste ein:

http://motion-videos

Jetzt weiß Nginx , welchen Server wir meinen, und kann auch die Videos und statischen Inhalte bereitstellen.

Hier ist die Nginx -Serverdatei, unsere Anwendung ist auf Port 6395, Sie können dies gerne ändern.

# /etc/nginx/sites-available/motion-videos
server {
  listen 80;
  server_name motion-videos;

  access_log /var/log/nginx/motion-videos.access.log;
  error_log  /var/log/nginx/motion-videos.error.log;

  location /static/ {
    alias <YOUR-PROJECT-DIRECTORY>/app/static/;
  }

  location /videos/ {
    alias <YOUR-PROJECT-DIRECTORY>/app/static/videos/;
  }


  location / {
    proxy_pass http://127.0.0.1:6395;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    client_max_body_size 1M;
  }
}

Vergewissern Sie sich, dass Sie hier den Speicherort Ihres Projekts angeben!

Aktivieren Sie dann Ihren Server:

sudo ln -s /etc/nginx/sites-available/motion-videos /etc/nginx/sites-enabled/motion-videos

Und starten Sie Nginx neu:

sudo systemctl restart nginx

JQuery zur Auswahl des Videos

Die Anwendung Flask erstellt eine Liste von Videolinks in der Vorlage. Wenn wir auf einen Videolink klicken, verwenden wir JQuery, um das neue Video wie folgt zu starten:

  • Holen Sie sich den 'video run HTML template code', dieser befindet sich in einer 'div'
  • Ersetzen Sie die Werte für die Videoinfo und die Video-URL
  • Ersetzen Sie den aktuellen Code des Videolaufs HTML ('div') durch den neuen Code des Videos HTML

Der Code

Für unsere Flask Anwendung verwenden wir Bootstrap und JQuery. Hier verwenden wir einen CDN, aber Sie können diesen auch lokal installieren.

Unsere Anwendung zeigt zwei Spalten:

  • Datumsauswahl, mit Links zu den Videos
  • Video-Fenster

Der Datumsselektor zeigt auch die Zeiten der Videos für ein ausgewähltes Datum an. Wir erstellen zwei Spalten in voller Höhe mit flexbox. Wir können keine Bootstrap -Spalten verwenden, da sie "float-left" verwenden, und wir haben auch Probleme mit der langen Liste der Videos. Der Datumsselektor kann mehr Elemente enthalten, als auf dem Bildschirm angezeigt werden können, was bedeutet, dass wir eine 'div' mit "overflow-auto" verwenden.

Hier ist der Code. Wir binden unsere Flask -Anwendung an 0.0.0.0 , was bedeutet, dass sie für andere Rechner in unserem lokalen Netzwerk verfügbar ist. Wenn Sie einen firewall verwenden, müssen Sie den Port öffnen, damit andere Rechner eine Verbindung herstellen können.

# run.py
from app import main

app = main.create_app()

if __name__ == '__main__':
    app.run(
        host= '0.0.0.0',
        port=6395,
        use_debugger=True,
        use_reloader=True,
    )

Das Wichtigste in unserer Flask -Anwendung ist die Auswahl der verfügbaren Termine und Videos.

# main.py
import datetime
import glob
import os
import pathlib
import re

from flask import Flask, request, url_for, redirect, render_template

video_files_subdir = 'videos'
video_files_ext = '.mp4'

class Video:
    def __init__(self, f, d):
        self.d = d
        self.filename = os.path.basename(f)
        if d is None:
            self.page_url = url_for('index', filename=self.filename)
            self.play_url = f'/videos/{self.filename}'
        else:
            self.page_url = url_for('date_dir', date=d, filename=self.filename)
            self.play_url = f'/videos/{d}/{self.filename}'
        name_parts = pathlib.Path(self.filename).stem.split('-')
        self.cam = name_parts[0].split('_')[0]
        self.dt = datetime.datetime.strptime(name_parts[2], '%Y%m%d%H%M%S')
        self.info = self.dt.strftime('%Y-%m-%d') + ' ' + self.filename

def create_app():
    app = Flask(__name__)
    app.jinja_env.auto_reload = True
    app.config['TEMPLATES_AUTO_RELOAD'] = True
    
    def get_dates_videos(date_dir=None):
        # get date_dirs (dates)
        base_dir = os.path.join(app.static_folder, video_files_subdir)
        base_path = pathlib.Path(base_dir)
        date_dirs = [os.path.split(x)[-1] for x in base_path.rglob('????-??-??') if x.is_dir()]
        date_dirs.sort(reverse=True)
        # get files
        current_dir_parts = [base_dir]
        if date_dir is not None:
            current_dir_parts.append(date_dir)
        current_dir_parts.append('*' + video_files_ext)
        path = os.path.join(*current_dir_parts)
        files = glob.glob(path)
        files.sort(key=os.path.getmtime, reverse=True)
        app.logger.info(f'date_dirs = {date_dirs}, files = {files}')
        return dict(
			dates=date_dirs,
			videos=[Video(f, d=date_dir) for f in files],
			date_selected=date_dir,
		)

    @app.route('/')
    def index():
        dates_videos_date = get_dates_videos()
        return render_template('index.html', **dates_videos_date)

    @app.route('/<date>')
    def date_dir(date):
        dates_videos_date = get_dates_videos(date)
        return render_template('index.html', **dates_videos_date)

    return app

In der Vorlage zeigen wir die Termine und Videos an. Für die Videos wird eine Schaltfläche verwendet, die beim Anklicken ein Ereignis auslöst.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<title>Videos</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<style>
body {
	height: 100%;
}
.col-container {
	width: 100vw;
	height: 100vh;
	display: flex;
	flex-direction: row;
}
.col-start {
	min-width: 200px;
}
.col-end {
	position: relative;
	flex: 1;
}
.video-selector {
	max-height: 100%;
	width: 100%;
	position:relative;
	overflow-y:auto;	
}	
</style>
</head>
<body>

{% macro list_videos(videos, filename) %}
	{% if videos and videos|length > 0 %}
		{% for video in videos %}
		<p class="my-0 small">
			{{ video.cam }} &nbsp;
			<button type="button" 
				class="btn btn-link btn-sm my-0 py-0 small video-button" 
				data-video-info="{{ video.info }}"
				data-video-play-url="{{ video.play_url }}">
				{{ video.dt.strftime('%H:%M:%S') }}
			</button>
		</p>
		{% endfor %}
	{% else %} 
		No videos found
	{% endif %}
{% endmacro %}

<div class="col-container">
	<div class="col-start">
		<div class="video-selector p-2">

    {% if dates and dates|length > 0 %}
	<table class="table table-sm">
	<tbody>
		{% if date_selected %}
		<tr><td></td><td>
			<a href="{{ url_for('index') }}">
			&lt;&lt;
			</a>
		</td></tr>
		{% endif %}
	{% for date in dates %}
		{% set date_is_date_selected = false %}
		{% if date_selected and date == date_selected %}
			{% set date_is_date_selected = true %}
		{% endif %}
		<tr>
		<td>
			{{ 'v' if date_is_date_selected else '>' }}
		</td>
		<td>
			<a href="{{ url_for('date_dir', date=date) }}">
			{{ date }}
			</a>
		</td>
		</tr>
		{% if date_is_date_selected %}
		<tr>
		<td>
		</td>
		<td>
			{{ list_videos(videos, filename) }}
		</td>
		</tr>
		{% endif %} 
	{% endfor %}

	{% if date_selected is none %}
	<tr><td colspan="2">
		{{ list_videos(videos, filename) }}
	</td></tr>
	{% endif %} 
	</tbody>
	</table>    
    {% endif %} 

		</div>
	</div>
	<div class="col-end bg-black text-light p-2">
		<div id="video-area"></div>	
	</div>
</div>

<div id="video-area-template" class="visually-hidden">
	<div id="video-area">
		<p class="mb-0">{VIDEO_INFO}</p>
		<div class="embed-responsive embed-responsive-16by9">
			<video class="embed-responsive-item" controls autoplay muted>
				<source src="{VIDEO_PLAY_URL}" type="video/mp4">
			</video>
		</div>
	</div>
</div>

<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
<script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>
<script>
$(document).ready(function(){
	$('.video-button').click(function(){
		var video_info = $(this).data('video-info');
		var video_play_url = $(this).data('video-play-url');
		var video_area_template = $('#video-area-template').html();
		var video_area_data = video_area_template.replace('{VIDEO_INFO}', video_info);
		video_area_data = video_area_data.replace('{VIDEO_PLAY_URL}', video_play_url);
		$('#video-area').replaceWith(video_area_data);
    });
});
</script>

</body>
</html>

Um unsere Anwendung zu starten, gehen Sie in das Projektverzeichnis und geben Sie ein:

python run.py

Rufen Sie nun Ihren Browser auf:

http://motion-videos

Zusammenfassung

Dies ist ein kleines Projekt, das noch viel zu wünschen übrig lässt, aber für unsere Zwecke gut genug funktioniert. Flask, Jinja, Bootstrap und JQuery sind einfach unglaubliche Bausteine, um kleine Webanwendungen in sehr kurzer Zeit zu erstellen.

Links / Impressum

Basic concepts of flexbox
https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_flexible_box_layout/Basic_concepts_of_flexbox

Flask
https://flask.palletsprojects.com/en/stable

Jinja
https://jinja.palletsprojects.com/en/stable

Print raw HTTP request in Flask or WSGI
https://stackoverflow.com/questions/25466904/print-raw-http-request-in-flask-or-wsgi

Einen Kommentar hinterlassen

Kommentieren Sie anonym oder melden Sie sich zum Kommentieren an.

Kommentare

Eine Antwort hinterlassen

Antworten Sie anonym oder melden Sie sich an, um zu antworten.