summaryrefslogtreecommitdiff
path: root/inc/lib
diff options
context:
space:
mode:
Diffstat (limited to 'inc/lib')
-rw-r--r--inc/lib/gettext/examples/index.php27
-rw-r--r--inc/lib/gettext/examples/locale/de_CH/LC_MESSAGES/messages.mobin585 -> 0 bytes
-rw-r--r--inc/lib/gettext/examples/locale/de_CH/LC_MESSAGES/messages.po30
-rw-r--r--inc/lib/gettext/examples/locale/sr_CS/LC_MESSAGES/messages.mobin829 -> 0 bytes
-rw-r--r--inc/lib/gettext/examples/locale/sr_CS/LC_MESSAGES/messages.po30
-rw-r--r--inc/lib/gettext/examples/pigs_dropin.php89
-rw-r--r--inc/lib/gettext/examples/pigs_fallback.php88
-rwxr-xr-xinc/lib/gettext/examples/update14
-rw-r--r--inc/lib/gettext/tests/LocalesTest.php75
-rw-r--r--inc/lib/gettext/tests/ParsingTest.php60
-rw-r--r--inc/lib/webm/README.md26
-rw-r--r--inc/lib/webm/matroska-elements.txt224
-rw-r--r--inc/lib/webm/matroska.php524
-rw-r--r--inc/lib/webm/posthandler.php49
-rw-r--r--inc/lib/webm/videodata.php173
15 files changed, 996 insertions, 413 deletions
diff --git a/inc/lib/gettext/examples/index.php b/inc/lib/gettext/examples/index.php
deleted file mode 100644
index 263cd3d3..00000000
--- a/inc/lib/gettext/examples/index.php
+++ /dev/null
@@ -1,27 +0,0 @@
-<html>
-<head>
-<title>PHP-gettext examples</title>
-</head>
-<body>
-<h1>PHP-gettext</h1>
-
-<h2>Introduction</h2>
-<p>PHP-gettext provides a simple gettext replacement that works independently from the system's gettext abilities.
-It can read MO files and use them for translating strings.</p>
-<p>This version has the ability to cache all strings and translations to speed up the string lookup.
-While the cache is enabled by default, it can be switched off with the second parameter in the constructor (e.g. when using very large MO files
-that you don't want to keep in memory)</p>
-
-
-<h2>Examples</h2>
-<ul>
- <li><a href="pigs_dropin.php">PHP-gettext as a dropin replacement</a></li>
- <li><a href="pigs_fallback.php">PHP-gettext as a fallback solution</a></li>
-</ul>
-
-<hr />
-<p>Copyright (c) 2003-2006 Danilo Segan</p>
-<p>Copyright (c) 2005-2006 Steven Armstrong</p>
-
-</body>
-</html>
diff --git a/inc/lib/gettext/examples/locale/de_CH/LC_MESSAGES/messages.mo b/inc/lib/gettext/examples/locale/de_CH/LC_MESSAGES/messages.mo
deleted file mode 100644
index 91930376..00000000
--- a/inc/lib/gettext/examples/locale/de_CH/LC_MESSAGES/messages.mo
+++ /dev/null
Binary files differ
diff --git a/inc/lib/gettext/examples/locale/de_CH/LC_MESSAGES/messages.po b/inc/lib/gettext/examples/locale/de_CH/LC_MESSAGES/messages.po
deleted file mode 100644
index 6e4886b5..00000000
--- a/inc/lib/gettext/examples/locale/de_CH/LC_MESSAGES/messages.po
+++ /dev/null
@@ -1,30 +0,0 @@
-# Sample translation for PHP-gettext 1.0
-# Copyright (c) 2003 Danilo Segan <danilo@kvota.net>
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: pigs\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2003-10-23 04:50+0200\n"
-"PO-Revision-Date: 2003-11-01 23:40+0100\n"
-"Last-Translator: Danilo Segan <danilo@kvota.net>\n"
-"Language-Team: Serbian (sr) <danilo@kvota.net>\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-#"Plural-Forms: nplurals=2; plural=n != 1;\n"
-
-#: pigs.php:19
-msgid ""
-"This is how the story goes.\n"
-"\n"
-msgstr ""
-"Und so geht die Geschichte.\n"
-"\n"
-
-#: pigs.php:21
-#, php-format
-msgid "%d pig went to the market\n"
-msgid_plural "%d pigs went to the market\n"
-msgstr[0] "%d Schwein ging zum Markt\n"
-msgstr[1] "%d Schweine gingen zum Markt\n"
diff --git a/inc/lib/gettext/examples/locale/sr_CS/LC_MESSAGES/messages.mo b/inc/lib/gettext/examples/locale/sr_CS/LC_MESSAGES/messages.mo
deleted file mode 100644
index 497c8830..00000000
--- a/inc/lib/gettext/examples/locale/sr_CS/LC_MESSAGES/messages.mo
+++ /dev/null
Binary files differ
diff --git a/inc/lib/gettext/examples/locale/sr_CS/LC_MESSAGES/messages.po b/inc/lib/gettext/examples/locale/sr_CS/LC_MESSAGES/messages.po
deleted file mode 100644
index e5da0e94..00000000
--- a/inc/lib/gettext/examples/locale/sr_CS/LC_MESSAGES/messages.po
+++ /dev/null
@@ -1,30 +0,0 @@
-# Sample translation for PHP-gettext 1.0
-# Copyright (c) 2003,2006 Danilo Segan <danilo@kvota.net>
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: pigs\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2003-10-23 04:50+0200\n"
-"PO-Revision-Date: 2006-02-02 21:06+0100\n"
-"Last-Translator: Danilo Segan <danilo@kvota.net>\n"
-"Language-Team: Serbian (sr) <danilo@kvota.net>\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
-"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
-
-#: pigs.php:19
-msgid ""
-"This is how the story goes.\n"
-"\n"
-msgstr "Овако иде прича.\n\n"
-
-#: pigs.php:21
-#, php-format
-msgid "%d pig went to the market\n"
-msgid_plural "%d pigs went to the market\n"
-msgstr[0] "%d мало прасе је отишло на пијац\n"
-msgstr[1] "%d мала прасета су отишла на пијац\n"
-msgstr[2] "%d малих прасића је отишло на пијац\n"
diff --git a/inc/lib/gettext/examples/pigs_dropin.php b/inc/lib/gettext/examples/pigs_dropin.php
deleted file mode 100644
index 94fd8507..00000000
--- a/inc/lib/gettext/examples/pigs_dropin.php
+++ /dev/null
@@ -1,89 +0,0 @@
-<?php
-/*
- Copyright (c) 2003,2004,2005,2009 Danilo Segan <danilo@kvota.net>.
- Copyright (c) 2005,2006 Steven Armstrong <sa@c-area.ch>
-
- This file is part of PHP-gettext.
-
- PHP-gettext is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- PHP-gettext is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with PHP-gettext; if not, write to the Free Software
- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-
-*/
-
-error_reporting(E_ALL | E_STRICT);
-
-// define constants
-define('PROJECT_DIR', realpath('./'));
-define('LOCALE_DIR', PROJECT_DIR .'/locale');
-define('DEFAULT_LOCALE', 'en_US');
-
-require_once('../gettext.inc');
-
-$supported_locales = array('en_US', 'sr_CS', 'de_CH');
-$encoding = 'UTF-8';
-
-$locale = (isset($_GET['lang']))? $_GET['lang'] : DEFAULT_LOCALE;
-
-// gettext setup
-T_setlocale(LC_MESSAGES, $locale);
-// Set the text domain as 'messages'
-$domain = 'messages';
-bindtextdomain($domain, LOCALE_DIR);
-// bind_textdomain_codeset is supported only in PHP 4.2.0+
-if (function_exists('bind_textdomain_codeset'))
- bind_textdomain_codeset($domain, $encoding);
-textdomain($domain);
-
-header("Content-type: text/html; charset=$encoding");
-?>
-<html>
-<head>
-<title>PHP-gettext dropin example</title>
-</head>
-<body>
-<h1>PHP-gettext as a dropin replacement</h1>
-<p>Example showing how to use PHP-gettext as a dropin replacement for the native gettext library.</p>
-<?php
-print "<p>";
-foreach($supported_locales as $l) {
- print "[<a href=\"?lang=$l\">$l</a>] ";
-}
-print "</p>\n";
-
-if (!locale_emulation()) {
- print "<p>locale '$locale' is supported by your system, using native gettext implementation.</p>\n";
-}
-else {
- print "<p>locale '$locale' is _not_ supported on your system, using the default locale '". DEFAULT_LOCALE ."'.</p>\n";
-}
-?>
-
-<hr />
-
-<?php
-// using PHP-gettext
-print "<pre>";
-print _("This is how the story goes.\n\n");
-for ($number=6; $number>=0; $number--) {
- print sprintf(T_ngettext("%d pig went to the market\n",
- "%d pigs went to the market\n", $number),
- $number );
-}
-print "</pre>\n";
-?>
-
-<hr />
-<p>&laquo; <a href="./">back</a></p>
-</body>
-</html>
diff --git a/inc/lib/gettext/examples/pigs_fallback.php b/inc/lib/gettext/examples/pigs_fallback.php
deleted file mode 100644
index 353190da..00000000
--- a/inc/lib/gettext/examples/pigs_fallback.php
+++ /dev/null
@@ -1,88 +0,0 @@
-<?php
-/*
- Copyright (c) 2003,2004,2005,2009 Danilo Segan <danilo@kvota.net>.
- Copyright (c) 2005,2006 Steven Armstrong <sa@c-area.ch>
-
- This file is part of PHP-gettext.
-
- PHP-gettext is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- PHP-gettext is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with PHP-gettext; if not, write to the Free Software
- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-
-*/
-
-error_reporting(E_ALL | E_STRICT);
-
-// define constants
-define('PROJECT_DIR', realpath('./'));
-define('LOCALE_DIR', PROJECT_DIR .'/locale');
-define('DEFAULT_LOCALE', 'en_US');
-
-require_once('../gettext.inc');
-
-$supported_locales = array('en_US', 'sr_CS', 'de_CH');
-$encoding = 'UTF-8';
-
-$locale = (isset($_GET['lang']))? $_GET['lang'] : DEFAULT_LOCALE;
-
-// gettext setup
-T_setlocale(LC_MESSAGES, $locale);
-// Set the text domain as 'messages'
-$domain = 'messages';
-T_bindtextdomain($domain, LOCALE_DIR);
-T_bind_textdomain_codeset($domain, $encoding);
-T_textdomain($domain);
-
-header("Content-type: text/html; charset=$encoding");
-?>
-<html>
-<head>
-<title>PHP-gettext fallback example</title>
-</head>
-<body>
-<h1>PHP-gettext as a fallback solution</h1>
-<p>Example showing how to use PHP-gettext as a fallback solution if the native gettext library is not available or the system does not support the requested locale.</p>
-
-<?php
-print "<p>";
-foreach($supported_locales as $l) {
- print "[<a href=\"?lang=$l\">$l</a>] ";
-}
-print "</p>\n";
-
-if (!locale_emulation()) {
- print "<p>locale '$locale' is supported by your system, using native gettext implementation.</p>\n";
-}
-else {
- print "<p>locale '$locale' is <strong>not</strong> supported on your system, using custom gettext implementation.</p>\n";
-}
-?>
-
-<hr />
-
-<?php
-// using PHP-gettext
-print "<pre>";
-print T_("This is how the story goes.\n\n");
-for ($number=6; $number>=0; $number--) {
- print sprintf( T_ngettext("%d pig went to the market\n",
- "%d pigs went to the market\n", $number),
- $number );
-}
-print "</pre>\n";
-?>
-
-<hr />
-<p>&laquo; <a href="./">back</a></p>
-</body>
-</html>
diff --git a/inc/lib/gettext/examples/update b/inc/lib/gettext/examples/update
deleted file mode 100755
index 76b4308a..00000000
--- a/inc/lib/gettext/examples/update
+++ /dev/null
@@ -1,14 +0,0 @@
-#!/bin/sh
-TEMPLATE=pigs.pot
-xgettext -kT_ngettext:1,2 -kT_ -L PHP -o $TEMPLATE pigs_dropin.php
-if [ "x$1" = "x-p" ]; then
- msgfmt --statistics $TEMPLATE
-else
- if [ -f $1.po ]; then
- msgmerge -o .tmp$1.po $1.po $TEMPLATE
- mv .tmp$1.po $1.po
- msgfmt --statistics $1.po
- else
- echo "Usage: $0 [-p|<basename>]"
- fi
-fi
diff --git a/inc/lib/gettext/tests/LocalesTest.php b/inc/lib/gettext/tests/LocalesTest.php
deleted file mode 100644
index fab21f86..00000000
--- a/inc/lib/gettext/tests/LocalesTest.php
+++ /dev/null
@@ -1,75 +0,0 @@
-<?php
-require_once('PHPUnit/Framework.php');
-require_once('gettext.inc');
-
-class LocaleTest extends PHPUnit_Framework_TestCase
-{
- public function test_setlocale()
- {
- putenv("LC_ALL=");
- // _setlocale defaults to a locale name from environment variable LANG.
- putenv("LANG=sr_RS");
- $this->assertEquals('sr_RS', _setlocale(LC_MESSAGES, 0));
- }
-
- public function test_setlocale_system()
- {
- putenv("LC_ALL=");
- // For an existing locale, it never needs emulation.
- putenv("LANG=C");
- _setlocale(LC_MESSAGES, "");
- $this->assertEquals(0, locale_emulation());
- }
-
- public function test_setlocale_emulation()
- {
- putenv("LC_ALL=");
- // If we set it to a non-existent locale, it still works, but uses
- // emulation.
- _setlocale(LC_MESSAGES, "xxx_XXX");
- $this->assertEquals('xxx_XXX', _setlocale(LC_MESSAGES, 0));
- $this->assertEquals(1, locale_emulation());
- }
-
- public function test_get_list_of_locales()
- {
- // For a locale containing country code, we prefer
- // full locale name, but if that's not found, fall back
- // to the language only locale name.
- $this->assertEquals(array("sr_RS", "sr"),
- get_list_of_locales("sr_RS"));
-
- // If language code is used, it's the only thing returned.
- $this->assertEquals(array("sr"),
- get_list_of_locales("sr"));
-
- // There is support for language and charset only.
- $this->assertEquals(array("sr.UTF-8", "sr"),
- get_list_of_locales("sr.UTF-8"));
-
- // It can also split out character set from the full locale name.
- $this->assertEquals(array("sr_RS.UTF-8", "sr_RS", "sr"),
- get_list_of_locales("sr_RS.UTF-8"));
-
- // There is support for @modifier in locale names as well.
- $this->assertEquals(array("sr_RS.UTF-8@latin", "sr_RS@latin", "sr@latin",
- "sr_RS.UTF-8", "sr_RS", "sr"),
- get_list_of_locales("sr_RS.UTF-8@latin"));
-
- // We can pass in only language and modifier.
- $this->assertEquals(array("sr@latin", "sr"),
- get_list_of_locales("sr@latin"));
-
-
- // If locale name is not following the regular POSIX pattern,
- // it's used verbatim.
- $this->assertEquals(array("something"),
- get_list_of_locales("something"));
-
- // Passing in an empty string returns an empty array.
- $this->assertEquals(array(),
- get_list_of_locales(""));
- }
-}
-
-?>
diff --git a/inc/lib/gettext/tests/ParsingTest.php b/inc/lib/gettext/tests/ParsingTest.php
deleted file mode 100644
index ff561989..00000000
--- a/inc/lib/gettext/tests/ParsingTest.php
+++ /dev/null
@@ -1,60 +0,0 @@
-<?php
-require_once('PHPUnit/Framework.php');
-//require_once('gettext.php');
-
-class ParsingTest extends PHPUnit_Framework_TestCase
-{
- public function test_extract_plural_forms_header_from_po_header()
- {
- $parser = new gettext_reader(NULL);
- // It defaults to a "Western-style" plural header.
- $this->assertEquals(
- 'nplurals=2; plural=n == 1 ? 0 : 1;',
- $parser->extract_plural_forms_header_from_po_header(""));
-
- // Extracting it from the middle of the header works.
- $this->assertEquals(
- 'nplurals=1; plural=0;',
- $parser->extract_plural_forms_header_from_po_header(
- "Content-type: text/html; charset=UTF-8\n"
- ."Plural-Forms: nplurals=1; plural=0;\n"
- ."Last-Translator: nobody\n"
- ));
-
- // It's also case-insensitive.
- $this->assertEquals(
- 'nplurals=1; plural=0;',
- $parser->extract_plural_forms_header_from_po_header(
- "PLURAL-forms: nplurals=1; plural=0;\n"
- ));
-
- // It falls back to default if it's not on a separate line.
- $this->assertEquals(
- 'nplurals=2; plural=n == 1 ? 0 : 1;',
- $parser->extract_plural_forms_header_from_po_header(
- "Content-type: text/html; charset=UTF-8" // note the missing \n here
- ."Plural-Forms: nplurals=1; plural=0;\n"
- ."Last-Translator: nobody\n"
- ));
- }
-
- /**
- * @dataProvider data_provider_test_npgettext
- */
- public function test_npgettext($number, $expected) {
- $parser = new gettext_reader(NULL);
- $result = $parser->npgettext("context",
- "%d pig went to the market\n",
- "%d pigs went to the market\n",
- $number);
- $this->assertSame($expected, $result);
- }
- public static function data_provider_test_npgettext() {
- return array(
- array(1, "%d pig went to the market\n"),
- array(2, "%d pigs went to the market\n"),
- );
- }
-
-}
-?>
diff --git a/inc/lib/webm/README.md b/inc/lib/webm/README.md
new file mode 100644
index 00000000..baa7a641
--- /dev/null
+++ b/inc/lib/webm/README.md
@@ -0,0 +1,26 @@
+This directory contains files being the integration of containerchan with vichan-devel imageboards.
+
+Containerchan allows posting of webm files, like they were the regular images.
+
+An original board using this code can be found at:
+http://containerchan.org/tb/demo/
+
+The original repo containing the containerchan (possibly with no Tinyboard integration) can be found here:
+https://github.com/ccd0/containerchan
+
+
+Be aware that this is beta software. Please report any bugs you find.
+
+Installation
+------------
+
+Add these lines to inc/instance-config.php:
+
+ $config['allowed_ext_files'][] = 'webm';
+ $config['additional_javascript'][] = 'js/webm-settings.js';
+ $config['additional_javascript'][] = 'js/expand-video.js';
+
+License
+-------
+
+See [LICENSE.md](https://github.com/ccd0/containerchan/blob/master/LICENSE.md).
diff --git a/inc/lib/webm/matroska-elements.txt b/inc/lib/webm/matroska-elements.txt
new file mode 100644
index 00000000..07f8eecc
--- /dev/null
+++ b/inc/lib/webm/matroska-elements.txt
@@ -0,0 +1,224 @@
+a45dfa3 container EBML root
+286 uint EBMLVersion a45dfa3
+2f7 uint EBMLReadVersion a45dfa3
+2f2 uint EBMLMaxIDLength a45dfa3
+2f3 uint EBMLMaxSizeLength a45dfa3
+282 string DocType a45dfa3
+287 uint DocTypeVersion a45dfa3
+285 uint DocTypeReadVersion a45dfa3
+6c binary Void *
+3f binary CRC-32 *
+b538667 container SignatureSlot *
+3e8a uint SignatureAlgo b538667
+3e9a uint SignatureHash b538667
+3ea5 binary SignaturePublicKey b538667
+3eb5 binary Signature b538667
+3e5b container SignatureElements b538667
+3e7b container SignatureElementList 3e5b
+2532 binary SignedElement 3e7b
+8538067 container Segment root
+14d9b74 container SeekHead 8538067
+dbb container Seek 14d9b74
+13ab binary SeekID dbb
+13ac uint SeekPosition dbb
+549a966 container Info 8538067
+33a4 binary SegmentUID 549a966
+3384 string SegmentFilename 549a966
+1cb923 binary PrevUID 549a966
+1c83ab string PrevFilename 549a966
+1eb923 binary NextUID 549a966
+1e83bb string NextFilename 549a966
+444 binary SegmentFamily 549a966
+2924 container ChapterTranslate 549a966
+29fc uint ChapterTranslateEditionUID 2924
+29bf uint ChapterTranslateCodec 2924
+29a5 binary ChapterTranslateID 2924
+ad7b1 uint TimecodeScale 549a966
+489 float Duration 549a966
+461 date DateUTC 549a966
+3ba9 string Title 549a966
+d80 string MuxingApp 549a966
+1741 string WritingApp 549a966
+f43b675 container Cluster 8538067
+67 uint Timecode f43b675
+1854 container SilentTracks f43b675
+18d7 uint SilentTrackNumber 1854
+27 uint Position f43b675
+2b uint PrevSize f43b675
+23 binary SimpleBlock f43b675
+20 container BlockGroup f43b675
+21 binary Block 20
+22 binary BlockVirtual 20
+35a1 container BlockAdditions 20
+26 container BlockMore 35a1
+6e uint BlockAddID 26
+25 binary BlockAdditional 26
+1b uint BlockDuration 20
+7a uint ReferencePriority 20
+7b int ReferenceBlock 20
+7d int ReferenceVirtual 20
+24 binary CodecState 20
+35a2 int DiscardPadding 20
+e container Slices 20
+68 container TimeSlice e
+4c uint LaceNumber 68
+4d uint FrameNumber 68
+4b uint BlockAdditionID 68
+4e uint Delay 68
+4f uint SliceDuration 68
+48 container ReferenceFrame 20
+49 uint ReferenceOffset 48
+4a uint ReferenceTimeCode 48
+2f binary EncryptedBlock f43b675
+654ae6b container Tracks 8538067
+2e container TrackEntry 654ae6b
+57 uint TrackNumber 2e
+33c5 uint TrackUID 2e
+3 uint TrackType 2e
+39 uint FlagEnabled 2e
+8 uint FlagDefault 2e
+15aa uint FlagForced 2e
+1c uint FlagLacing 2e
+2de7 uint MinCache 2e
+2df8 uint MaxCache 2e
+3e383 uint DefaultDuration 2e
+34e7a uint DefaultDecodedFieldDuration 2e
+3314f float TrackTimecodeScale 2e
+137f int TrackOffset 2e
+15ee uint MaxBlockAdditionID 2e
+136e string Name 2e
+2b59c string Language 2e
+6 string CodecID 2e
+23a2 binary CodecPrivate 2e
+58688 string CodecName 2e
+3446 uint AttachmentLink 2e
+1a9697 string CodecSettings 2e
+1b4040 string CodecInfoURL 2e
+6b240 string CodecDownloadURL 2e
+2a uint CodecDecodeAll 2e
+2fab uint TrackOverlay 2e
+16aa uint CodecDelay 2e
+16bb uint SeekPreRoll 2e
+2624 container TrackTranslate 2e
+26fc uint TrackTranslateEditionUID 2624
+26bf uint TrackTranslateCodec 2624
+26a5 binary TrackTranslateTrackID 2624
+60 container Video 2e
+1a uint FlagInterlaced 60
+13b8 uint StereoMode 60
+13c0 uint AlphaMode 60
+13b9 uint OldStereoMode 60
+30 uint PixelWidth 60
+3a uint PixelHeight 60
+14aa uint PixelCropBottom 60
+14bb uint PixelCropTop 60
+14cc uint PixelCropLeft 60
+14dd uint PixelCropRight 60
+14b0 uint DisplayWidth 60
+14ba uint DisplayHeight 60
+14b2 uint DisplayUnit 60
+14b3 uint AspectRatioType 60
+eb524 binary ColourSpace 60
+fb523 float GammaValue 60
+383e3 float FrameRate 60
+61 container Audio 2e
+35 float SamplingFrequency 61
+38b5 float OutputSamplingFrequency 61
+1f uint Channels 61
+3d7b binary ChannelPositions 61
+2264 uint BitDepth 61
+62 container TrackOperation 2e
+63 container TrackCombinePlanes 62
+64 container TrackPlane 63
+65 uint TrackPlaneUID 64
+66 uint TrackPlaneType 64
+69 container TrackJoinBlocks 62
+6d uint TrackJoinUID 69
+40 uint TrickTrackUID 2e
+41 binary TrickTrackSegmentUID 2e
+46 uint TrickTrackFlag 2e
+47 uint TrickMasterTrackUID 2e
+44 binary TrickMasterTrackSegmentUID 2e
+2d80 container ContentEncodings 2e
+2240 container ContentEncoding 2d80
+1031 uint ContentEncodingOrder 2240
+1032 uint ContentEncodingScope 2240
+1033 uint ContentEncodingType 2240
+1034 container ContentCompression 2240
+254 uint ContentCompAlgo 1034
+255 binary ContentCompSettings 1034
+1035 container ContentEncryption 2240
+7e1 uint ContentEncAlgo 1035
+7e2 binary ContentEncKeyID 1035
+7e3 binary ContentSignature 1035
+7e4 binary ContentSigKeyID 1035
+7e5 uint ContentSigAlgo 1035
+7e6 uint ContentSigHashAlgo 1035
+c53bb6b container Cues 8538067
+3b container CuePoint c53bb6b
+33 uint CueTime 3b
+37 container CueTrackPositions 3b
+77 uint CueTrack 37
+71 uint CueClusterPosition 37
+70 uint CueRelativePosition 37
+32 uint CueDuration 37
+1378 uint CueBlockNumber 37
+6a uint CueCodecState 37
+5b container CueReference 37
+16 uint CueRefTime 5b
+17 uint CueRefCluster 5b
+135f uint CueRefNumber 5b
+6b uint CueRefCodecState 5b
+941a469 container Attachments 8538067
+21a7 container AttachedFile 941a469
+67e string FileDescription 21a7
+66e string FileName 21a7
+660 string FileMimeType 21a7
+65c binary FileData 21a7
+6ae uint FileUID 21a7
+675 binary FileReferral 21a7
+661 uint FileUsedStartTime 21a7
+662 uint FileUsedEndTime 21a7
+43a770 container Chapters 8538067
+5b9 container EditionEntry 43a770
+5bc uint EditionUID 5b9
+5bd uint EditionFlagHidden 5b9
+5db uint EditionFlagDefault 5b9
+5dd uint EditionFlagOrdered 5b9
+36 container ChapterAtom 5b9 36
+33c4 uint ChapterUID 36
+1654 string ChapterStringUID 36
+11 uint ChapterTimeStart 36
+12 uint ChapterTimeEnd 36
+18 uint ChapterFlagHidden 36
+598 uint ChapterFlagEnabled 36
+2e67 binary ChapterSegmentUID 36
+2ebc uint ChapterSegmentEditionUID 36
+23c3 uint ChapterPhysicalEquiv 36
+f container ChapterTrack 36
+9 uint ChapterTrackNumber f
+0 container ChapterDisplay 36
+5 string ChapString 0
+37c string ChapLanguage 0
+37e string ChapCountry 0
+2944 container ChapProcess 36
+2955 uint ChapProcessCodecID 2944
+50d binary ChapProcessPrivate 2944
+2911 container ChapProcessCommand 2944
+2922 uint ChapProcessTime 2911
+2933 binary ChapProcessData 2911
+254c367 container Tags 8538067
+3373 container Tag 254c367
+23c0 container Targets 3373
+28ca uint TargetTypeValue 23c0
+23ca string TargetType 23c0
+23c5 uint TagTrackUID 23c0
+23c9 uint TagEditionUID 23c0
+23c4 uint TagChapterUID 23c0
+23c6 uint TagAttachmentUID 23c0
+27c8 container SimpleTag 3373 27c8
+5a3 string TagName 27c8
+47a string TagLanguage 27c8
+484 uint TagDefault 27c8
+487 string TagString 27c8
+485 binary TagBinary 27c8
diff --git a/inc/lib/webm/matroska.php b/inc/lib/webm/matroska.php
new file mode 100644
index 00000000..5f1c38ee
--- /dev/null
+++ b/inc/lib/webm/matroska.php
@@ -0,0 +1,524 @@
+<?php
+/* This file is dedicated to the public domain; you may do as you wish with it. */
+
+// Information needed to parse an element type
+class EBMLElementType {
+ public $name;
+ public $datatype;
+ public $validParents;
+}
+
+// Information needed to parse all possible element types in a document
+class EBMLElementTypeList {
+ private $_els;
+ private $_ids;
+
+ public function __construct($filename) {
+ $lines = file($filename);
+ foreach($lines as $line) {
+ $fields = explode(' ', trim($line));
+ $t = new EBMLElementType;
+ $id = hexdec($fields[0]);
+ $t->datatype = $fields[1];
+ $t->name = $fields[2];
+ $t->validParents = array();
+ for ($i = 0; $i + 3 < count($fields); $i++) {
+ if ($fields[$i+3] == '*' || $fields[$i+3] == 'root') {
+ $t->validParents[$i] = $fields[$i+3];
+ } else {
+ $t->validParents[$i] = hexdec($fields[$i+3]);
+ }
+ }
+ $this->_els[$id] = $t;
+ $this->_ids[strtoupper($t->name)] = $id;
+ }
+ }
+
+ public function exists($id) {
+ return isset($this->_els[$id]);
+ }
+
+ public function name($id) {
+ if (!isset($this->_els[$id])) return NULL;
+ return $this->_els[$id]->name;
+ }
+
+ public function id($name) {
+ $name = strtoupper($name);
+ if (!isset($this->_ids[$name])) return NULL;
+ return $this->_ids[$name];
+ }
+
+ public function datatype($id) {
+ if ($id == 'root') return 'container';
+ if (!isset($this->_els[$id])) return 'binary';
+ return $this->_els[$id]->datatype;
+ }
+
+ public function validChild($id1, $id2) {
+ if (!isset($this->_els[$id2])) return TRUE;
+ $parents = $this->_els[$id2]->validParents;
+ return in_array('*', $parents) || in_array($id1, $parents);
+ }
+}
+
+// Matroska element types
+global $EBML_ELEMENTS;
+$EBML_ELEMENTS = new EBMLElementTypeList(dirname(__FILE__) . '/matroska-elements.txt');
+
+// Decode big-endian integer
+function ebmlDecodeInt($data, $signed=FALSE, $carryIn=0) {
+ $n = $carryIn;
+ if (strlen($data) > 8) throw new Exception('not supported: integer too long');
+ for ($i = 0; $i < strlen($data); $i++) {
+ if ($n > (PHP_INT_MAX >> 8) || $n < ((-PHP_INT_MAX-1) >> 8)) {
+ $n = floatval($n);
+ }
+ $n = $n * 0x100 + ord($data[$i]);
+ if ($i == 0 && $signed && ($n & 0x80) != 0) {
+ $n -= 0x100;
+ }
+ }
+ return $n;
+}
+
+// Decode big-endian IEEE float
+function ebmlDecodeFloat($data) {
+ switch (strlen($data)) {
+ case 0:
+ return 0;
+ case 4:
+ switch(pack('f', 1e9)) {
+ case '(knN':
+ $arr = unpack('f', strrev($data));
+ return $arr[1];
+ case 'Nnk(':
+ $arr = unpack('f', $data);
+ return $arr[1];
+ default:
+ error_log('cannot decode floats');
+ return NULL;
+ }
+ case 8:
+ switch(pack('d', 1e9)) {
+ case "\x00\x00\x00\x00\x65\xcd\xcd\x41":
+ $arr = unpack('d', strrev($data));
+ return $arr[1];
+ case "\x41\xcd\xcd\x65\x00\x00\x00\x00":
+ $arr = unpack('d', $data);
+ return $arr[1];
+ default:
+ error_log('cannot decode floats');
+ return NULL;
+ }
+ default:
+ error_log('unsupported float length');
+ return NULL;
+ }
+}
+
+// Decode big-endian signed offset from Jan 01, 2000 in nanoseconds
+// Convert to offset from Jan 01, 1970 in seconds
+function ebmlDecodeDate($data) {
+ return ebmlDecodeInt($data, TRUE) * 1e-9 + 946684800;
+}
+
+// Decode data of specified datatype
+function ebmlDecode($data, $datatype) {
+ switch ($datatype) {
+ case 'int': return ebmlDecodeInt($data, TRUE);
+ case 'uint': return ebmlDecodeInt($data, FALSE);
+ case 'float': return ebmlDecodeFloat($data);
+ case 'string': return chop($data, "\0");
+ case 'date': return ebmlDecodeDate($data);
+ case 'binary': return $data;
+ default: throw new Exception('unknown datatype');
+ }
+}
+
+// Methods for reading data from section of EBML file
+class EBMLReader {
+ private $_fileHandle;
+ private $_offset;
+ private $_size;
+ private $_position;
+
+ public function __construct($fileHandle, $offset=0, $size=NULL) {
+ $this->_fileHandle = $fileHandle;
+ $this->_offset = $offset;
+ $this->_size = $size;
+ $this->_position = 0;
+ }
+
+ // Tell position within data section
+ public function position() {
+ return $this->_position;
+ }
+
+ // Set position within data section
+ public function setPosition($position) {
+ $this->_position = $position;
+ }
+
+ // Total size of data section (NULL if unknown)
+ public function size() {
+ return $this->_size;
+ }
+
+ // Set end of data section
+ public function setSize($size) {
+ if ($this->_size === NULL) {
+ $this->_size = $size;
+ } else {
+ throw new Exception('size already set');
+ }
+ }
+
+ // Determine whether we are at end of data
+ public function endOfData() {
+ if ($this->_size === NULL) {
+ fseek($this->_fileHandle, $this->_offset + $this->_position);
+ fread($this->_fileHandle, 1);
+ if (feof($this->_fileHandle)) {
+ $this->_size = $this->_position;
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+ } else {
+ return $this->_position >= $this->_size;
+ }
+ }
+
+ // Create EBMLReader containing $size bytes and advance
+ public function nextSlice($size) {
+ $slice = new EBMLReader($this->_fileHandle, $this->_offset + $this->_position, $size);
+ if ($size !== NULL) {
+ $this->_position += $size;
+ if ($this->_size !== NULL && $this->_position > $this->_size) {
+ throw new Exception('unexpected end of data');
+ }
+ }
+ return $slice;
+ }
+
+ // Read entire region
+ public function readAll() {
+ if ($this->_size == 0) return '';
+ if ($this->_size === NULL) throw new Exception('unknown length');
+ fseek($this->_fileHandle, $this->_offset);
+ $data = fread($this->_fileHandle, $this->_size);
+ if ($data === FALSE || strlen($data) != $this->_size) {
+ throw new Exception('error reading from file');
+ }
+ return $data;
+ }
+
+ // Read $size bytes
+ public function read($size) {
+ return $this->nextSlice($size)->readAll();
+ }
+
+ // Read variable-length integer
+ public function readVarInt($signed=FALSE) {
+ // Read size and remove flag
+ $n = ord($this->read(1));
+ $size = 0;
+ if ($n == 0) {
+ throw new Exception('not supported: variable-length integer too long');
+ }
+ $flag = 0x80;
+ while (($n & $flag) == 0) {
+ $flag = $flag >> 1;
+ $size++;
+ }
+ $n -= $flag;
+
+ // Read remaining data
+ $rawInt = $this->read($size);
+
+ // Check for all ones
+ if ($n == $flag - 1 && $rawInt == str_repeat("\xFF", $size)) {
+ return NULL;
+ }
+
+ // Range shift for signed integers
+ if ($signed) {
+ if ($flag == 0x01) {
+ $n = ord($rawInt[0]) - 0x80;
+ $rawInt = $rawInt.substr(1);
+ } else {
+ $n -= ($flag >> 1);
+ }
+ }
+
+ // Convert to integer
+ $n = ebmlDecodeInt($rawInt, FALSE, $n);
+
+ // Range shift for signed integers
+ if ($signed) {
+ if ($n == PHP_INT_MAX) {
+ $n = floatval($n);
+ }
+ $n++;
+ }
+
+ return $n;
+ }
+}
+
+// EBML element
+class EBMLElement {
+ private $_id;
+ private $_name;
+ private $_datatype;
+ private $_content;
+ private $_headSize;
+
+ public function __construct($id, $content, $headSize) {
+ global $EBML_ELEMENTS;
+ $this->_id = $id;
+ $this->_name = $EBML_ELEMENTS->name($this->_id);
+ $this->_datatype = $EBML_ELEMENTS->datatype($this->_id);
+ $this->_content = $content;
+ $this->_headSize = $headSize;
+ }
+
+ public function id() {return $this->_id;}
+ public function name() {return $this->_name;}
+ public function datatype() {return $this->_datatype;}
+ public function content() {return $this->_content;}
+ public function headSize() {return $this->_headSize;}
+
+ // Total size of element (including ID and datasize)
+ public function size() {
+ return $this->_headSize + $this->_content->size();
+ }
+
+ // Read and interpret content
+ public function value() {
+ if ($this->_datatype == 'binary') {
+ return $this->_content;
+ } else {
+ return ebmlDecode($this->_content->readAll(), $this->_datatype);
+ }
+ }
+}
+
+// Iterate over EBML elements in data
+class EBMLElementList extends EBMLElement implements Iterator {
+ private $_cache;
+ private $_position;
+ private static $MAX_ELEMENTS = 10000;
+
+ public function __construct($id, $content, $headSize) {
+ parent::__construct($id, $content, $headSize);
+ $this->_cache = array();
+ $this->_position = 0;
+ }
+
+ public function rewind() {
+ $this->_position = 0;
+ }
+
+ public function current() {
+ if ($this->valid()) {
+ return $this->_cache[$this->_position];
+ } else {
+ return NULL;
+ }
+ }
+
+ public function key() {
+ return $this->_position;
+ }
+
+ public function next() {
+ $this->_position += $this->current()->size();
+ if ($this->content()->size() !== NULL && $this->_position > $this->content()->size()) {
+ throw new Exception('unexpected end of data');
+ }
+ }
+
+ public function valid() {
+ global $EBML_ELEMENTS;
+ if (isset($this->_cache[$this->_position])) return TRUE;
+ $this->content()->setPosition($this->_position);
+ if ($this->content()->endOfData()) return FALSE;
+ $id = $this->content()->readVarInt();
+ if ($id === NULL) throw new Exception('invalid ID');
+ if ($this->content()->size() === NULL && !$EBML_ELEMENTS->validChild($this->id(), $id)) {
+ $this->content()->setSize($this->_position);
+ return FALSE;
+ }
+ $size = $this->content()->readVarInt();
+ $headSize = $this->content()->position() - $this->_position;
+ $content = $this->content()->nextSlice($size);
+ if ($EBML_ELEMENTS->datatype($id) == 'container') {
+ $element = new EBMLElementList($id, $content, $headSize);
+ } else {
+ if ($size === NULL) {
+ throw new Exception('non-container element of unknown size');
+ }
+ $element = new EBMLElement($id, $content, $headSize);
+ }
+ $this->_cache[$this->_position] = $element;
+ return TRUE;
+ }
+
+ // Total size of element (including ID and size)
+ public function size() {
+ if ($this->content()->size() === NULL) {
+ $iElement = 0;
+ foreach ($this as $element) { // iterate over elements to find end
+ $iElement++;
+ if ($iElement > self::$MAX_ELEMENTS) throw new Exception('not supported: too many elements');
+ }
+ }
+ return $this->headSize() + $this->content()->size();
+ }
+
+ // Read and interpret content
+ public function value() {
+ return $this;
+ }
+
+ // Get element value by name
+ public function get($name, $defaultValue=NULL) {
+ $iElement = 0;
+ foreach ($this as $element) {
+ $iElement++;
+ if ($iElement > self::$MAX_ELEMENTS) throw new Exception('not supported: too many elements');
+ if (strtoupper($element->name()) == strtoupper($name)) {
+ return $element->value();
+ }
+ }
+ return $defaultValue;
+ }
+}
+
+// Parse block
+class MatroskaBlock {
+ const LACING_NONE = 0;
+ const LACING_XIPH = 1;
+ const LACING_EBML = 3;
+ const LACING_FIXED = 2;
+ public $trackNumber;
+ public $timecode;
+ public $keyframe;
+ public $invisible;
+ public $lacing;
+ public $discardable;
+ public $frames;
+
+ public function __construct($reader) {
+ # Header
+ $this->trackNumber = $reader->readVarInt();
+ $this->timecode = ebmlDecodeInt($reader->read(2), TRUE);
+ $flags = ord($reader->read(1));
+ if (($flags & 0x70) != 0) {
+ throw new Exception('reserved flags set');
+ }
+ $this->keyframe = (($flags & 0x80) != 0);
+ $this->invisible = (($flags & 0x08) != 0);
+ $this->lacing = ($flags >> 1) & 0x03;
+ $this->discardable = (($flags & 0x01) != 0);
+
+ # Lacing sizes
+ if ($this->lacing == self::LACING_NONE) {
+ $nsizes = 0;
+ } else {
+ $nsizes = ord($reader->read(1));
+ }
+ $sizes = array();
+ switch ($this->lacing) {
+ case self::LACING_XIPH:
+ for ($i = 0; $i < $nsizes; $i++) {
+ $size = 0;
+ $x = 255;
+ while ($x == 255) {
+ $x = ord($reader->read(1));
+ $size += $x;
+ if ($size > 65536) throw new Exception('not supported: laced frame too long');
+ }
+ $sizes[$i] = $size;
+ }
+ break;
+ case self::LACING_EBML:
+ $size = 0;
+ for ($i = 0; $i < $nsizes; $i++) {
+ $dsize = $reader->readVarInt($i != 0);
+ if ($dsize === NULL || $size + $dsize < 0) {
+ throw new Exception('invalid frame size');
+ }
+ $size += $dsize;
+ $sizes[$i] = $size;
+ }
+ break;
+ case self::LACING_FIXED:
+ $lenRemaining = $reader->size() - $reader->position();
+ if ($lenRemaining % ($nsizes + 1) != 0) {
+ throw new Exception('data size not divisible by frame count');
+ }
+ $size = (int) ($lenRemaining / ($nsizes + 1));
+ for ($i = 0; $i < $nsizes; $i++) {
+ $sizes[$i] = $size;
+ }
+ break;
+ }
+
+ # Frames
+ $this->frames = array();
+ for ($i = 0; $i < $nsizes; $i++) {
+ $this->frames[$i] = $reader->nextSlice($sizes[$i]);
+ }
+ $this->frames[$nsizes] = $reader->nextSlice($reader->size() - $reader->position());
+ }
+}
+
+// Create element list from $fileHandle
+function readMatroska($fileHandle) {
+ $reader = new EBMLReader($fileHandle);
+ if ($reader->read(4) != "\x1a\x45\xdf\xa3") {
+ throw new Exception('not an EBML file');
+ }
+ $root = new EBMLElementList('root', $reader, 0);
+ $header = $root->get('EBML');
+ $ebmlVersion = $header->get('EBMLReadVersion', 1);
+ $docType = $header->get('DocType');
+ $docTypeVersion = $header->get('DocTypeReadVersion', 1);
+ if ($ebmlVersion != 1) {
+ throw new Exception('unsupported EBML version');
+ }
+ if ($docType != 'matroska' && $docType != 'webm') {
+ throw new Exception ('unsupported document type');
+ }
+ if ($docTypeVersion < 1 || $docTypeVersion > 4) {
+ throw new Exception ('unsupported document type version');
+ }
+ return $root;
+}
+
+function ebmlEncodeVarInt($n) {
+ $data = '';
+ $flag = 0x80;
+ while ($n >= $flag) {
+ if ($flag == 0) {
+ throw new Exception('not supported: number too large');
+ }
+ $data = chr($n & 0xFF) . $data;
+ $n = $n >> 8;
+ $flag = $flag >> 1;
+ }
+ $data = chr($n | $flag) . $data;
+ return $data;
+}
+
+function ebmlEncodeElementName($name) {
+ global $EBML_ELEMENTS;
+ return ebmlEncodeVarInt($EBML_ELEMENTS->id($name));
+}
+
+function ebmlEncodeElement($name, $content) {
+ return ebmlEncodeElementName($name) . ebmlEncodeVarInt(strlen($content)) . $content;
+}
diff --git a/inc/lib/webm/posthandler.php b/inc/lib/webm/posthandler.php
new file mode 100644
index 00000000..50adbe80
--- /dev/null
+++ b/inc/lib/webm/posthandler.php
@@ -0,0 +1,49 @@
+<?php
+// Glue code for handling a Tinyboard post.
+// Portions of this file are derived from Tinyboard code.
+
+function postHandler($post) {
+ global $board, $config;
+
+ if ($post->has_file && $post->extension == 'webm') {
+ require_once dirname(__FILE__) . '/videodata.php';
+ $videoDetails = videoData($post->file_path);
+ if (!isset($videoDetails['container']) || $videoDetails['container'] != 'webm') return "not a WebM file";
+
+ // Set thumbnail
+ $thumbName = $board['dir'] . $config['dir']['thumb'] . $post->file_id . '.webm';
+ if ($config['spoiler_images'] && isset($_POST['spoiler'])) {
+ // Use spoiler thumbnail
+ $post->thumb = 'spoiler';
+ $size = @getimagesize($config['spoiler_image']);
+ $post->thumbwidth = $size[0];
+ $post->thumbheight = $size[1];
+ } elseif (isset($videoDetails['frame']) && $thumbFile = fopen($thumbName, 'wb')) {
+ // Use single frame from video as pseudo-thumbnail
+ fwrite($thumbFile, $videoDetails['frame']);
+ fclose($thumbFile);
+ $post->thumb = $post->file_id . '.webm';
+ } else {
+ // Fall back to file thumbnail
+ $post->thumb = 'file';
+ }
+ unset($videoDetails['frame']);
+
+ // Set width and height
+ if (isset($videoDetails['width']) && isset($videoDetails['height'])) {
+ $post->width = $videoDetails['width'];
+ $post->height = $videoDetails['height'];
+ if ($post->thumb != 'file' && $post->thumb != 'spoiler') {
+ $thumbMaxWidth = $post->op ? $config['thumb_op_width'] : $config['thumb_width'];
+ $thumbMaxHeight = $post->op ? $config['thumb_op_height'] : $config['thumb_height'];
+ if ($videoDetails['width'] > $thumbMaxWidth || $videoDetails['height'] > $thumbMaxHeight) {
+ $post->thumbwidth = min($thumbMaxWidth, intval(round($videoDetails['width'] * $thumbMaxHeight / $videoDetails['height'])));
+ $post->thumbheight = min($thumbMaxHeight, intval(round($videoDetails['height'] * $thumbMaxWidth / $videoDetails['width'])));
+ } else {
+ $post->thumbwidth = $videoDetails['width'];
+ $post->thumbheight = $videoDetails['height'];
+ }
+ }
+ }
+ }
+}
diff --git a/inc/lib/webm/videodata.php b/inc/lib/webm/videodata.php
new file mode 100644
index 00000000..d54d484b
--- /dev/null
+++ b/inc/lib/webm/videodata.php
@@ -0,0 +1,173 @@
+<?php
+/* This file is dedicated to the public domain; you may do as you wish with it. */
+require dirname(__FILE__) . '/matroska.php';
+
+function matroskaSeekElement($name, $pos) {
+ return ebmlEncodeElement('Seek',
+ ebmlEncodeElement('SeekID', ebmlEncodeElementName($name))
+ . ebmlEncodeElement('SeekPosition', pack('N', $pos))
+ );
+}
+
+// Make video from single WebM keyframe
+function muxWebMFrame($videoTrack, $frame) {
+ $lenSeekHead = 73;
+ $lenCues = 24;
+
+ // Determine version for EBML header
+ $version = 2;
+ $videoAttr = $videoTrack->get('Video');
+ if (isset($videoAttr)) {
+ if ($videoAttr->get('StereoMode') !== NULL) $version = 3;
+ if ($videoAttr->get('AlphaMode') !== NULL) $version = 3;
+ }
+ if ($videoTrack->get('CodecDelay') !== NULL) $version = 4;
+ if ($videoTrack->get('SeekPreRoll') !== NULL) $version = 4;
+ if ($frame->name() == 'BlockGroup' && $frame->get('DiscardPadding') !== NULL) $version = 4;
+
+ // EBML header
+ $ebml = ebmlEncodeElement('EBML',
+ ebmlEncodeElement('DocType', "webm")
+ . ebmlEncodeElement('DocTypeVersion', chr($version))
+ . ebmlEncodeElement('DocTypeReadVersion', "\x02")
+ );
+
+ // Segment
+ $info = ebmlEncodeElement('Info',
+ ebmlEncodeElement('Duration', "\x41\x20\x00\x00")
+ . ebmlEncodeElement('MuxingApp', 'ccframe')
+ . ebmlEncodeElement('WritingApp', 'ccframe')
+ );
+ $tracks = ebmlEncodeElement('Tracks',
+ ebmlEncodeElement('TrackEntry', $videoTrack->content()->readAll())
+ );
+ $cues = ebmlEncodeElement('Cues',
+ ebmlEncodeElement('CuePoint',
+ ebmlEncodeElement('CueTime', "\x00")
+ . ebmlEncodeElement('CueTrackPositions',
+ ebmlEncodeElement('CueTrack', pack('N', $videoTrack->get('TrackNumber')))
+ . ebmlEncodeElement('CueClusterPosition', pack('N', $lenSeekHead + strlen($info) + strlen($tracks) + $lenCues))
+ )
+ )
+ );
+ if (strlen($cues) != $lenCues) throw new Exception('length of Cues element wrong');
+ $cluster = ebmlEncodeElement('Cluster',
+ ebmlEncodeElement('Timecode', "\x00")
+ . ebmlEncodeElement($frame->name(), $frame->content()->readAll())
+ . ebmlEncodeElement('Void', '')
+ );
+ $seekHead = ebmlEncodeElement('SeekHead',
+ matroskaSeekElement('Info', $lenSeekHead)
+ . matroskaSeekElement('Tracks', $lenSeekHead + strlen($info))
+ . matroskaSeekElement('Cues', $lenSeekHead + strlen($info) + strlen($tracks))
+ . matroskaSeekElement('Cluster', $lenSeekHead + strlen($info) + strlen($tracks) + $lenCues)
+ );
+ if (strlen($seekHead) != $lenSeekHead) throw new Exception('length of SeekHead element wrong');
+ $segment = ebmlEncodeElement('Segment', $seekHead . $info . $tracks . $cues . $cluster);
+
+ return $ebml . $segment;
+}
+
+// Locate first WebM keyframe of track $trackNumber after timecode $skip
+function firstWebMFrame($segment, $trackNumber, $skip=0) {
+ foreach($segment as $x1) {
+ if ($x1->name() == 'Cluster') {
+ $cluserTimecode = $x1->Get('Timecode');
+ foreach($x1 as $blockGroup) {
+ $blockRaw = NULL;
+ if ($blockGroup->name() == 'SimpleBlock') {
+ $blockRaw = $blockGroup->value();
+ } elseif ($blockGroup->name() == 'BlockGroup') {
+ $blockRaw = $blockGroup->get('Block');
+ }
+ if (isset($blockRaw)) {
+ $block = new MatroskaBlock($blockRaw);
+ if ($block->trackNumber == $trackNumber && $block->keyframe) {
+ if (!isset($cluserTimecode) || $cluserTimecode + $block->timecode >= $skip) {
+ return $blockGroup;
+ } elseif (!isset($frame1)) {
+ $frame1 = $blockGroup;
+ }
+ }
+ }
+ }
+ }
+ }
+ return isset($frame1) ? $frame1 : NULL;
+}
+
+function videoData($filename) {
+ $data = array();
+
+ // Open file
+ $fileHandle = fopen($filename, 'rb');
+ if (!$fileHandle) {
+ error_log('could not open file');
+ return $data;
+ }
+
+ try {
+ $root = readMatroska($fileHandle);
+ $data['container'] = $root->get('EBML')->get('DocType');
+
+ // Locate segment information and tracks
+ $segment = $root->get('Segment');
+ if (!isset($segment)) throw new Exception('missing Segment element');
+
+ // Get segment information
+ $info = $segment->get('Info');
+ if (isset($info)) {
+ $timecodeScale = $info->get('TimecodeScale');
+ $duration = $info->get('Duration');
+ if (isset($timecodeScale) && isset($duration)) {
+ $data['duration'] = 1e-9 * $timecodeScale * $duration;
+ }
+ }
+
+ // Locate video track
+ $tracks = $segment->get('Tracks');
+ if (!isset($tracks)) throw new Exception('missing Tracks element');
+ foreach($tracks as $trackEntry) {
+ if ($trackEntry->name() == 'TrackEntry' && $trackEntry->get('TrackType') == 1) {
+ $videoTrack = $trackEntry;
+ break;
+ }
+ }
+ if (!isset($videoTrack)) throw new Exception('no video track');
+
+ // Get track information
+ $videoAttr = $videoTrack->get('Video');
+ if (!isset($videoAttr)) throw new Exception('missing video parameters');
+ $pixelWidth = $videoAttr->get('PixelWidth');
+ $pixelHeight = $videoAttr->get('PixelHeight');
+ if (!isset($pixelWidth) || !isset($pixelHeight)) throw new Exception('no width or height');
+ if ($pixelWidth == 0 || $pixelHeight == 0) throw new Exception('bad PixelWidth/PixelHeight');
+ $displayWidth = $videoAttr->get('DisplayWidth', $pixelWidth);
+ $displayHeight = $videoAttr->get('DisplayHeight', $pixelHeight);
+ if ($displayWidth == 0 || $displayHeight == 0) throw new Exception('bad DisplayWidth/DisplayHeight');
+ $data['width'] = $displayWidth;
+ $data['height'] = $displayHeight;
+
+ // Extract frame to use as thumbnail
+ if ($videoAttr->get('AlphaMode') != NULL) {
+ if (!($pixelWidth % 2 == 0 && $pixelHeight % 2 == 0 && $displayWidth % 2 == 0 && $displayHeight % 2 == 0)) {
+ throw new Exception('preview frame blocked due to Chromium bug');
+ }
+ }
+ $trackNumber = $videoTrack->get('TrackNumber');
+ if (!isset($trackNumber)) throw new Exception('missing track number');
+ if (isset($data['duration']) && $data['duration'] >= 5) {
+ $skip = 1e9 / $timecodeScale;
+ } else {
+ $skip = 0;
+ }
+ $frame = firstWebMFrame($segment, $trackNumber, $skip);
+ if (!isset($frame)) throw new Exception('no keyframes');
+ $data['frame'] = muxWebMFrame($videoTrack, $frame);
+ } catch (Exception $e) {
+ error_log($e->getMessage());
+ }
+
+ fclose($fileHandle);
+ return $data;
+}