Module:Media

From Remilia Wiki
Jump to navigation Jump to search

Native-file and archive media wrappers: Template:Image, Template:Audio and Template:Video.


-- Shared author-facing media interface. File rendering and archive identity
-- verification remain with their MediaWiki handlers.
local p = {}
local getArgs = require('Module:Arguments').getArgs
local yesno = require('Module:Yesno')

local function arguments(frame)
	return getArgs(frame, { trim = true, removeBlanks = true })
end

local function failure(message)
	return tostring(mw.html.create('strong'):addClass('error'):wikitext(message))
end

local function dimension(value)
	if value == nil then return nil end
	local n = tonumber(value)
	if not n or n % 1 ~= 0 or n < 1 or n > 16384 then return false end
	return tostring(n)
end

local function fileTitle(value)
	if not value then return nil end
	local title = mw.title.new(value, 6)
	if not title or title.namespace ~= 6 or title.fragment ~= '' or title.isExternal then return nil end
	return title.prefixedText
end

local function archive(frame, kind, a)
	local values
	if kind == 'image' then
		values = { a.site or '', a.sha256 or '', a.alt or '', a.width or '',
			a.height or '', yesno(a.decorative) and 'yes' or 'no', a.filename or '',
			a.frame or 'none', a.align or '', a.caption or '' }
	elseif kind == 'audio' then
		values = { a.site or '', a.label or '', a.transcript or '' }
		for i = 1, 4 do
			values[#values + 1] = a['source' .. i .. '_sha256'] or ''
			values[#values + 1] = a['source' .. i .. '_type'] or ''
		end
		for i = 1, 4 do values[#values + 1] = a['source' .. i .. '_filename'] or '' end
	else
		values = { a.site or '', a.label or '', a.width or '', a.height or '',
			yesno(a.loop) and '1' or '0', yesno(a.muted) and '1' or '0',
			a.poster_sha256 or '', a.poster_filename or '' }
		for i = 1, 4 do
			for _, suffix in ipairs({ '_sha256', '_type', '_filename' }) do
				values[#values + 1] = a['source' .. i .. suffix] or ''
			end
		end
	end
	return frame:callParserFunction('#preservation' .. kind, values)
end

local function render(frame, kind)
	local a = arguments(frame)
	local file = a.file or a[1]
	local hasArchive = a.site or a.sha256 or a.source1_sha256
	if hasArchive then
		if file then return failure('Choose a wiki file or archive sources, not both.') end
		if a.start or a['end'] or a.thumbtime then return failure('Clip times require a wiki file.') end
		return archive(frame, kind, a)
	end
	local title = fileTitle(file)
	if not title then return failure('A valid wiki file is required.') end
	local width, height = dimension(a.width), dimension(a.height)
	if width == false or height == false then
		return failure('Media dimensions must be whole pixels from 1 to 16384.')
	end
	local options = { title }
	if kind == 'image' then
		local formats = { none = true, thumb = true, frame = true, frameless = true }
		local aligns = { left = true, right = true, center = true, none = true }
		if a.frame and not formats[a.frame] then return failure('Invalid image frame.') end
		if a.align and not aligns[a.align] then return failure('Invalid image alignment.') end
		if a.frame and a.frame ~= 'none' then options[#options + 1] = a.frame end
		if a.align then options[#options + 1] = a.align end
		if yesno(a.decorative) and a.alt then return failure('A decorative image must have empty alternative text.') end
		if a.alt or yesno(a.decorative) then options[#options + 1] = 'alt=' .. (a.alt or '') end
		if yesno(a.decorative) then options[#options + 1] = 'link=' end
	end
	if width or height then
		options[#options + 1] = (width or '') .. (height and 'x' .. height or '') .. 'px'
	end
	if kind == 'image' and a.caption then options[#options + 1] = a.caption end
	if kind ~= 'image' then
		if yesno(a.loop) then options[#options + 1] = 'loop' end
		if yesno(a.muted) then options[#options + 1] = 'muted' end
		for _, key in ipairs({'start', 'end', 'thumbtime'}) do
			if a[key] then options[#options + 1] = key .. '=' .. a[key] end
		end
	end
	local rendered = '[[' .. table.concat(options, '|') .. ']]'
	if kind == 'image' then
		local tag = (a.frame == 'thumb' or a.frame == 'frame' or a.align) and 'div' or 'span'
		return frame:extensionTag('templatestyles', '', { src = 'Module:Media/styles.css' }) ..
			tostring(mw.html.create(tag):addClass('remilia-image'):wikitext(rendered))
	end
	if not a.label then return failure('An accessible media label is required.') end
	-- Playback remains user initiated. Native player capabilities and available
	-- source formats come from TimedMediaHandler and the file repository.
	if a.poster_sha256 or a.poster_filename then
		return failure('Source-poster options require archive sources.')
	end
	local node = mw.html.create('div'):addClass('remilia-media'):addClass('remilia-media-' .. kind)
		:attr('role', 'group'):attr('aria-label', a.label):wikitext(rendered)
	node:tag('div'):addClass('remilia-media-caption'):wikitext(a.label)
	if a.transcript then node:tag('div'):addClass('remilia-media-transcript'):wikitext(a.transcript) end
	return frame:extensionTag('templatestyles', '', { src = 'Module:Media/styles.css' }) .. tostring(node)
end

function p.image(frame) return render(frame, 'image') end
function p.audio(frame) return render(frame, 'audio') end
function p.video(frame) return render(frame, 'video') end
return p