r23858 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r23857‎ | r23858 | r23859 >
Date:22:57, 7 July 2007
Author:mikeb
Status:old
Tags:
Comment:
The largest single pieces of work are in includes/MimeMagic.php, includes/media/MimePlugin.php, and includes/media/AV.php. My MimeMagic and MimePlugin work work attempts to propose a general framework for file identification/validation, while the classes of AV.php target audio and video specifically.

Small tweaks include modifying the interface to and bahavior of the UploadVerification hook. My version sends the file extension along to the hook, since it has just been determined in SpecialUpload.php...but my hooks could also just recompute this themselves. Also, now upload verification hooks can issue upload warnings as well as flat-out rejections.

Other changes unrelated to my SoC project:
* Made it possible to specify only the name of a class as a hook. If the string specified is not a function and is a class, wfRunHooks now probes that class for a static method called on[NameOfHook] and calls it if it exists.
* Incorporated fix to bug 10348
* Enhanced reliability of JobQueue::pop_type. Used to give up if the selected job had already been claimed by a concurrent process, now finds another job to do.
Modified paths:
  • /branches/mikeb/phase3/docs/hooks.txt (modified) (history)
  • /branches/mikeb/phase3/includes/AutoLoader.php (modified) (history)
  • /branches/mikeb/phase3/includes/DefaultSettings.php (modified) (history)
  • /branches/mikeb/phase3/includes/Hooks.php (modified) (history)
  • /branches/mikeb/phase3/includes/JobQueue.php (modified) (history)
  • /branches/mikeb/phase3/includes/MimeMagic.php (modified) (history)
  • /branches/mikeb/phase3/includes/SpecialUpload.php (modified) (history)
  • /branches/mikeb/phase3/includes/media/AV.php (added) (history)
  • /branches/mikeb/phase3/includes/media/Generic.php (modified) (history)
  • /branches/mikeb/phase3/includes/media/MimePlugin.php (added) (history)

Diff [purge]

