Add files via upload
This commit is contained in:
@@ -81,6 +81,47 @@ local function Count(t)
|
|||||||
return n
|
return n
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-----------------------------------------------------------------------
|
||||||
|
-- Sound-CVar snapshot / restore.
|
||||||
|
--
|
||||||
|
-- Every CVar this addon writes to (MasterSoundEffects, EnableMusic,
|
||||||
|
-- MasterVolume, ...) is snapshotted into SavedVariables the FIRST time
|
||||||
|
-- we change it in a session, and restored on PLAYER_LOGOUT.
|
||||||
|
--
|
||||||
|
-- Why SavedVariables and not just an in-memory table:
|
||||||
|
-- KillAllSounds() sets MasterSoundEffects="0" and re-enables it ~50ms
|
||||||
|
-- later from the ticker. If the player /reloads, logs out, or the
|
||||||
|
-- client crashes inside that window, "0" is what WoW persists to
|
||||||
|
-- Config.wtf and every sound effect is muted on the next login with
|
||||||
|
-- no obvious cause. Persisting the pre-mute value lets us recover
|
||||||
|
-- on the very next VARIABLES_LOADED even after a hard crash.
|
||||||
|
-----------------------------------------------------------------------
|
||||||
|
local function SnapshotCVar(name)
|
||||||
|
if type(RelationshipsJukeboxDB) ~= "table" then RelationshipsJukeboxDB = {} end
|
||||||
|
if type(RelationshipsJukeboxDB.savedCVars) ~= "table" then
|
||||||
|
RelationshipsJukeboxDB.savedCVars = {}
|
||||||
|
end
|
||||||
|
if RelationshipsJukeboxDB.savedCVars[name] ~= nil then
|
||||||
|
return -- already snapshotted this session (or from a previous crashed session)
|
||||||
|
end
|
||||||
|
local cv = GetCVar or function() return nil end
|
||||||
|
local ok, val = pcall(cv, name)
|
||||||
|
if ok and val ~= nil then
|
||||||
|
RelationshipsJukeboxDB.savedCVars[name] = tostring(val)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function RestoreSavedCVars()
|
||||||
|
if type(RelationshipsJukeboxDB) ~= "table" then return end
|
||||||
|
local saved = RelationshipsJukeboxDB.savedCVars
|
||||||
|
if type(saved) ~= "table" then return end
|
||||||
|
local sc = SetCVar or function() end
|
||||||
|
for name, value in pairs(saved) do
|
||||||
|
pcall(sc, name, value)
|
||||||
|
end
|
||||||
|
RelationshipsJukeboxDB.savedCVars = nil
|
||||||
|
end
|
||||||
|
|
||||||
local function Trim(s)
|
local function Trim(s)
|
||||||
if not s then return "" end
|
if not s then return "" end
|
||||||
return (string.gsub(s, "^%s*(.-)%s*$", "%1"))
|
return (string.gsub(s, "^%s*(.-)%s*$", "%1"))
|
||||||
@@ -366,6 +407,10 @@ end
|
|||||||
|
|
||||||
local function KillAllSounds(afterReenableCallback)
|
local function KillAllSounds(afterReenableCallback)
|
||||||
local sc = SetCVar or function() end
|
local sc = SetCVar or function() end
|
||||||
|
-- Snapshot MasterSoundEffects BEFORE muting, so if the player /reloads
|
||||||
|
-- or crashes before the ticker re-enables it, we can restore it on
|
||||||
|
-- the next login instead of leaving Config.wtf with SFX muted.
|
||||||
|
SnapshotCVar("MasterSoundEffects")
|
||||||
-- Kill ALL currently playing sounds by disabling the master sound CVar.
|
-- Kill ALL currently playing sounds by disabling the master sound CVar.
|
||||||
pcall(sc, "MasterSoundEffects", "0")
|
pcall(sc, "MasterSoundEffects", "0")
|
||||||
-- Also stop any PlayMusic track as a fallback.
|
-- Also stop any PlayMusic track as a fallback.
|
||||||
@@ -1352,9 +1397,12 @@ local function CreateUI()
|
|||||||
slider:SetScript("OnValueChanged", function()
|
slider:SetScript("OnValueChanged", function()
|
||||||
local pct = this:GetValue()
|
local pct = this:GetValue()
|
||||||
local sc = SetCVar or function() end
|
local sc = SetCVar or function() end
|
||||||
|
-- Snapshot MasterVolume once so we can restore the player's
|
||||||
|
-- original master output level on logout.
|
||||||
|
SnapshotCVar("MasterVolume")
|
||||||
-- MasterVolume is 0..1, slider is 0..100
|
-- MasterVolume is 0..1, slider is 0..100
|
||||||
pcall(sc, "MasterVolume", string.format("%.2f", pct / 100))
|
pcall(sc, "MasterVolume", string.format("%.2f", pct / 100))
|
||||||
-- Persist to SavedVariables
|
-- Persist slider position (not applied at login — see VARIABLES_LOADED)
|
||||||
RelationshipsJukeboxDB.volume = pct
|
RelationshipsJukeboxDB.volume = pct
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
@@ -1438,6 +1486,7 @@ local function CreateUI()
|
|||||||
-- Reset volume slider to default (100%)
|
-- Reset volume slider to default (100%)
|
||||||
do
|
do
|
||||||
local sc = SetCVar or function() end
|
local sc = SetCVar or function() end
|
||||||
|
SnapshotCVar("MasterVolume")
|
||||||
pcall(sc, "MasterVolume", "1.0")
|
pcall(sc, "MasterVolume", "1.0")
|
||||||
RelationshipsJukeboxDB.volume = 100
|
RelationshipsJukeboxDB.volume = 100
|
||||||
if RJ.volumeSlider then
|
if RJ.volumeSlider then
|
||||||
@@ -1911,16 +1960,18 @@ logoutHandler:SetScript("OnEvent", function()
|
|||||||
RJ.pausedElapsed = nil
|
RJ.pausedElapsed = nil
|
||||||
RJ.currentDuration = 0
|
RJ.currentDuration = 0
|
||||||
end
|
end
|
||||||
-- Always restore the player's original EnableMusic on logout,
|
-- Suppress the pending MasterSoundEffects re-enable — we're about to
|
||||||
-- even if no track was playing.
|
-- restore it explicitly from the snapshot below, and the ticker will
|
||||||
if RJ._originalEnableMusic then
|
-- not fire again this session.
|
||||||
local sc = SetCVar or function() end
|
|
||||||
pcall(sc, "EnableMusic", RJ._originalEnableMusic)
|
|
||||||
RJ._originalEnableMusic = nil
|
|
||||||
end
|
|
||||||
-- Suppress any remaining pending state
|
|
||||||
RJ._pendingReenableAt = nil
|
RJ._pendingReenableAt = nil
|
||||||
RJ._pendingReenableCallback = nil
|
RJ._pendingReenableCallback = nil
|
||||||
|
-- Clear legacy in-memory copy (superseded by SavedVariables snapshot).
|
||||||
|
RJ._originalEnableMusic = nil
|
||||||
|
-- Restore every sound CVar the addon ever changed this session
|
||||||
|
-- (EnableMusic, MasterSoundEffects, MasterVolume, ...) back to the
|
||||||
|
-- value the player had before the addon loaded, and clear the
|
||||||
|
-- snapshot so the next login starts clean.
|
||||||
|
RestoreSavedCVars()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
-----------------------------------------------------------------------
|
-----------------------------------------------------------------------
|
||||||
@@ -2085,21 +2136,32 @@ loader:SetScript("OnEvent", function()
|
|||||||
|
|
||||||
EnsureDB()
|
EnsureDB()
|
||||||
|
|
||||||
|
-- CRASH-RECOVERY: if a previous session ended (logout, /reload, or crash)
|
||||||
|
-- without our PLAYER_LOGOUT handler firing — for example inside the ~50ms
|
||||||
|
-- window where KillAllSounds() has MasterSoundEffects="0" — restore the
|
||||||
|
-- pre-mute values from SavedVariables FIRST, before we do anything else.
|
||||||
|
-- This guarantees the player logs in with their real sound settings even
|
||||||
|
-- after a hard crash mid-mute.
|
||||||
|
RestoreSavedCVars()
|
||||||
|
|
||||||
-- Save the player's original EnableMusic value and disable game engine
|
-- Save the player's original EnableMusic value and disable game engine
|
||||||
-- music so it does not overlap with jukebox playback. This is restored
|
-- music so it does not overlap with jukebox playback. Restored on
|
||||||
-- on logout or when the Reset button is pressed.
|
-- PLAYER_LOGOUT via RestoreSavedCVars() (and on Reset).
|
||||||
|
--
|
||||||
|
-- IMPORTANT: We deliberately do NOT touch MasterVolume here. MasterVolume
|
||||||
|
-- is the global master output CVar and controls ALL game audio (SFX,
|
||||||
|
-- ambience, jukebox, everything). A previous version applied the saved
|
||||||
|
-- jukebox slider value to MasterVolume at login, which muted every game
|
||||||
|
-- sound whenever the slider had been dragged down. The only thing that
|
||||||
|
-- should be silenced when this addon loads is the in-game music ("the
|
||||||
|
-- music box"), which is what EnableMusic="0" does.
|
||||||
do
|
do
|
||||||
local cv = GetCVar or function() return "1" end
|
local cv = GetCVar or function() return "1" end
|
||||||
local sc = SetCVar or function() end
|
local sc = SetCVar or function() end
|
||||||
|
SnapshotCVar("EnableMusic")
|
||||||
local ok, val = pcall(cv, "EnableMusic")
|
local ok, val = pcall(cv, "EnableMusic")
|
||||||
RJ._originalEnableMusic = (ok and val) or "1"
|
RJ._originalEnableMusic = (ok and val) or "1"
|
||||||
pcall(sc, "EnableMusic", "0")
|
pcall(sc, "EnableMusic", "0")
|
||||||
-- Apply saved MasterVolume (master output volume). If the
|
|
||||||
-- player has never set it, the CVar default (1.0) remains.
|
|
||||||
if RelationshipsJukeboxDB.volume then
|
|
||||||
pcall(sc, "MasterVolume",
|
|
||||||
string.format("%.2f", RelationshipsJukeboxDB.volume / 100))
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
imported, updated = MergeAutoPlaylist()
|
imported, updated = MergeAutoPlaylist()
|
||||||
|
|||||||
@@ -771,6 +771,106 @@ if ($renameSkipped -gt 0) {
|
|||||||
Write-Host ""
|
Write-Host ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# -- Rename numeric-only MP3 filenames using embedded ID3 title --------------
|
||||||
|
# Some MP3s have filenames that are just numbers (e.g. "01.mp3", "1234.mp3",
|
||||||
|
# "01 02 03.mp3"). These are useless in the playlist UI. When we detect such
|
||||||
|
# a file, read the embedded ID3 title tag via Shell.Application and rename the
|
||||||
|
# file to "<Title>.mp3" (sanitized for WoW 1.12 path compatibility).
|
||||||
|
|
||||||
|
function Get-Mp3EmbeddedTitle {
|
||||||
|
param([Parameter(Mandatory=$true)][string]$FilePath)
|
||||||
|
try {
|
||||||
|
$shell = New-Object -ComObject Shell.Application
|
||||||
|
$folder = $shell.Namespace((Split-Path -Parent $FilePath))
|
||||||
|
if ($null -eq $folder) { return $null }
|
||||||
|
$item = $folder.ParseName((Split-Path -Leaf $FilePath))
|
||||||
|
if ($null -eq $item) { return $null }
|
||||||
|
# Property index 21 = Title on Windows; fall back to scanning if empty.
|
||||||
|
$title = $folder.GetDetailsOf($item, 21)
|
||||||
|
if ([string]::IsNullOrWhiteSpace($title)) {
|
||||||
|
for ($i = 0; $i -lt 400; $i++) {
|
||||||
|
$header = $folder.GetDetailsOf($null, $i)
|
||||||
|
if ($header -eq 'Title') {
|
||||||
|
$title = $folder.GetDetailsOf($item, $i)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ([string]::IsNullOrWhiteSpace($title)) { return $null }
|
||||||
|
return $title.Trim()
|
||||||
|
} catch {
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$numericRenamed = 0
|
||||||
|
$numericSkipped = 0
|
||||||
|
$mp3Files = @(Get-ChildItem -LiteralPath $Media -Recurse -File |
|
||||||
|
Where-Object { $_.Extension -match '^(?i)\.mp3$' -and $_.Name -notmatch '\.sanitize\.tmp\.' })
|
||||||
|
|
||||||
|
foreach ($mf in $mp3Files) {
|
||||||
|
$base = [System.IO.Path]::GetFileNameWithoutExtension($mf.Name)
|
||||||
|
# "Only numbers": digits, optionally separated by spaces / dots / dashes / underscores.
|
||||||
|
if ($base -notmatch '^[\d\s\.\-_]+$' -or $base -notmatch '\d') { continue }
|
||||||
|
|
||||||
|
$title = Get-Mp3EmbeddedTitle -FilePath $mf.FullName
|
||||||
|
if ([string]::IsNullOrWhiteSpace($title)) {
|
||||||
|
Write-Host " Skipped (no embedded title): $($mf.Name)"
|
||||||
|
$numericSkipped++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
# Sanitize the title for filesystem + WoW 1.12 compatibility.
|
||||||
|
$safeTitle = $title
|
||||||
|
foreach ($ch in $problematicChars) { $safeTitle = $safeTitle.Replace($ch, '_') }
|
||||||
|
foreach ($ch in [System.IO.Path]::GetInvalidFileNameChars()) {
|
||||||
|
$safeTitle = $safeTitle.Replace([string]$ch, '_')
|
||||||
|
}
|
||||||
|
while ($safeTitle.Contains('__')) { $safeTitle = $safeTitle.Replace('__', '_') }
|
||||||
|
$safeTitle = $safeTitle.Trim(' ', '_', '.')
|
||||||
|
|
||||||
|
if ([string]::IsNullOrWhiteSpace($safeTitle)) {
|
||||||
|
Write-Host " Skipped (title sanitized to empty): $($mf.Name)"
|
||||||
|
$numericSkipped++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
$newName = "$safeTitle.mp3"
|
||||||
|
$newPath = Join-Path $mf.DirectoryName $newName
|
||||||
|
if ($newPath -eq $mf.FullName) { continue }
|
||||||
|
|
||||||
|
# Avoid collisions by appending a numeric suffix.
|
||||||
|
if (Test-Path -LiteralPath $newPath) {
|
||||||
|
$n = 2
|
||||||
|
do {
|
||||||
|
$candidate = Join-Path $mf.DirectoryName ("$safeTitle ($n).mp3")
|
||||||
|
$n++
|
||||||
|
} while (Test-Path -LiteralPath $candidate)
|
||||||
|
$newPath = $candidate
|
||||||
|
$newName = Split-Path -Leaf $candidate
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Rename-Item -LiteralPath $mf.FullName -NewName $newName -Force
|
||||||
|
Write-Host " Renamed (numeric -> title): $($mf.Name) -> $newName"
|
||||||
|
$numericRenamed++
|
||||||
|
} catch {
|
||||||
|
Write-Host " WARNING: Could not rename $($mf.Name): $_"
|
||||||
|
$numericSkipped++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($numericRenamed -gt 0) {
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "Renamed $numericRenamed numeric-only MP3 file(s) using embedded ID3 title."
|
||||||
|
Write-Host ""
|
||||||
|
}
|
||||||
|
if ($numericSkipped -gt 0) {
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "$numericSkipped numeric-only MP3 file(s) skipped (no title / collision / error)."
|
||||||
|
Write-Host ""
|
||||||
|
}
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Now that ffmpeg may be available, set up a helper to call it from the
|
# Now that ffmpeg may be available, set up a helper to call it from the
|
||||||
# local ffmpeg folder when the system PATH doesn't have it.
|
# local ffmpeg folder when the system PATH doesn't have it.
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
## Title: Relationships Jukebox
|
## Title: Relationships Jukebox
|
||||||
## Notes: Simple in-game MP3/WAV/OGG player with GUI.
|
## Notes: Simple in-game MP3/WAV/OGG player with GUI.
|
||||||
## Author: Relationship
|
## Author: Relationship
|
||||||
## Version: 1.3
|
## Version: 1.3.2
|
||||||
## SavedVariables: RelationshipsJukeboxDB
|
## SavedVariables: RelationshipsJukeboxDB
|
||||||
## X-Website: https://github.com/Relationship-OctoWoW/RelationshipsJukeBox
|
## X-Website: https://github.com/Relationship-OctoWoW/RelationshipsJukeBox
|
||||||
## X-GitHub: https://github.com/Relationship-OctoWoW/RelationshipsJukeBox
|
## X-GitHub: https://github.com/Relationship-OctoWoW/RelationshipsJukeBox
|
||||||
|
|||||||
Reference in New Issue
Block a user