Browse Source

Add video download button

Fixes #263
Zed 2 months ago
parent
commit
279fa09771

+ 38 - 33
public/css/fontello.css

@@ -1,12 +1,12 @@
 @font-face {
   font-family: "fontello";
-  src: url("/fonts/fontello.eot?49059696");
+  src: url("/fonts/fontello.eot?59696369");
   src:
-    url("/fonts/fontello.eot?49059696#iefix") format("embedded-opentype"),
-    url("/fonts/fontello.woff2?49059696") format("woff2"),
-    url("/fonts/fontello.woff?49059696") format("woff"),
-    url("/fonts/fontello.ttf?49059696") format("truetype"),
-    url("/fonts/fontello.svg?49059696#fontello") format("svg");
+    url("/fonts/fontello.eot?59696369#iefix") format("embedded-opentype"),
+    url("/fonts/fontello.woff2?59696369") format("woff2"),
+    url("/fonts/fontello.woff?59696369") format("woff"),
+    url("/fonts/fontello.ttf?59696369") format("truetype"),
+    url("/fonts/fontello.svg?59696369#fontello") format("svg");
   font-weight: normal;
   font-style: normal;
 }
@@ -36,113 +36,118 @@
   -moz-osx-font-smoothing: grayscale;
 }
 
+.icon-group:before {
+  content: "\e0c3";
+}
+
+/* '' */
 .icon-views:before {
   content: "\e800";
 }
 
-/* '' */
+/* '' */
 .icon-heart:before {
   content: "\e801";
 }
 
-/* '' */
+/* '' */
 .icon-quote:before {
   content: "\e802";
 }
 
-/* '' */
+/* '' */
 .icon-comment:before {
   content: "\e803";
 }
 
-/* '' */
-.icon-group:before {
-  content: "\e804";
-}
-
-/* '' */
+/* '' */
 .icon-play:before {
   content: "\e805";
 }
 
-/* '' */
+/* '' */
 .icon-link:before {
   content: "\e806";
 }
 
-/* '' */
+/* '' */
 .icon-calendar:before {
   content: "\e807";
 }
 
-/* '' */
+/* '' */
 .icon-location:before {
   content: "\e808";
 }
 
-/* '' */
+/* '' */
 .icon-picture:before {
   content: "\e809";
 }
 
-/* '' */
+/* '' */
 .icon-lock:before {
   content: "\e80a";
 }
 
-/* '' */
+/* '' */
 .icon-down:before {
   content: "\e80b";
 }
 
-/* '' */
+/* '' */
 .icon-retweet:before {
   content: "\e80c";
 }
 
-/* '' */
+/* '' */
 .icon-search:before {
   content: "\e80d";
 }
 
-/* '' */
+/* '' */
 .icon-pin:before {
   content: "\e80e";
 }
 
-/* '' */
+/* '' */
 .icon-cog:before {
   content: "\e80f";
 }
 
-/* '' */
+/* '' */
 .icon-rss:before {
   content: "\e810";
 }
 
-/* '' */
+/* '' */
 .icon-ok:before {
   content: "\e811";
 }
 