Index: branches/mikeb/phase3/docs/hooks.txt
@@ -214,9 +214,11 @@
215215 # ...
216216 function protect() {
217217 global $wgUser;
218 - if (wfRunHooks('ArticleProtect', array(&$this, &$wgUser))) {
 218+ if (wfRunHooks('ArticleProtect', array(&$this,
 219+ &$wgUser))) {
219220 # protect the article
220 - wfRunHooks('ArticleProtectComplete', array(&$this, &$wgUser));
 221+ wfRunHooks('ArticleProtectComplete',
 222+ array(&$this, &$wgUser));
221223 }
222224 }
223225 }
@@ -304,7 +306,8 @@
305307 $create: Whether or not the restoration caused the page to be created
306308 (i.e. it didn't exist before)
307309
308 -'ArticleViewHeader': Before the parser cache is about to be tried for article viewing.
 310+'ArticleViewHeader': Before the parser cache is about to be tried for article
 311+viewing.
309312 &$pcache: whether to try the parser cache or not
310313 &$outputDone: whether the output for this page finished or not
311314
@@ -372,7 +375,8 @@
373376 saved, that is before insertNewArticle() is called
374377 &$editpage_Obj: the current EditPage object
375378
376 -'EditFormPreloadText': Allows population of the edit form when creating new pages
 379+'EditFormPreloadText': Allows population of the edit form when creating new
 380+pages
377381 &$text: Text to preload with
378382 &$title: Title object representing the page being created
379383
@@ -387,9 +391,12 @@
388392 $section: Section being edited
389393 &$error: Error message to return
390394
391 -Return false to halt editing; you'll need to handle error messages, etc. yourself.
392 -Alternatively, modifying $error and returning true will cause the contents of $error
393 -to be echoed at the top of the edit form as wikitext. Return true without altering
 395+Return false to halt editing; you'll need to handle error messages, etc.
 396+yourself.
 397+Alternatively, modifying $error and returning true will cause the contents of
 398+$error
 399+to be echoed at the top of the edit form as wikitext. Return true without
 400+altering
394401 $error to allow the edit to proceed.
395402
396403 'EditSectionLink': Override the return value of Linker::editSectionLink()
@@ -399,7 +406,8 @@
400407 $link: Default link
401408 $result: Result (alter this to override the generated links)
402409
403 -'EditSectionLinkForOther': Override the return value of Linker::editSectionLinkForOther()
 410+'EditSectionLinkForOther': Override the return value of
 411+Linker::editSectionLinkForOther()
404412 $skin: Skin rendering the UI
405413 $title: Title being linked to
406414 $section: Section to link to
@@ -425,10 +433,12 @@
426434 $subject: subject of the mail
427435 $text: text of the mail
428436
429 -'FetchChangesList': When fetching the ChangesList derivative for a particular user
 437+'FetchChangesList': When fetching the ChangesList derivative for a particular
 438+user
430439 &$user: User the list is being fetched for
431440 &$skin: Skin object to be used with the list
432 -&$list: List object (defaults to NULL, change it to an object instance and return
 441+&$list: List object (defaults to NULL, change it to an object instance and
 442+return
433443 false override the list derivative used)
434444
435445 'FileUpload': When a file upload occurs
@@ -449,21 +459,26 @@
450460 $url: string value as output (out parameter, can modify)
451461 $query: query options passed to Title::getFullURL()
452462
453 -'ImageOpenShowImageInlineBefore': Call potential extension just before showing the image on an image page
 463+'ImageOpenShowImageInlineBefore': Call potential extension just before showing
 464+the image on an image page
454465 $imagePage: ImagePage object ($this)
455466 $output: $wgOut
456467
457 -'InternalParseBeforeLinks': during Parser's internalParse method before links but
 468+'InternalParseBeforeLinks': during Parser's internalParse method before links
 469+but
458470 after noinclude/includeonly/onlyinclude and other processing.
459471 &$this: Parser object
460472 &$text: string containing partially parsed text
461473 &$this->mStripState: Parser's internal StripState object
462474
463 -'LoginAuthenticateAudit': a login attempt for a valid user account either succeeded or failed.
464 - No return data is accepted; this hook is for auditing only.
 475+'LoginAuthenticateAudit': a login attempt for a valid user account either
 476+succeeded or failed.
 477+ No return data is accepted; this hook is for auditing
 478+ only.
465479 $user: the User object being authenticated against
466480 $password: the password being submitted and found wanting
467 -$retval: a LoginForm class constant with authenticateUserData() return value (SUCCESS, WRONG_PASS, etc)
 481+$retval: a LoginForm class constant with authenticateUserData() return value
 482+(SUCCESS, WRONG_PASS, etc)
468483
469484 'LogPageValidTypes': action being logged. DEPRECATED: Use $wgLogTypes
470485 &$type: array of strings
@@ -471,10 +486,12 @@
472487 'LogPageLogName': name of the logging page(s). DEPRECATED: Use $wgLogNames
473488 &$typeText: array of strings
474489
475 -'LogPageLogHeader': strings used by wfMsg as a header. DEPRECATED: Use $wgLogHeaders
 490+'LogPageLogHeader': strings used by wfMsg as a header. DEPRECATED: Use
 491+$wgLogHeaders
476492 &$headerText: array of strings
477493
478 -'LogPageActionText': strings used by wfMsg as a header. DEPRECATED: Use $wgLogActions
 494+'LogPageActionText': strings used by wfMsg as a header. DEPRECATED: Use
 495+$wgLogActions
479496 &$actionText: array of strings
480497
481498 'MarkPatrolled': before an edit is marked patrolled
@@ -494,6 +511,10 @@
495512 $errmsg: error message, in HTML (string). Nonempty indicates failure
496513 of rendering the formula
497514
 515+'MimeMagicRegisterPlugins': Tell the MIME detection module to consider a
 516+plugin for more accurate identification of particular MIME type(s).
 517+This hook should do nothing more than instantiate MimePlugin(s).
 518+
498519 'OutputPageBeforeHTML': a page has been processed by the parser and
499520 the resulting HTML is about to be displayed.
500521 $parserOutput: the parserOutput (object) that corresponds to the page
@@ -529,7 +550,8 @@
530551 Change $result and return false to give a definitive answer, otherwise
531552 the built-in rate limiting checks are used, if enabled.
532553
533 -'PreferencesUserInformationPanel': Add HTML bits to user information list in preferences form
 554+'PreferencesUserInformationPanel': Add HTML bits to user information list in
 555+preferences form
534556 $form : PreferencesForm object
535557 &$html : HTML to append to
536558
@@ -552,7 +574,8 @@
553575 &$siteNotice: HTML sitenotice
554576 Alter the contents of $siteNotice to add to/alter the sitenotice/anonnotice.
555577
556 -'SkinTemplateOutputPageBeforeExec': Before SkinTemplate::outputPage() starts page output
 578+'SkinTemplateOutputPageBeforeExec': Before SkinTemplate::outputPage() starts
 579+page output
557580 &$sktemplate: SkinTemplate object
558581 &$tpl: Template engine object
559582
@@ -643,7 +666,9 @@
644667 'SkinTemplateContentActions': after building the $content_action array right
645668 before returning it, see Content_action.php in
646669 the extensions/examples/ directory
647 - ( http://svn.wikimedia.org/viewvc/mediawiki/trunk/extensions/examples/ )
 670+ (
 671+ http://svn.wikimedia.org/viewvc/mediawiki/trunk/ex
 672+ tensions/examples/ )
648673 for a demonstration of how to use this hook.
649674 $content_actions: The array of content actions
650675
Index: branches/mikeb/phase3/includes/MimeMagic.php
@@ -75,7 +75,8 @@
7676 * Implements functions related to mime types such as detection and mapping to
7777 * file extension.
7878 *
79 - * Instances of this class are stateles, there only needs to be one global instance
 79+ * Instances of this class are stateles, there only needs to be one global
 80+ instance
8081 * of MimeMagic. Please use MimeMagic::singleton() to get that instance.
8182 */
8283 class MimeMagic {
@@ -98,21 +99,62 @@
99100 */
100101 var $mExtToMime= NULL;
101102
 103+ /**
 104+ * Multidimensional array indexed by MIME type, then subtype,
 105+ * containing among other things references to any appropriate MimePlugins.
 106+ */
 107+ private $pluginsByType;
 108+
 109+ /**
 110+ * Multidimensional array indexed by file extension, containing
 111+ * references to any appropriate MimePlugins.
 112+ */
 113+ private $pluginsByExt;
 114+
 115+ /**
 116+ * flat list of extensions with reliable detection. Generation is
 117+ * quite expensive, so this list is computed once and cached here.
 118+ */
 119+ private $recognizableExtensions;
 120+
 121+ /**
 122+ * Mime Type strings from plugins (same format as MM_WELL_KNOWN_MIME_TYPES)
 123+ * are aggregated here
 124+ */
 125+ private $pluginMimeTypes;
 126+
 127+ /**
 128+ * Mime Info strings from plugins (same format as MM_WELL_KNOWN_MIME_INFO)
 129+ * are aggregated here
 130+ */
 131+ private $pluginMimeInfo;
 132+
102133 /** The singleton instance
103134 */
104135 private static $instance;
105136
106 - /** Initializes the MimeMagic object. This is called by MimeMagic::singleton().
 137+ /** Initializes the MimeMagic object. Declared private; use
 138+ * MimeMagic::singleton() to obtain the instance in client code.
107139 *
108 - * This constructor parses the mime.types and mime.info files and build internal mappings.
 140+ * This constructor parses the mime.types and mime.info files and build
 141+ * internal mappings.
 142+ *
109143 */
110 - function __construct() {
 144+ private function __construct() {
 145+ global $wgMimeTypeFile, $IP, $wgHooks;
 146+
 147+ # Add hooks to register plugins included in the main distribution
 148+ $wgHooks['MimeMagicRegisterPlugins'][] = 'AVMimePlugin';
 149+
 150+ $this->pluginMimeTypes = $this->pluginMimeInfo = '';
 151+ $this->pluginsByType = array();
 152+ //Ask for special detection plugins. Return value doesn't matter.
 153+ wfRunHooks('MimeMagicRegisterPlugins', array($this));
 154+
111155 /*
112156 * --- load mime.types ---
113157 */
114158
115 - global $wgMimeTypeFile, $IP;
116 -
117159 $types = MM_WELL_KNOWN_MIME_TYPES;
118160
119161 if ( $wgMimeTypeFile == 'includes/mime.types' ) {
@@ -120,18 +162,24 @@
121163 }
122164
123165 if ( $wgMimeTypeFile ) {
124 - if ( is_file( $wgMimeTypeFile ) and is_readable( $wgMimeTypeFile ) ) {
125 - wfDebug( __METHOD__.": loading mime types from $wgMimeTypeFile\n" );
 166+ if ( is_file( $wgMimeTypeFile ) and is_readable( $wgMimeTypeFile ) )
 167+ {
 168+ wfDebug( __METHOD__.": loading mime types from
 169+ $wgMimeTypeFile\n" );
126170 $types .= "\n";
127171 $types .= file_get_contents( $wgMimeTypeFile );
128172 } else {
129 - wfDebug( __METHOD__.": can't load mime types from $wgMimeTypeFile\n" );
 173+ wfDebug( __METHOD__.": can't load mime types from $wgMimeTypeFile. Proceeding with buit-in & plugin types only.\n" );
130174 }
131175 } else {
132 - wfDebug( __METHOD__.": no mime types file defined, using build-ins only.\n" );
 176+ wfDebug( __METHOD__.": no mime types file defined, using built-in & plugin types only.\n" );
133177 }
134178
135 - $types = str_replace( array( "\r\n", "\n\r", "\n\n", "\r\r", "\r" ), "\n", $types );
 179+ $types .= $this->pluginMimeTypes; //will start with newline or be empty
 180+ unset($this->pluginMimeTypes); //no longer needed
 181+
 182+ $types = str_replace( array( "\r\n", "\n\r", "\n\n", "\r\r", "\r" ),
 183+ "\n", $types );
136184 $types = str_replace( "\t", " ", $types );
137185
138186 $this->mMimeToExt = array();
@@ -178,16 +226,16 @@
179227 /*
180228 * --- load mime.info ---
181229 */
 230+ $info = MM_WELL_KNOWN_MIME_INFO;
182231
183232 global $wgMimeInfoFile;
184233 if ( $wgMimeInfoFile == 'includes/mime.info' ) {
185234 $wgMimeInfoFile = "$IP/$wgMimeInfoFile";
186235 }
187236
188 - $info = MM_WELL_KNOWN_MIME_INFO;
189 -
190237 if ( $wgMimeInfoFile ) {
191 - if ( is_file( $wgMimeInfoFile ) and is_readable( $wgMimeInfoFile ) ) {
 238+ if ( is_file( $wgMimeInfoFile ) and is_readable( $wgMimeInfoFile ) )
 239+ {
192240 wfDebug( __METHOD__.": loading mime info from $wgMimeInfoFile\n" );
193241 $info .= "\n";
194242 $info .= file_get_contents( $wgMimeInfoFile );
@@ -195,10 +243,14 @@
196244 wfDebug(__METHOD__.": can't load mime info from $wgMimeInfoFile\n");
197245 }
198246 } else {
199 - wfDebug(__METHOD__.": no mime info file defined, using build-ins only.\n");
 247+ wfDebug(__METHOD__.": no mime info file defined, using built-in & plugin types only.\n");
200248 }
201249
202 - $info = str_replace( array( "\r\n", "\n\r", "\n\n", "\r\r", "\r" ), "\n", $info);
 250+ $info .= $this->pluginMimeInfo; //appended here so plugins get final say
 251+ unset($this->pluginMimeInfo);
 252+
 253+ $info = str_replace( array( "\r\n", "\n\r", "\n\n", "\r\r", "\r" ),
 254+ "\n", $info);
203255 $info = str_replace( "\t", " ", $info );
204256
205257 $this->mMimeTypeAliases = array();
@@ -218,8 +270,9 @@
219271 #print "processing MIME INFO line $s<br>";
220272
221273 $match = array();
222 - if ( preg_match( '!\[\s*(\w+)\s*\]!', $s, $match ) ) {
223 - $s = preg_replace( '!\[\s*(\w+)\s*\]!', '', $s );
 274+ $exp = '!\s*\[\s*(\w+)\s*\]!';
 275+ if ( preg_match($exp, $s, $match ) ) {
 276+ $s = preg_replace($exp, '', $s );
224277 $mtype = trim( strtoupper( $match[1] ) );
225278 } else {
226279 $mtype = MEDIATYPE_UNKNOWN;
@@ -247,6 +300,31 @@
248301 }
249302 }
250303
 304+ /* Build index of plugins by extension. Plugins can influence the
 305+ * extensions they are indexed under by implementing
 306+ * MimePlugin->mimeTypes()
 307+ */
 308+ $this->pluginsByExt = array();
 309+ foreach($this->pluginsByType AS $type => $subtypes)
 310+ {
 311+ foreach($subtypes AS $subtype => $detectors)
 312+ {
 313+ foreach($detectors AS $detector)
 314+ {
 315+ if($detector[1]) //if authoritative
 316+ {
 317+ $exts = $this->getExtensionsForType("$type/$subtype");
 318+
 319+ foreach(explode(' ', $exts) AS $ext)
 320+ {
 321+ $ext = trim($ext); if(empty($ext)) continue;
 322+ $this->pluginsByExt[$ext][] = $detector;
 323+ }
 324+ }
 325+ }
 326+ }
 327+ }
 328+
251329 }
252330
253331 /**
@@ -256,23 +334,89 @@
257335 if ( !isset( self::$instance ) ) {
258336 self::$instance = new MimeMagic;
259337 }
 338+
260339 return self::$instance;
261340 }
262341
 342+ /**
 343+ * Populates private plugin arrays by MIME type (and subtype). This method should only be
 344+ * called by MimePlugin::registerContentType
 345+ */
 346+ public function registerPluginByContentType(MimePlugin $detector, $type,
 347+ $subtype, $authoritative)
 348+ {
 349+ $this->pluginsByType[$type][$subtype][] = array($detector,
 350+ $authoritative);
 351+ }
 352+
 353+ /**
 354+ * Intended to be called by plugins during their construction/registration
 355+ * process. By allowing plugins to append MIME type information to the
 356+ * built-in well-known types, complete support for a new MIME type can be
 357+ * assured by simply installing a plugin for it, without admins having to worry
 358+ * about the contents of their mime.types or mime.info files as well.
 359+ *
 360+ * @todo Move loading code in constructor to a helper method, make it more
 361+ * verbose about formatting errors, and use it to validate typesStrings on a
 362+ * plugin by plugin basis.
 363+ *
 364+ * @param string typesString The mime types and extensions supported by this
 365+ * plugin, in the format of a mime.types file. (See MM_WELL_KNOWN_MIME_TYPES
 366+ * for an example.)
 367+ */
 368+ public function addMimeTypes(&$typesString)
 369+ {
 370+ $this->pluginMimeTypes .= "\n" . $typesString;
 371+ }
 372+
 373+ /**
 374+ * Intended to be called by plugins.
 375+ *
 376+ * @param string infoString Mime types supported by this plugin and their
 377+ * associated media types, in the format of a mime.info file. (See
 378+ * MM_WELL_KNOWN_MIME_INFO for an example.)
 379+ */
 380+ public function addMimeInfo(&$infoString)
 381+ {
 382+ $this->pluginMimeInfo .= "\n" . $infoString;
 383+ }
 384+
263385 /** returns a list of file extensions for a given mime type
264386 * as a space separated string.
265387 */
266388 function getExtensionsForType( $mime ) {
267 - $mime = strtolower( $mime );
 389+ if(func_num_args() == 1)
 390+ {
 391+ $mime = strtolower( func_get_arg(0) );
 392+ $parts = MimeMagic::splitMimeString($mime);
 393+ } else {
 394+ $mime = strtolower(func_get_arg(0) . "/" . func_get_arg(1));
 395+ $parts[0] = strtolower(func_get_arg(0));
 396+ $parts[1] = strtolower(func_get_arg(1));
 397+ }
268398
269 - $r = @$this->mMimeToExt[$mime];
 399+ if($parts[1] === '*')
 400+ {
 401+ //lookup all main types matching parts[0]
 402+ $r = '';
 403+ foreach($this->mMimeToExt AS $mimecheck => $exts)
 404+ {
 405+ $p = MimeMagic::splitMimeString($mimecheck);
 406+ if(strcmp($p[0], $parts[0]) === 0)
 407+ {
 408+ $r .= ' ' . $exts;
 409+ }
 410+ }
 411+ } else {
 412+ $r = @$this->mMimeToExt[$mime];
270413
271 - if ( @!$r and isset( $this->mMimeTypeAliases[$mime] ) ) {
272 - $mime = $this->mMimeTypeAliases[$mime];
273 - $r = @$this->mMimeToExt[$mime];
 414+ if ( @!$r and isset( $this->mMimeTypeAliases[$mime] ) ) {
 415+ $mime = $this->mMimeTypeAliases[$mime];
 416+ $r = @$this->mMimeToExt[$mime];
 417+ }
274418 }
275419
276 - return $r;
 420+ return trim($r);
277421 }
278422
279423 /** returns a list of mime types for a given file extension
@@ -286,7 +430,8 @@
287431 }
288432
289433 /** returns a single mime type for a given file extension.
290 - * This is always the first type from the list returned by getTypesForExtension($ext).
 434+ * This is always the first type from the list returned by
 435+ getTypesForExtension($ext).
291436 */
292437 function guessTypesForExtension( $ext ) {
293438 $m = $this->getTypesForExtension( $ext );
@@ -345,27 +490,49 @@
346491 * invalid uploads; if we can't identify the type we won't
347492 * be able to say if it's invalid.
348493 *
349 - * @todo Be more accurate when using fancy mime detector plugins;
350 - * right now this is the bare minimum getimagesize() list.
351494 * @return bool
352495 */
353496 function isRecognizableExtension( $extension ) {
354 - static $types = array(
355 - 'gif', 'jpeg', 'jpg', 'png', 'swf', 'psd',
356 - 'bmp', 'tiff', 'tif', 'jpc', 'jp2',
357 - 'jpx', 'jb2', 'swc', 'iff', 'wbmp',
358 - 'xbm', 'djvu'
359 - );
360 - return in_array( strtolower( $extension ), $types );
 497+ if(! isset($this->recognizableExtensions))
 498+ {
 499+ $pluginTypes = array();
 500+ //go over plugins by extension and add authoritative ones
 501+ foreach($this->pluginsByExt AS $ext => $detectors)
 502+ {
 503+ foreach($detectors AS $detector)
 504+ {
 505+ if($detector[1])
 506+ {
 507+ $pluginTypes[] = $ext;
 508+ break;
 509+ }
 510+ }
 511+ }
 512+
 513+ $this->recognizableExtensions = array_merge(array(
 514+ 'gif', 'jpeg', 'jpg', 'png', 'swf', 'psd',
 515+ 'bmp', 'tiff', 'tif', 'jpc', 'jp2',
 516+ 'jpx', 'jb2', 'swc', 'iff', 'wbmp',
 517+ 'xbm', 'djvu'
 518+ ),
 519+ $pluginTypes
 520+ );
 521+ }
 522+
 523+ return in_array( strtolower( $extension ), $this->recognizableExtensions );
361524 }
362525
363526
364 - /** mime type detection. This uses detectMimeType to detect the mime type of the file,
365 - * but applies additional checks to determine some well known file formats that may be missed
366 - * or misinterpreter by the default mime detection (namely xml based formats like XHTML or SVG).
 527+ /** mime type detection. This uses detectMimeType to detect the mime type of
 528+ the file,
 529+ * but applies additional checks to determine some well known file formats
 530+ that may be missed
 531+ * or misinterpreter by the default mime detection (namely xml based formats
 532+ like XHTML or SVG).
367533 *
368534 * @param string $file The file to check
369 - * @param mixed $ext The file extension, or true to extract it from the filename.
 535+ * @param mixed $ext The file extension, or true to extract it from the
 536+ filename.
370537 * Set it to false to ignore the extension.
371538 *
372539 * @return string the mime type of $file
@@ -405,9 +572,11 @@
406573 $xml_type = "ASCII";
407574 } elseif ( substr( $head, 0, 8 ) == "\xef\xbb\xbf<?xml") {
408575 $xml_type = "UTF-8";
409 - } elseif ( substr( $head, 0, 10 ) == "\xfe\xff\x00<\x00?\x00x\x00m\x00l" ) {
 576+ } elseif ( substr( $head, 0, 10 ) ==
 577+ "\xfe\xff\x00<\x00?\x00x\x00m\x00l" ) {
410578 $xml_type = "UTF-16BE";
411 - } elseif ( substr( $head, 0, 10 ) == "\xff\xfe<\x00?\x00x\x00m\x00l\x00") {
 579+ } elseif ( substr( $head, 0, 10 ) ==
 580+ "\xff\xfe<\x00?\x00x\x00m\x00l\x00") {
412581 $xml_type = "UTF-16LE";
413582 }
414583
@@ -420,10 +589,12 @@
421590 $doctype = "";
422591 $tag = "";
423592
424 - if ( preg_match( '%<!DOCTYPE\s+[\w-]+\s+PUBLIC\s+["'."'".'"](.*?)["'."'".'"].*>%sim',
425 - $head, $match ) ) {
426 - $doctype = $match[1];
427 - }
 593+ if (
 594+ preg_match( '%<!DOCTYPE\s+[\w-]+\s+PUBLIC\s+["'."'".'"](.*?)["'."'".'"].*>%sim',
 595+ $head, $match ) )
 596+ {
 597+ $doctype = $match[1];
 598+ }
428599 if ( preg_match( '%<(\w+).*>%sim', $head, $match ) ) {
429600 $tag = $match[1];
430601 }
@@ -495,6 +666,34 @@
496667
497668 }
498669
 670+ //run plugins
 671+ //if aliased, fetch plugins for both types
 672+ //remove plugins registered as both
 673+ //getContentType
 674+ //re-run final result through aliases
 675+
 676+ $plugins = $this->getPluginsForType($mime);
 677+
 678+ if(isset($this->mMimeTypeAliases[$mime] ))
 679+ {
 680+ $mime2 = $this->mMimeTypeAliases[$mime];
 681+ $plugins = array_merge((array) $plugins, (array) $this->getPluginsForType($mime2));
 682+ MimeMagic::killPluginDupes($plugins);
 683+ }
 684+
 685+ foreach($plugins AS $detector)
 686+ {
 687+ $plugin = $detector[0];
 688+ $authoritative = $detector[1];
 689+
 690+ $pMime = $plugin->getContentType($file);
 691+ if(strpos($pMime, 'unknown/unknown') === false || $authoritative)
 692+ {
 693+ $mime = $pMime;
 694+ break;
 695+ }
 696+ }
 697+
499698 if ( isset( $this->mMimeTypeAliases[$mime] ) ) {
500699 $mime = $this->mMimeTypeAliases[$mime];
501700 }
@@ -503,16 +702,92 @@
504703 return $mime;
505704 }
506705
507 - /** Internal mime type detection, please use guessMimeType() for application code instead.
508 - * Detection is done using an external program, if $wgMimeDetectorCommand is set.
509 - * Otherwise, the fileinfo extension and mime_content_type are tried (in this order), if they are available.
510 - * If the dections fails and $ext is not false, the mime type is guessed from the file extension, using
 706+ private static function killPluginDupes(&$plugins)
 707+ {
 708+ $out = array();
 709+ $existingPlugins = array();
 710+
 711+ foreach($plugins AS $newDetector)
 712+ {
 713+ if(! in_array($newDetector[0], $existingPlugins))
 714+ {
 715+ $out[] = $newDetector;
 716+ $existingPlugins[] = $newDetector[0];
 717+ }
 718+ }
 719+
 720+ $plugins = $out;
 721+ }
 722+
 723+ public static function splitMimeString($type)
 724+ {
 725+ //split $type into type and subtype
 726+ $subtype = '*';
 727+
 728+ $slashPos = strpos($type, '/');
 729+ if($slashPos > 0)
 730+ {
 731+ # assume the presence of a slash indicates this is a type/subtype
 732+ if(strlen($type) - 1 === $slashPos)
 733+ {
 734+ #..or maybe not. Just throw out the ending /
 735+ $type = substr($type, 0, -1);
 736+ } else {
 737+ $subtype = substr($type, $slashPos + 1);
 738+ $type = substr($type, 0, $slashPos);
 739+ }
 740+ }
 741+
 742+ return array($type, $subtype);
 743+ }
 744+
 745+ private function getPluginsForType($mime)
 746+ {
 747+ $type = MimeMagic::splitMimeString($mime);
 748+
 749+ if($type[1] != '*')
 750+ {
 751+ $plugins = $this->pluginsByType[ $type[0] ][ $type[1] ];
 752+ $plugins = array_merge((array) $plugins, (array) $this->pluginsByType[ $type[0] ][ '*' ]);
 753+ } else {
 754+ /* no subtype specified...spit out all plugins from all subtypes.
 755+ * this code is currently unused (and untested), but included for
 756+ * completeness.
 757+ */
 758+ $plugins = array();
 759+ foreach($this->pluginsByType[ $type[0] ] AS $subtype)
 760+ {
 761+ foreach($subtype AS $detectors)
 762+ {
 763+ foreach($detectors AS $detector)
 764+ {
 765+ $plugins[] = $detector;
 766+ }
 767+ }
 768+ }
 769+ }
 770+
 771+ return $plugins;
 772+ }
 773+
 774+
 775+ /** Internal mime type detection, please use guessMimeType() for application
 776+ code instead.
 777+ * Detection is done using an external program, if $wgMimeDetectorCommand is
 778+ set.
 779+ * Otherwise, the fileinfo extension and mime_content_type are tried (in this
 780+ order), if they are available.
 781+ * If the dections fails and $ext is not false, the mime type is guessed from
 782+ the file extension, using
511783 * guessTypesForExtension.
512 - * If the mime type is still unknown, getimagesize is used to detect the mime type if the file is an image.
513 - * If no mime type can be determined, this function returns "unknown/unknown".
 784+ * If the mime type is still unknown, getimagesize is used to detect the mime
 785+ type if the file is an image.
 786+ * If no mime type can be determined, this function returns
 787+ "unknown/unknown".
514788 *
515789 * @param string $file The file to check
516 - * @param mixed $ext The file extension, or true to extract it from the filename.
 790+ * @param mixed $ext The file extension, or true to extract it from the
 791+ filename.
517792 * Set it to false to ignore the extension.
518793 *
519794 * @return string the mime type of $file
@@ -525,7 +800,8 @@
526801 if ( $wgMimeDetectorCommand ) {
527802 $fn = wfEscapeShellArg( $file );
528803 $m = `$wgMimeDetectorCommand $fn`;
529 - } elseif ( function_exists( "finfo_open" ) && function_exists( "finfo_file" ) ) {
 804+ } elseif ( function_exists( "finfo_open" ) && function_exists(
 805+ "finfo_file" ) ) {
530806
531807 # This required the fileinfo extension by PECL,
532808 # see http://pecl.php.net/package/fileinfo
@@ -537,13 +813,15 @@
538814 # If you may need to load the fileinfo extension at runtime, set
539815 # $wgLoadFileinfoExtension in LocalSettings.php
540816
541 - $mime_magic_resource = finfo_open(FILEINFO_MIME); /* return mime type ala mimetype extension */
 817+ $mime_magic_resource = finfo_open(FILEINFO_MIME); /* return mime
 818+ type ala mimetype extension */
542819
543820 if ($mime_magic_resource) {
544821 $m = finfo_file( $mime_magic_resource, $file );
545822 finfo_close( $mime_magic_resource );
546823 } else {
547 - wfDebug( __METHOD__.": finfo_open failed on ".FILEINFO_MIME."!\n" );
 824+ wfDebug( __METHOD__.": finfo_open failed on
 825+ ".FILEINFO_MIME."!\n" );
548826 }
549827 } elseif ( function_exists( "mime_content_type" ) ) {
550828
@@ -559,10 +837,12 @@
560838 $m = mime_content_type($file);
561839
562840 if ( $m == 'text/plain' ) {
563 - // mime_content_type sometimes considers DJVU files to be text/plain.
 841+ // mime_content_type sometimes considers DJVU files to be
 842+ text/plain.
564843 $deja = new DjVuImage( $file );
565844 if( $deja->isValid() ) {
566 - wfDebug( __METHOD__.": (re)detected $file as image/vnd.djvu\n" );
 845+ wfDebug( __METHOD__.": (re)detected $file as
 846+ image/vnd.djvu\n" );
567847 $m = 'image/vnd.djvu';
568848 }
569849 }
@@ -653,27 +933,31 @@
654934 }
655935
656936 /**
657 - * Determine the media type code for a file, using its mime type, name and possibly
658 - * its contents.
 937+ * Determine the media type code for a file, using its mime type, name and
 938+ * possibly its contents.
659939 *
660940 * This function relies on the findMediaType(), mapping extensions and mime
661941 * types to media types.
662942 *
663 - * @todo analyse file if need be
664943 * @todo look at multiple extension, separately and together.
665944 *
666 - * @param string $path full path to the image file, in case we have to look at the contents
667 - * (if null, only the mime type is used to determine the media type code).
668 - * @param string $mime mime type. If null it will be guessed using guessMimeType.
 945+ * @param string $path full path to the image file, in case we have to look
 946+ * at the contents
 947+ * (if null, only the mime type is used to determine the media type code).
669948 *
670 - * @return (int?string?) a value to be used with the MEDIATYPE_xxx constants.
 949+ * @param string $mime mime type. If null it will be guessed using
 950+ * guessMimeType.
 951+ *
 952+ * @return string a value to be used with the MEDIATYPE_xxx constants.
671953 */
672954 function getMediaType( $path = NULL, $mime = NULL ) {
673955 if( !$mime && !$path ) return MEDIATYPE_UNKNOWN;
674956
675957 # If mime type is unknown, guess it
676958 if( !$mime ) $mime = $this->guessMimeType( $path, false );
 959+ if(strpos($mime, 'unknown/unknown') === 0) return MEDIATYPE_UNKNOWN;
677960
 961+ /* obsoleted by plugins
678962 # Special code for ogg - detect if it's video (theora),
679963 # else label it as sound.
680964 if( $mime == "application/ogg" && file_exists( $path ) ) {
@@ -688,41 +972,55 @@
689973
690974 # This is an UGLY HACK, file should be parsed correctly
691975 if ( strpos( $head, 'theora' ) !== false ) return MEDIATYPE_VIDEO;
692 - elseif ( strpos( $head, 'vorbis' ) !== false ) return MEDIATYPE_AUDIO;
 976+ elseif ( strpos( $head, 'vorbis' ) !== false ) return
 977+ MEDIATYPE_AUDIO;
693978 elseif ( strpos( $head, 'flac' ) !== false ) return MEDIATYPE_AUDIO;
694 - elseif ( strpos( $head, 'speex' ) !== false ) return MEDIATYPE_AUDIO;
 979+ elseif ( strpos( $head, 'speex' ) !== false ) return
 980+ MEDIATYPE_AUDIO;
695981 else return MEDIATYPE_MULTIMEDIA;
696982 }
 983+ */
697984
 985+ $type = MEDIATYPE_UNKNOWN;
698986 # check for entry for full mime type
699 - if( $mime ) {
700 - $type = $this->findMediaType( $mime );
701 - if( $type !== MEDIATYPE_UNKNOWN ) return $type;
702 - }
 987+ $type = $this->findMediaType( $mime );
 988+ if($type == MEDIATYPE_UNKNOWN)
 989+ {
 990+ # Check for entry for file extension
 991+ $e = NULL;
 992+ if ( $path ) {
 993+ $i = strrpos( $path, '.' );
 994+ $e = strtolower( $i ? substr( $path, $i + 1 ) : '' );
703995
704 - # Check for entry for file extension
705 - $e = NULL;
706 - if ( $path ) {
707 - $i = strrpos( $path, '.' );
708 - $e = strtolower( $i ? substr( $path, $i + 1 ) : '' );
 996+ # TODO: look at multi-extension if this fails, parse from full path
709997
710 - # TODO: look at multi-extension if this fails, parse from full path
711 -
712 - $type = $this->findMediaType( '.' . $e );
713 - if ( $type !== MEDIATYPE_UNKNOWN ) return $type;
714 - }
715 -
716 - # Check major mime type
717 - if( $mime ) {
 998+ $type = $this->findMediaType( '.' . $e );
 999+ }
 1000+ } else if($type == MEDIATYPE_UNKNOWN)
 1001+ {
 1002+ # Check major mime type
7181003 $i = strpos( $mime, '/' );
7191004 if( $i !== false ) {
7201005 $major = substr( $mime, 0, $i );
7211006 $type = $this->findMediaType( $major );
722 - if( $type !== MEDIATYPE_UNKNOWN ) return $type;
7231007 }
7241008 }
7251009
726 - if( !$type ) $type = MEDIATYPE_UNKNOWN;
 1010+ if( $type == MEDIATYPE_MULTIMEDIA && file_exists($path))
 1011+ {
 1012+ /* check if there's a registered plugin that wants to make the
 1013+ * determination by examination of contents
 1014+ */
 1015+ $detectors = $this->getPluginsForType($mime);
 1016+ foreach($detectors AS $detector)
 1017+ {
 1018+ if($detector[1]) //assume non-authoritative plugins won't know
 1019+ {
 1020+ $t = $detector[0]->getMediaType($path, $mime);
 1021+ if($t) return $t;
 1022+ }
 1023+ }
 1024+ }
7271025
7281026 return $type;
7291027 }
@@ -731,6 +1029,8 @@
7321030 * File extensions are represented by a string starting with a dot (.) to
7331031 * distinguish them from mime types.
7341032 *
 1033+ * @todo make this function match "major" mime types, getMediaType seems to
 1034+ * think it can use it in this capacity.
7351035 * This funktion relies on the mapping defined by $this->mMediaTypes
7361036 * @access private
7371037 */
@@ -750,6 +1050,7 @@
7511051 }
7521052
7531053 foreach ( $m as $mime ) {
 1054+ $mime = trim($mime); if(empty($mime)) continue;
7541055 foreach ( $this->mMediaTypes as $type => $codes ) {
7551056 if ( in_array($mime, $codes, true ) ) {
7561057 return $type;
Index: branches/mikeb/phase3/includes/JobQueue.php
@@ -63,7 +63,8 @@
6464
6565 if ($affected == 0) {
6666 wfProfileOut( __METHOD__ );
67 - return false;
 67+ //but there may still be other jobs of $type we can claim...
 68+ return self::pop_type($type);
6869 }
6970
7071 $namespace = $row->job_namespace;
@@ -180,6 +181,12 @@
181182 return new EmaillingJob($params);
182183 case 'enotifNotify':
183184 return new EnotifNotifyJob($title, $params);
 185+ case 'AudioRecode': case 'VideoRecode':
 186+ $class = $command . 'Job';
 187+ if(class_exists($class, true))
 188+ {
 189+ return new $class($title, $params, $id);
 190+ }
184191 default:
185192 throw new MWException( "Invalid job command \"$command\"" );
186193 }
Index: branches/mikeb/phase3/includes/SpecialUpload.php
@@ -4,7 +4,6 @@
55 * @addtogroup SpecialPage
66 */
77
8 -
98 /**
109 * Entry point
1110 */
@@ -262,6 +261,14 @@
263262 function processUpload() {
264263 global $wgUser, $wgOut;
265264
 265+ /*
 266+ * Add audio and video checking. Probably there is a better place to put
 267+ * these.
 268+ */
 269+ global $wgHooks;
 270+ $wgHooks['UploadVerification'][] = new AudioUploadHandler;
 271+ $wgHooks['UploadVerification'][] = new VideoUploadHandler;
 272+
266273 if( !wfRunHooks( 'UploadForm:BeforeProcessing', array( &$this ) ) )
267274 {
268275 wfDebug( "Hook 'UploadForm:BeforeProcessing' broke processing the file." );
@@ -372,7 +379,7 @@
373380 */
374381 $error = '';
375382 if( !wfRunHooks( 'UploadVerification',
376 - array( $this->mDestName, $this->mTempPath, &$error ) ) ) {
 383+ array( $this->mDestName, $this->mTempPath, &$error, $finalExt ) ) ) {
377384 return $this->uploadError( $error );
378385 }
379386 }
@@ -384,6 +391,17 @@
385392 if ( ! $this->mIgnoreWarning ) {
386393 $warning = '';
387394
 395+ /* if upload hooks wrote to $error, report as warning.
 396+ * This isn't perfect in that if multiple UploadVerification hooks
 397+ * made warnings, only the last one will show. As if that's really
 398+ * ever going to happen. Easy fix is to have the hooks append to an
 399+ * array instead.
 400+ */
 401+ if(strlen($error))
 402+ {
 403+ $warning .= "<li>$error</li>";
 404+ }
 405+
388406 global $wgCapitalLinks;
389407 if( $wgCapitalLinks ) {
390408 $filtered = ucfirst( $filtered );
@@ -920,7 +938,6 @@
921939 #magically determine mime type
922940 $magic=& MimeMagic::singleton();
923941 $mime= $magic->guessMimeType($tmpfile,false);
924 -
925942 #check mime type, if desired
926943 global $wgVerifyMimeType;
927944 if ($wgVerifyMimeType) {
Index: branches/mikeb/phase3/includes/Hooks.php
@@ -105,7 +105,36 @@
106106 } elseif ( false !== ( $pos = strpos( $func, '::' ) ) ) {
107107 $callback = array( substr( $func, 0, $pos ), substr( $func, $pos + 2 ) );
108108 } else {
109 - $callback = $func;
 109+ if(function_exists($func))
 110+ {
 111+ $callback = $func;
 112+ } else {
 113+ //see if it's really a class (without a static method specified)
 114+ //I will document this feature if there aren't objections to it
 115+ if(class_exists($func, true))
 116+ {
 117+ //see if it has an on[Event] method
 118+ if(in_array("on$event", get_class_methods($func)))
 119+ {
 120+ //see if it's static & callable
 121+ $method = new ReflectionMethod($func, "on$event");
 122+ if($method->isStatic() && $method->isPublic())
 123+ {
 124+ $callback = array($func, "on$event");
 125+ } else {
 126+ throw new MWException("Method on$event in class $func must be public and static to be used in hooks for $event\n");
 127+ }
 128+ } else {
 129+ throw new MWException("$func is a class, not a function in hooks for $event. Define $func::on$event or see docs/hooks.txt for more usage information.");
 130+ }
 131+ } else {
 132+ /*past behavior was to try to call it anyway...slightly
 133+ * worried throwing an exception instead might break
 134+ * something
 135+ */
 136+ throw new MWException("No function or class \"$func\" in hooks for $event.");
 137+ }
 138+ }
110139 }
111140
112141 /* Call the hook. */
@@ -126,4 +155,5 @@
127156
128157 return true;
129158 }
 159+
130160 ?>
Index: branches/mikeb/phase3/includes/media/Generic.php
@@ -23,8 +23,13 @@
2424 static function getHandler( $type ) {
2525 global $wgMediaHandlers;
2626 if ( !isset( $wgMediaHandlers[$type] ) ) {
27 - wfDebug( __METHOD__ . ": no handler found for $type.\n");
28 - return false;
 27+ if( !isset( $wgMediaHandlers[$mediaType] ))
 28+ {
 29+ wfDebug( __METHOD__ . ": no handler found for $type.\n");
 30+ return false;
 31+ } else {
 32+ $type = $mediaType;
 33+ }
2934 }
3035 $class = $wgMediaHandlers[$type];
3136 if ( !isset( self::$handlers[$class] ) ) {
Index: branches/mikeb/phase3/includes/media/AV.php
@@ -0,0 +1,477 @@
 2+<?php
 3+/**
 4+* Currently this file contains a set of classes (inheriting from
 5+* AVUploadHandler) to be used with the UploadVerification hook for precision
 6+* audio and video upload verification. These upload handler classes utilize the
 7+* various AVInspector classes also included in this file. I have implemented
 8+* AVInspectors powered by ffmpeg-php and MPlayer, as well as a composite
 9+* class that implements its methods in terms of the other AVInspectors.
 10+*
 11+* For audio file verification, ffmpeg is completely suitable, and so the
 12+* AudioUploadHandler only uses FfmpegAVInspector. However, ffmpeg occasionally
 13+* fails to validate a file that MPlayer can decode, and in this event the
 14+* composite class will enlist the help of MPlayerAVInspector as well. Thus,
 15+* VideoUploadHandler works directly with CompositeAVInspector.
 16+*
 17+* Since the tools powering the AVInspector classes also provide ready access to
 18+* metadata, I will also be calling on these classes to extract information like
 19+* (chronological) file length and bitrates. Current design has this metadata
 20+* extraction taking place in media handlers, not upload verification handlers,
 21+* so for now I'm having AVInspectors cache the results of file examinations,
 22+* expecting that media handlers will request metadata information later as part
 23+* of the same http request.
 24+*/
 25+
 26+interface AVInspector
 27+{
 28+ function hasVideoStream();
 29+
 30+ function hasAudioStream();
 31+
 32+ function hasDecodableVideoStream();
 33+
 34+ function hasDecodableAudioStream();
 35+
 36+ function getFrameWidth();
 37+
 38+ function getFrameHeight();
 39+
 40+ /*
 41+ * These last three methods can be implemented in terms of the others, and I
 42+ * have done so in AVInspectorHelper.
 43+ */
 44+ function isDecodable();
 45+
 46+ function isAV();
 47+
 48+ function isAudio();
 49+}
 50+
 51+abstract class AVInspectorHelper implements AVInspector
 52+{
 53+ /*
 54+ * Shortcut method that passes good audio, or video with or without audio.
 55+ * Use has(Decodable)AudioStream for that.
 56+ */
 57+ public function isDecodable()
 58+ {
 59+ if($this->isAudio())
 60+ {
 61+ return $this->hasDecodableAudioStream();
 62+ } else {
 63+ return $this->hasDecodableVideoStream();
 64+ }
 65+ }
 66+
 67+ public function isAV()
 68+ {
 69+ return $this->hasAudioStream() || $this->hasVideoStream();
 70+ }
 71+
 72+ public function isAudio()
 73+ {
 74+ return $this->isAV() && ! $this->hasVideoStream();
 75+ }
 76+}
 77+
 78+class CompositeAVInspector extends AVInspectorHelper implements AVInspector
 79+{
 80+ private $inspectors;
 81+ private $file;
 82+
 83+ public function __construct($file)
 84+ {
 85+ $this->file = $file;
 86+
 87+ /* inspectors are given as classnames only...instantiating
 88+ * now would cause all inspectors to run, defeating the purpose of the
 89+ * composite class.
 90+ */
 91+ $this->inspectors = array();
 92+
 93+ // see if we have access to the ffmpeg api...
 94+ $extension = "ffmpeg";
 95+ $extension_soname = $extension . "." . PHP_SHLIB_SUFFIX;
 96+ // load extension
 97+ if(!extension_loaded($extension)) {
 98+ @dl($extension_soname);
 99+ }
 100+
 101+ // if we can use the ffmpeg api, make an inspector based on it
 102+ if(class_exists('ffmpeg_movie'))
 103+ $this->inspectors[] = "FfmpegAVInspector";
 104+
 105+ // todo: scan for midentify
 106+ $this->inspectors[] = "MPlayerAVInspector";
 107+ }
 108+
 109+ /* the below duplication is necessary for the parser to think we've
 110+ * implemented the interface. Otherwise, using __call would work.
 111+ */
 112+ function hasVideoStream()
 113+ {
 114+ return $this->composite(__FUNCTION__);
 115+ }
 116+
 117+ function hasAudioStream()
 118+ {
 119+ return $this->composite(__FUNCTION__);
 120+ }
 121+
 122+ function hasDecodableVideoStream()
 123+ {
 124+ return $this->composite(__FUNCTION__);
 125+ }
 126+
 127+ function hasDecodableAudioStream()
 128+ {
 129+ return $this->composite(__FUNCTION__);
 130+ }
 131+
 132+ function getFrameWidth()
 133+ {
 134+ return $this->composite(__FUNCTION__);
 135+ }
 136+
 137+ function getFrameHeight()
 138+ {
 139+ return $this->composite(__FUNCTION__);
 140+ }
 141+
 142+ private function composite($method)
 143+ {
 144+ foreach($this->inspectors AS &$inspector)
 145+ {
 146+ if(is_string($inspector)) $inspector = new $inspector($this->file);
 147+ $value = $inspector->$method();
 148+
 149+ echo(get_class($inspector) . " sets $method as ");
 150+ if($value === true) echo 'true'; else if($value === false) echo 'false'; else echo $value;
 151+ echo "<br>\n";
 152+
 153+ if((bool) $value)
 154+ {
 155+ return $value;
 156+ }
 157+ }
 158+ return false;
 159+ }
 160+}
 161+
 162+class MPlayerAVInspector extends AVInspectorHelper implements AVInspector
 163+{
 164+ /* Since current code involves upload handlers validating through this
 165+ * class, and media handlers extracting metadata from this class, results
 166+ * of file inspections are cached here by path. This sucks, it should really
 167+ * just all be done in the upload handler, and have the media handler do
 168+ * presentation only...but nobody wants to discuss this.
 169+ */
 170+ private static $info;
 171+ private $path;
 172+
 173+ public function __construct($path)
 174+ {
 175+ $path = escapeshellarg($path);
 176+ $this->path = $path;
 177+ if(! is_array(self::$info[$path]))
 178+ {
 179+ exec("/data/mplayer-checkout-2007-06-08/TOOLS/midentify $path", $output);
 180+
 181+ self::$info[$path] = array();
 182+ foreach($output AS $line)
 183+ {
 184+ $p = strpos($line, '=');
 185+ $key = substr($line, 0, $p);
 186+ $value = substr($line, $p+1);
 187+ self::$info[$path][$key] = $value;
 188+ }
 189+ }
 190+ }
 191+
 192+ /* enables some shorthand for most AVInspector methods.
 193+ * Exact vaules can still be acquired through $this->info
 194+ */
 195+ private function __get($attribute)
 196+ {
 197+ if($attribute != 'info')
 198+ {
 199+ $value = trim(self::$info[$this->path]['ID_' . strtoupper($attribute)]);
 200+ if(strlen($value))
 201+ {
 202+ return true;
 203+ } else {
 204+ return false;
 205+ }
 206+ } else {
 207+ return self::$info[$this->path];
 208+ }
 209+ }
 210+
 211+ function hasVideoStream()
 212+ {
 213+ return $this->video_format;
 214+ }
 215+
 216+ function hasAudioStream()
 217+ {
 218+ return $this->audio_id;
 219+ }
 220+
 221+ function hasDecodableVideoStream()
 222+ {
 223+ return $this->video_codec;
 224+ }
 225+
 226+ function hasDecodableAudioStream()
 227+ {
 228+ return $this->audio_codec;
 229+ }
 230+
 231+ function getFrameWidth()
 232+ {
 233+ return $this->info['ID_VIDEO_WIDTH'];
 234+ }
 235+
 236+ function getFrameHeight()
 237+ {
 238+ return $this->info['ID_VIDEO_HEIGHT'];
 239+ }
 240+}
 241+
 242+class FfmpegAVInspector extends AVInspectorHelper implements AVInspector
 243+{
 244+ private static $cache;
 245+ private $path;
 246+
 247+ /* used to be private $movie; cache was added later.
 248+ * The __get magic method was the easy way to make it still
 249+ * work.
 250+ */
 251+ public function __get($name)
 252+ {
 253+ return self::$cache[$this->path];
 254+ }
 255+
 256+ public function __construct($file)
 257+ {
 258+ $this->path = $file;
 259+ if(! is_object(self::$cache[$file]))
 260+ {
 261+ // see if we have access to the ffmpeg api...
 262+ $extension = "ffmpeg";
 263+ $extension_soname = $extension . "." . PHP_SHLIB_SUFFIX;
 264+ // load extension
 265+ if(!extension_loaded($extension)) {
 266+ @dl($extension_soname);
 267+ }
 268+
 269+ self::$cache[$file] = @new ffmpeg_movie($file);
 270+
 271+ /* if ffmpeg can't decypher this file, we don't even get an object.
 272+ * The below little hack prevents all the member methods from having to
 273+ * check for this conidtion.
 274+ */
 275+ if(! is_object(self::$cache[$file]))
 276+ {
 277+ self::$cache[$file] = new FalseClass();
 278+ //no anonymous classes in php :(
 279+ }
 280+ }
 281+ }
 282+
 283+ public function hasVideoStream()
 284+ {
 285+ return $this->movie->getFrameCount() > 1;
 286+ }
 287+
 288+ public function hasAudioStream()
 289+ {
 290+ return $this->movie->hasAudio();
 291+ }
 292+
 293+ public function hasDecodableVideoStream()
 294+ {
 295+ //for now, do this by attempting to render the first frame.
 296+ //There may be a less expensive way...getVideoCodec, but not sure
 297+ $frame = $this->movie->getFrame(1);
 298+ return $frame !== false && $frame->getWidth() && $frame->getHeight();
 299+ }
 300+
 301+ public function hasDecodableAudioStream()
 302+ {
 303+ return $this->hasAudioStream() && (bool) $this->movie->getAudioCodec() && (bool) $this->movie->getAudioSampleRate();
 304+ }
 305+
 306+
 307+ public function getFrameWidth()
 308+ {
 309+ return $this->movie->getFrameWidth();
 310+ }
 311+
 312+ public function getFrameHeight()
 313+ {
 314+ return $this->movie->getFrameHeight();
 315+ }
 316+}
 317+
 318+class FalseClass
 319+{
 320+ public function __call($name, $args)
 321+ {
 322+ return false;
 323+ }
 324+}
 325+
 326+abstract class AVUploadHandler
 327+{
 328+ protected $inspector;
 329+
 330+ public function __construct()
 331+ {
 332+ $this->inspector = "CompositeAVInspector";
 333+ }
 334+
 335+ public abstract function getSupportedExtensions();
 336+
 337+ public function onUploadVerification($useless, $path, &$errorRef, $ext)
 338+ {
 339+ $exts = $this->getSupportedExtensions();
 340+ if(!in_array($ext, $exts)) return true;
 341+ /*SUCKS! This way, everything else doesn't matter if the user misnames a
 342+ * file. We need *exclusively* upload handlers by extension.
 343+ */
 344+
 345+ /*Ultimately uploading should be rewritten so that
 346+ * this code only gets called if MimeMagic says the extension must
 347+ * validate as an audio or video file.
 348+ */
 349+
 350+ $start = microtime(true);
 351+
 352+ $inspector = new $this->inspector($path);
 353+
 354+ // todo whatever needs to happen to make errors multilingual.
 355+ // some debug & performance output is left for now for your reference,
 356+ // should you choose to run this.
 357+ if(! $inspector->isAV())
 358+ {
 359+ $errorRef .= "<br />This audio or video file is unrecognized or corrupt.";
 360+ echo ("AV upload checking done in " . (microtime(true) - $start));
 361+ return false;
 362+ }
 363+
 364+ if(! $inspector->isDecodable())
 365+ {
 366+ $errorRef = "This file cannot be prepared for the WikiMedia player. You will be unable to add it to article pages. It will be available for direct download only.";
 367+ echo ("AV upload checking done in " . (microtime(true) - $start));
 368+ return true;
 369+ }
 370+
 371+ if(! $inspector->hasDecodableAudioStream())
 372+ {
 373+ $errorRef = "The audio component of this video file is in an unrecognized format. No sound will be available in the WikiMedia player.";
 374+ echo ("AV upload checking done in " . (microtime(true) - $start));
 375+ return true;
 376+ }
 377+
 378+ //also could easily add warnings for too small/to large frame size or
 379+ //other metadata attributs here.
 380+
 381+ echo ("AV upload passed all checks in " . (microtime(true) - $start));
 382+ return true;
 383+ }
 384+}
 385+
 386+class VideoUploadHandler extends AVUploadHandler
 387+{
 388+ public function getSupportedExtensions()
 389+ {
 390+ $mime = MimeMagic::singleton();
 391+ $mimeKnownVideoExts = $mime->getExtensionsForType('video');
 392+
 393+ $others = "avi mov asf yuv mp4 mpeg";
 394+
 395+ $out = array_merge(
 396+ explode(' ', $mimeKnownAudioExts),
 397+ explode(' ', $mimeKnownVideoExts),
 398+ explode(' ', $others)
 399+ );
 400+
 401+ return $out;
 402+ }
 403+}
 404+
 405+class AudioUploadHandler extends AVUploadHandler
 406+{
 407+ public function __construct()
 408+ {
 409+ $this->inspector = "FfmpegAVInspector";
 410+ }
 411+
 412+ public function getSupportedExtensions()
 413+ {
 414+ $mime = MimeMagic::singleton();
 415+ return explode(' ', $mime->getExtensionsForType('audio'));
 416+ }
 417+}
 418+
 419+
 420+
 421+
 422+
 423+
 424+
 425+
 426+
 427+
 428+
 429+
 430+/**
 431+* This is just a stub class I was using to test my work making MimeMagic more
 432+* modular. See my versions of MimeMagic.php and includes/media/MimePlugin.php.
 433+*/
 434+
 435+class AVMimePlugin extends MimePlugin
 436+{
 437+ public static function onMimeMagicRegisterPlugins($core)
 438+ {
 439+ /*make one of itself..it'd be slick if a universal implementation of
 440+ * this method could be made in the superclass, but alas...php bug
 441+ * #30423...
 442+ */
 443+ new self($core);
 444+ }
 445+
 446+ protected function __construct($core)
 447+ {
 448+ parent::__construct($core);
 449+
 450+ //$this->registerContentType('video', true);
 451+ }
 452+
 453+ public function getContentType($file)
 454+ {
 455+ return 'unknown/unknown';
 456+ }
 457+
 458+ protected function mimeTypes()
 459+ {
 460+ //return 'video/x-ms-asf asf asx';
 461+ }
 462+
 463+ protected function mimeInfo()
 464+ {
 465+ //return 'video/x-ms-asf [VIDEO]';
 466+ }
 467+
 468+ public function getMediaType($file, $mime)
 469+ {
 470+ if($mime == 'application/ogg')
 471+ {
 472+ return MEDIATYPE_VIDEO;
 473+ } else {
 474+ return false;
 475+ }
 476+ }
 477+}
 478+?>
\ No newline at end of file
Property changes on: branches/mikeb/phase3/includes/media/AV.php
___________________________________________________________________
Added: svn:eol-style
1479 + native
Index: branches/mikeb/phase3/includes/media/MimePlugin.php
@@ -0,0 +1,140 @@
 2+<?php
 3+/** Comments should be inserted here ;) */
 4+
 5+abstract class MimePlugin
 6+{
 7+ /** some convenience named constants
 8+ */
 9+ const EXAMINE_UNKNOWN = true;
 10+ const IGNORE_UNKNOWN = false;
 11+
 12+ private $unknownFileSafe;
 13+
 14+ /**
 15+ * The MimeMagic instance. Plugins need to reference MimeMagic during their
 16+ * construction, and because this occurs *within MimeMagic's own
 17+ * constructor*, using MimeMagic::singleton() would just create a new
 18+ * MimeMagic instance (and create an endless loop.)
 19+ */
 20+ protected $MagicCore;
 21+
 22+ protected function __construct(MimeMagic $core, $arbitraryFileSafe = MimePlugin::EXAMINE_UNKNOWN)
 23+ {
 24+ $this->MagicCore = $core;
 25+ $this->MagicCore->addMimeTypes($this->mimeTypes());
 26+ $this->MagicCore->addMimeInfo($this->mimeInfo());
 27+
 28+ if($arbitraryFileSafe)
 29+ {
 30+ $this->registerContentType('unknown/unknown');
 31+ }
 32+ }
 33+
 34+ /**
 35+ * Register this plugin for a MIME content-type.
 36+ * This should be called as many times as necessary in your child class
 37+ * constructor.
 38+ *
 39+ * You can register for a precise content-type (ex. audio/mpeg), or
 40+ * for all media in a main media-type group (ex. video). See
 41+ * http://www.iana.org/assignments/media-types/ for the possibilities.
 42+ *
 43+ * MimeMagic uses this information to decide what plugins to seek help from.
 44+ * There are two reasons for you to register a given content type X:
 45+ * 1) This plugin targets content type X. In general,
 46+ * $authoritativeRejector should be true for any such types. If you
 47+ * register as authoritative for an entire media-type group, be sure
 48+ * your plugin really can identify every subtype (that your wiki cares
 49+ * about, anyway.)
 50+ * 2) If MimeMagic is known to misidentify the plugin's target type as X. For
 51+ * example, .asf files are usually guessed as application/octet-stream.
 52+ * Thus, if your plugin targets .asf files, you should also register it
 53+ * for application/octet-stream non-authoritatively. (Actually,
 54+ * application/octet stream is aliased to unknown/unknown by default and
 55+ * so EXAMINE_UNKNOWN plugins will get used anyway, but the example
 56+ * still serves to illustrate.)
 57+ * There is no shortcut to register your plugin for all content types, and
 58+ * please don't do it. This defeats the purpose of a plugin, and
 59+ * really attempts to replace Magic Mime checking by thoroughly processing
 60+ * all files as all file types, a generally silly idea.
 61+ *
 62+ * @param $type string A content type or type/subtype pair.
 63+ * @param $authoritativeRejector boolean Whether this plugin's inability to
 64+ * identify a particular file as $type really means it is not $type.
 65+ */
 66+ protected final function registerContentType($type,
 67+ $authoritativeRejector = false)
 68+ {
 69+ $type = MimeMagic::splitMimeString($type);
 70+
 71+ $this->MagicCore->registerPluginByContentType($this, $type[0], $type[1], $authoritativeRejector);
 72+ }
 73+
 74+ /**
 75+ * Attempt to identify the MIME content-type of $file.
 76+ *
 77+ * MimeMagic will call this in the event that
 78+ * A) It has no clue about $file and this plugin was constructed with
 79+ * MimePlugin::EXAMINE_UNKNOWN.
 80+ * B) It guessed $file to be a type that this plugin registered itself for.
 81+ *
 82+ * The rationale behind case B) is to correct wrong guesses by MimeMagic.
 83+ * This can happen in two ways. MimeMagic can:
 84+ * 1) Misidentify a file as of a type this plugin specializes in (and for which this plugin registered itself
 85+ * authoritative), when the file is in fact something else. This plugin probably cannot determine what the file
 86+ * really is, but knows (by attempting to actually render as an image,
 87+ * say) that it is not what MimeMagic guessed. In this case, the return
 88+ * value should be 'unknown/unknown', which will override the Magic guess
 89+ * (and give any other EXAMINE_UNKNOWN plugins a crack at it)
 90+ * 2) Misidentify a file as of a type this plugin does not specialize in, when the file is in fact of a type this plugin can identify. This means that as part
 91+ * of creating your plugin, you should determine if MimeMagic is known to
 92+ * misidentify your target media as something else, and register the
 93+ * plugin non-authoritatively for those type(s) as necessary. When registered non-authoritatively, returning unknown/unknown will not override actual identifications from MimeMagic.
 94+ * In all cases, if this method returns any valid mime content-type string
 95+ * other than unknown/unknown, all further processing stops and that string
 96+ * will be the final type reported by MimeMagic.
 97+ */
 98+ public abstract function getContentType($file);
 99+
 100+ /**
 101+ * @return string typesString The mime types and extensions supported by this
 102+ * plugin, in the format of a mime.types file. (See MM_WELL_KNOWN_MIME_TYPES
 103+ * in MimeMagic.php for an example.)
 104+ */
 105+ protected abstract function mimeTypes();
 106+
 107+ /**
 108+ * @return string infoString The mime types supported by this plugin and
 109+ * their associated media types, in the format of a mime.info file. (See
 110+ * MM_WELL_KNOWN_MIME_INFO for an example.)
 111+ */
 112+ protected abstract function mimeInfo();
 113+
 114+ /**
 115+ * Possibly classify $file as one of the MEDIATYPE constants.
 116+ *
 117+ * Usually there is no need for a plugin to explicitly declare this;
 118+ * for most mime types MimeMagic can already figure it out. However, a few
 119+ * types (the classic example being application/ogg) do not in themselves
 120+ * necessitate one particular MEDIATYPE. If your plugin will be registered
 121+ * for such a type and is capable of parsing the file's contents to make this
 122+ * distinction, please override this method and do so.
 123+ *
 124+ * Because the assumption is that plugins are smarter than MimeMagic for the
 125+ * specialized types they were written for, this method will get called for
 126+ * *every* content-type the plugin is registered for, whether MimeMagic
 127+ * thinks it could classify it or not. To delegate this responsibility back
 128+ * to MimeMagic, just return false if $mime is not of a type that needs
 129+ * special parsing of the file's contents.
 130+ *
 131+ * @param string $file the file to classify
 132+ * @param string $mime MimeMagic's guess for $file (including help from
 133+ * plugins)
 134+ * @return mixed a MEDIATYPE constant, or false to let MimeMagic guess.
 135+ */
 136+ public function getMediaType($file, $mime)
 137+ {
 138+ return false;
 139+ }
 140+}
 141+?>
\ No newline at end of file
Property changes on: branches/mikeb/phase3/includes/media/MimePlugin.php
___________________________________________________________________
Added: svn:eol-style
1142 + native
Index: branches/mikeb/phase3/includes/AutoLoader.php
@@ -266,13 +266,21 @@
267267 'UnregisteredLocalFile' => 'includes/filerepo/UnregisteredLocalFile.php',
268268
269269 # Media
 270+ 'AudioHandler' => 'includes/media/AV.php',
 271+ 'AudioUploadHandler' => 'includes/media/AV.php',
270272 'BitmapHandler' => 'includes/media/Bitmap.php',
271273 'BmpHandler' => 'includes/media/BMP.php',
272274 'DjVuHandler' => 'includes/media/DjVu.php',
273275 'MediaHandler' => 'includes/media/Generic.php',
274276 'ImageHandler' => 'includes/media/Generic.php',
275277 'SvgHandler' => 'includes/media/SVG.php',
 278+ 'VideoHandler' => 'includes/media/AV.php',
 279+ 'VideoUploadHandler' => 'includes/media/AV.php',
276280
 281+ # MIME plugins
 282+ 'AVMimePlugin' => 'includes/media/AV.php',
 283+ 'MimePlugin' => 'includes/media/MimePlugin.php',
 284+
277285 # Normal
278286 'UtfNormal' => 'includes/normal/UtfNormal.php',
279287
Index: branches/mikeb/phase3/includes/DefaultSettings.php
@@ -1557,6 +1557,8 @@
15581558 'image/x-ms-bmp' => 'BmpHandler',
15591559 'image/svg+xml' => 'SvgHandler',
15601560 'image/vnd.djvu' => 'DjVuHandler',
 1561+ MEDIATYPE_VIDEO => 'VideoHandler',
 1562+ MEDIATYPE_AUDIO => 'AudioHandler'
15611563 );
15621564
15631565
@@ -1646,7 +1648,19 @@
16471649 /** Obsolete, always true, kept for compatibility with extensions */
16481650 $wgUseImageResize = true;
16491651
 1652+/**
 1653+ * Available bitrates at which audio uploads should be streamable.
 1654+ * Leave empty to skip making ogg vorbis recodes of audio uploads.
 1655+ */
 1656+ $wgAudioRecoding = array();
16501657
 1658+/**
 1659+* Available widths in pixels for streamable video, and associated bitrates.
 1660+* Index is integer pixel width, value is integer bitrate.
 1661+* Leave empty to skip making ogg theora recodes of video uploads.
 1662+*/
 1663+ $wgVideoRecoding = array(320 => 500);
 1664+
16511665 /** Set $wgCommandLineMode if it's not set already, to avoid notices */
16521666 if( !isset( $wgCommandLineMode ) ) {
16531667 $wgCommandLineMode = false;

Status & tagging log