128 lines
3.2 KiB
Python
128 lines
3.2 KiB
Python
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() |