Sfoglia il codice sorgente

Breaking change to PPVM entry metadata format

Billy Barrow 3 anni fa
parent
commit
fd34856550
8 ha cambiato i file con 510 aggiunte e 136 eliminazioni
  1. 3 1
      README.md
  2. 23 7
      index.php
  3. 56 0
      ppvm.php
  4. 84 0
      ppvm_player.js
  5. 0 43
      pvpd.php
  6. 0 39
      pvpd_player.js
  7. 330 0
      video_player.php
  8. 14 46
      video_template.php

+ 3 - 1
README.md

@@ -3,4 +3,6 @@
 
 This is a simple blog engine using PPUB and PPIX formats that started with [PyPPUB](https://github.com/Tilo15/PyPPUB). It is minimalistic but configurable. It makes use of [Vanilla CSS](https://vanillacss.com/) and generates some very simple and standards compliant HTML.
 
-It can show an index based on a raw folder of ppub files, or it can be configured to use a PPIX index file to enable tags and searching.
+It can show an index based on a raw folder of ppub files, or it can be configured to use a PPIX index file to enable tags and searching.
+
+TODO: add support for licence-label in PPUB metadata. Update RSS feed template to use poster image if applicable

+ 23 - 7
index.php

@@ -149,6 +149,15 @@ else {
     $asset = $ppub->asset_index[$asset];
 }
 
+if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
+    if(strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) < filectime(PUBLICATION_DIR . "/" . $file)) {
+        header('HTTP/1.1 304 Not Modified');
+        exit;
+    }
+}
+
+header("Cache-Control: public, max-age=604800");
+
 if(strpos($accepts, "text/html") !== false && $asset->mimetype == "text/markdown") {
     header("content-type: text/html");
     include("content_template.php");
@@ -158,18 +167,25 @@ if(strpos($accepts, "text/html") !== false && $asset->mimetype == "text/markdown
     content_html($pd->text($ppub->read_asset($asset)));
     content_end($ppub);
 }
-else if(strpos($accepts, "text/html") !== false && $asset->mimetype == "application/x-pvpd") {
+else if(strpos($accepts, "text/html") !== false && $asset->mimetype == "application/x-ppvm") {
     header("content-type: text/html");
-    include("video_template.php");
-    include("pvpd.php");
+    include("ppvm.php");
     include("Parsedown.php");
     $pd = new Parsedown();
-    $video = new Pvpd();
+    $video = new Ppvm();
     $video->from_string($ppub->read_asset($asset));
     
-    content_start($ppub, $file_name, $video);
-    content_html($pd->text($video->description));
-    content_end($ppub);
+    if(isset($_GET["embed"])) {
+        include("video_player.php");
+        ppvm_player($ppub, $file_name, $video);
+    }
+    else {
+        include("video_template.php");
+        content_start($ppub, $file_name, $video);
+        $description = $ppub->asset_index[$video->metadata["description"]];
+        content_html($pd->text($ppub->read_asset($description)));
+        content_end($ppub);
+    }
 }
 else {
     header("content-type: " . $asset->mimetype);

+ 56 - 0
ppvm.php

@@ -0,0 +1,56 @@
+<?php
+class VideoEntry {
+    public $type = null;
+    public $label = null;
+    public $filename = null;
+    public $metadata = array();
+
+    public function from_string($str) {
+        $parts = explode(":", $str, 2); 
+        $data = explode(",", $parts[1], 3);
+        $this->type = trim($parts[0]);
+        $this->label = trim($data[0]);
+        $this->filename = trim($data[1]);
+
+        $meta = trim($data[2]);
+        $properties = explode("\";", $meta);
+        foreach ($properties as $property) {
+            if($property == ""){
+                continue;
+            }
+            $entry = explode("=\"", $property, 2);
+            $this->metadata[trim($entry[0])] = trim($entry[1]);
+        }
+    }
+}
+
+
+class Ppvm {
+    public $entries = array();
+    public $metadata = array();
+
+    public function from_string($str) {
+        $parts = explode("\n\n", $str, 2);
+
+        $lines = explode("\n", $parts[0]);
+        foreach ($lines as $line) {
+            if($line != "PPVM"){
+                $entry = explode(":", $line, 2);
+                $this->metadata[$entry[0]] = trim($entry[1]);
+            }
+        }
+
+        $lines = explode("\n", $parts[1]);
+        foreach ($lines as $line) {
+            if($line == "" or $line[0] == "#") {
+                continue;
+            }
+            $entry = new VideoEntry();
+            $entry->from_string($line);
+            array_push($this->entries, $entry);
+        }
+    }
+}
+
+
+?>

+ 84 - 0
ppvm_player.js

@@ -0,0 +1,84 @@
+// @license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3-or-Later
+
+
+function setup_playback(description) {
+    video_manifest = description;
+    console.log(description);
+    player = document.getElementById("player");
+
+    player.addEventListener("pause", (event) => playStateChanged(false));
+    player.addEventListener("playing", (event) => playStateChanged(true));
+
+    filteredOptions = filterOptions(description.entries);
+    if(filteredOptions.length == 0){
+        player.src = "invalid://invalid";
+        document.getElementById("unplayable-modal").showModal();
+        return;
+    }
+
+    player.src = filteredOptions[0].path;
+
+    qualitySelector = document.getElementById("quality-selector");
+    filteredOptions.forEach(option => {
+        opt = document.createElement("option")
+        opt.value = option.path;
+        opt.innerHTML = option.label;
+        qualitySelector.appendChild(opt);
+    });
+
+    startRes = findOptimalOptionForScreen(filteredOptions);
+    qualitySelector.value = startRes.path
+    qualitySelected();
+}
+
+function qualitySelected() {
+    qualitySelector = document.getElementById("quality-selector");
+    player = document.getElementById("player");
+
+    time = player.currentTime;
+    paused = player.paused;
+    player.src = qualitySelector.value;
+    player.currentTime = time;
+    if(!paused) {
+        player.play();
+    }
+}
+
+function filterOptions(options) {
+    player = document.getElementById("player");
+    return options.filter(o => o.type === "video" && player.canPlayType(`${o.mimetype}; codecs="${o.metadata.codecs}"`));
+}
+
+function findOptimalOptionForScreen(options) {
+    screenSize = Math.max(window.screen.width, window.screen.height);
+    filtered = options.filter(o => o.type === "video" && Math.max(o.metadata.size.split("x")[0], o.metadata.size.split("x")[1]) <= screenSize);
+    console.log(filtered);
+    if(filtered.length == 0){
+        return options[options.length - 1];
+    }
+    return filtered[0];
+}
+
+function downloadVideo() {
+    document.getElementById("download-modal").showModal();
+}
+
+function showInfo() {
+    document.getElementById("info-modal").showModal();
+}
+
+function shareVideo() {
+    document.getElementById("share-modal").showModal();
+}
+
+function playStateChanged(playing) {
+    controls = document.getElementById("controls")
+    if(playing) {
+        controls.classList.remove("paused")
+    }
+    else {
+        controls.classList.add("paused")
+    }
+}
+
+// @license-end

+ 0 - 43
pvpd.php

@@ -1,43 +0,0 @@
-<?php
-class VideoEntry {
-    public $filename = null;
-    public $label = null;
-    public $codecs = null;
-
-    public function from_string($str) {
-        $parts = explode(",", $str, 3);
-        $this->label = trim($parts[0]);
-        $this->filename = trim($parts[1]);
-        $this->codecs = trim($parts[2]);
-    }
-}
-
-
-class Pvpd {
-    public $entries = array();
-    public $description = null;
-    public $files = array();
-
-    public function from_string($str) {
-        $parts = explode("\n\n", $str, 3);
-        $this->description = $parts[2];
-
-        $lines = explode("\n", $parts[0]);
-        foreach ($lines as $line) {
-            if($line != "PVPD"){
-                $entry = explode(":", $line, 2);
-                $this->files[$entry[0]] = $entry[1];
-            }
-        }
-
-        $lines = explode("\n", $parts[1]);
-        foreach ($lines as $line) {
-            $entry = new VideoEntry();
-            $entry->from_string($line);
-            array_push($this->entries, $entry);
-        }
-    }
-}
-
-
-?>

+ 0 - 39
pvpd_player.js

@@ -1,39 +0,0 @@
-
-function setup_playback(description) {
-    console.log(description);
-    player = document.getElementById("player");
-
-    filteredOptions = filterOptions(description.entries);
-    if(filterOptions.length == 0){
-        alert("Sorry, no available codecs are playable on your device/web browser.");
-        return;
-    }
-
-    player.src = filteredOptions[0].relativePath;
-
-    qualitySelector = document.getElementById("quality-selector");
-    filteredOptions.forEach(option => {
-        opt = document.createElement("option")
-        opt.value = option.relativePath;
-        opt.innerHTML = option.label;
-        qualitySelector.appendChild(opt);
-    });
-}
-
-function qualitySelected() {
-    qualitySelector = document.getElementById("quality-selector");
-    player = document.getElementById("player");
-
-    time = player.currentTime;
-    paused = player.paused;
-    player.src = qualitySelector.value;
-    player.currentTime = time;
-    if(!paused) {
-        player.play();
-    }
-}
-
-function filterOptions(options) {
-    player = document.getElementById("player");
-    return options.filter(o => player.canPlayType(o.mimetypeWithCodec));
-}

+ 330 - 0
video_player.php

@@ -0,0 +1,330 @@
+<?php
+
+function ppvm_player($ppub, $path, $video) {
+    $metadata = $ppub->metadata;
+    ?>
+<!DOCTYPE html>
+<html lang="<?php echo($metadata["language"] ?? SITE_LANGUAGE);?>">
+    <head>
+        <meta charset="utf-8">
+        <title><?php echo(htmlentities($metadata["title"]));?> - <?php echo(SITE_NAME);?></title>
+        <meta name="description" content="<?php echo(htmlentities($metadata["description"]));?>">
+        <meta name="author" content="<?php echo(htmlentities($metadata["author"]));?>">
+        <link rel="alternate" type="application/x-ppub" title="<?php echo(htmlentities($metadata["title"]));?> (as PPUB)" href="?download=true" />
+        <script type="text/javascript" src="<?php echo(SITE_URL);?>/ppvm_player.js"></script>
+        <style type="text/css">
+            body {
+                margin: 0px;
+                background-color: #000;
+            }
+
+            video {
+                display: block;
+                height: auto; 
+                width: 100vw;
+                height: 100vh;
+            }
+
+            .additional-contols {
+                background: rgba(45, 45, 45, 0.8);
+                padding: 6px;
+                color: #ffffff;
+                height: 24px;
+                position: absolute;
+                top: -36px;
+                left: 0;
+                right: 0;
+                opacity: 0;
+                transition: 0.2s all;
+                display: flex;
+                align-items: center;
+                justify-content: space-between;
+                gap: 8px;
+            }
+
+            body:hover .additional-contols, .paused {
+                top: 0px;
+                opacity: 1;
+            }
+
+            body, input {
+                font: 1rem -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto, Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji", "Segoe UI Symbol";
+            }
+
+            dialog {
+                background: rgba(45, 45, 45, 1);
+                color: #ffffff;
+                border: 1px solid aliceblue;
+                max-width: 75%;
+            }
+
+            dialog p{
+                margin-top: 2px;
+            }
+
+            dialog::backdrop {
+                background: rgba(45, 45, 45, 0.8);
+            }
+
+            dialog .contents {
+                margin: -15px;
+                margin-bottom: 0px;
+                padding: 15px;
+                padding-bottom: 0px;
+                max-height: calc(75vh - 55px);
+                overflow-y: auto;
+            }
+
+            dialog .controls {
+                border-top: 1px solid dimgrey;
+                padding-top: 10px;
+            }
+
+            a {
+                color: skyblue;
+            }
+            a:visited {
+                color: aquamarine;
+            }
+
+            .additional-contols a {
+                color: #ffffff;
+            }
+            .additional-control {
+                display: inline-block;
+            }
+
+            .filler {
+                flex: 1;
+            }
+
+            .site a {
+                text-decoration: none;
+                color: #ffffff;
+                margin: 8px;
+                height: 18px;
+                line-height: 20px;
+            }
+            .site a:hover {
+                color: skyblue;
+            }
+
+            .additional-control button, .additional-control select {
+                background: rgba(45, 45, 45, 0);
+                border: 1px solid rgba(45, 45, 45, 0);
+                color: #ffffff;
+                cursor: pointer;
+                text-decoration: underline;
+                text-align: right;
+                margin-top: 0px;
+                margin-bottom: 2px;
+                margin-left: 2px;
+                margin-right: 2px;
+                padding-left: 6px;
+                padding-right: 6px;
+                padding-bottom: 3px;
+                padding-top: 1px;
+                height: 23px;
+                -webkit-appearance: none;
+            }
+            .additional-control button:hover, .additional-control select:hover {
+                color: skyblue;
+            }
+            .additional-control button:focus, .additional-control select:focus  {
+                color: skyblue;
+            }
+
+
+            .additional-control select option {
+                color: #000000;
+            }
+
+            summary {
+                cursor: pointer;
+            }
+
+            #share-modal textarea, pre {
+                width: 500px;
+                background: rgb(30, 30, 30);
+                color: #fff;
+                border: none;
+                padding: 8px;
+                margin-top: 8px;
+                margin-bottom: 18px;
+                border-radius: 4px;
+                overflow: scroll;
+                white-space: pre;
+            }
+
+            #download-modal li {
+                margin-bottom: 8px;
+            }
+
+
+        </style>
+    </head>
+    <body>
+
+        <video controls id="player" poster="<?php echo($video->metadata["poster"]);?>">
+        </video>
+
+        <div id="controls" class="additional-contols paused">
+            <div class="additional-control site">
+                <a href="<?php echo(SITE_URL);?>/<?php echo($_GET["ppub"]);?>/<?php echo($_GET["asset"]);?>" target="_blank"><strong><?php echo(htmlentities($ppub->metadata["title"]));?></strong></a>
+                <button onclick="showInfo()">Info</button>
+                <button onclick="shareVideo()">Share</button>
+                <button onclick="downloadVideo()">Download</button>
+            </div>
+            <div class="additional-control quality">
+                <select name="quality" id="quality-selector" onchange="qualitySelected()">  
+                </select>
+            </div>
+        </div>
+
+
+        <dialog id="download-modal">
+            <form method="dialog">
+                <div class="contents">
+                <p><strong>Download video</strong><br/>
+                <a href="<?php echo($video->metadata["master"]); ?>" download>Download the full quality version of this video</a> or select a different version to suit your needs below.
+                </p>
+
+                <details>
+                    <summary>Other formats and versions</summary>
+                    <ul>
+<?php
+
+                    foreach ($video->entries as $entry) {
+                        $entry_asset = $ppub->asset_index[$entry->filename];
+                        echo("                        <li><a href=\"" . $entry->filename . "\" download>" . htmlentities($entry->label)  . "</a><br/>\n");
+                        echo("                            <small>" . round(($entry_asset->end_location - $entry_asset->start_location) / 1000000, 2) . "MB, " . $entry_asset->mimetype . "</small></li>\n");
+                    }
+
+?>
+                    </ul>
+                </details>
+                <p>
+                <?php if ($ppub->metadata["copyright"] != null) { ?>
+                <br/>
+                <?php echo($ppub->metadata["copyright"]);?>
+                <?php } if ($ppub->metadata["licence"] != null) { ?>
+                <br/>
+                <a href="<?php echo($ppub->metadata["licence"]);?>">See Licence</a>
+                <?php } ?></p>
+                </div>
+                <div class="controls">
+                <button value="cancel">Close</button>
+                </div>
+            </form>
+        </dialog>
+
+        <dialog id="info-modal">
+            <form method="dialog">  
+                <div class="contents"> 
+                <p><strong><?php echo(htmlentities($ppub->metadata["title"]));?></strong>
+                <?php if($ppub->metadata["author"] != null) {
+                    preg_match("/^([^<]*(?= *<|$))<*([^>]*)>*/", $ppub->metadata["author"], $author);
+                ?>
+                <br/>By <?php
+                    if(isset($author[2]) && $author[2] != '') {
+                        echo("<a href=\"mailto:".$author[2]."\">");
+                        echo(htmlentities(trim($author[1])));
+                        echo("</a>");
+                    } else {
+                        echo(htmlentities($ppub->metadata["author"]));
+                    }
+                ?>.
+                <?php } if ($ppub->metadata["tags"] != null and USE_PPIX) { ?>
+                <br/>Tagged with: 
+                <?php
+                    foreach(explode(" ", $ppub->metadata["tags"]) as $tag) {
+                        ?>
+                        <a href="<?php echo(SITE_URL);?>/?tag=<?php echo(urlencode($tag));?>" target="_blank"><?php echo(htmlentities($tag));?></a>
+                        <?php
+                    }
+                ?>
+                <?php } if ($ppub->metadata["date"] != null) { ?>
+                <br/>Last updated on <?php echo(htmlentities((new DateTime($ppub->metadata["date"]))->format(DATE_FORMAT)));?>.
+                <br/><?php } if ($ppub->metadata["copyright"] != null) { ?>
+                <?php echo($ppub->metadata["copyright"]);?>
+                <?php } if ($ppub->metadata["licence"] != null) { ?>
+                <a href="<?php echo($ppub->metadata["licence"]);?>" target="_blank">See Licence</a>
+                <?php } ?></p>
+                <br/><small>Powered by <a href="https://github.com/Tilo15/php-ppub" target="_blank">php-ppub</a></small></p>
+                </div>
+                <div class="controls">
+                <button value="cancel">Close</button>
+                </div>
+            </form>
+        </dialog>
+
+        <dialog id="share-modal">
+            <form method="dialog">
+                <div class="contents">
+                <p><strong>Share this video</strong><br/>
+                With your friends, colleagues, distant family, or strangers on the internet.
+                </p>
+                <div>
+                    <label>Link:</label>
+                    <pre><?php echo(SITE_URL);?>/<?php echo($_GET["ppub"]);?>/<?php echo($_GET["asset"]);?></pre>
+                </div>
+                <div>
+                    <label>Embed:</label><br/>
+                    <textarea readonly rows="3"><?php generate_embed($path, $video);?></textarea>
+                </div>
+                </div>
+                <div class="controls">
+                <button value="cancel" autofocus>Close</button>
+                </div>
+            </form>
+        </dialog>
+
+        <dialog id="unplayable-modal">
+            <form method="dialog">
+                <div class="contents">
+                <p><strong>Unable to playback content</strong><br/>
+                Your browser does not appear to support any of the available codecs.
+                </p>
+                </div>
+                <div class="controls">
+                <button value="cancel">Close</button>
+                </div>
+            </form>
+        </dialog>
+
+
+        <script type="text/javascript">
+        setup_playback({
+            entries: [
+<?php
+                foreach ($video->entries as $entry) {
+                    $entry_asset = $ppub->asset_index[$entry->filename];
+                    echo("            { type: \"" . $entry->type . "\",  mimetype: \"" . $entry_asset->mimetype . "\", path: \"" . $entry->filename . "\", label: \"" . $entry->label . "\", metadata: { ");
+                    foreach ($entry->metadata as $key => $val) {
+                        echo($key . ": \"" . $val . "\", ");
+                    }
+                    echo("}},\n");
+                }
+?>
+            ]
+        });
+        </script>
+    </body>
+</html>
+
+    <?php
+}
+
+function generate_embed($path, $video) {
+    $percent = 56.25;
+    if(isset($video->metadata["ratio"])) {
+        $ratio = explode(":", $video->metadata["ratio"]);
+        $percent = (min($ratio[0], $ratio[1]) / max($ratio[0], $ratio[1])) * 100;
+    }
+    ?>
+<div class="ppvm-player" style="position: relative; height:0; background: #2d2d2d; padding-bottom: <?php echo($percent);?>%">
+    <iframe src="<?php echo(SITE_URL);?>/<?php echo($_GET["ppub"]);?>/<?php echo($_GET["asset"]);?>?embed=true/" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"></iframe>
+</div><?php
+}
+
+?>

+ 14 - 46
video_template.php

@@ -13,25 +13,18 @@ function content_start($ppub, $path, $video) {
         <meta name="author" content="<?php echo(htmlentities($metadata["author"]));?>">
         <link rel="stylesheet" href="<?php echo(SITE_URL);?>/vanilla.css">
         <link rel="alternate" type="application/x-ppub" title="<?php echo(htmlentities($metadata["title"]));?> (as PPUB)" href="?download=true" />
-        <script type="text/javascript" src="<?php echo(SITE_URL);?>/pvpd_player.js"></script>
         <style type="text/css">
-            video {
-                display: block;
-                height: auto; 
-                max-width: 100%; 
+            .player {
+                position: relative;
+                height: 0;
+                padding-bottom: 56.25%;
+                background: #2d2d2d;
             }
-            .additional-contols {
-                background: var(--text-color);
-                padding: 6px;
-                font-size: 12px;
-                color: #ffffff;
-            }
-            .additional-contols a {
-                color: #ffffff;
-            }
-            .additional-control {
-                display: inline-block;
-                margin-right: 8px;
+            .player iframe{
+                position: absolute;
+                top: 0; left: 0;
+                width: 100%;
+                height: 100%;
             }
         </style>
     </head>
@@ -44,33 +37,10 @@ function content_start($ppub, $path, $video) {
         </h1>
     </header>
 
-    <video controls id="player" poster="<?php echo($video->files["poster"]);?>">
-    </video>
-    <div class="additional-contols">
-        <div class="additional-control quality">
-            <label for="quality">Playback Quality: </label>
-            <select name="quality" id="quality-selector" onchange="qualitySelected()">  
-            </select>
-        </div>
-        <div class="additional-control download">
-            <a href="<?php echo($video->files["master"]);?>" download>Download Full Quality Video</a>
-        </div>
-    </div>
-
-    <script type="text/javascript">
-    setup_playback({
-        entries: [
-<?php
-            foreach ($video->entries as $entry) {
-                $entry_asset = $ppub->asset_index[$entry->filename];
-                echo("            { mimetypeWithCodec: \"" . $entry_asset->mimetype . " codecs=\\\"" . $entry->codecs . "\\\"\", relativePath: \"" . $entry->filename . "\", label: \"" . $entry->label . "\" },\n");
-            }
-?>
-        ]
-    });
-    </script>
-
     <?php
+
+    include("video_player.php");
+    generate_embed($path, $video);
 }
 
 function content_html($content) {
@@ -116,6 +86,4 @@ function content_end($ppub) {
     </body>
 </html>
     <?php
-}
-
-?>
+}