Hugo Peertube Shortcode

This is a shortcode include videos hosted on any Peertube instance.

Peertube is a decentralized and federated video streaming and hosting platform: using ActivityPub protocol for federation and P2P directly in the web browser for distributed downloading of data.

At this point this shortcode is early beta and was only tested localy with Hugo 0.76.0, last Firefox and Chrome on MacOS, and with video from various instance of Peertube.

Install

Just copy the peertube.html file in the layouts/shortcodes/ directory at the root of your Hugo site. You have to create the layouts and shortcodes directories if they does not exist. You can learn more about shortcodes from Hugo documentation.

That’s all Folks.

Usage

Insert the Shortcode in your Markdown content…

Simplified syntax
Easy to use, but only default options.

{{< peertube vidcommons.org a547c41d-3f0e-4689-bb1c-44d533d16397 >}}

Complete syntax
Give access to the complete set of options avaible for Peertube iframe.

{{< peertube host="indymotion.fr" id="a11de1b8-dbb2-4cef-9b1d-3f01e0af8425" title="0" >}}
Params Comments Default
host Domain Name of instance: without https:// nor training slatch (/) None
id Identification Code of the video (ex: d49f95a9-b183-4f16-9341-8637ac3597ff) None
title Display the video Title: 0 (no) or 1 (yes) 0
warning Display a Warning about privacy: 0 (no) or 1 (yes) 0
subtitle Display the video subtitle if exist: langage code (ex: fr) 0

Note: The default options are hard coded, if you want du change, you have to modify the source.

Privacy

1) Peertube by itself use absolutely no cookies, no trackers.

2) The uses the BitTorrent P2P protocol to share bandwidth between users by default to help lower the load on the server induce that your IP address is being stored in the instance’s BitTorrent tracker as long as you download or watch the video For more info read this Peertube statement.

4 Likes

Thank you, I have been happily using your shortcode for a few months. Is there a way to embed Peertube Playlists?

Hi,

It seems the peertube shortcode isn’t working anymore…
Is there any way to insert Peertube videos in Hugo?

Thank you.

See the example in this comment:
https://github.com/gohugoio/hugo/issues/13280#issuecomment-2603635137

1 Like

Thank you.

I copied the peertube shortcode to my shortcode folder and added {{< peertube url="https://video.ploud.fr/w/bmTZ68Grh97VsArAdSJtqW" >}} to my .md post, deployed it on my server.

However it doesn’t work, you can have a look: Popcorn Haikou - Mon blog

Maybe I am missing something…

Which version of Hugo are you running?

Here is what I have when trying to render my blog locally:

~/Bureau/hugo/blog > hugo server
port 1313 already in use, attempting to use an available port
Watching for changes in /home/arnauld/Bureau/hugo/blog/{archetypes,assets,content,data,i18n,layouts,static,themes}
Watching for config changes in /home/arnauld/Bureau/hugo/blog/hugo.toml
Start building sites …
hugo v0.147.3+extended+withdeploy linux/amd64 BuildDate=unknown

WARN Raw HTML omitted while rendering “/home/arnauld/Bureau/hugo/blog/content/about.md”; see Configure markup
You can suppress this warning by adding the following to your site configuration:
ignoreLogs = [‘warning-goldmark-raw-html’]
Built in 30 ms
Error: error building site: render: failed to render pages: render of “/home/arnauld/Bureau/hugo/blog/content/posts/popcorn-haikou.md” failed: “/home/arnauld/Bureau/hugo/blog/themes/archie/layouts/_default/single.html:17:13”: execute of template failed: template: single.html:17:13: executing “main” at <.Content>: error calling Content: “/home/arnauld/Bureau/hugo/blog/content/posts/popcorn-haikou.md:14:1”: failed to render shortcode “peertube”: failed to process shortcode: “/home/arnauld/Bureau/hugo/blog/themes/archie/layouts/shortcodes/peertube.html:161:45”: execute of template failed: template: _shortcodes/peertube.html:161:45: executing “_shortcodes/peertube.html” at <div 1>: error calling div: can’t apply the operator to the values

The API is returning null for the aspect ratio, which is (a) unexpected and (b) not helpful. I need to think about this for a bit. API calls for other videos on the same server are fine. For example:

