Module:Soundtrack
此模組用來生成原聲帶列表。
--- Handles track title, number, order and motif inference.
-- @module soundtrack
-- @alias p
-- @require Module:User error
-- @author [[User:en:KockaAdmiralac|KockaAdmiralac]]
-- <nowiki>
-- <nowiki>
local p = {}
local data = mw.loadData('Module:Soundtrack/data')
local title = mw.title.getCurrentTitle()
-- Private logic
--- 將曲目名稱轉換為對應於該曲目的 Bandcamp 網址段。
-- @function bandcamp
-- @param {string} track 曲目名稱
-- @return {string} 對應於該曲目的 Bandcamp 網址段
local function bandcamp(track)
local _
track, _ = mw.ustring.gsub(track, ' ', '-')
track, _ = mw.ustring.gsub(track, '[^a-zA-Z0-9-]', '')
return mw.ustring.lower(track)
end
--- 根據某專輯中曲目的索引與總曲目數量,回傳該曲目的分類排序鍵值。
-- @function sortkey
-- @param {string} album 專輯名稱
-- @param {number} trackNum 曲目編號
-- @return {string} 用於排序的補零曲目編號
local function sortkey(album, trackNum)
local numTracks = data.albums[album].length
if numTracks > 100 then
return string.format('%03d', trackNum)
elseif numTracks > 10 then
return string.format('%02d', trackNum)
else
return trackNum
end
end
--- 以以下方式標準化曲目名稱:
-- 1. 將曲目名稱轉為小寫(以便正確匹配 "sans.")
-- 2. 移除底線(以便正確匹配 "Soulmate_Locatedalbums")
-- 3. 去除首尾空白(以便正確匹配 "END OF THE LINE_")
-- 這用於將曲目名稱與其對應的條目名稱進行匹配。
-- @function normalizeTrackName
-- @param {string} name 要標準化的曲目名稱
-- @return {string} 標準化後的曲目名稱
local function normalizeTrackName(name)
local lowerName = mw.ustring.lower(name)
local noUnderscores, _ = mw.ustring.gsub(lowerName, '_', ' ')
local trimmed = mw.text.trim(noUnderscores)
return trimmed
end
---原文名稱 → 翻譯條目。
-- @function En2Zh
-- @param {string} 英文
-- @return {string} 中文
local function En2Zh(En)
return data.zhtitle[En] or En
end
--- 翻譯條目 → 原文名稱。
-- @function Zh2En
-- @param {string} 中文名稱
-- @return {string} 英文名稱
local function Zh2En(Zh)
return data.reverseMapping[Zh] or Zh
end
--- 翻譯條目 → 原文名稱 (回傳列表用)。
function p.Zh2En(frame)
return Zh2En(frame.args[1] or title.fullText)
end
--- 回傳一首曲目出現在幾張專輯中。
-- @function albumCount
-- @param {string} track 曲目名稱
-- @return {number} 該曲目出現的專輯數量
local function albumCount(track)
local count = 0
for _, __ in pairs(data.tracks[track]) do
count = count + 1
end
return count
end
-- Package items.
--- 回傳指定頁面與專輯下的曲目編號。
-- @function p.trackNum
-- @param {string} page 條目名稱
-- @return {string} 指定頁面與專輯下的曲目編號
function p.trackNum(frame)
local pageName = frame.args[1]
local albumName = frame.args[2]
return data.tracks[Zh2En(pageName)][Zh2En(albumName)]
end
--- 回傳指定頁面之曲目在所有專輯中的曲目編號清單。
-- @function p.trackNumList
-- @param {table} frame Scribunto 框架物件
-- @return {string} 所有專輯中曲目編號的 Wikitext 清單
function p.trackNumList(frame)
local pageName = frame.args[1] or title.fullText
local trackName = Zh2En(pageName)
local trackNumList = {}
local hasMultipleAlbums = albumCount(trackName) > 1
for _, albumName in pairs(data.order) do
local trackNum = data.tracks[trackName][albumName]
if trackNum ~= nil then
local trackRow = {'* ', trackNum}
if hasMultipleAlbums then
table.insert(trackRow, ' ([[')
table.insert(trackRow, En2Zh(albumName))
table.insert(trackRow, ']])')
end
if title.namespace == 0 then
table.insert(trackRow, '[[Category:')
table.insert(trackRow, En2Zh(albumName))
table.insert(trackRow, '|')
table.insert(trackRow, sortkey(albumName, trackNum))
table.insert(trackRow, ']]')
end
table.insert(trackNumList, table.concat(trackRow))
end
end
return table.concat(trackNumList, '\n')
end
--- 回傳指定頁面對應的曲目名稱。
-- @function p.trackTitle
-- @param {table} frame Scribunto 框架物件
-- @return {string} 指定頁面對應的曲目名稱
function p.trackTitle(frame)
return Zh2En(frame.args[1] or title.fullText)
end
--- 回傳所有專輯中,距離指定頁面曲目指定偏移量的曲目名稱清單,用於上一首和下一首選擇。
-- @function p.trackNav
-- @param {table} frame Scribunto 框架物件
-- @return {string} 所有專輯中偏移曲目的 Wikitext 清單
function p.trackNav(frame)
local offset = tonumber(frame.args[1])
local pageName = frame.args[2] or title.fullText
local trackName = Zh2En(pageName)
local trackList = {}
local hasMultipleAlbums = albumCount(trackName) > 1
for _, albumName in pairs(data.order) do
local trackNum = data.tracks[trackName][albumName]
if trackNum ~= nil then
local offsetTrackName = data.albums[albumName][trackNum + offset]
if offsetTrackName ~= nil then
if not hasMultipleAlbums then
return table.concat({
'[[',
En2Zh(offsetTrackName),
'|',
En2Zh(offsetTrackName):gsub('%s*%(%s*原聲帶%s*%)', ''),
']]'
})
end
table.insert(trackList, table.concat({
'* [[',
En2Zh(offsetTrackName),
'|',
En2Zh(offsetTrackName):gsub('%s*%(%s*原聲帶%s*%)', ''),
']] ([[',
En2Zh(albumName),
']])'
}))
end
end
end
return table.concat(trackList, '\n')
end
--- 回傳指定曲目中出現的主導動機清單。
-- @function p.motifs
-- @param {table} frame Scribunto 框架物件
-- @return {string} 當前曲目的主導動機
function p.motifs(frame)
local motifs = mw.loadData('Module:Soundtrack/motifs')
local formattedMotifs = {}
local currentTrack = frame.args[1] or title.fullText
local normCurrentTrack = normalizeTrackName(currentTrack)
for motif, data in pairs(motifs) do
for _, track in ipairs(data) do
if normCurrentTrack == normalizeTrackName(track) then
local fmt = data.major and '' or '\'\''
table.insert(formattedMotifs, table.concat({
fmt, '[[主導動機#', motif, '|', motif, ']]', fmt
}))
end
end
end
return table.concat(formattedMotifs, '<br />')
end
--- 回傳指定曲目頁面的 Bandcamp/ Spotify 小工具。
-- @function p.bandcamp
-- @param {table} frame Scribunto 框架物件
-- @return {string} 指定曲目頁面的 Bandcamp 網址段
function p.bandcamp(frame)
local trackId = tonumber(frame.args[1])
local spotifyId = frame.args[3]
if trackId == nil and spotifyId == nil then
-- No track ID was passed, do not generate any widget.
return ''
end
if trackId == nil and spotifyId ~= nil then
return tostring(mw.html.create('div'):attr({
['class'] = 'bandcamp-widget',
['data-track'] = spotifyId,
['data-platform'] = 'spotify'
}):wikitext(table.concat({
'[https://open.spotify.com/track/',
spotifyId,
' Link]'
})))
end
local pageName = frame.args[2] or title.fullText
local trackName = Zh2En(pageName)
for albumName, _ in pairs(data.tracks[trackName]) do
local albumId = data.albums[albumName].id
return tostring(mw.html.create('div'):attr({
['class'] = 'bandcamp-widget',
['data-album'] = albumId,
['data-track'] = trackId
}):wikitext(table.concat({
'[https://tobyfox.bandcamp.com/track/',
bandcamp(trackName),
' Link]'
})))
end
end
--- 為其他平台新增連結
-- @function p.distribution
-- @param {table} frame Scribunto frame object
-- @return {string} Div element with relevant track icon links
function p.distribution(frame)
local disttype = frame.args[1]
local args = frame:getParent().args
local links = {'<div class="soundtrack-links">'}
local hasAny = false
for platform, pdata in pairs(data.platforms) do
local id = args[platform]
if id ~= nil then
hasAny = true
table.insert(links, '[[File:')
table.insert(links, pdata.icon)
table.insert(links, '|32px|')
table.insert(links, pdata.title)
table.insert(links, '|link=')
table.insert(links, pdata[disttype])
table.insert(links, id)
table.insert(links, ']]')
end
end
if not hasAny then
return ''
end
table.insert(links, '</div>')
return table.concat(links)
end
--- 自動為提供的作者名稱清單加上連結。
-- @function p.authors
-- @param {table} frame Scribunto 框架物件
-- @return {string} 加上連結的作者名稱清單
function p.authors(frame)
local authors = frame.args[1]
local processedAuthors = {}
for author in mw.text.gsplit(authors, ', ', true) do
if data.authors[author] then
table.insert(processedAuthors, table.concat({
'[[', data.authors[author], '|', author, ']]'
}))
else
table.insert(processedAuthors, author)
end
end
return table.concat(processedAuthors, '<br />')
end
return p