Resource icon

OBS Lua Datetime digital clock 2019-12-24

WWKB

New Member
When I add the lua, an error message show as following, what wrong?

[DigitClock.lua] Error loading file: [string "/Applications/OBS.app/Contents/PlugIns/fronte..."]:1: unexpected symbol near '{'
 

NiteCyper

New Member
i'm first-time poster.
i like ISO 8601 formatting. i vibe-coded this via Google Gemini Flash 2.5.
Here's my branch of the OG script. Didn't see community revisions/addenda until after, so Suslik V's v4 code is not incorporated.

Input: %Y-%m-%dT%X%z %ZZ %a
Output: 2025-12-11T04:55:20-8 PST Thu.

Timezone tags:
%z Timezone offset (1 digit) (e.g., -8)
%zz Timezone offset (2 digit) (e.g., -08)
%zzz Timezone offset (4 digit) (e.g., -0800)
%ZZ Timezone abbreviation (e.g., PST)

Lua:
--[[ OBS Studio datetime script

This script transforms a text source into a digital clock. The datetime format
is configurable and uses the same syntax than the Lua os.date() call.
]]

obs             = obslua
source_name     = ""
datetime_format = ""

activated       = false


-- Helper function to generate the three desired numerical timezone formats
function get_custom_timezone_offset()
    -- Get the standard full offset (e.g., "-0800")
    local full_offset = os.date("%z")
 
    if not full_offset or string.len(full_offset) ~= 5 then
        return "", "", ""
    end

    -- 1. %zzz: The 4-value standard offset (e.g., -0800)
    local offset_zzz = full_offset

    -- 2. %zz: The 2-value offset, keeping the zero-padding (e.g., -08)
    local offset_zz = string.sub(full_offset, 1, 3)

    -- 3. %z: The 1-value offset, removing the leading zero (e.g., -8)
    local offset_z = offset_zz
    offset_z = string.gsub(offset_z, "([+-])0(%d)", "%1%2", 1)
 
    return offset_z, offset_zz, offset_zzz
end


-- Helper function to convert the full timezone name to a three-letter acronym
function get_timezone_acronym()
    -- Get the full timezone name (e.g., "Pacific Standard Time")
    local full_name = os.date("%Z")
 
    -- Manual lookup table for acronyms (keys MUST match OS output)
    local acronym_map = {
        ["Pacific Standard Time"]   = "PST",
        ["Pacific Daylight Time"]   = "PDT",
        ["Mountain Standard Time"]  = "MST",
        ["Mountain Daylight Time"]  = "MDT",
        ["Central Standard Time"]   = "CST",
        ["Central Daylight Time"]   = "CDT",
        ["Eastern Standard Time"]   = "EST",
        ["Eastern Daylight Time"]   = "EDT",
        ["Greenwich Mean Time"]     = "GMT",
        ["Coordinated Universal Time"] = "UTC",
    }

    local acronym = acronym_map[full_name]
 
    return acronym or full_name
end


-- Function to set the time text (MODIFIED)
function set_datetime_text(source, format)
 
    -- Get ALL custom offsets/acronyms first
    local offset_z, offset_zz, offset_zzz = get_custom_timezone_offset()
    local acronym_ZZ = get_timezone_acronym()

    -- 1. Create a replacement map of all custom codes to their actual values
    local custom_replacements = {
        ["%%zzz"] = offset_zzz,
        ["%%zz"]  = offset_zz,
        ["%%z"]   = offset_z,
        ["%%ZZ"]  = acronym_ZZ
    }
 
    -- 2. Create the final format string by substituting the custom codes
    local final_format = format
    for pattern, value in pairs(custom_replacements) do
        if value and value ~= "" then
            final_format = string.gsub(final_format, pattern, value)
        end
    end
 
    -- 3. Run os.date() ONLY on the final string
    local text = os.date(final_format)
 
    local settings = obs.obs_data_create()

    obs.obs_data_set_string(settings, "text", text)
    obs.obs_source_update(source, settings)
    obs.obs_data_release(settings)
