added video support
This commit is contained in:
@@ -1,3 +1,6 @@
|
||||
[submodule "themes/gallery"]
|
||||
path = themes/gallery
|
||||
url = https://github.com/nicokaiser/hugo-theme-gallery.git
|
||||
[submodule "themes/hugo-video"]
|
||||
path = themes/hugo-video
|
||||
url = https://github.com/martignoni/hugo-video.git
|
||||
|
||||
+17
-4
@@ -1,19 +1,32 @@
|
||||
FROM hugomods/hugo:exts AS builder
|
||||
WORKDIR /src
|
||||
|
||||
COPY archetypes/ archetypes/
|
||||
COPY assets/ assets/
|
||||
COPY content/ content/
|
||||
COPY themes/ themes/
|
||||
COPY layouts/ layouts/
|
||||
COPY static/ static/
|
||||
COPY themes/ themes/
|
||||
COPY assets/ assets/
|
||||
COPY content/ content/
|
||||
COPY hugo.toml .
|
||||
|
||||
RUN hugo --gc --minify
|
||||
|
||||
FROM alpine:3.20 AS video-posters
|
||||
|
||||
RUN apk add --no-cache python3 ffmpeg
|
||||
|
||||
WORKDIR /src
|
||||
|
||||
COPY --from=builder /src/public /src/public
|
||||
COPY scripts/add-video-posters.py /add-video-posters.py
|
||||
|
||||
RUN python3 /add-video-posters.py /src/public
|
||||
|
||||
FROM nginx:alpine
|
||||
|
||||
RUN apk add --no-cache openssl
|
||||
|
||||
COPY --from=builder /src/public /usr/share/nginx/html
|
||||
COPY --from=video-posters /src/public /usr/share/nginx/html
|
||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||
COPY docker-entrypoint.sh /docker-entrypoint.sh
|
||||
|
||||
|
||||
@@ -278,6 +278,30 @@ section.galleries .card img:hover {
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
/* ── Videos ────────────────────────────────── */
|
||||
|
||||
.prose {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(500px, 1fr));
|
||||
border-top: 1px solid var(--border);
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
padding: 0;
|
||||
padding-top: 4rem;
|
||||
gap: 0.5rem;
|
||||
max-width: unset !important;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 500px) {
|
||||
.prose {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.prose video {
|
||||
background: #000;
|
||||
}
|
||||
|
||||
/* ── Gallery back link ────────────────────────────────── */
|
||||
|
||||
.gallery-back {
|
||||
|
||||
@@ -7,3 +7,7 @@ resources:
|
||||
params:
|
||||
cover: true
|
||||
---
|
||||
|
||||
{{< video src="test.mp4" >}}
|
||||
|
||||
{{< video src="test2.mp4" >}}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
baseURL = "https://plassonke.de/"
|
||||
title = "Hochzeit"
|
||||
theme = "gallery"
|
||||
theme = ["hugo-video", "gallery"]
|
||||
defaultContentLanguage = "de"
|
||||
disableKinds = ["taxonomy"]
|
||||
enableRobotsTXT = true
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from urllib.parse import unquote
|
||||
|
||||
|
||||
VIDEO_TAG_RE = re.compile(
|
||||
r"<video(?P<attrs>[^>]*)>(?P<body>.*?)</video>",
|
||||
re.IGNORECASE | re.DOTALL,
|
||||
)
|
||||
|
||||
SOURCE_RE = re.compile(
|
||||
r'<source[^>]+src=["\'](?P<src>[^"\']+)["\'][^>]*>',
|
||||
re.IGNORECASE,
|
||||
)
|
||||
|
||||
|
||||
def extract_src(video_attrs: str, video_body: str) -> str | None:
|
||||
"""Extract video path from either <video src=""> or nested <source src="">."""
|
||||
|
||||
video_src = re.search(r'src=["\']([^"\']+)["\']', video_attrs, re.IGNORECASE)
|
||||
if video_src:
|
||||
return video_src.group(1)
|
||||
|
||||
source_src = SOURCE_RE.search(video_body)
|
||||
if source_src:
|
||||
return source_src.group("src")
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def make_poster(video_path: Path, poster_path: Path) -> None:
|
||||
"""Extract a poster frame from a video using ffmpeg."""
|
||||
|
||||
poster_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
subprocess.run(
|
||||
[
|
||||
"ffmpeg",
|
||||
"-y",
|
||||
"-ss",
|
||||
"00:00:01",
|
||||
"-i",
|
||||
str(video_path),
|
||||
"-frames:v",
|
||||
"1",
|
||||
"-q:v",
|
||||
"3",
|
||||
str(poster_path),
|
||||
],
|
||||
check=True,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
)
|
||||
|
||||
|
||||
def process_file(html_file: Path, root: Path) -> None:
|
||||
"""Replace video preload mode and add poster attributes."""
|
||||
|
||||
content = html_file.read_text(encoding="utf-8")
|
||||
changed = False
|
||||
|
||||
def replace_video(match: re.Match[str]) -> str:
|
||||
nonlocal changed
|
||||
|
||||
attrs = match.group("attrs")
|
||||
body = match.group("body")
|
||||
|
||||
if 'preload="auto"' not in attrs and "preload='auto'" not in attrs:
|
||||
return match.group(0)
|
||||
|
||||
src = extract_src(attrs, body)
|
||||
if not src:
|
||||
return match.group(0)
|
||||
|
||||
src_clean = unquote(src.split("#")[0].split("?")[0])
|
||||
|
||||
if src_clean.startswith(("http://", "https://", "//")):
|
||||
return match.group(0)
|
||||
|
||||
video_path = (html_file.parent / src_clean).resolve()
|
||||
|
||||
try:
|
||||
video_path.relative_to(root.resolve())
|
||||
except ValueError:
|
||||
return match.group(0)
|
||||
|
||||
if not video_path.exists():
|
||||
print(f"Missing video: {video_path}")
|
||||
return match.group(0)
|
||||
|
||||
poster_path = video_path.with_suffix(".jpg")
|
||||
poster_src = src_clean.rsplit(".", 1)[0] + ".jpg"
|
||||
|
||||
if not poster_path.exists():
|
||||
make_poster(video_path, poster_path)
|
||||
|
||||
attrs_new = attrs
|
||||
attrs_new = attrs_new.replace('preload="auto"', 'preload="none"')
|
||||
attrs_new = attrs_new.replace("preload='auto'", 'preload="none"')
|
||||
|
||||
if "poster=" not in attrs_new.lower():
|
||||
attrs_new += f' poster="{poster_src}"'
|
||||
|
||||
changed = True
|
||||
return f"<video{attrs_new}>{body}</video>"
|
||||
|
||||
new_content = VIDEO_TAG_RE.sub(replace_video, content)
|
||||
|
||||
if changed:
|
||||
html_file.write_text(new_content, encoding="utf-8")
|
||||
print(f"Updated: {html_file}")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""Process all HTML files below the given root directory."""
|
||||
|
||||
root = Path(sys.argv[1]).resolve()
|
||||
|
||||
for html_file in root.rglob("*.html"):
|
||||
process_file(html_file, root)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Submodule
+1
Submodule themes/hugo-video added at fb3686a015
Reference in New Issue
Block a user