Difference between revisions of "Module:Listen"
(MIDIs are now playable without the Score extension) |
m (1 revision imported) |
||
(2 intermediate revisions by 2 users not shown) | |||
Line 1: | Line 1: | ||
− | |||
− | |||
local mFileLink = require('Module:File link') | local mFileLink = require('Module:File link') | ||
local mTableTools = require('Module:TableTools') | local mTableTools = require('Module:TableTools') | ||
local mSideBox = require('Module:Side box') | local mSideBox = require('Module:Side box') | ||
+ | local lang = mw.language.new('en') | ||
local p = {} | local p = {} | ||
− | |||
− | function | + | local function formatLength(length) |
− | + | -- Formats a duration in seconds in "(h:)mm:ss" (minutes are zero-padded | |
− | local | + | -- only if there are hours). |
− | + | if not length or length == 0 then | |
− | + | return nil | |
− | if | + | end |
− | + | ||
+ | -- Add 0.5 to offset the rounding down | ||
+ | local t = lang:getDurationIntervals(length + 0.5, { 'hours', 'minutes', 'seconds' }) | ||
+ | local s = t.seconds and string.format('%02d', t.seconds) or '00' | ||
+ | local m = t.minutes or 0 | ||
+ | |||
+ | local span = mw.html.create('span'):addClass('duration') | ||
+ | if t.hours then | ||
+ | span | ||
+ | :tag('span') | ||
+ | :addClass('h') | ||
+ | :wikitext(t.hours) | ||
+ | :done() | ||
+ | :wikitext(':') | ||
+ | m = string.format('%02d', m) | ||
+ | end | ||
+ | span | ||
+ | :tag('span') | ||
+ | :addClass('min') | ||
+ | :wikitext(m) | ||
+ | :done() | ||
+ | :wikitext(':') | ||
+ | :tag('span') | ||
+ | :addClass('s') | ||
+ | :wikitext(s) | ||
+ | :done() | ||
+ | return tostring(span) | ||
+ | end | ||
+ | |||
+ | local function renderRow(filename, title, play, alt, description, start, length, hasImage) | ||
+ | -- Renders the HTML for one file description row. | ||
+ | if not filename then | ||
+ | return nil | ||
+ | end | ||
+ | |||
+ | length = formatLength(length) | ||
+ | length = length and string.format(' (%s)', length) or '' | ||
+ | |||
+ | local root = mw.html.create('') | ||
+ | root:tag('div') | ||
+ | :addClass('haudio') | ||
+ | :newline() | ||
+ | :tag('div') | ||
+ | :addClass('listen-file-header') | ||
+ | :wikitext(string.format( | ||
+ | '[[:File:%s|%s]]%s', | ||
+ | filename, | ||
+ | title or '', | ||
+ | length | ||
+ | )) | ||
+ | :done() | ||
+ | :newline() | ||
+ | :tag('div') | ||
+ | :wikitext(play ~= 'no' and mFileLink._main{ | ||
+ | file = filename, | ||
+ | size = hasImage and '232px' or '215px', | ||
+ | alt = alt, | ||
+ | start = start | ||
+ | } | ||
+ | or nil | ||
+ | ) | ||
+ | :done() | ||
+ | :newline() | ||
+ | :tag('div') | ||
+ | :addClass('description') | ||
+ | :wikitext(description) | ||
+ | :done() | ||
+ | :done() | ||
+ | return tostring(root) | ||
+ | end | ||
+ | |||
+ | local function renderTrackingCategories(isPlain, hasMissing, isEmpty, titleObj) | ||
+ | -- Renders all tracking categories produced by the template. | ||
+ | -- isPlain, hasMissing and isEmpty are passed through from p._main, | ||
+ | -- and the titleObj is only used for testing purposes. | ||
+ | local cats = {} | ||
+ | local currentTitle = titleObj or mw.title.getCurrentTitle() | ||
+ | if currentTitle.namespace == 0 then | ||
+ | -- We are in mainspace. | ||
+ | if not isEmpty then | ||
+ | cats[#cats + 1] = 'Articles with hAudio microformats' | ||
+ | end | ||
+ | if hasMissing then | ||
+ | cats[#cats + 1] = 'Articles with empty listen template' | ||
end | end | ||
end | end | ||
− | return | + | if isPlain then |
+ | cats[#cats + 1] = 'Listen template using plain parameter' | ||
+ | end | ||
+ | for i, cat in ipairs(cats) do | ||
+ | cats[i] = string.format('[[Category:%s]]', cat) | ||
+ | end | ||
+ | return table.concat(cats) | ||
end | end | ||
function p._main(args) | function p._main(args) | ||
− | -- | + | -- Organise the arguments by number. |
local isPlain = args.plain == 'yes' | local isPlain = args.plain == 'yes' | ||
local isEmbedded = args.embed and true | local isEmbedded = args.embed and true | ||
+ | local hasImage = not isPlain and not isEmbedded and args.image ~= 'none' | ||
− | + | local numArgs, missingFiles = {}, {} | |
− | local numArgs = {} | ||
do | do | ||
local origNumArgs = mTableTools.numData(args) | local origNumArgs = mTableTools.numData(args) | ||
Line 33: | Line 120: | ||
for i, t in ipairs(origNumArgs) do | for i, t in ipairs(origNumArgs) do | ||
-- Check if the files exist. | -- Check if the files exist. | ||
− | local obj = t.filename and mw.title. | + | local obj = t.filename and mw.title.makeTitle(-2, t.filename) |
if obj and obj.exists then | if obj and obj.exists then | ||
+ | if t.length == 'yes' or | ||
+ | -- Show length if the video height would be less than 150px | ||
+ | obj.file.width / obj.file.height > (hasImage and 1.547 or 1.434) | ||
+ | then | ||
+ | t.length = obj.file.length | ||
+ | else | ||
+ | t.length = nil | ||
+ | end | ||
numArgs[#numArgs + 1] = t | numArgs[#numArgs + 1] = t | ||
else | else | ||
− | + | missingFiles[#missingFiles + 1] = t.filename or i | |
end | end | ||
end | end | ||
− | + | end | |
− | + | ||
− | + | -- Render warning | |
+ | local hasMissing = #missingFiles ~= 0 | ||
+ | local previewWarning = '' | ||
+ | if hasMissing then | ||
+ | for i, v in ipairs(missingFiles) do | ||
+ | missingFiles[i] = type(v) == 'string' | ||
+ | and string.format('missing file "%s"', v) | ||
+ | or string.format('empty filename #%s', v) | ||
end | end | ||
+ | previewWarning = string.format( | ||
+ | 'Page using [[Template:Listen]] with %s', | ||
+ | mw.text.listToText(missingFiles) | ||
+ | ) | ||
+ | previewWarning = require('Module:If preview')._warning({previewWarning}) | ||
+ | end | ||
+ | |||
+ | -- Exit early if none exist. | ||
+ | if #numArgs == 0 then | ||
+ | return previewWarning .. renderTrackingCategories(isPlain, hasMissing, true) | ||
end | end | ||
-- Build the arguments for {{side box}} | -- Build the arguments for {{side box}} | ||
− | local | + | local sbArgs = { |
− | + | metadata = 'no', | |
− | + | position = (isPlain or isEmbedded) and 'left' or args.pos, | |
− | + | style = args.style, | |
+ | templatestyles = 'Module:Listen/styles.css' | ||
+ | } | ||
− | -- | + | -- Class arguments |
do | do | ||
− | local | + | local class = { |
+ | 'listen', | ||
+ | 'noprint' | ||
+ | } | ||
if isPlain then | if isPlain then | ||
− | + | table.insert(class, 'listen-plain') | |
− | |||
− | |||
end | end | ||
if isEmbedded then | if isEmbedded then | ||
− | + | table.insert(class, 'listen-embedded') | |
− | |||
− | |||
− | |||
− | |||
end | end | ||
− | if args.pos == 'left' then | + | if not hasImage then |
− | + | table.insert(class, 'listen-noimage') | |
+ | end | ||
+ | if args.pos == 'left' and not isPlain and not isEmbedded then | ||
+ | table.insert(class, 'listen-left') | ||
elseif args.pos == 'center' then | elseif args.pos == 'center' then | ||
− | + | table.insert(class, 'listen-center') | |
− | |||
− | |||
end | end | ||
− | + | ||
− | + | sbArgs.class = table.concat(class, ' ') | |
− | |||
end | end | ||
− | |||
-- Image | -- Image | ||
if not isPlain and not isEmbedded then | if not isPlain and not isEmbedded then | ||
if args.image then | if args.image then | ||
− | + | sbArgs.image = args.image | |
else | else | ||
local images = { | local images = { | ||
speech = 'Audio-input-microphone.svg', | speech = 'Audio-input-microphone.svg', | ||
− | music = 'Gnome-mime-audio-openclipart.svg' | + | music = 'Gnome-mime-audio-openclipart.svg', |
+ | default = 'Gnome-mime-sound-openclipart.svg' | ||
} | } | ||
− | + | sbArgs.image = mFileLink._main{ | |
− | + | file = args.type and images[args.type] or images.default, | |
− | |||
− | |||
− | |||
size = '65x50px', | size = '65x50px', | ||
location = 'center', | location = 'center', | ||
Line 107: | Line 215: | ||
if args.header then | if args.header then | ||
header = mw.html.create('div') | header = mw.html.create('div') | ||
− | header: | + | header:addClass('listen-header') |
− | |||
− | |||
− | |||
− | |||
:wikitext(args.header) | :wikitext(args.header) | ||
− | header = tostring(header) | + | header = tostring(header) .. '\n' |
− | |||
else | else | ||
header = '' | header = '' | ||
Line 120: | Line 223: | ||
local text = {} | local text = {} | ||
for i, t in ipairs(numArgs) do | for i, t in ipairs(numArgs) do | ||
− | text[#text + 1] = | + | text[#text + 1] = renderRow( |
− | t.filename, t.title, t.play, t.alt, t.description, t.start | + | t.filename, t.title, t.play, t.alt, t.description, t.start, |
+ | t.length, hasImage | ||
) | ) | ||
if numArgs[i + 1] then | if numArgs[i + 1] then | ||
Line 127: | Line 231: | ||
end | end | ||
end | end | ||
− | + | sbArgs.text = header .. table.concat(text) | |
end | end | ||
-- Below | -- Below | ||
if not isPlain and not isEmbedded and args.help ~= 'no' then | if not isPlain and not isEmbedded and args.help ~= 'no' then | ||
− | + | sbArgs.below = string.format( | |
'<hr/><i class="selfreference">Problems playing %s? See [[Help:Media|media help]].</i>', | '<hr/><i class="selfreference">Problems playing %s? See [[Help:Media|media help]].</i>', | ||
#numArgs == 1 and 'this file' or 'these files' | #numArgs == 1 and 'this file' or 'these files' | ||
Line 139: | Line 243: | ||
-- Render the side box. | -- Render the side box. | ||
− | local sideBox = mSideBox._main( | + | local sideBox = mSideBox._main(sbArgs) |
-- Render the tracking categories. | -- Render the tracking categories. | ||
− | local trackingCategories = | + | local trackingCategories = renderTrackingCategories(isPlain, hasMissing) |
− | return sideBox .. trackingCategories | + | return previewWarning .. sideBox .. trackingCategories |
end | end | ||
− | function p. | + | function p.main(frame) |
− | + | local origArgs = frame:getParent().args | |
− | + | local args = {} | |
− | + | for k, v in pairs(origArgs) do | |
− | + | if v ~= '' then | |
− | local | + | args[k] = v |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | local | ||
− | |||
− | |||
− | |||
− | if | ||
− | |||
− | |||
− | |||
− | |||
end | end | ||
end | end | ||
− | + | return p._main(args) | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | return | ||
end | end | ||
return p | return p |
Latest revision as of 00:37, 19 January 2024
This module implements the {{listen}} template.
Usage from wikitext
This module cannot be used directly from wikitext. It can only be used through the {{listen}} template. Please see the template page for documentation.
Usage from Lua modules
To use this module from other Lua modules, first load the module.
local mListen = require('Module:Listen')
You can then generate the listen box by using the _main function.
mListen._main(args)
The args variable should be a table containing the arguments to pass to the module. To see the different arguments that can be specified and how they affect the module output, please refer to the {{listen}} template documentation.
Tracking/maintenance categories
- Category:Articles with hAudio microformats (12)
- Category:Articles with empty listen template (0)
- Category:Listen template using plain parameter (0)
local mFileLink = require('Module:File link')
local mTableTools = require('Module:TableTools')
local mSideBox = require('Module:Side box')
local lang = mw.language.new('en')
local p = {}
local function formatLength(length)
-- Formats a duration in seconds in "(h:)mm:ss" (minutes are zero-padded
-- only if there are hours).
if not length or length == 0 then
return nil
end
-- Add 0.5 to offset the rounding down
local t = lang:getDurationIntervals(length + 0.5, { 'hours', 'minutes', 'seconds' })
local s = t.seconds and string.format('%02d', t.seconds) or '00'
local m = t.minutes or 0
local span = mw.html.create('span'):addClass('duration')
if t.hours then
span
:tag('span')
:addClass('h')
:wikitext(t.hours)
:done()
:wikitext(':')
m = string.format('%02d', m)
end
span
:tag('span')
:addClass('min')
:wikitext(m)
:done()
:wikitext(':')
:tag('span')
:addClass('s')
:wikitext(s)
:done()
return tostring(span)
end
local function renderRow(filename, title, play, alt, description, start, length, hasImage)
-- Renders the HTML for one file description row.
if not filename then
return nil
end
length = formatLength(length)
length = length and string.format(' (%s)', length) or ''
local root = mw.html.create('')
root:tag('div')
:addClass('haudio')
:newline()
:tag('div')
:addClass('listen-file-header')
:wikitext(string.format(
'[[:File:%s|%s]]%s',
filename,
title or '',
length
))
:done()
:newline()
:tag('div')
:wikitext(play ~= 'no' and mFileLink._main{
file = filename,
size = hasImage and '232px' or '215px',
alt = alt,
start = start
}
or nil
)
:done()
:newline()
:tag('div')
:addClass('description')
:wikitext(description)
:done()
:done()
return tostring(root)
end
local function renderTrackingCategories(isPlain, hasMissing, isEmpty, titleObj)
-- Renders all tracking categories produced by the template.
-- isPlain, hasMissing and isEmpty are passed through from p._main,
-- and the titleObj is only used for testing purposes.
local cats = {}
local currentTitle = titleObj or mw.title.getCurrentTitle()
if currentTitle.namespace == 0 then
-- We are in mainspace.
if not isEmpty then
cats[#cats + 1] = 'Articles with hAudio microformats'
end
if hasMissing then
cats[#cats + 1] = 'Articles with empty listen template'
end
end
if isPlain then
cats[#cats + 1] = 'Listen template using plain parameter'
end
for i, cat in ipairs(cats) do
cats[i] = string.format('[[Category:%s]]', cat)
end
return table.concat(cats)
end
function p._main(args)
-- Organise the arguments by number.
local isPlain = args.plain == 'yes'
local isEmbedded = args.embed and true
local hasImage = not isPlain and not isEmbedded and args.image ~= 'none'
local numArgs, missingFiles = {}, {}
do
local origNumArgs = mTableTools.numData(args)
origNumArgs[1] = origNumArgs.other -- Overwrite args.filename1 etc. with args.filename etc.
origNumArgs = mTableTools.compressSparseArray(origNumArgs)
for i, t in ipairs(origNumArgs) do
-- Check if the files exist.
local obj = t.filename and mw.title.makeTitle(-2, t.filename)
if obj and obj.exists then
if t.length == 'yes' or
-- Show length if the video height would be less than 150px
obj.file.width / obj.file.height > (hasImage and 1.547 or 1.434)
then
t.length = obj.file.length
else
t.length = nil
end
numArgs[#numArgs + 1] = t
else
missingFiles[#missingFiles + 1] = t.filename or i
end
end
end
-- Render warning
local hasMissing = #missingFiles ~= 0
local previewWarning = ''
if hasMissing then
for i, v in ipairs(missingFiles) do
missingFiles[i] = type(v) == 'string'
and string.format('missing file "%s"', v)
or string.format('empty filename #%s', v)
end
previewWarning = string.format(
'Page using [[Template:Listen]] with %s',
mw.text.listToText(missingFiles)
)
previewWarning = require('Module:If preview')._warning({previewWarning})
end
-- Exit early if none exist.
if #numArgs == 0 then
return previewWarning .. renderTrackingCategories(isPlain, hasMissing, true)
end
-- Build the arguments for {{side box}}
local sbArgs = {
metadata = 'no',
position = (isPlain or isEmbedded) and 'left' or args.pos,
style = args.style,
templatestyles = 'Module:Listen/styles.css'
}
-- Class arguments
do
local class = {
'listen',
'noprint'
}
if isPlain then
table.insert(class, 'listen-plain')
end
if isEmbedded then
table.insert(class, 'listen-embedded')
end
if not hasImage then
table.insert(class, 'listen-noimage')
end
if args.pos == 'left' and not isPlain and not isEmbedded then
table.insert(class, 'listen-left')
elseif args.pos == 'center' then
table.insert(class, 'listen-center')
end
sbArgs.class = table.concat(class, ' ')
end
-- Image
if not isPlain and not isEmbedded then
if args.image then
sbArgs.image = args.image
else
local images = {
speech = 'Audio-input-microphone.svg',
music = 'Gnome-mime-audio-openclipart.svg',
default = 'Gnome-mime-sound-openclipart.svg'
}
sbArgs.image = mFileLink._main{
file = args.type and images[args.type] or images.default,
size = '65x50px',
location = 'center',
link = '',
alt = ''
}
end
end
-- Text
do
local header
if args.header then
header = mw.html.create('div')
header:addClass('listen-header')
:wikitext(args.header)
header = tostring(header) .. '\n'
else
header = ''
end
local text = {}
for i, t in ipairs(numArgs) do
text[#text + 1] = renderRow(
t.filename, t.title, t.play, t.alt, t.description, t.start,
t.length, hasImage
)
if numArgs[i + 1] then
text[#text + 1] = '<hr/>'
end
end
sbArgs.text = header .. table.concat(text)
end
-- Below
if not isPlain and not isEmbedded and args.help ~= 'no' then
sbArgs.below = string.format(
'<hr/><i class="selfreference">Problems playing %s? See [[Help:Media|media help]].</i>',
#numArgs == 1 and 'this file' or 'these files'
)
end
-- Render the side box.
local sideBox = mSideBox._main(sbArgs)
-- Render the tracking categories.
local trackingCategories = renderTrackingCategories(isPlain, hasMissing)
return previewWarning .. sideBox .. trackingCategories
end
function p.main(frame)
local origArgs = frame:getParent().args
local args = {}
for k, v in pairs(origArgs) do
if v ~= '' then
args[k] = v
end
end
return p._main(args)
end
return p