diff --git a/README.md b/README.md index d50e998..f2a3169 100644 --- a/README.md +++ b/README.md @@ -62,11 +62,12 @@ http { And here's minimal viable config for 4 locations you need to set up. These locations are described in the sections below: ``` # video location -location ~ ^/(?(video))/(?([0-9a-zA-Z_,\.:]+)\/|)(?[0-9a-zA-Z_\-\.]+)\.(?(mp4))$ { +location ~ ^/(?.*?\/upload\/)(?([^\/]+)\/|\/|)(v\d+|)\/(?.*\/|\/|)(?[0-9a-zA-Z_\-\.]+)\.(?(mp4))$ { # these two are required to be set regardless + set $luamp_media_type "video"; set $luamp_original_file ""; set $luamp_transcoded_file ""; - + # these are needed to be set if you did not use them in regex matching location set $luamp_prefix ""; set $luamp_postfix ""; @@ -81,7 +82,12 @@ location @luamp_video_process { } # image location -location ~ ^/(?(image))/(?([0-9a-zA-Z_,\.:]+)\/|)(?[0-9a-zA-Z_\-\.]+)\.(?(jpe?g|png|gif|bmp|tiff?|svg|pdf|webp))$ { +location ~ ^/(?.*?\/upload\/)(?([^\/]+)\/|\/|)(v\d+|)\/(?.*\/|\/|)(?[0-9a-zA-Z_\-\.]+)\.(?(jpe?g|png|gif|bmp|tiff?|svg|pdf|webp))$ { + # these are needed to be set if you did not use them in regex matching location + set $luamp_media_type "image"; + set $luamp_prefix ""; + set $luamp_postfix ""; + #pass to transcoder location try_files $uri @luamp_media_processor; } @@ -91,10 +97,6 @@ location @luamp_media_processor { # these two are required to be set regardless set $luamp_original_file ""; set $luamp_transcoded_file ""; - - # these are needed to be set if you did not use them in regex matching location - set $luamp_prefix ""; - set $luamp_postfix ""; content_by_lua_file "/absolute/path/to/nginx-lua-mp4/media-processor.lua"; } @@ -124,14 +126,13 @@ This location used as an entry point and to set initial variables. This is usual There are two variables you need to `set`/initialise: `$luamp_original_file` and `$luamp_transcoded_file`. -There are six variables that may be used as a named capture group in location regex: `luamp_prefix`, `luamp_media_type`, `luamp_flags`, `luamp_postfix`, `luamp_media_id`, `luamp_media_extension`. +There are six variables that may be used as a named capture group in location regex: `luamp_prefix`, `luamp_flags`, `luamp_postfix`, `luamp_media_id`, `luamp_media_extension`. For example: ``` https://example.com/asset/video/width_1980,height_1080,crop_padding/2019/12/new_year_boardgames_party.mp4 -luamp_prefix: asset/ -luamp_media_type: video +luamp_prefix: asset/video/ luamp_flags: width_1980,height_1080,crop_padding luamp_postfix: 2019/12/ luamp_media_id: new_year_boardgames_party @@ -145,10 +146,6 @@ location @luamp_media_processor { # these two are required to be set regardless set $luamp_original_file ""; set $luamp_transcoded_file ""; - - # these are needed to be set if you did not use them in regex matching location - set $luamp_prefix ""; - set $luamp_postfix ""; content_by_lua_file "/absolute/path/to/nginx-lua-mp4/media-processor.lua"; } @@ -174,6 +171,10 @@ Process location is pretty simple, it just passes execution to the LUA part of l ``` location @luamp_media_processor { + # these two are required to be set regardless + set $luamp_original_file ""; + set $luamp_transcoded_file ""; + content_by_lua_file "/absolute/path/to/nginx-lua-mp4/media-processor.lua"; } ``` @@ -232,7 +233,7 @@ $ nano config.lua When set to `true`, `luamp` will attempt to download missing original videos from the upstream. Set it to `false` if you have original videos provided by other means to this directory: ``` -config.mediaBaseFilepath//original/$prefix/$postfix/$media_id +config.mediaBaseFilepath/$prefix/$postfix/$media_id.$media_extension ``` #### `config.ffmpeg` @@ -244,6 +245,15 @@ $ which ffmpeg /usr/local/bin/ffmpeg ``` +#### `config.magick` + +Path to the `magick` executable. Can be figured out by using `which` command in the terminal: + +``` +$ which magick +/usr/local/bin/magick +``` + #### `config.ffmpegDevNull` Where to redirect `ffmpeg` output if `config.logFfmpegOutput` is set to false. @@ -337,9 +347,9 @@ Log level, available values: `ngx.STDERR`, `ngx.EMERG`, `ngx.ALERT`, `ngx.CRIT`, Whether to prepend `ffmpeg` command with `time` utility, if you wish to log time spent in transcoding. -#### `config.maxHeight` and `config.maxWidth` +#### `config.maxVideoHeight`,`config.maxVideoWidth`, `config.maxImageHeight` and `config.maxImageWidth` -Limit the output video's maximum height or width. If the resulting height or width is exceeding the limit (for example, after a high DPR calculation), it will be capped at the `config.maxHeight` and `config.maxWidth`. +Limit the maximum height or width of the output media. If the resulting height or width exceeds the limit (for example, after a high DPR calculation), it will be limited to the specified limits. #### `config.mediaBaseFilepath` @@ -355,6 +365,14 @@ During the transcoding, errors may occur and ffmpeg sometimes leaves corrupt fil Serve original file when transcode failed. If set to `false`, luamp will respond with 404 in this case +#### `config.stripColorProfile` + +Removes a color profile from the original file. + +#### `config.colorProfilePath` + +Applies a color profile from specified location. + ## Update To update luamp, just do a `git pull`. @@ -389,9 +407,9 @@ You definitely want to keep what was customized by you but also to get new confi ``` nginx-lua-mp4 $ sdiff -s config.lua config.lua.example | grep -e '\s*>' | sed -ne "s/^[[:space:]]*>\t//p" -- top limit for output video height (default 4k UHD) -config.maxHeight = 2160 +config.maxVideoHeight = 2160 -- top limit for output video width (default 4k UHD) -config.maxWidth = 3840 +config.maxVideoWidth = 3840 ``` You can now just copy and paste these lines above the `return config` in `config.lua` and that's it 👌 diff --git a/command.lua b/command.lua index 1e660a5..e02a24a 100644 --- a/command.lua +++ b/command.lua @@ -17,7 +17,7 @@ local function getCanvas(config, file, flags) canvas = file.originalFileIdPath .. ' -size %wx%h gradient:' .. dominantColors .. ' -delete 0 ' elseif background == 'blurred' then - canvas = file.originalFileIdPath .. ' -crop 80%x80% +repage -blur 0x8 ' + canvas = file.originalFileIdPath .. ' -crop 80%x80% +repage -scale 10% -blur 0x2.5 -resize 1000% ' else canvas = file.originalFileIdPath .. ' -size %wx%h xc:' .. (background or '') .. ' -delete 0 ' end @@ -25,6 +25,21 @@ local function getCanvas(config, file, flags) return canvas end +local function getMask(radius) + local mask = + ' -size %[origwidth]x%[origheight]' .. + ' xc:black' .. + ' -fill white' + + if radius then + mask = mask .. ' -draw "roundrectangle 0,0,%[origwidth],%[origheight],' .. radius .. ',' .. radius .. '"' + else + mask = mask .. ' -draw "rectangle 0,0,%[origwidth],%[origheight]"' + end + + return mask .. ' -alpha Copy' +end + -- Build image processing command ---@param config table ---@param file table @@ -39,6 +54,7 @@ local function buildImageProcessingCommand(config, file, flags) local height = flags[Flag.IMAGE_HEIGHT_NAME].value local radius = flags[Flag.IMAGE_RADIUS_NAME].value local quality = flags[Flag.IMAGE_QUALITY_NAME].value + local minpad = flags[Flag.IMAGE_MINPAD_NAME].value -- Construct a command local command = '' @@ -47,13 +63,8 @@ local function buildImageProcessingCommand(config, file, flags) ' -quality ' .. quality .. ' -gravity ' .. gravity .. ' ' local canvas = getCanvas(config, file, flags) - local image = file.originalFileIdPath .. ' -modulate 100,120,100 ' - local mask = - '-size %[origwidth]x%[origheight]' .. - ' xc:black' .. - ' -fill white' .. - ' -draw "roundrectangle 0,0,%[origwidth],%[origheight],' .. radius .. ',' .. radius .. '"' .. - ' -alpha Copy' + local image = file.originalFileIdPath .. ' -modulate 100,120,100' + local mask = getMask(radius) local dimensions = (width or '') .. 'x' .. (height or '') if crop == 'fill' and (width or height) then @@ -76,7 +87,7 @@ local function buildImageProcessingCommand(config, file, flags) ' -crop ' .. dimensions .. '+0+0 ' .. ' \\( ' .. image .. - ' -resize ' .. dimensions .. '\\>' .. + ' -resize ' .. (width - (minpad or 0)) .. 'x' .. (height - (minpad or 0)) .. '\\>' .. ' -set option:origwidth %w' .. ' -set option:origheight %h' .. ' \\( ' .. mask .. ' \\) -compose CopyOpacity -composite' .. diff --git a/config.lua.example b/config.lua.example index 6d701f0..0d839cb 100644 --- a/config.lua.example +++ b/config.lua.example @@ -66,6 +66,7 @@ config.flagImageMap = { w = Flag.IMAGE_WIDTH_NAME, r = Flag.IMAGE_RADIUS_NAME, q = Flag.IMAGE_QUALITY_NAME, + minp = Flag.IMAGE_MINPAD_NAME, } -- override URL flag values. Useful when you migrate from another transcoding solution and already have diff --git a/file.lua b/file.lua index 0984668..4371053 100644 --- a/file.lua +++ b/file.lua @@ -27,7 +27,10 @@ local function buildCacheDirPath(basePath, flags) -- Add the flag name to the ordered list for flagName, _ in pairs(flags) do - table.insert(flagNamesOrdered, flagName) + local flag = flags[flagName] + if flag.makeDir then + table.insert(flagNamesOrdered, flagName) + end end -- Sort flags so path will be the same for `w_1280,h_960` and `h_960,w_1280` table.sort(flagNamesOrdered) diff --git a/flag.lua b/flag.lua index 57535f1..b84f5c4 100644 --- a/flag.lua +++ b/flag.lua @@ -10,22 +10,32 @@ Flag.IMAGE_HEIGHT_NAME = 'height' Flag.IMAGE_WIDTH_NAME = 'width' Flag.IMAGE_RADIUS_NAME = 'radius' Flag.IMAGE_QUALITY_NAME = 'quality' +Flag.IMAGE_MINPAD_NAME = 'minpad' local IMAGE_DEFAULTS = { [Flag.IMAGE_BACKGROUND_NAME] = 'white', - [Flag.IMAGE_DPR_NAME] = 1, [Flag.IMAGE_GRAVITY_NAME] = 'center', [Flag.IMAGE_X_NAME] = 0, [Flag.IMAGE_Y_NAME] = 0, - [Flag.IMAGE_RADIUS_NAME] = 0.1, [Flag.IMAGE_QUALITY_NAME] = 80 } -- Base class method new -function Flag.new(name, value) +function Flag.new(config, name, value) local self = {} + self.config = config self.name = name self.value = value or IMAGE_DEFAULTS[name] + self.isScalable = false + self.makeDir = true + + if self.name == Flag.IMAGE_HEIGHT_NAME or self.name == Flag.IMAGE_WIDTH_NAME or self.name == Flag.IMAGE_RADIUS_NAME or self.name == Flag.IMAGE_MINPAD_NAME then + self.isScalable = true + end + + if self.name == Flag.IMAGE_DPR_NAME then + self.makeDir = false + end setmetatable(self, { __index = Flag }) return self @@ -41,15 +51,19 @@ function Flag:setValue(value, valueMapper) end end --- Apply limits to a given dimension +-- Scale dimension ---@param dpr number ----@param max number -function Flag:scaleDimension(dpr, max) - if (self.name == Flag.IMAGE_HEIGHT_NAME or self.name == Flag.IMAGE_WIDTH_NAME) and self.value and dpr then - self.value = math.ceil(self.value * dpr) +function Flag:scale(dpr) + if self.value and self.value ~= '' then + self.value = math.ceil(self.value * (dpr or 1)) + + -- Apply limits + if self.name == Flag.IMAGE_HEIGHT_NAME and self.config.maxImageHeight and self.value > self.config.maxImageHeight then + self.value = self.config.maxImageHeight + end - if self.value > max then - self.value = max + if self.name == Flag.IMAGE_WIDTH_NAME and self.config.maxImageWidth and self.value > self.config.maxImageWidth then + self.value = self.config.maxImageWidth end end end diff --git a/media-processor.lua b/media-processor.lua index f03abcb..e5997ad 100644 --- a/media-processor.lua +++ b/media-processor.lua @@ -68,16 +68,17 @@ local function main() if mediaType == File.IMAGE_TYPE then if luampFlags ~= '' then flags = { - [Flag.IMAGE_BACKGROUND_NAME] = Flag.new(Flag.IMAGE_BACKGROUND_NAME), - [Flag.IMAGE_CROP_NAME] = Flag.new(Flag.IMAGE_CROP_NAME), - [Flag.IMAGE_DPR_NAME] = Flag.new(Flag.IMAGE_DPR_NAME), - [Flag.IMAGE_GRAVITY_NAME] = Flag.new(Flag.IMAGE_GRAVITY_NAME), - [Flag.IMAGE_X_NAME] = Flag.new(Flag.IMAGE_X_NAME), - [Flag.IMAGE_Y_NAME] = Flag.new(Flag.IMAGE_Y_NAME), - [Flag.IMAGE_HEIGHT_NAME] = Flag.new(Flag.IMAGE_HEIGHT_NAME), - [Flag.IMAGE_WIDTH_NAME] = Flag.new(Flag.IMAGE_WIDTH_NAME), - [Flag.IMAGE_RADIUS_NAME] = Flag.new(Flag.IMAGE_RADIUS_NAME), - [Flag.IMAGE_QUALITY_NAME] = Flag.new(Flag.IMAGE_QUALITY_NAME), + [Flag.IMAGE_BACKGROUND_NAME] = Flag.new(config, Flag.IMAGE_BACKGROUND_NAME), + [Flag.IMAGE_CROP_NAME] = Flag.new(config, Flag.IMAGE_CROP_NAME), + [Flag.IMAGE_DPR_NAME] = Flag.new(config, Flag.IMAGE_DPR_NAME), + [Flag.IMAGE_GRAVITY_NAME] = Flag.new(config, Flag.IMAGE_GRAVITY_NAME), + [Flag.IMAGE_X_NAME] = Flag.new(config, Flag.IMAGE_X_NAME), + [Flag.IMAGE_Y_NAME] = Flag.new(config, Flag.IMAGE_Y_NAME), + [Flag.IMAGE_HEIGHT_NAME] = Flag.new(config, Flag.IMAGE_HEIGHT_NAME), + [Flag.IMAGE_WIDTH_NAME] = Flag.new(config, Flag.IMAGE_WIDTH_NAME), + [Flag.IMAGE_RADIUS_NAME] = Flag.new(config, Flag.IMAGE_RADIUS_NAME), + [Flag.IMAGE_QUALITY_NAME] = Flag.new(config, Flag.IMAGE_QUALITY_NAME), + [Flag.IMAGE_MINPAD_NAME] = Flag.new(config, Flag.IMAGE_MINPAD_NAME), } flagMapper = config.flagImageMap valueMapper = config.flagValueMap @@ -104,15 +105,16 @@ local function main() end end - -- Scale dimensions with respect to limits - if flags[Flag.IMAGE_HEIGHT_NAME] then - local maxHeight = (mediaType == File.IMAGE_TYPE and config.maxImageHeight) or config.maxVideoHeight - flags[Flag.IMAGE_HEIGHT_NAME]:scaleDimension(flags[Flag.IMAGE_DPR_NAME].value, maxHeight) - end - if flags[Flag.IMAGE_WIDTH_NAME] then - local maxWidth = (mediaType == File.IMAGE_TYPE and config.maxImageWidth) or config.maxVideoWidth - flags[Flag.IMAGE_WIDTH_NAME]:scaleDimension(flags[Flag.IMAGE_DPR_NAME].value, maxWidth) + local dpr = flags[Flag.IMAGE_DPR_NAME] + if dpr and dpr.value then + for flagName, _ in pairs(flags) do + local flag = flags[flagName] + if flag.isScalable then + log('Scaling flag: ' .. flagName) + flag:scale(dpr.value) + end + end end local file = File.new(config, prefix, postfix, mediaId, mediaExtension, mediaType, flags) @@ -140,6 +142,7 @@ local function main() log('Original is present on local FS. Transcoding to ' .. file.cachedFilePath) local cmd = Command.new(config, file, flags) + log('Command: ' .. cmd.command) local executeSuccess = cmd:execute() if executeSuccess then