summaryrefslogtreecommitdiff
path: root/www/wiki/extensions/MultimediaViewer/resources/mmv/provider/mmv.provider.GuessedThumbnailInfo.js
diff options
context:
space:
mode:
Diffstat (limited to 'www/wiki/extensions/MultimediaViewer/resources/mmv/provider/mmv.provider.GuessedThumbnailInfo.js')
-rw-r--r--www/wiki/extensions/MultimediaViewer/resources/mmv/provider/mmv.provider.GuessedThumbnailInfo.js307
1 files changed, 307 insertions, 0 deletions
diff --git a/www/wiki/extensions/MultimediaViewer/resources/mmv/provider/mmv.provider.GuessedThumbnailInfo.js b/www/wiki/extensions/MultimediaViewer/resources/mmv/provider/mmv.provider.GuessedThumbnailInfo.js
new file mode 100644
index 00000000..5ad76dbd
--- /dev/null
+++ b/www/wiki/extensions/MultimediaViewer/resources/mmv/provider/mmv.provider.GuessedThumbnailInfo.js
@@ -0,0 +1,307 @@
+/*
+ * This file is part of the MediaWiki extension MediaViewer.
+ *
+ * MediaViewer 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.
+ *
+ * MediaViewer 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 MediaViewer. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+( function ( mw, $ ) {
+ /**
+ * This provider is similar to mw.mmv.provider.ThumbnailInfo, but instead of making an API call
+ * to get the thumbnail URL, it tries to guess it. There are two failure modes:
+ * - known failure: in the given situation it does not seem possible or safe to guess the URL.
+ * It is up to the caller to obtain it by falling back to the normal provider.
+ * - unexpected failure: we guess an URL but it does not work. The current implementation is
+ * conservative so at least on WMF wikis this probably won't happen, but should be reckoned
+ * with anyway. On other wikis (especially ones which do not generate thumbnails on demand
+ * via the 404 handler) this could be more frequent. Again, it is the caller's resonsibility
+ * to handle this by detecting image loading errors and falling back to the normal provider.
+ *
+ * @class mw.mmv.provider.GuessedThumbnailInfo
+ * @constructor
+ */
+ function GuessedThumbnailInfo() {}
+
+ /**
+ * File extensions which are vector types (as opposed to bitmap).
+ * Thumbnails of vector types can be larger than the original file.
+ * @property {Object.<string, number>}
+ */
+ GuessedThumbnailInfo.prototype.vectorExtensions = {
+ svg: 1
+ };
+
+ /**
+ * File extensions which can be displayed in the browser.
+ * Other file types need to be thumbnailed even if the size of the original file would be right.
+ * @property {Object.<string, number>}
+ */
+ GuessedThumbnailInfo.prototype.displayableExtensions = {
+ png: 1,
+ jpg: 1,
+ jpeg: 1,
+ gif: 1
+ };
+
+ /**
+ * Try to guess the thumbnailinfo for a thumbnail without doing an API request.
+ * An existing thumbnail URL is required.
+ *
+ * There is no guarantee this function will be successful - in some cases, it is impossible
+ * to guess how the URL would look. If that's the case, the promise just rejects.
+ *
+ * @param {mw.Title} file
+ * @param {string} sampleUrl a thumbnail URL for the same file (but with different size).
+ * @param {number} width thumbnail width in pixels
+ * @param {number} originalWidth width of original image in pixels
+ * @param {number} originalHeight height of original image in pixels
+ * @return {jQuery.Promise.<mw.mmv.model.Thumbnail>}
+ */
+ GuessedThumbnailInfo.prototype.get = function ( file, sampleUrl, width, originalWidth, originalHeight ) {
+ var url = this.getUrl( file, sampleUrl, width, originalWidth );
+ if ( url ) {
+ return $.Deferred().resolve( new mw.mmv.model.Thumbnail(
+ url,
+ this.guessWidth( file, width, originalWidth ),
+ this.guessHeight( file, width, originalWidth, originalHeight )
+ ) );
+ } else {
+ return $.Deferred().reject( 'Could not guess thumbnail URL' );
+ }
+ };
+
+ /**
+ * Try to guess the URL of a thumbnail without doing an API request.
+ * See #get().
+ *
+ * @param {mw.Title} file
+ * @param {string} sampleUrl a thumbnail URL for the same file (but with different size)
+ * @param {number} width thumbnail width in pixels
+ * @param {number} originalWidth width of original image in pixels
+ * @return {string|undefined} a thumbnail URL or nothing
+ */
+ GuessedThumbnailInfo.prototype.getUrl = function ( file, sampleUrl, width, originalWidth ) {
+ var needsFullSize = this.needsOriginal( file, width, originalWidth ),
+ sampleIsFullSize = this.isFullSizeUrl( sampleUrl, file );
+
+ if ( sampleIsFullSize && needsFullSize ) {
+ // sample thumbnail uses full size, and we need full size as well - the sample URL
+ // happens to be just the right one for us
+ return sampleUrl;
+ } else if ( !sampleIsFullSize && !needsFullSize ) {
+ // need to convert a scaled thumbnail URL to another scaled thumbnail URL
+ return this.replaceSize( file, sampleUrl, width );
+ } else if ( !sampleIsFullSize && needsFullSize ) {
+ if ( this.canBeDisplayedInBrowser( file ) ) {
+ // the size requested is larger than the original - we need to return an URL
+ // to the original file instead
+ return this.guessFullUrl( file, sampleUrl );
+ } else {
+ // the size requested is larger than the original, but this file type cannot
+ // be displayed by all browsers, so needs to be thumbnailed anyway,
+ // but the thumbnail still cannot be larger than the original file
+ return this.replaceSize( file, sampleUrl, originalWidth );
+ }
+ } else { // sampleIsFullSize && !needsOriginal
+ return this.guessThumbUrl( file, sampleUrl, width );
+ }
+ };
+
+ /**
+ * True if the the original image needs to be used as a thumbnail.
+ *
+ * @protected
+ * @param {mw.Title} file
+ * @param {number} width thumbnail width in pixels
+ * @param {number} originalWidth width of original image in pixels
+ * @return {boolean}
+ */
+ GuessedThumbnailInfo.prototype.needsOriginal = function ( file, width, originalWidth ) {
+ return width >= originalWidth && !this.canHaveLargerThumbnailThanOriginal( file );
+ };
+
+ /**
+ * Checks if a given thumbnail URL is full-size (the original image) or scaled
+ *
+ * @protected
+ * @param {string} url a thumbnail URL
+ * @param {mw.Title} file
+ * @return {boolean}
+ */
+ GuessedThumbnailInfo.prototype.isFullSizeUrl = function ( url, file ) {
+ return !this.obscureFilename( url, file ).match( '/thumb/' );
+ };
+
+ /**
+ * Removes the filename in a reversible way. This is useful because the filename can be nearly
+ * anything and could cause false positives when looking for patterns.
+ *
+ * @protected
+ * @param {string} url a thumbnail URL
+ * @param {mw.Title} file
+ * @return {string} thumbnnail URL with occurences of the filename replaced by `<filename>`
+ */
+ GuessedThumbnailInfo.prototype.obscureFilename = function ( url, file ) {
+ // corresponds to File::getUrlRel() which uses rawurlencode()
+ var filenameInUrl = mw.util.rawurlencode( file.getMain() );
+
+ // In the URL to the original file the filename occurs once. In a thumbnail URL it usually
+ // occurs twice, but can occur once if it is too short. We replace twice, can't hurt.
+ return url.replace( filenameInUrl, '<filename>' ).replace( filenameInUrl, '<filename>' );
+ };
+
+ /**
+ * Undoes #obscureFilename().
+ *
+ * @protected
+ * @param {string} url a thumbnail URL (with obscured filename)
+ * @param {mw.Title} file
+ * @return {string} original thumbnnail URL
+ */
+ GuessedThumbnailInfo.prototype.restoreFilename = function ( url, file ) {
+ // corresponds to File::getUrlRel() which uses rawurlencode()
+ var filenameInUrl = mw.util.rawurlencode( file.getMain() );
+
+ // <> cannot be used in titles, so this is safe
+ return url.replace( '<filename>', filenameInUrl ).replace( '<filename>', filenameInUrl );
+ };
+
+ /**
+ * True if the file is of a type for which the thumbnail can be scaled beyond the original size.
+ *
+ * @protected
+ * @param {mw.Title} file
+ * @return {boolean}
+ */
+ GuessedThumbnailInfo.prototype.canHaveLargerThumbnailThanOriginal = function ( file ) {
+ return ( file.getExtension().toLowerCase() in this.vectorExtensions );
+ };
+
+ /**
+ * True if the file type can be displayed in most browsers, false if it needs thumbnailing
+ *
+ * @protected
+ * @param {mw.Title} file
+ * @return {boolean}
+ */
+ GuessedThumbnailInfo.prototype.canBeDisplayedInBrowser = function ( file ) {
+ return ( file.getExtension().toLowerCase() in this.displayableExtensions );
+ };
+
+ /**
+ * Guess what will be the width of the thumbnail. (Thumbnails for most file formats cannot be
+ * larger than the original file so this might be smaller than the requested width.)
+ *
+ * @protected
+ * @param {mw.Title} file
+ * @param {number} width thumbnail width in pixels
+ * @param {number} originalWidth width of original image in pixels
+ * @return {number} guessed width
+ */
+ GuessedThumbnailInfo.prototype.guessWidth = function ( file, width, originalWidth ) {
+ if ( width >= originalWidth && !this.canHaveLargerThumbnailThanOriginal( file ) ) {
+ return originalWidth;
+ } else {
+ return width;
+ }
+ };
+
+ /**
+ * Guess what will be the height of the thumbnail, given its width.
+ *
+ * @protected
+ * @param {mw.Title} file
+ * @param {number} width thumbnail width in pixels
+ * @param {number} originalWidth width of original image in pixels
+ * @param {number} originalHeight height of original image in pixels
+ * @return {number} guessed height
+ */
+ GuessedThumbnailInfo.prototype.guessHeight = function ( file, width, originalWidth, originalHeight ) {
+ if ( width >= originalWidth && !this.canHaveLargerThumbnailThanOriginal( file ) ) {
+ return originalHeight;
+ } else {
+ // might be off 1px due to rounding (we don't know what exact scaling method the
+ // backend uses) but that should not cause any issues
+ return Math.round( width * ( originalHeight / originalWidth ) );
+ }
+ };
+
+ /**
+ * Given a thumbnail URL with a wrong size, returns one with the right size.
+ *
+ * @protected
+ * @param {mw.Title} file
+ * @param {string} sampleUrl a thumbnail URL for the same file (but with different size)
+ * @param {number} width thumbnail width in pixels
+ * @return {string|undefined} thumbnail URL with the correct size
+ */
+ GuessedThumbnailInfo.prototype.replaceSize = function ( file, sampleUrl, width ) {
+ var url = this.obscureFilename( sampleUrl, file ),
+ sizeRegexp = /\b\d{1,5}px\b/;
+
+ // this should never happen, but let's play it safe - returning the sample URL and believing
+ // it is the resized one would be bad. Returning a wrong filename is not catastrophical
+ // as long as we return a non-working wrong filename, which would not be the case here.
+ if ( !url.match( sizeRegexp ) ) {
+ return undefined;
+ }
+
+ // we are assuming here that the other thumbnail parameters do not look like a size
+ url = url.replace( sizeRegexp, width + 'px' );
+
+ return this.restoreFilename( url, file );
+ };
+
+ /**
+ * Try to guess the original URL to the file, from a thumb URL.
+ *
+ * @protected
+ * @param {mw.Title} file
+ * @param {string} thumbnailUrl
+ * @return {string} URL of the original file
+ */
+ GuessedThumbnailInfo.prototype.guessFullUrl = function ( file, thumbnailUrl ) {
+ var url = this.obscureFilename( thumbnailUrl, file );
+
+ if ( url === thumbnailUrl ) {
+ // Did not find the filename, maybe due to URL encoding issues. Bail out.
+ return undefined;
+ }
+
+ // this depends on some config settings, but will work with default or WMF settings.
+ url = url.replace( /<filename>.*/, '<filename>' );
+ url = url.replace( '/thumb', '' );
+
+ return this.restoreFilename( url, file );
+ };
+
+ /**
+ * Hardest version: try to guess thumbnail URL from original
+ *
+ * @protected
+ * @param {mw.Title} file
+ * @param {string} originalUrl URL for the original file
+ * @param {number} width thumbnail width in pixels
+ * @return {string|undefined} thumbnail URL
+ */
+ GuessedThumbnailInfo.prototype.guessThumbUrl = function () {
+ // Not implemented. This can be very complicated (the thumbnail might have other
+ // parameters than the size, which are impossible to guess, might be converted to some
+ // other format, might have a special shortened format depending on the length of the
+ // filename) and it is unlikely to be useful - it would be only called when we need
+ // a thumbnail that is smaller than the sample (the thumbnail which is already on the page).
+ return undefined;
+ };
+
+ mw.mmv.provider.GuessedThumbnailInfo = GuessedThumbnailInfo;
+}( mediaWiki, jQuery ) );