I’m using the following shortcode (saved as gallery.html) in Hugo to generate a gallery containing both video and photos. Comments in the code.

May it be helpful for you. It comes (of course) with no support. The cover photo handling is done by my Hugo theme (terminal) btw.

<!--
    All img/*.{webm,ogv} video files are taken and simularly the caption file is used
    to generate a video control.
-->

{{- $videos := .Page.Resources.Match "img/*.webm" -}}
{{- $videos = $videos | append ( .Page.Resources.Match "img/*.ogv" ) -}}
{{- range $videos -}}
    {{- $caption := "" -}}
    {{- $captionfile :=  printf "%s.%s" ( strings.TrimSuffix (path.Ext .RelPermalink) .RelPermalink ) "txt" -}}
    {{- if os.FileExists $captionfile -}}
        {{ $caption = os.ReadFile $captionfile | markdownify -}}
    {{- end -}}
    {{- $type := printf "%s/%s" "video" (slicestr (path.Ext .RelPermalink) 1) -}}
    {{- $type = replace $type "ogv" "ogg" -}}
    <figure>
    <video controls preload="auto" width="100%" playsinline class="html-video">
        <source src="{{ .RelPermalink }}" type="{{ $type }}">
    </video>
    {{- if $caption -}}
        <figcaption>
            <p>{{ $caption }}</p>
        </figcaption>
    {{- end -}}
    </figure>
{{ end }}

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/lightbox2/2.11.1/css/lightbox.min.css" integrity="sha256-tBxlolRHP9uMsEFKVk+hk//ekOlXOixLKvye5W2WR5c=" crossorigin="anonymous" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js" integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lightbox2/2.11.1/js/lightbox.min.js" integrity="sha256-CtKylYan+AJuoH8jrMht1+1PMhMqrKnB8K5g012WN5I=" crossorigin="anonymous"></script>

<!--
    The galleries must be all lowercase otherwise the caption are not found.
    We use a flexport to show 3 photos in a row, and that should work in mobile as well.
    Thumbs are generated on the fly.

    Each gallery is placed under a content/galleries as:

        content/galleries/album1
            /index.md - normal index stuff, with the 'gallery 'short code in it.
            img/      - symlink to all photos (and captions)
            cover.jpg -> img/cover.jpg  - if exist, this will be used as the cover image.
            video.webm -> see snippet above

    In the photo directory a .txt file with the same basename as the photo is used as
    a caption. This is in markdown format. The cover.jpg (or whatever extension) is skipped
    when generating the gallery.

    Clicking used the lightbox JS to show a slideshow like thing - I may selfhost the javascript
    at some point.
-->

{{ $image := (.Page.Resources.ByType "image") }}
{{ $i := 0 }}
{{ with $image }}
    {{ range . }}
    {{ if not ( hasPrefix .Name "cover." ) }}
        {{ if not ( hasPrefix .Name "img/cover." ) }}
            {{ if eq (mod $i 3) 0 }}
                <div class="flex-container">
            {{ end }}
            {{ $resized := .Fill "300x230 q70" }}
            {{ $caption := "" }}
            {{ $captionfile :=  printf "%s.%s" ( strings.TrimSuffix (path.Ext .RelPermalink) .RelPermalink ) "txt" }}
            {{ if os.FileExists $captionfile }}
                {{ $caption = os.ReadFile $captionfile | markdownify }}
            {{ end }}
            <div style="width:{{ $resized.Width }}px">
                <figure>
                    <a href="{{ .Permalink }}" data-lightbox="x" data-title="{{ $caption }}">
                    <img loading="lazy" src="{{ $resized.Permalink }}" />
                </a>
                {{ if $caption }}
                  <figcaption>
                    <p>{{ $caption }}</p>
                  </figcaption>
                {{- end }}
                </figure>
            </div>
            {{ if eq (mod $i 3) 2 }}
                </div>
            {{ end }}
            {{ $i = add $i 1 }}
        {{ end }}
    {{ end }}
    {{ end }}
{{ end }}