diff options
author | Andrew Dolgov <[email protected]> | 2019-08-15 14:38:39 +0300 |
---|---|---|
committer | Andrew Dolgov <[email protected]> | 2019-08-15 14:38:39 +0300 |
commit | 5b9467ca92850a7d25074859ec4c4c6777d1530e (patch) | |
tree | 27d683208a53aa1acd2a17a9e21e7d8685a435c4 |
initial
-rw-r--r-- | README.md | 15 | ||||
-rw-r--r-- | init.php | 95 |
2 files changed, 110 insertions, 0 deletions
diff --git a/README.md b/README.md new file mode 100644 index 0000000..0f5a992 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +## Fills missing posters of HTML5 video elements + +Requires ffmpeg to be usable. Using this in production is probably a terrible +idea. + +As a side effect, both video stream and generated poster are cached locally. + +## Installation + +1. git clone to ``plugins.local/af_z_video_fill_poster`` +2. Enable plugin + +## License + +GPLv3 diff --git a/init.php b/init.php new file mode 100644 index 0000000..6d713b7 --- /dev/null +++ b/init.php @@ -0,0 +1,95 @@ +<?php +class Af_Z_Video_Fill_Poster extends Plugin { + + /* @var PluginHost $host */ + private $host; + + /* @var DiskCache $cache */ + private $cache; + + function about() { + return array(1.0, + "Fills missing poster attributes of HTML5 video elements (uses ffmpeg)", + "fox"); + } + + function init($host) { + $this->host = $host; + $this->cache = new DiskCache("images"); + + $host->add_hook($host::HOOK_ARTICLE_FILTER, $this); + } + + private function ffmpeg_thumbnail($input_filename, $output_filename) { + $cmdline_tpl = "ffmpeg -ss 00:00:01 -i %s -frames:v 1 ". + "-f singlejpeg %s 2>/dev/null"; + + $rc = -1; + $cmdline = sprintf($cmdline_tpl, escapeshellarg($input_filename), escapeshellarg($output_filename)); + + exec($cmdline, $output, $rc);; + + return $rc === 0; + } + + function hook_article_filter($article) { + $need_saving = false; + $doc = new DOMDocument(); + + if (@$doc->loadHTML('<?xml encoding="UTF-8">' . $article["content"])) { + $xpath = new DOMXPath($doc); + $videos = $xpath->query('(//video)'); + + foreach ($videos as $video) { + if (!$video->hasAttribute("poster")) { + $source = $xpath->query("//source[@src]", $video)->item(0); + + if ($source) { + $video_url = $source->getAttribute("src"); + $local_filename = sha1($video_url); + $thumb_filename = $local_filename . "-POSTER"; + + if ($this->cache->exists($thumb_filename)) { + $video->setAttribute("poster", $this->cache->getUrl($thumb_filename)); + $need_saving = true; + + } else { + if ($this->cache->exists($local_filename)) { + if ($this->ffmpeg_thumbnail( + $this->cache->getFullPath($local_filename), + $this->cache->getFullPath($thumb_filename))) { + + $video->setAttribute("poster", $this->cache->getUrl($thumb_filename)); + $need_saving = true; + } + } else { + $data = fetch_file_contents(["url" => $video_url, "max_size" => MAX_CACHE_FILE_SIZE]); + + if ($data) { + if ($this->cache->put($local_filename, $data)) { + if ($this->ffmpeg_thumbnail( + $this->cache->getFullPath($local_filename), + $this->cache->getFullPath($thumb_filename))) { + + $video->setAttribute("poster", $this->cache->getUrl($thumb_filename)); + $need_saving = true; + } + } + } + } + } + } + } + } + + if ($need_saving) + $article["content"] = $doc->saveHTML(); + } + + return $article; + } + + function api_version() { + return 2; + } +} |