-/* '' */
-.icon-attention:before {
+/* '' */
+.icon-attention-circled:before {
   content: "\e812";
 }
 
-/* '' */
+/* '' */
+.icon-download-alt:before {
+  content: "\e813";
+}
+
+/* '' */
 .icon-circle:before {
   content: "\f111";
 }
 
-/* '' */
+/* '' */
 .icon-info:before {
   content: "\f128";
 }
 
-/* '' */
+/* '' */
 .icon-bird:before {
   content: "\f309";
 }
 
-/* '' */
+/* '' */

BIN
public/fonts/fontello.eot


+ 4 - 2
public/fonts/fontello.svg

@@ -6,6 +6,8 @@
 <font id="fontello" horiz-adv-x="1000" >
 <font-face font-family="fontello" font-weight="400" font-stretch="normal" units-per-em="1000" ascent="850" descent="-150" />
 <missing-glyph horiz-adv-x="1000" />
+<glyph glyph-name="group" unicode="&#xe0c3;" d="M0 106l0 134q0 26 18 32l171 80q-66 39-68 131 0 56 35 103 37 41 90 43 31 0 63-19-49-125 23-237-12-11-25-19l-114-55q-48-23-52-84l0-143-114 0q-25 0-27 34z m193-59l0 168q0 27 22 37l152 70 57 28q-37 23-60 66t-22 94q0 76 46 130t110 54 109-54 45-130q0-105-78-158l61-30 146-70q24-10 24-37l0-168q-2-37-37-41l-541 0q-14 2-24 14t-10 27z m473 330q68 106 22 231 31 19 66 21 49 0 90-43 35-41 35-103 0-82-65-131l168-80q18-10 18-32l0-134q0-32-27-34l-118 0 0 143q0 57-50 84l-110 53q-15 8-29 25z" horiz-adv-x="1000" />
+
 <glyph glyph-name="views" unicode="&#xe800;" d="M180 516l0-538-180 0 0 538 180 0z m250-138l0-400-180 0 0 400 180 0z m250 344l0-744-180 0 0 744 180 0z" horiz-adv-x="680" />
 
 <glyph glyph-name="heart" unicode="&#xe801;" d="M790 644q70-64 70-156t-70-158l-360-330-360 330q-70 66-70 158t70 156q62 58 151 58t153-58l56-52 58 52q62 58 150 58t152-58z" horiz-adv-x="860" />
@@ -14,8 +16,6 @@
 
 <glyph glyph-name="comment" unicode="&#xe803;" d="M1000 350q0-97-67-179t-182-130-251-48q-39 0-81 4-110-97-257-135-27-8-63-12-10-1-17 5t-10 16v1q-2 2 0 6t1 6 2 5l4 5t4 5 4 5q4 5 17 19t20 22 17 22 18 28 15 33 15 42q-88 50-138 123t-51 157q0 73 40 139t106 114 160 76 194 28q136 0 251-48t182-130 67-179z" horiz-adv-x="1000" />
 
-<glyph glyph-name="group" unicode="&#xe804;" d="M0 106l0 134q0 26 18 32l171 80q-66 39-68 131 0 56 35 103 37 41 90 43 31 0 63-19-49-125 23-237-12-11-25-19l-114-55q-48-23-52-84l0-143-114 0q-25 0-27 34z m193-59l0 168q0 27 22 37l152 70 57 28q-37 23-60 66t-22 94q0 76 46 130t110 54 109-54 45-130q0-105-78-158l61-30 146-70q24-10 24-37l0-168q-2-37-37-41l-541 0q-14 2-24 14t-10 27z m473 330q68 106 22 231 31 19 66 21 49 0 90-43 35-41 35-103 0-82-65-131l168-80q18-10 18-32l0-134q0-32-27-34l-118 0 0 143q0 57-50 84l-110 53q-15 8-29 25z" horiz-adv-x="1000" />
-
 <glyph glyph-name="play" unicode="&#xe805;" d="M772 333l-741-412q-13-7-22-2t-9 20v822q0 14 9 20t22-2l741-412q13-7 13-17t-13-17z" horiz-adv-x="785.7" />
 
 <glyph glyph-name="link" unicode="&#xe806;" d="M294 116q14 14 34 14t36-14q32-34 0-70l-42-40q-56-56-132-56-78 0-134 56t-56 132q0 78 56 134l148 148q70 68 144 77t128-43q16-16 16-36t-16-36q-36-32-70 0-50 48-132-34l-148-146q-26-26-26-64t26-62q26-26 63-26t63 26z m450 574q56-56 56-132 0-78-56-134l-158-158q-74-72-150-72-62 0-112 50-14 14-14 34t14 36q14 14 35 14t35-14q50-48 122 24l158 156q28 28 28 64 0 38-28 62-24 26-56 31t-60-21l-50-50q-16-14-36-14t-34 14q-34 34 0 70l50 50q54 54 127 51t129-61z" horiz-adv-x="800" />
@@ -44,6 +44,8 @@
 
 <glyph glyph-name="attention-circled" unicode="&#xe812;" d="M429 779q116 0 215-58t156-156 57-215-57-215-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58z m71-696v106q0 8-5 13t-12 5h-107q-8 0-13-5t-6-13v-106q0-8 6-13t13-6h107q7 0 12 6t5 13z m-1 192l10 346q0 7-6 10-5 5-13 5h-123q-8 0-13-5-6-3-6-10l10-346q0-6 5-10t14-4h103q8 0 13 4t6 10z" horiz-adv-x="857.1" />
 
+<glyph glyph-name="download-alt" unicode="&#xe813;" d="M0-150l0 135 1000 0 0-135-1000 0z m88 586l228 0 0 414 370 0 0-414 228 0-414-385z" horiz-adv-x="1000" />
+
 <glyph glyph-name="circle" unicode="&#xf111;" d="M857 350q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
 
 <glyph glyph-name="info" unicode="&#xf128;" d="M393 149v-134q0-9-7-15t-15-7h-134q-9 0-16 7t-7 15v134q0 9 7 16t16 6h134q9 0 15-6t7-16z m176 335q0-30-8-56t-20-43-31-33-32-25-34-19q-23-13-38-37t-15-37q0-10-7-18t-16-9h-134q-8 0-14 11t-6 20v26q0 46 37 87t79 60q33 16 47 32t14 42q0 24-26 41t-60 18q-36 0-60-16-20-14-60-64-7-9-17-9-7 0-14 4l-91 70q-8 6-9 14t3 16q89 148 259 148 45 0 90-17t81-46 59-72 23-88z" horiz-adv-x="571.4" />

BIN
public/fonts/fontello.ttf


BIN
public/fonts/fontello.woff


BIN
public/fonts/fontello.woff2


+ 1 - 1
src/sass/tweet/_base.scss

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

+ 34 - 0
src/sass/tweet/video.scss

@@ -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;

+ 3 - 3
src/views/general.nim

@@ -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="";

+ 13 - 2
src/views/tweet.nim

@@ -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()