end
-- END OF MODIFIED BLOCK

function timer_callback()
    local source = obs.obs_get_source_by_name(source_name)
    if source ~= nil then
        set_datetime_text(source, datetime_format)
        obs.obs_source_release(source)
    end
end

function activate(activating)
    if activated == activating then
        return
    end

    activated = activating

    if activating then
        obs.timer_add(timer_callback, 1000)
    else
        obs.timer_remove(timer_callback)
    end
end

-- Called when a source is activated/deactivated
function activate_signal(cd, activating)
    local source = obs.calldata_source(cd, "source")
    if source ~= nil then
        local name = obs.obs_source_get_name(source)
        if (name == source_name) then
            activate(activating)
        end
    end
end

function source_activated(cd)
    activate_signal(cd, true)
end

function source_deactivated(cd)
    activate_signal(cd, false)
end

function reset()
    activate(false)
    local source = obs.obs_get_source_by_name(source_name)
    if source ~= nil then
        local active = obs.obs_source_showing(source)
        obs.obs_source_release(source)
        activate(active)
    end
end

----------------------------------------------------------

function script_description()
    return "Sets a text source to act as a clock when the source is active.\
\
The datetime format can use the following tags:\
\
    %a    abbreviated weekday name (e.g., Wed)\
    %A    full weekday name (e.g., Wednesday)\
    %b    abbreviated month name (e.g., Sep)\
    %B    full month name (e.g., September)\
    %c    date and time (e.g., 09/16/98 23:48:10)\
    %d    day of the month (16) [01-31]\
    %H    hour, using a 24-hour clock (23) [00-23]\
    %I    hour, using a 12-hour clock (11) [01-12]\
    %M    minute (48) [00-59]\
    %m    month (09) [01-12]\
    %p    either \"am\" or \"pm\" (pm)\
    %S    second (10) [00-61]\
    %w    weekday (3) [0-6 = Sunday-Saturday]\
    %x    date (e.g., 09/16/98)\
    %X    time (e.g., 23:48:10)\
    %Y    full year (1998)\
    %y    two-digit year (98) [00-99]\
    %%    the character `%´\
    \
    CUSTOM TIMEZONE TAGS:\
    %z    Timezone hour offset (e.g., -8)\
    %zz    Timezone hour offset zero-padded (e.g., -08)\
    %zzz    Full timezone offset (e.g., -0800)\
    %ZZ    Timezone acronym (e.g., PST)"
end

function script_properties()
    local props = obs.obs_properties_create()

    obs.obs_properties_add_text(props, "format", "Datetime format", obs.OBS_TEXT_DEFAULT)

    local p = obs.obs_properties_add_list(props, "source", "Text Source", obs.OBS_COMBO_TYPE_EDITABLE, obs.OBS_COMBO_FORMAT_STRING)
    local sources = obs.obs_enum_sources()
    if sources ~= nil then
        for _, source in ipairs(sources) do
            source_id = obs.obs_source_get_id(source)
            if source_id == "text_gdiplus" or source_id == "text_ft2_source" then
                local name = obs.obs_source_get_name(source)
                obs.obs_property_list_add_string(p, name, name)
            end
        end
    end
    obs.source_list_release(sources)

    return props
end

function script_defaults(settings)
    obs.obs_data_set_default_string(settings, "format", "%X")
end

function script_update(settings)
    activate(false)

    source_name = obs.obs_data_get_string(settings, "source")
    datetime_format = obs.obs_data_get_string(settings, "format")

    reset()
end

function script_load(settings)
    local sh = obs.obs_get_signal_handler()
    obs.signal_handler_connect(sh, "source_show", source_activated)
    obs.signal_handler_connect(sh, "source_hide", source_deactivated)
end
 
Top