diff options
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.js | 307 |
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 ) ); |