{{< peertube url="https://video.ploud.fr/w/8jfTRePDqxerZYkVbsqUTA" >}}

Based on some release notes, I suspect that videos uploaded prior to v6.1 (released April 29, 2024) have aspectRatio: null.

1 Like

Maybe it is more related with the Peertube instance where I host my videos, https://video.ploud.fr, as if I use the video adress provided by tpl/tplimpl: Add Mastodon and PeerTube shortcodes ¡ Issue #13280 ¡ gohugoio/hugo ¡ GitHub , and deploy my blog, it is working fine: Popcorn Haikou - Mon blog

I edited my previous reply. The instance isn’t the problem. I think it’s the API version under which the video was originally uploaded. Perhaps you can confirm that.

1 Like

I am going to upload a new video and try.

1 Like

I just uploaded a video and inserted it in my blog post and it is working: Popcorn Haikou - Mon blog

So, should I upload again all of my videos? :slight_smile:

If this were an embedded (built-in) shortcode, I might spend some time coming up with a reliable way to determine the aspect ratio when not provided. However, I would be more inclined to throw an error, something like:

The Peertube API reported a null aspect ratio for this video, probably indicating that it was originally uploaded to an instance running Peertube v6.0.4 or earlier. This shortcode is compatible with videos uploaded to an instance running Peertube v6.1.0 or later.

So yeah, I would re-upload under the newer API. Unless there’s a way to update-in-place.

1 Like

I disagree with my previous reply. It’s impractical and not user-friendly.

For now…

I would re-upload under the newer API. Unless there’s a way to update-in-place.

And I will spend some time on this when I can.

Notes to self:

  • If aspect ratio is null, make a second call (yuck) to metadataUrl and use the display_aspect_ratio from the VideoHandler stream if present. I can’t come up with another way to do this.
  • This problem may be specific to one or more instances. When upgrading the API, sometimes you need to manually execute a migration scrip to update video files metadata in the database. Perhaps the migration script wasn’t run, or perhaps the migration script didn’t handle this part of the update.
1 Like

Thank you for your help.

I’ll be re-uploading my videos little by little.

This revision of the shortcode also handles videos uploaded prior to v6.1.0 (i.e., those without an aspect ratio in the initial API response). We have to make a secondary API call (which isn’t great), but (a) it shouldn’t be required too often, and (b) we cache the result of the API call(s).

layouts/_shortcodes/peertube.html
{{- /*
Embeds a PeerTube video.

@param {string} url The URL of the PeerTube video.
@param {string} [start] The time, from the start of the video, when the player should start playing the video (e.g., 42s, 6m7s).
@param {string} [stop] The time, from the start of the video, when the player should stop playing the video (e.g., 42s, 6m7s).
@param {string} [loading=eager] The loading attribute of the iframe element, either eager or lazy. Default is eager.
@param {int} [width=0] The width of the video in pixels. Responsive if `0`.
@param {bool} [allowFullScreen=true] Whether to allow full screen playback.
@param {bool} [autoplay=false] Whether to automatically play the video. Forces mute to true.
@param {bool} [controls=true] Whether to display the video controls.
@param {bool} [displayLink=true] Whether to display the PeerTube link.
@param {bool} [displayTitle=true] Whether to display the video title.
@param {bool} [displayWarning=true] Whether to display the privacy warning.
@param {bool} [loop=false] Whether to indefinitely repeat the video.
@param {bool} [mute=false] Whether to mute the video. Always true when autoplay is true.
@param {bool} [p2p=true] Whether to enable peer-to-peer bandwidth sharing.

@returns {template.HTML}

@link https://docs.joinpeertube.org/api/embed-player

@example {{< peertube url="https://toobnix.org/w/5jBegFpNbffA1nhmp32kqR" >}}
*/}}

{{- /* Set constants. */}}
{{- $trueValues := slice "true" true 1 }}
{{- $falseValues := slice "false" false 0 }}
{{- $sandbox := "allow-same-origin allow-scripts allow-popups allow-forms" }}

