mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-31 02:17:01 -04:00 
			
		
		
		
	
		
			
				
	
	
		
			292 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			292 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /*jshint node:true*/
 | |
| 'use strict';
 | |
| 
 | |
| /*
 | |
|  *! Size helpers
 | |
|  */
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Return filters to pad video to width*height,
 | |
|  *
 | |
|  * @param {Number} width output width
 | |
|  * @param {Number} height output height
 | |
|  * @param {Number} aspect video aspect ratio (without padding)
 | |
|  * @param {Number} color padding color
 | |
|  * @return scale/pad filters
 | |
|  * @private
 | |
|  */
 | |
| function getScalePadFilters(width, height, aspect, color) {
 | |
|   /*
 | |
|     let a be the input aspect ratio, A be the requested aspect ratio
 | |
| 
 | |
|     if a > A, padding is done on top and bottom
 | |
|     if a < A, padding is done on left and right
 | |
|    */
 | |
| 
 | |
|   return [
 | |
|     /*
 | |
|       In both cases, we first have to scale the input to match the requested size.
 | |
|       When using computed width/height, we truncate them to multiples of 2
 | |
|      */
 | |
|     {
 | |
|       filter: 'scale',
 | |
|       options: {
 | |
|         w: 'if(gt(a,' + aspect + '),' + width + ',trunc(' + height + '*a/2)*2)',
 | |
|         h: 'if(lt(a,' + aspect + '),' + height + ',trunc(' + width + '/a/2)*2)'
 | |
|       }
 | |
|     },
 | |
| 
 | |
|     /*
 | |
|       Then we pad the scaled input to match the target size
 | |
|       (here iw and ih refer to the padding input, i.e the scaled output)
 | |
|      */
 | |
| 
 | |
|     {
 | |
|       filter: 'pad',
 | |
|       options: {
 | |
|         w: width,
 | |
|         h: height,
 | |
|         x: 'if(gt(a,' + aspect + '),0,(' + width + '-iw)/2)',
 | |
|         y: 'if(lt(a,' + aspect + '),0,(' + height + '-ih)/2)',
 | |
|         color: color
 | |
|       }
 | |
|     }
 | |
|   ];
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Recompute size filters
 | |
|  *
 | |
|  * @param {Object} output
 | |
|  * @param {String} key newly-added parameter name ('size', 'aspect' or 'pad')
 | |
|  * @param {String} value newly-added parameter value
 | |
|  * @return filter string array
 | |
|  * @private
 | |
|  */
 | |
| function createSizeFilters(output, key, value) {
 | |
|   // Store parameters
 | |
|   var data = output.sizeData = output.sizeData || {};
 | |
|   data[key] = value;
 | |
| 
 | |
|   if (!('size' in data)) {
 | |
|     // No size requested, keep original size
 | |
|     return [];
 | |
|   }
 | |
| 
 | |
|   // Try to match the different size string formats
 | |
|   var fixedSize = data.size.match(/([0-9]+)x([0-9]+)/);
 | |
|   var fixedWidth = data.size.match(/([0-9]+)x\?/);
 | |
|   var fixedHeight = data.size.match(/\?x([0-9]+)/);
 | |
|   var percentRatio = data.size.match(/\b([0-9]{1,3})%/);
 | |
|   var width, height, aspect;
 | |
| 
 | |
|   if (percentRatio) {
 | |
|     var ratio = Number(percentRatio[1]) / 100;
 | |
|     return [{
 | |
|       filter: 'scale',
 | |
|       options: {
 | |
|         w: 'trunc(iw*' + ratio + '/2)*2',
 | |
|         h: 'trunc(ih*' + ratio + '/2)*2'
 | |
|       }
 | |
|     }];
 | |
|   } else if (fixedSize) {
 | |
|     // Round target size to multiples of 2
 | |
|     width = Math.round(Number(fixedSize[1]) / 2) * 2;
 | |
|     height = Math.round(Number(fixedSize[2]) / 2) * 2;
 | |
| 
 | |
|     aspect = width / height;
 | |
| 
 | |
|     if (data.pad) {
 | |
|       return getScalePadFilters(width, height, aspect, data.pad);
 | |
|     } else {
 | |
|       // No autopad requested, rescale to target size
 | |
|       return [{ filter: 'scale', options: { w: width, h: height }}];
 | |
|     }
 | |
|   } else if (fixedWidth || fixedHeight) {
 | |
|     if ('aspect' in data) {
 | |
|       // Specified aspect ratio
 | |
|       width = fixedWidth ? fixedWidth[1] : Math.round(Number(fixedHeight[1]) * data.aspect);
 | |
|       height = fixedHeight ? fixedHeight[1] : Math.round(Number(fixedWidth[1]) / data.aspect);
 | |
| 
 | |
|       // Round to multiples of 2
 | |
|       width = Math.round(width / 2) * 2;
 | |
|       height = Math.round(height / 2) * 2;
 | |
| 
 | |
|       if (data.pad) {
 | |
|         return getScalePadFilters(width, height, data.aspect, data.pad);
 | |
|       } else {
 | |
|         // No autopad requested, rescale to target size
 | |
|         return [{ filter: 'scale', options: { w: width, h: height }}];
 | |
|       }
 | |
|     } else {
 | |
|       // Keep input aspect ratio
 | |
| 
 | |
|       if (fixedWidth) {
 | |
|         return [{
 | |
|           filter: 'scale',
 | |
|           options: {
 | |
|             w: Math.round(Number(fixedWidth[1]) / 2) * 2,
 | |
|             h: 'trunc(ow/a/2)*2'
 | |
|           }
 | |
|         }];
 | |
|       } else {
 | |
|         return [{
 | |
|           filter: 'scale',
 | |
|           options: {
 | |
|             w: 'trunc(oh*a/2)*2',
 | |
|             h: Math.round(Number(fixedHeight[1]) / 2) * 2
 | |
|           }
 | |
|         }];
 | |
|       }
 | |
|     }
 | |
|   } else {
 | |
|     throw new Error('Invalid size specified: ' + data.size);
 | |
|   }
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  *! Video size-related methods
 | |
|  */
 | |
| 
 | |
| module.exports = function(proto) {
 | |
|   /**
 | |
|    * Keep display aspect ratio
 | |
|    *
 | |
|    * This method is useful when converting an input with non-square pixels to an output format
 | |
|    * that does not support non-square pixels.  It rescales the input so that the display aspect
 | |
|    * ratio is the same.
 | |
|    *
 | |
|    * @method FfmpegCommand#keepDAR
 | |
|    * @category Video size
 | |
|    * @aliases keepPixelAspect,keepDisplayAspect,keepDisplayAspectRatio
 | |
|    *
 | |
|    * @return FfmpegCommand
 | |
|    */
 | |
|   proto.keepPixelAspect = // Only for compatibility, this is not about keeping _pixel_ aspect ratio
 | |
|   proto.keepDisplayAspect =
 | |
|   proto.keepDisplayAspectRatio =
 | |
|   proto.keepDAR = function() {
 | |
|     return this.videoFilters([
 | |
|       {
 | |
|         filter: 'scale',
 | |
|         options: {
 | |
|           w: 'if(gt(sar,1),iw*sar,iw)',
 | |
|           h: 'if(lt(sar,1),ih/sar,ih)'
 | |
|         }
 | |
|       },
 | |
|       {
 | |
|         filter: 'setsar',
 | |
|         options: '1'
 | |
|       }
 | |
|     ]);
 | |
|   };
 | |
| 
 | |
| 
 | |
|   /**
 | |
|    * Set output size
 | |
|    *
 | |
|    * The 'size' parameter can have one of 4 forms:
 | |
|    * - 'X%': rescale to xx % of the original size
 | |
|    * - 'WxH': specify width and height
 | |
|    * - 'Wx?': specify width and compute height from input aspect ratio
 | |
|    * - '?xH': specify height and compute width from input aspect ratio
 | |
|    *
 | |
|    * Note: both dimensions will be truncated to multiples of 2.
 | |
|    *
 | |
|    * @method FfmpegCommand#size
 | |
|    * @category Video size
 | |
|    * @aliases withSize,setSize
 | |
|    *
 | |
|    * @param {String} size size string, eg. '33%', '320x240', '320x?', '?x240'
 | |
|    * @return FfmpegCommand
 | |
|    */
 | |
|   proto.withSize =
 | |
|   proto.setSize =
 | |
|   proto.size = function(size) {
 | |
|     var filters = createSizeFilters(this._currentOutput, 'size', size);
 | |
| 
 | |
|     this._currentOutput.sizeFilters.clear();
 | |
|     this._currentOutput.sizeFilters(filters);
 | |
| 
 | |
|     return this;
 | |
|   };
 | |
| 
 | |
| 
 | |
|   /**
 | |
|    * Set output aspect ratio
 | |
|    *
 | |
|    * @method FfmpegCommand#aspect
 | |
|    * @category Video size
 | |
|    * @aliases withAspect,withAspectRatio,setAspect,setAspectRatio,aspectRatio
 | |
|    *
 | |
|    * @param {String|Number} aspect aspect ratio (number or 'X:Y' string)
 | |
|    * @return FfmpegCommand
 | |
|    */
 | |
|   proto.withAspect =
 | |
|   proto.withAspectRatio =
 | |
|   proto.setAspect =
 | |
|   proto.setAspectRatio =
 | |
|   proto.aspect =
 | |
|   proto.aspectRatio = function(aspect) {
 | |
|     var a = Number(aspect);
 | |
|     if (isNaN(a)) {
 | |
|       var match = aspect.match(/^(\d+):(\d+)$/);
 | |
|       if (match) {
 | |
|         a = Number(match[1]) / Number(match[2]);
 | |
|       } else {
 | |
|         throw new Error('Invalid aspect ratio: ' + aspect);
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     var filters = createSizeFilters(this._currentOutput, 'aspect', a);
 | |
| 
 | |
|     this._currentOutput.sizeFilters.clear();
 | |
|     this._currentOutput.sizeFilters(filters);
 | |
| 
 | |
|     return this;
 | |
|   };
 | |
| 
 | |
| 
 | |
|   /**
 | |
|    * Enable auto-padding the output
 | |
|    *
 | |
|    * @method FfmpegCommand#autopad
 | |
|    * @category Video size
 | |
|    * @aliases applyAutopadding,applyAutoPadding,applyAutopad,applyAutoPad,withAutopadding,withAutoPadding,withAutopad,withAutoPad,autoPad
 | |
|    *
 | |
|    * @param {Boolean} [pad=true] enable/disable auto-padding
 | |
|    * @param {String} [color='black'] pad color
 | |
|    */
 | |
|   proto.applyAutopadding =
 | |
|   proto.applyAutoPadding =
 | |
|   proto.applyAutopad =
 | |
|   proto.applyAutoPad =
 | |
|   proto.withAutopadding =
 | |
|   proto.withAutoPadding =
 | |
|   proto.withAutopad =
 | |
|   proto.withAutoPad =
 | |
|   proto.autoPad =
 | |
|   proto.autopad = function(pad, color) {
 | |
|     // Allow autopad(color)
 | |
|     if (typeof pad === 'string') {
 | |
|       color = pad;
 | |
|       pad = true;
 | |
|     }
 | |
| 
 | |
|     // Allow autopad() and autopad(undefined, color)
 | |
|     if (typeof pad === 'undefined') {
 | |
|       pad = true;
 | |
|     }
 | |
| 
 | |
|     var filters = createSizeFilters(this._currentOutput, 'pad', pad ? color || 'black' : false);
 | |
| 
 | |
|     this._currentOutput.sizeFilters.clear();
 | |
|     this._currentOutput.sizeFilters(filters);
 | |
| 
 | |
|     return this;
 | |
|   };
 | |
| };
 |