Add video download button

Fixes #263
This commit is contained in:
Zed 2026-06-21 20:37:29 +02:00
commit 279fa09771
10 changed files with 93 additions and 41 deletions

View file

@ -305,7 +305,7 @@
margin-top: 4px;
margin-bottom: -2px;
.icon-attention {
.icon-attention-circled {
margin-right: -3px;
}
}

View file

@ -28,6 +28,40 @@ video {
}
}
.video-download {
position: absolute;
top: 8px;
right: 8px;
z-index: 2;
color: var(--accent);
text-decoration: none;
opacity: 0;
transition: opacity 0.2s;
filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.7));
.icon-container {
margin: 0;
}
.icon-download-alt {
font-size: 18px;
}
}
.attachment:hover .video-download {
opacity: 1;
&:hover {
color: var(--fg_color);
}
}
@media (hover: none) {
.video-download {
opacity: 1;
}
}
.video-overlay {
@include play-button;
background-color: $shadow;

View file

@ -50,8 +50,8 @@ proc renderHead*(prefs: Prefs; cfg: Config; req: Request; titleText=""; desc="";
let opensearchUrl = getUrlPrefix(cfg) & "/opensearch"
buildHtml(head):
link(rel="stylesheet", type="text/css", href="/css/style.css?v=43")
link(rel="stylesheet", type="text/css", href="/css/fontello.css?v=5")
link(rel="stylesheet", type="text/css", href="/css/style.css?v=44")
link(rel="stylesheet", type="text/css", href="/css/fontello.css?v=7")
if theme.len > 0:
link(rel="stylesheet", type="text/css", href=(&"/css/themes/{theme}.css"))
@ -119,7 +119,7 @@ proc renderHead*(prefs: Prefs; cfg: Config; req: Request; titleText=""; desc="";
# this is last so images are also preloaded
# if this is done earlier, Chrome only preloads one image for some reason
link(rel="preload", type="font/woff2", `as`="font",
href="/fonts/fontello.woff2?61663884", crossorigin="anonymous")
href="/fonts/fontello.woff2?59696369", crossorigin="anonymous")
proc renderMain*(body: VNode; req: Request; cfg: Config; prefs=defaultPrefs;
titleText=""; desc=""; ogTitle=""; rss=""; video="";

View file

@ -97,6 +97,12 @@ proc renderVideoUnavailable(video: Video): VNode =
else:
p: text "This media is unavailable"
proc getVideoDownloadUrl(videoData: Video): string =
let mp4Vars = videoData.variants.filterIt(it.contentType == mp4)
if mp4Vars.len == 0: return ""
let best = mp4Vars.sortedByIt(it.bitrate)[^1].url
if best.startsWith("http"): getVidUrl(best) else: best
proc renderVideoAttachment(videoData: Video; prefs: Prefs; path=""; bigThumb=false): VNode =
let
playbackType = if not prefs.proxyVideos and videoData.hasMp4Url: mp4
@ -127,6 +133,11 @@ proc renderVideoAttachment(videoData: Video; prefs: Prefs; path=""; bigThumb=fal
if videoData.durationMs > 0:
tdiv(class="overlay-duration"): text getDuration(videoData)
verbatim "</div>"
if videoData.available:
let dlUrl = getVideoDownloadUrl(videoData)
if dlUrl.len > 0:
a(class="video-download", href=dlUrl, download="",
title="Download video"): icon "download-alt"
proc renderVideo*(video: Video; prefs: Prefs; path: string; bigThumb=false): VNode =
let hasCardContent = video.description.len > 0 or video.title.len > 0
@ -345,10 +356,10 @@ proc renderDisclosures*(tweet: Tweet): VNode =
buildHtml(tdiv(class="disclosures")):
if tweet.isAI:
span(data-disclosure="ai"):
icon "attention", "Made with AI"
icon "attention-circled", "Made with AI"
if tweet.isAd:
span(data-disclosure="ad"):
icon "attention", "Paid partnership (ad)"
icon "attention-circled", "Paid partnership (ad)"
proc renderLocation*(tweet: Tweet): string =
let (place, url) = tweet.getLocation()