Skip to content

Commit

Permalink
add: yt-dlp music system (#5792)
Browse files Browse the repository at this point in the history
* add: yogstation music system

* flag name change

* lobby music cfg

* cfg

* MORECFG

* style

* хуйню сделал

* better cfg

* prettier

* blya

* v1.5

* avtobundle

* Revert "avtobundle"

This reverts commit 291cb64.

* fix logging

* fix and tweaks

* doble equels

* fix quotes in path

---------

Co-authored-by: Aziz Chynaliev <hello@aziz.space>
  • Loading branch information
ROdenFL and Bizzonium authored Aug 24, 2024
1 parent 2523e1f commit c9fae1f
Show file tree
Hide file tree
Showing 14 changed files with 438 additions and 14 deletions.
1 change: 1 addition & 0 deletions code/_globalvars/_regexes.dm
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
GLOBAL_DATUM_INIT(is_http_protocol, /regex, regex("^https?://"))
GLOBAL_DATUM_INIT(filename_forbidden_chars, /regex, regex(@{""|[\\\n\t/?%*:|<>]|\.\."}, "g"))
GLOBAL_PROTECT(filename_forbidden_chars)
2 changes: 1 addition & 1 deletion code/controllers/configuration/configuration.dm
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ GLOBAL_LIST_EMPTY(overflow_whitelist)
InitEntries()

//Note: `$include`s are supported. Feel free to use them.
var/list/configs = list("game_options.txt", "dbconfig.txt", "config.txt", "emojis.txt", "resources.txt")
var/list/configs = list("game_options.txt", "dbconfig.txt", "config.txt", "emojis.txt", "resources.txt", "music.txt")
for(var/I in configs)
if(fexists("[directory]/[I]"))
for(var/J in configs)
Expand Down
6 changes: 6 additions & 0 deletions code/controllers/configuration/entries/config.dm
Original file line number Diff line number Diff line change
Expand Up @@ -825,3 +825,9 @@

/datum/config_entry/flag/save_spritesheets
default = FALSE


/datum/config_entry/string/invoke_youtubedl
protection = CONFIG_ENTRY_LOCKED | CONFIG_ENTRY_HIDDEN

/datum/config_entry/str_list/lobby_music
1 change: 1 addition & 0 deletions code/controllers/subsystem/statpanel.dm
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ SUBSYSTEM_DEF(statpanels)
list("Byond:", "(FPS:[world.fps]) (TickCount:[world.time / world.tick_lag]) (TickDrift:[round(Master.tickdrift, 1)]([round((Master.tickdrift / (world.time / world.tick_lag)) * 100, 0.1)]%)) (Internal Tick Usage: [round(MAPTICK_LAST_INTERNAL_TICK_USAGE, 0.1)]%)"),
list("Master Controller:", Master.stat_entry(), "[Master.UID()]"),
list("Failsafe Controller:", Failsafe.stat_entry(), "[Failsafe.UID()]"),
list("Configuration Controller:", config.stat_entry(), "[config.UID()]"),
list("","")
)
for(var/datum/controller/subsystem/sub_system as anything in Master.subsystems)
Expand Down
61 changes: 52 additions & 9 deletions code/controllers/subsystem/ticker.dm
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ SUBSYSTEM_DEF(ticker)
var/datum/game_mode/mode = null
/// The current pick of lobby music played in the lobby
var/login_music
var/login_music_data
var/selected_lobby_music
/// List of all minds in the game. Used for objective tracking
var/list/datum/mind/minds = list()
/// icon_state the chaplain has chosen for his bible
Expand Down Expand Up @@ -75,17 +77,14 @@ SUBSYSTEM_DEF(ticker)
var/list/randomtips = list()
var/list/memetips = list()

var/music_available = 0

/datum/controller/subsystem/ticker/Initialize()
login_music = pick(\
'sound/music/thunderdome.ogg',\
'sound/music/space.ogg',\
'sound/music/pilotpriest-origin-one.ogg',\
'sound/music/pilotpriest-tell-them-now.ogg',\
'sound/music/pilotpriest-now-be-the-light.ogg',\
'sound/music/title1.ogg',\
'sound/music/title2.ogg',\
'sound/music/title3.ogg',)
login_music_data = list()
login_music = choose_lobby_music()

if(!login_music)
to_chat(world, span_boldwarning("Could not load lobby music.")) //yogs end

randomtips = file2list("strings/tips.txt")
memetips = file2list("strings/sillytips.txt")
Expand Down Expand Up @@ -390,6 +389,50 @@ SUBSYSTEM_DEF(ticker)
//addtimer(VARSET_CALLBACK(SStime_track, update_gliding, TRUE), 1 MINUTES)
return TRUE

/datum/controller/subsystem/ticker/proc/choose_lobby_music()
var/list/songs = CONFIG_GET(str_list/lobby_music)
selected_lobby_music = pick(songs)

if(SSholiday.holidays) // What's this? Events are initialized before tickers? Let's do something with that!
for(var/holidayname in SSholiday.holidays)
var/datum/holiday/holiday = SSholiday.holidays[holidayname]
if(LAZYLEN(holiday.lobby_music))
selected_lobby_music = pick(holiday.lobby_music)
break

var/ytdl = CONFIG_GET(string/invoke_youtubedl)
if(!ytdl)
to_chat(world, span_boldwarning("yt-dlp was not configured."))
log_world("Could not play lobby song because yt-dlp is not configured properly, check the config.")
return

var/list/output = world.shelleo("[ytdl] -x --audio-format mp3 --audio-quality 0 --geo-bypass --no-playlist -o \"cache/songs/%(id)s.%(ext)s\" --dump-single-json --no-simulate \"[selected_lobby_music]\"")
var/errorlevel = output[SHELLEO_ERRORLEVEL]
var/stdout = output[SHELLEO_STDOUT]
var/stderr = output[SHELLEO_STDERR]

if(!errorlevel)
var/list/data
try
data = json_decode(stdout)
catch(var/exception/e)
to_chat(world, span_boldwarning("yt-dlp JSON parsing FAILED."))
log_world(span_boldwarning("yt-dlp JSON parsing FAILED:"))
log_world(span_warning("[e]: [stdout]"))
return
if(data["title"])
login_music_data["title"] = data["title"]
login_music_data["url"] = data["url"]
login_music_data["link"] = data["webpage_url"]
login_music_data["path"] = "cache/songs/[data["id"]].mp3"
login_music_data["title_link"] = data["webpage_url"] ? "<a href=\"[data["webpage_url"]]\">[data["title"]]</a>" : data["title"]

if(errorlevel)
to_chat(world, span_boldwarning("yt-dlp failed."))
log_world("Could not play lobby song [selected_lobby_music]: [stderr]")
return
return stdout


/datum/controller/subsystem/ticker/proc/station_explosion_cinematic(station_missed = 0, override = null)

Expand Down
27 changes: 24 additions & 3 deletions code/game/sound.dm
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
GLOBAL_LIST_EMPTY(cached_songs)

///Default override for echo
/sound
Expand Down Expand Up @@ -184,12 +185,32 @@ falloff_distance - Distance at which falloff begins. Sound is at peak volume (in
S.status = SOUND_UPDATE
SEND_SOUND(src, S)

/client/proc/playtitlemusic(vol = 85)
set waitfor = FALSE

/client/proc/playtitlemusic()
if(!SSticker || !SSticker.login_music || CONFIG_GET(flag/disable_lobby_music))
if(!SSticker || CONFIG_GET(flag/disable_lobby_music) || !CONFIG_GET(string/invoke_youtubedl))
return

UNTIL(SSticker.login_music) //wait for SSticker init to set the login music
UNTIL(tgui_panel)
UNTIL(SSassets.initialized)

var/url = SSticker.login_music_data["url"]
switch(CONFIG_GET(string/asset_transport))
if ("webroot")
var/datum/asset/music/my_asset
var/filepath = SSticker.login_music_data["path"]
if(GLOB.cached_songs[filepath])
my_asset = GLOB.cached_songs[filepath]
else
my_asset = new /datum/asset/music(filepath)
GLOB.cached_songs[filepath] = my_asset

url = my_asset.get_url()

if(prefs.sound & SOUND_LOBBY)
SEND_SOUND(src, sound(SSticker.login_music, repeat = 0, wait = 0, volume = 85 * prefs.get_channel_volume(CHANNEL_LOBBYMUSIC), channel = CHANNEL_LOBBYMUSIC)) // MAD JAMS
tgui_panel?.play_music(url, SSticker.login_music_data)
to_chat(src, span_notice("Сейчас играет: [SSticker.login_music_data["title_link"]]"))


/proc/get_rand_frequency()
Expand Down
1 change: 1 addition & 0 deletions code/modules/admin/admin_verbs.dm
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ GLOBAL_LIST_INIT(admin_verbs_sounds, list(
/client/proc/play_server_sound,
/client/proc/play_intercomm_sound,
/client/proc/stop_global_admin_sounds,
/client/proc/play_web_sound,
))
GLOBAL_LIST_INIT(admin_verbs_event, list(
/client/proc/secrets,
Expand Down
107 changes: 107 additions & 0 deletions code/modules/admin/verbs/playsound.dm
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,113 @@ GLOBAL_LIST_EMPTY(sounds_cache)
playsound(get_turf(src.mob), S, 50, 0, 0)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Play Local Sound") //If you are copy-pasting this, ensure the 4th parameter is unique to the new proc!


/client/proc/play_web_sound()
set category = "Event"
set name = "Play Internet Sound"
if(!check_rights(R_SOUNDS))
return

if(!tgui_panel || !SSassets.initialized)
return

var/ytdl = CONFIG_GET(string/invoke_youtubedl)
if(!ytdl)
to_chat(src, span_boldwarning("yt-dlp was not configured, action unavailable"), confidential=TRUE) //Check config.txt for the INVOKE_YOUTUBEDL value
return

var/web_sound_input = input("Enter content URL (supported sites only, leave blank to stop playing)", "Play Internet Sound via yt-dlp") as text|null
if(istext(web_sound_input))
var/web_sound_path = ""
var/web_sound_url = ""
var/stop_web_sounds = FALSE
var/list/music_extra_data = list()
if(length(web_sound_input))
web_sound_input = trim(web_sound_input)
if(findtext(web_sound_input, ":") && !findtext(web_sound_input, GLOB.is_http_protocol))
to_chat(src, span_boldwarning("Non-http(s) URIs are not allowed."), confidential=TRUE)
to_chat(src, span_warning("For yt-dlp shortcuts like ytsearch: please use the appropriate full url from the website."), confidential=TRUE)
return
var/shell_scrubbed_input = shell_url_scrub(web_sound_input)
var/list/output = world.shelleo("[ytdl] -x --audio-format mp3 --audio-quality 0 --geo-bypass --no-playlist -o \"cache/songs/%(id)s.%(ext)s\" --dump-single-json --no-simulate \"[shell_scrubbed_input]\"")
var/errorlevel = output[SHELLEO_ERRORLEVEL]
var/stdout = output[SHELLEO_STDOUT]
var/stderr = output[SHELLEO_STDERR]
if(!errorlevel)
var/list/data
try
data = json_decode(stdout)
catch(var/exception/e)
to_chat(src, span_boldwarning("yt-dlp JSON parsing FAILED:"), confidential=TRUE)
to_chat(src, span_warning("[e]: [stdout]"), confidential=TRUE)
return

if(data["url"])
web_sound_path = "cache/songs/[data["id"]].mp3"
web_sound_url = data["url"]
var/title = "[data["title"]]"
var/webpage_url = title
if(data["webpage_url"])
webpage_url = "<a href=\"[data["webpage_url"]]\">[title]</a>"
music_extra_data["start"] = data["start_time"]
music_extra_data["end"] = data["end_time"]
music_extra_data["link"] = data["webpage_url"]
music_extra_data["title"] = data["title"]
if(data["duration"])
var/mus_len = data["duration"] SECONDS
if(data["start_time"])
mus_len -= data["start_time"] SECONDS
if(data["end_time"])
mus_len -= (data["duration"] SECONDS - data["end_time"] SECONDS)
SSticker.music_available = REALTIMEOFDAY + mus_len

var/res = tgui_alert(usr, "Show the title of and link to this song to the players?\n[title]",, list("No", "Yes", "Cancel"))
switch(res)
if("Yes")
to_chat(world, span_boldannounceooc("Сейчас играет: [webpage_url]"))
if("Cancel")
return

SSblackbox.record_feedback("nested tally", "played_url", 1, list("[ckey]", "[web_sound_input]"))
log_admin("[key_name(src)] played web sound: [web_sound_input]")
message_admins("[key_name(src)] played web sound: [web_sound_input]")
else
to_chat(src, span_boldwarning("yt-dlp URL retrieval FAILED:"), confidential=TRUE)
to_chat(src, span_warning("[stderr]"), confidential=TRUE)

else //pressed ok with blank
log_admin("[key_name(src)] stopped web sound")
message_admins("[key_name(src)] stopped web sound")
web_sound_path = null
stop_web_sounds = TRUE
SSticker.music_available = 0

if(stop_web_sounds)
for(var/m in GLOB.player_list)
var/mob/M = m
var/client/C = M.client
if(C.prefs.toggles & SOUND_MIDI)
C.tgui_panel?.stop_music()
else
var/url = web_sound_url
switch(CONFIG_GET(string/asset_transport))
if ("webroot")
var/datum/asset/music/my_asset
if(GLOB.cached_songs[web_sound_path])
my_asset = GLOB.cached_songs[web_sound_path]
else
my_asset = new /datum/asset/music(web_sound_path)
GLOB.cached_songs[web_sound_path] = my_asset
url = my_asset.get_url()

for(var/m in GLOB.player_list)
var/mob/M = m
var/client/C = M.client
if(C.prefs.sound & SOUND_MIDI)
C.tgui_panel?.play_music(url, music_extra_data)

SSblackbox.record_feedback("tally", "admin_verb", 1, "Play Internet Sound")

/client/proc/play_server_sound()
set category = "Event"
set name = "Play Server Sound"
Expand Down
25 changes: 25 additions & 0 deletions code/modules/asset_cache/asset_list.dm
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,12 @@ GLOBAL_LIST_EMPTY(asset_datums)
for(var/asset_name in assets)
.[asset_name] = SSassets.transport.get_asset_url(asset_name, assets[asset_name])

/datum/asset/simple/music
legacy = TRUE
keep_local_name = TRUE

/datum/asset/simple/music/New()
GLOB.asset_datums[type] = src

/// For registering or sending multiple others at once
/datum/asset/group
Expand Down Expand Up @@ -522,6 +528,25 @@ GLOBAL_LIST_EMPTY(asset_datums)
return
. = list("[item_filename]" = SSassets.transport.get_asset_url(item_filename))

/datum/asset/music
_abstract = /datum/asset/music
var/item_filename

/datum/asset/music/New(path)
item_filename = sanitize_filename(path)
SSassets.transport.register_asset(item_filename, file(path))
fdel(path)

/datum/asset/music/send(client)
if(!item_filename)
return
. = SSassets.transport.send_assets(client, item_filename)

/datum/asset/music/proc/get_url()
if(!item_filename)
return
. = url_decode(SSassets.transport.get_asset_url(item_filename))

/datum/asset/spritesheet/simple
_abstract = /datum/asset/spritesheet/simple
var/list/assets
Expand Down
3 changes: 2 additions & 1 deletion code/modules/client/preference/preferences_toggles.dm
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,8 @@
if(isnewplayer(usr))
user.playtitlemusic()
else
usr.stop_sound_channel(CHANNEL_LOBBYMUSIC)
// usr.stop_sound_channel(CHANNEL_LOBBYMUSIC)
user.tgui_panel?.stop_music()

/datum/preference_toggle/toggle_admin_midis
name = "Toggle Admin Midis"
Expand Down
1 change: 1 addition & 0 deletions code/modules/holiday/holiday.dm
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
var/end_day = 0 // Default of 0 means the holiday lasts a single day
var/end_month = 0
var/eventChance = 0
var/list/lobby_music = null // list of youtube URLs for lobby music to use during this holiday

/**
* NOTE FOR EVERYONE TRYING TO DO STUFF WHICH REQUIRES MAPPING, PLACING OBJECTS, ETC:
Expand Down
2 changes: 2 additions & 0 deletions code/modules/tgui/tgui_panel/audio.dm
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
/datum/tgui_panel/proc/play_music(url, extra_data)
if(!is_ready())
return
if(!findtext(url, GLOB.is_http_protocol))
return
var/list/payload = list()
if(length(extra_data) > 0)
for(var/key in extra_data)
Expand Down
8 changes: 8 additions & 0 deletions config/example/config.txt
Original file line number Diff line number Diff line change
Expand Up @@ -721,3 +721,11 @@ CACHE_ASSETS 0
## Useful for developers to debug potential spritesheet issues to determine where the issue is cropping up (either in DM-side sprite generation or in the TGUI-side display of said spritesheet).
## Will only seek to waste disk space if ran on production.
#SAVE_SPRITESHEETS

## System command that invokes yt-dlp, used by Play Internet Sound.
## You can install yt-dlp with
## "pip install yt-dlp" if you have pip installed
## from https://github.com/yt-dlp/yt-dlp/releases
## or your package manager
## The default value assumes yt-dlp is in your system PATH
# INVOKE_YOUTUBEDL yt-dlp
Loading

0 comments on commit c9fae1f

Please sign in to comment.