Explorar o código

Add PVPD and poster support

Billy Barrow %!s(int64=3) %!d(string=hai) anos
pai
achega
aecd0b2ed9
Modificáronse 7 ficheiros con 290 adicións e 4 borrados
  1. 49 1
      index.php
  2. 14 2
      index_template.php
  3. 23 0
      ppub.php
  4. 43 0
      pvpd.php
  5. 39 0
      pvpd_player.js
  6. 1 1
      router.php
  7. 121 0
      video_template.php

+ 49 - 1
index.php

@@ -158,9 +158,57 @@ 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") {
+    header("content-type: text/html");
+    include("video_template.php");
+    include("pvpd.php");
+    include("Parsedown.php");
+    $pd = new Parsedown();
+    $video = new Pvpd();
+    $video->from_string($ppub->read_asset($asset));
+    
+    content_start($ppub, $file_name, $video);
+    content_html($pd->text($video->description));
+    content_end($ppub);
+}
 else {
     header("content-type: " . $asset->mimetype);
-    echo($ppub->read_asset($asset));
+
+    if($ppub->can_stream_asset($asset)) {
+        $file_size = $ppub->get_asset_size($asset);
+        $start = 0;
+        $end = $file_size;
+    
+        header('Accept-Ranges: bytes', true);
+    
+        ### Parse Content-Range header for byte offsets, looks like "bytes=11525-" OR "bytes=11525-12451"
+        if( isset($_SERVER['HTTP_RANGE']) && preg_match('%bytes=(\d+)-(\d+)?%i', $_SERVER['HTTP_RANGE'], $match) )
+        {
+            $start = (int)$match[1];
+            $finish_bytes = 0;
+    
+            if( isset($match[2]) ){
+                $finish_bytes = (int)$match[2];
+                $end = $finish_bytes + 1;
+            } else {
+                $finish_bytes = $file_size - 1;
+            }
+        
+            $cr_header = sprintf('Content-Range: bytes %d-%d/%d', $start, $finish_bytes, $file_size);
+        
+            header("HTTP/1.1 206 Partial content");
+            header($cr_header);
+        }
+
+        header(sprintf('Content-Length: %d', $end - $start));
+
+        $ppub->stream_asset($asset, $start, $end);
+    
+    }
+    else {
+        echo($ppub->read_asset($asset));
+    }
+
 }
 
 ?>

+ 14 - 2
index_template.php

@@ -18,7 +18,7 @@ function index_start($index_type, $arg) {
                 padding: 16px;
                 max-width: 100vw;
                 width: 316px;
-                margin: 0px 0px 15px 0px;
+                margin: 0px 0px 15px 15px;
 
             }
             ul.tags {
@@ -32,6 +32,11 @@ function index_start($index_type, $arg) {
             details summary {
                 cursor: pointer;
             }
+            dd img {
+                width: 33%;
+                margin-top: 0px;
+                margin-bottom: 15px;
+            }
             @media screen and (max-width: 75ch) {
                 aside {
                     float: none;
@@ -110,7 +115,14 @@ function index_no_content($index_type, $arg) {
 function index_listing($ppub, $url) {
     ?>
             <dt><a href="<?php echo($url);?>"><?php echo(htmlentities($ppub->metadata["title"]));?></a></dt>
-            <dd><?php echo(htmlentities($ppub->metadata["description"]));?></dd>
+            <dd>
+                <?php echo(htmlentities($ppub->metadata["description"]));?>
+                <?php 
+                if($ppub->metadata["poster"] != null) {
+                    echo("<img src=\"" . $url . "/" . $ppub->metadata["poster"] . "\" alt='' />");
+                }
+                ?>
+            </dd>
     <?php
 }
 

+ 23 - 0
ppub.php

@@ -58,6 +58,29 @@ class Ppub {
         return $data;
     }
 
+    public function get_asset_size($asset) {
+        $start_location = $asset->start_location + $this->blob_start;
+        $length = $asset->end_location - $asset->start_location;
+        return $length;
+    }
+
+    public function can_stream_asset($asset) {
+        return !in_array("gzip", $asset->flags);
+    }
+
+    public function stream_asset($asset, $start = 0, $end = -1) {
+        $start_location = $asset->start_location + $this->blob_start + $start;
+        $length = ($end >= 0) ? $asset->end_location - $asset->start_location : $end;
+        fseek($this->handle, $start_location);
+        $pos = 0;
+        while($pos < $length) {
+            $chunksize = min(1024 * 1024, $length - $pos);
+            echo(fread($this->handle, $chunksize));
+            flush();
+            $pos += $chunksize;
+        }
+    }
+
     private function build_asset_list($data) {
         $asset_list = array();
         $lines = explode("\n", $data);

+ 43 - 0
pvpd.php

@@ -0,0 +1,43 @@
+<?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);
+        }
+    }
+}
+
+
+?>

+ 39 - 0
pvpd_player.js

@@ -0,0 +1,39 @@
+
+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));
+}

+ 1 - 1
router.php

@@ -3,7 +3,7 @@
 $path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
 
 // Exclude some paths from being rewritten
-if (dirname($path) == '/' && pathinfo($path, PATHINFO_EXTENSION) == 'css') {
+if (dirname($path) == '/' && (pathinfo($path, PATHINFO_EXTENSION) == 'css' || pathinfo($path, PATHINFO_EXTENSION) == 'js')) {
   return false;
 }
 else {

+ 121 - 0
video_template.php

@@ -0,0 +1,121 @@
+<?php
+
+function content_start($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="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%; 
+            }
+            .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;
+            }
+        </style>
+    </head>
+    <body>
+    <header>
+        <h1>
+            <a style="color: var(--text-color); text-decoration: none; display: inline-block;" href="<?php echo(SITE_URL);?>"><?php echo(SITE_NAME);?></a>
+            <!-- <small style="font-weight: light; margin: 0px 5px 0px 5px;" aria-label="<?php echo(PUBLICATION_NAME);?> title:">/</small> -->
+            <!-- <small><a style="color: var(--text-color); text-decoration: none; display: inline-block;" href="<?php echo(SITE_URL);?>/<?php echo($path);?>"><?php echo(htmlentities($metadata["title"]));?></a></small> -->
+        </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
+}
+
+function content_html($content) {
+    echo $content;
+}
+
+function content_end($ppub) {
+    ?>
+    <footer>
+        <hr>
+        <p><strong><?php echo(htmlentities($ppub->metadata["title"]));?></strong>
+        <?php if($ppub->metadata["author"] != null) {
+            preg_match("/^([^<]*(?= *<|$))<*([^>]*)>*/", $ppub->metadata["author"], $author);
+         ?>
+        <br/><?php echo(PUBLICATION_NAME);?> authored 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));?>"><?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"]);?>">See Licence</a>
+        <?php } ?></p>
+        <p><a href="<?php echo(SITE_URL);?>/">Return to <?php echo(PUBLICATION_NAME);?> Index</a> | <a href="<?php echo(SITE_URL);?>/feed.rss">Subscribe to <?php echo(SITE_NAME);?> RSS</a> | <a href="?download=true">Download <?php echo(PUBLICATION_NAME);?> PPUB</a>
+        <br/><small>Powered by <a href="https://github.com/Tilo15/php-ppub">php-ppub</a> and <a href="https://parsedown.org">Parsedown</a>, styled with <a href="https://vanillacss.com/">Vanilla CSS</a>.</small></p>
+    </footer>
+    </body>
+</html>
+    <?php
+}
+
+?>