{{- /* Set defaults. */}}
{{- $url := "" }}
{{- $start := "" }}
{{- $stop := "" }}
{{- $loading := "eager" }}
{{- $width := 0 }}
{{- $height := 0 }}
{{- $allowFullScreen := true }}
{{- $autoplay := false }}
{{- $controls := true }}
{{- $displayLink := true }}
{{- $displayTitle := true }}
{{- $displayWarning := true }}
{{- $loop := false }}
{{- $mute := false }}
{{- $p2p := true }}

{{- /* Get parameters. */}}
{{- $url = or (.Get "url") $url }}
{{- $start := or (.Get "start") $start }}
{{- $stop := or (.Get "stop") $stop }}
{{- $width = or (.Get "width") $width | int }}
{{- $loading := or (.Get "loading") $loading }}
{{- if in $trueValues (.Get "allowFullScreen") }}
  {{- $allowFullScreen = true }}
{{- else if in $falseValues (.Get "allowFullScreen") }}
  {{- $allowFullScreen = false }}
{{- end }}
{{- if in $trueValues (.Get "autoplay") }}
  {{- $autoplay = true }}
{{- else if in $falseValues (.Get "autoplay") }}
  {{- $autoplay = false }}
{{- end }}
{{- if in $trueValues (.Get "controls") }}
  {{- $controls = true }}
{{- else if in $falseValues (.Get "controls") }}
  {{- $controls = false }}
{{- end }}
{{- if in $trueValues (.Get "displayLink") }}
  {{- $displayLink = true }}
{{- else if in $falseValues (.Get "displayLink") }}
  {{- $displayLink = false }}
{{- end }}
{{- if in $trueValues (.Get "displayTitle") }}
  {{- $displayTitle = true }}
{{- else if in $falseValues (.Get "displayTitle") }}
  {{- $displayTitle = false }}
{{- end }}
{{- if in $trueValues (.Get "displayWarning") }}
  {{- $displayWarning = true }}
{{- else if in $falseValues (.Get "displayWarning") }}
  {{- $displayWarning = false }}
{{- end }}
{{- if in $trueValues (.Get "loop") }}
  {{- $loop = true }}
{{- else if in $falseValues (.Get "loop") }}
  {{- $loop = false }}
{{- end }}
{{- if in $trueValues (.Get "mute") }}
  {{- $mute = true }}
{{- else if in $falseValues (.Get "mute") }}
  {{- $mute = false }}
{{- end }}
{{- if in $trueValues (.Get "p2p") }}
  {{- $p2p = true }}
{{- else if in $falseValues (.Get "p2p") }}
  {{- $p2p = false }}
{{- end }}

{{- /* Mute if autoplay enabled. */}}
{{- if $autoplay }}
  {{- $mute = true }}
{{- end }}

{{- if $url }}
  {{- /* Determine API URL. */}}
  {{- $u := urls.Parse $url }}
  {{- $pathSegments := strings.Split $u.Path "/" }}
  {{- $shortUUID := index $pathSegments (sub (len $pathSegments) 1) }}
  {{- $api := fmt.Printf "%s://%s/api/v1/videos/%s" $u.Scheme $u.Host $shortUUID }}

  {{- /* Get data from API. */}}
  {{- $data := "" }}
  {{- with try (resources.GetRemote $api) }}
    {{- with .Err }}
      {{- errorf "%s" . }}
    {{- else with .Value }}
      {{- $data = . | transform.Unmarshal }}
      {{- /* Handle null aspect ratio for videos uploaded prior to PeerTube API v6.1.0. */}}
      {{- if not $data.aspectRatio }}
        {{- $aspectRatio := partial "inline/peertube/get-aspect-ratio.html" (dict "data" $data "name" $.Name "position" $.Position) }}
        {{- $data = merge $data (dict "aspectRatio" $aspectRatio) }}
      {{- end }}
    {{- else }}
      {{- warnidf "shortcode-peertube" "The %q shortcode was unable to retrieve the remote data. See %s" $.Name $.Position }}
    {{- end }}
  {{- end }}

  {{- if $data }}
    {{- /* Build src attribute. */}}
    {{- $qsp := dict
      "autoplay"      (cond $autoplay 1 0)
      "controlBar"    (cond $controls 1 0)
      "loop"          (cond $loop 1 0)
      "muted"         (cond $mute 1 0)
      "peertubeLink"  (cond $displayLink 1 0)
      "p2p"           (cond $p2p 1 0)
      "title"         (cond $displayTitle 1 0)
      "warningTitle"  (cond $displayWarning 1 0)
    }}
    {{- with $start }}
      {{- $qsp = merge $qsp (dict "start" .) }}
    {{- end }}
    {{- with $stop }}
      {{- $qsp = merge $qsp (dict "stop" .) }}
    {{- end }}
    {{- $qs := collections.Querify $qsp }}
    {{- $src := fmt.Printf "%s://%s%s?%s" $u.Scheme $u.Host $data.embedPath $qs }}

    {{- /* Render. */}}
    {{- if $width }}
      {{- $height = div $width $data.aspectRatio | math.Round }}
      <iframe
        {{- if $allowFullScreen }} allowfullscreen {{- end }}
        width="{{ $width }}"
        height="{{ $height }}"
        frameborder="0"
        loading="{{ $loading }}"
        title="{{ $data.name }}"
        sandbox="{{ $sandbox }}"
        src="{{ $src }}"
      ></iframe>
    {{- else }}
      {{- $width = "100%" }}
      {{- $height = "100%" }}
      {{- $paddingTop := $data.aspectRatio | div 1 | mul 100 | fmt.Printf "%2.2f%%" }}
      <div style="position: relative; padding-top: {{ $paddingTop }};">
        <iframe
          {{- if $allowFullScreen }} allowfullscreen {{- end }}
          width="{{ $width }}"
          height="{{ $height }}"
          frameborder="0"
          loading="{{ $loading }}"
          title="{{ $data.name }}"
          sandbox="{{ $sandbox }}"
          style="position: absolute; inset: 0px;"
          src="{{ $src }}"
        ></iframe>
      </div>
    {{- end }}
  {{- end }}
{{- else }}
  {{- errorf "The %q shortcode requires a %q argument. See %s" .Name "url" .Position }}
{{- end -}}

{{- define "_partials/inline/peertube/get-aspect-ratio.html" }}
  {{- $aspectRatio := 0 }}

  {{- $metadataUrl := "" }}
  {{- range .data.streamingPlaylists }}
    {{- range .files }}
      {{- if .hasVideo }}
        {{- $metadataUrl = .metadataUrl }}
        {{- break }}
      {{- end }}
    {{- end }}
    {{- if $metadataUrl }}
      {{- break }}
    {{- end }}
  {{- end }}

  {{- with $metadataUrl }}
    {{- with try (resources.GetRemote .) }}
      {{- with .Err }}
        {{- errorf "%s" . }}
      {{- else with .Value }}
        {{- with . | transform.Unmarshal }}
          {{- range .streams }}
            {{- if and .width .height  }}
              {{- $aspectRatio = div .width .height }}
              {{- break }}
            {{- end }}
          {{- end }}
          {{- if not $aspectRatio }}
            {{- errorf "The %q shortcode was unable to determine the aspect ratio. See %s" $.name $.position }}
          {{ end }}
        {{- end }}
      {{- else }}
        {{- errorf "The %q shortcode was unable to retrieve the metadata. See %s" $.name $.position }}
      {{- end }}
    {{- end }}
  {{- else }}
    {{- errorf "The %q shortcode was unable to determine the metadata URL. See %s" $.name $.position }}
  {{- end }}

  {{- return $aspectRatio }}
{{- end -}}

I’ve also updated the example repo:

git clone --single-branch -b hugo-github-issue-13280 https://github.com/jmooring/hugo-testing hugo-github-issue-13280
cd hugo-github-issue-13280
hugo server
1 Like

Thank you very much! It is working perfectly: Ballade Ă  moto dans le Fujian - Mon blog

I am still re-writing my old blog, the old modified theme I used didn’t work anymore.

1 Like

I modified the peertube.html so that there is a space between videos if adding multiple videos in the same post: Jirafeau, your web file repository

I didn’t do it myself but asked ChatGpt to do it, so not sure if it is right, but is is working at least :slight_smile: