mirror of
https://gitlab.freedesktop.org/pipewire/wireplumber.git
synced 2026-05-05 07:48:01 +02:00
scripts: add filter-utils.lua
Utilities Lua script for the policy to easily handle filter nodes.
This commit is contained in:
parent
b984af7c87
commit
1ba3844f2c
1 changed files with 401 additions and 0 deletions
401
src/scripts/lib/filter-utils.lua
Normal file
401
src/scripts/lib/filter-utils.lua
Normal file
|
|
@ -0,0 +1,401 @@
|
|||
-- WirePlumber
|
||||
|
||||
-- Copyright © 2023 Collabora Ltd.
|
||||
-- @author Julian Bouzas <julian.bouzas@collabora.com>
|
||||
|
||||
-- SPDX-License-Identifier: MIT
|
||||
|
||||
-- Script is a Lua Module of filter Lua utility functions
|
||||
|
||||
local cutils = require ("common-utils")
|
||||
|
||||
local module = {
|
||||
metadata = nil,
|
||||
filters = {},
|
||||
}
|
||||
|
||||
local function getFilterName (metadata, node)
|
||||
-- Check metadata
|
||||
if metadata ~= nil then
|
||||
local id = node["bound-id"]
|
||||
local value_str = metadata:find (id, "filter.name")
|
||||
if value_str ~= nil then
|
||||
local json = Json.Raw (value_str)
|
||||
if json:is_string() then
|
||||
return json:parse()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Check node properties
|
||||
local prop_str = node.properties ["filter.name"]
|
||||
if prop_str ~= nil then
|
||||
return prop_str
|
||||
end
|
||||
|
||||
-- Otherwise use link group as name
|
||||
return node.properties ["node.link-group"]
|
||||
end
|
||||
|
||||
local function getFilterEnabled (metadata, node)
|
||||
-- Check metadata
|
||||
if metadata ~= nil then
|
||||
local id = node["bound-id"]
|
||||
local value_str = metadata:find (id, "filter.enabled")
|
||||
if value_str ~= nil then
|
||||
local json = Json.Raw (value_str)
|
||||
if json:is_boolean() then
|
||||
return json:parse()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Check node properties
|
||||
local prop_str = node.properties ["filter.enabled"]
|
||||
if prop_str ~= nil then
|
||||
return cutils.parseBool (prop_str)
|
||||
end
|
||||
|
||||
-- Otherwise enable filter by defaul
|
||||
return true
|
||||
end
|
||||
|
||||
local function getFilterTarget (metadata, node, om)
|
||||
-- Check metadata and fallback to properties
|
||||
local id = node["bound-id"]
|
||||
local value_str = nil
|
||||
if metadata ~= nil then
|
||||
value_str = metadata:find (id, "filter.target")
|
||||
end
|
||||
if value_str == nil then
|
||||
value_str = node.properties ["filter.target"]
|
||||
if value_str == nil then
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
-- Parse match rules
|
||||
local match_rules_json = Json.Raw (value_str)
|
||||
if not match_rules_json:is_object () then
|
||||
return nil
|
||||
end
|
||||
local match_rules = match_rules_json:parse ()
|
||||
|
||||
-- Find target
|
||||
local target = nil
|
||||
for si_target in om:iterate { type = "SiLinkable" } do
|
||||
local n_target = si_target:get_associated_proxy ("node")
|
||||
if n_target == nil then
|
||||
goto skip_target
|
||||
end
|
||||
|
||||
-- Target nodes are only meant to be device nodes, without link-group
|
||||
if n_target.properties ["node.link-group"] ~= nil then
|
||||
goto skip_target
|
||||
end
|
||||
|
||||
-- Make sure the target node properties match all rules
|
||||
for key, val in ipairs(match_rules) do
|
||||
if n_target.properties[key] ~= val then
|
||||
goto skip_target
|
||||
end
|
||||
end
|
||||
|
||||
-- Target found
|
||||
target = si_target
|
||||
break;
|
||||
|
||||
::skip_target::
|
||||
end
|
||||
|
||||
return target;
|
||||
end
|
||||
|
||||
local function getFilterBefore (metadata, node)
|
||||
-- Check metadata and fallback to properties
|
||||
local id = node["bound-id"]
|
||||
local value_str = nil
|
||||
if metadata ~= nil then
|
||||
value_str = metadata:find (id, "filter.before")
|
||||
end
|
||||
if value_str == nil then
|
||||
value_str = node.properties ["filter.before"]
|
||||
if value_str == nil then
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
-- Parse
|
||||
local before_json = Json.Raw (value_str)
|
||||
if not before_json:is_array() then
|
||||
return nil
|
||||
end
|
||||
return before_json:parse ()
|
||||
end
|
||||
|
||||
local function getFilterAfter (metadata, node)
|
||||
-- Check metadata and fallback to properties
|
||||
local id = node["bound-id"]
|
||||
local value_str = nil
|
||||
if metadata ~= nil then
|
||||
value_str = metadata:find (id, "filter.after")
|
||||
end
|
||||
if value_str == nil then
|
||||
value_str = node.properties ["filter.after"]
|
||||
if value_str == nil then
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
-- Parse
|
||||
local after_json = Json.Raw (value_str)
|
||||
if not after_json:is_array() then
|
||||
return nil
|
||||
end
|
||||
return after_json:parse ()
|
||||
end
|
||||
|
||||
local function insertFilterSorted (curr_filters, filter)
|
||||
local before_filters = {}
|
||||
local after_filters = {}
|
||||
local new_filters = {}
|
||||
|
||||
-- Check if the current filters need to be inserted before or after
|
||||
for i, v in ipairs(curr_filters) do
|
||||
local insert_before = true
|
||||
local insert_after = false
|
||||
|
||||
if v.before ~= nil then
|
||||
for j, b in ipairs(v.before) do
|
||||
if filter.name == b then
|
||||
insert_after = false
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if v.after ~= nil then
|
||||
for j, b in ipairs(v.after) do
|
||||
if filter.name == b then
|
||||
insert_before = false
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if filter.before ~= nil then
|
||||
for j, b in ipairs(filter.before) do
|
||||
if v.name == b then
|
||||
insert_after = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if filter.after ~= nil then
|
||||
for j, b in ipairs(filter.after) do
|
||||
if v.name == b then
|
||||
insert_before = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if insert_before then
|
||||
if insert_after then
|
||||
Log.warning ("cyclic before/after found in filters " .. v.name .. " and " .. filter.name)
|
||||
end
|
||||
table.insert (before_filters, v)
|
||||
else
|
||||
table.insert (after_filters, v)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- Add the filters to the new table stored
|
||||
for i, v in ipairs(before_filters) do
|
||||
table.insert (new_filters, v)
|
||||
end
|
||||
table.insert (new_filters, filter)
|
||||
for i, v in ipairs(after_filters) do
|
||||
table.insert (new_filters, v)
|
||||
end
|
||||
|
||||
return new_filters
|
||||
end
|
||||
|
||||
local function rescanFilters (om, metadata_om)
|
||||
local metadata =
|
||||
metadata_om:lookup { Constraint { "metadata.name", "=", "filters" } }
|
||||
|
||||
-- Always clear all filters data on rescan
|
||||
module.filters = {}
|
||||
|
||||
Log.info ("rescanning filters...")
|
||||
|
||||
for si in om:iterate { type = "SiLinkable" } do
|
||||
local filter = {}
|
||||
|
||||
local n = si:get_associated_proxy ("node")
|
||||
if n == nil then
|
||||
goto skip_linkable
|
||||
end
|
||||
|
||||
-- Only handle nodes with link group (filters)
|
||||
filter.link_group = n.properties ["node.link-group"]
|
||||
if filter.link_group == nil then
|
||||
goto skip_linkable
|
||||
end
|
||||
|
||||
-- Only handle the main filter nodes
|
||||
filter.media_class = n.properties ["media.class"]
|
||||
if filter.media_class ~= "Audio/Sink" and
|
||||
filter.media_class ~= "Audio/Source" and
|
||||
filter.media_class ~= "Video/Source" then
|
||||
goto skip_linkable
|
||||
end
|
||||
|
||||
-- Filter direction
|
||||
if filter.media_class == "Audio/Sink" then
|
||||
filter.direction = "input"
|
||||
else
|
||||
filter.direction = "output"
|
||||
end
|
||||
|
||||
-- Get filter properties
|
||||
filter.name = getFilterName (metadata, n)
|
||||
filter.enabled = getFilterEnabled (metadata, n)
|
||||
filter.target = getFilterTarget (metadata, n, om)
|
||||
filter.before = getFilterBefore (metadata, n)
|
||||
filter.after = getFilterAfter (metadata, n)
|
||||
|
||||
-- Add the main and stream session items
|
||||
filter.main_si = si
|
||||
filter.stream_si = om:lookup {
|
||||
type = "SiLinkable",
|
||||
Constraint { "node.link-group", "=", filter.link_group },
|
||||
Constraint { "media.class", "#", "Stream/*", type = "pw-global" }
|
||||
}
|
||||
|
||||
-- Add the filter to the list sorted by before and after
|
||||
module.filters = insertFilterSorted (module.filters, filter)
|
||||
|
||||
::skip_linkable::
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
SimpleEventHook {
|
||||
name = "lib/filter-utils/rescan",
|
||||
interests = {
|
||||
EventInterest {
|
||||
Constraint { "event.type", "=", "rescan-for-linking" },
|
||||
},
|
||||
},
|
||||
execute = function (event)
|
||||
local source = event:get_source ()
|
||||
local om = source:call ("get-object-manager", "session-item")
|
||||
local metadata_om = source:call ("get-object-manager", "metadata")
|
||||
|
||||
rescanFilters (om, metadata_om)
|
||||
end
|
||||
}:register ()
|
||||
|
||||
SimpleEventHook {
|
||||
name = "lib/filter-utils/rescan-metadata-changed",
|
||||
interests = {
|
||||
EventInterest {
|
||||
Constraint { "event.type", "=", "metadata-changed" },
|
||||
Constraint { "metadata.name", "=", "filters" },
|
||||
},
|
||||
},
|
||||
execute = function (event)
|
||||
local source = event:get_source ()
|
||||
local om = source:call ("get-object-manager", "session-item")
|
||||
local metadata_om = source:call ("get-object-manager", "metadata")
|
||||
|
||||
rescanFilters (om, metadata_om)
|
||||
end
|
||||
}:register ()
|
||||
|
||||
function module.is_filter_enabled (direction, link_group)
|
||||
-- Make sure direction and link_group is valid
|
||||
if direction == nil or link_group == nil then
|
||||
return false
|
||||
end
|
||||
|
||||
for i, v in ipairs(module.filters) do
|
||||
if v.direction == direction and v.link_group == link_group then
|
||||
return v.enabled
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function module.get_filter_target (direction, link_group)
|
||||
-- Make sure direction and link_group are valid
|
||||
if direction == nil or link_group == nil then
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Find the current filter
|
||||
local filter = nil
|
||||
local index = nil
|
||||
for i, v in ipairs(module.filters) do
|
||||
if v.direction == direction and
|
||||
v.link_group == link_group and
|
||||
v.enabled then
|
||||
filter = v
|
||||
index = i
|
||||
break
|
||||
end
|
||||
end
|
||||
if filter == nil then
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Return the next filter with matching target
|
||||
for i, v in ipairs(module.filters) do
|
||||
if v.direction == direction and
|
||||
v.name ~= filter.name and
|
||||
v.link_group ~= link_group and
|
||||
v.enabled and
|
||||
((v.target == nil and v.target == filter.target) or
|
||||
(v.target.id == filter.target.id)) and
|
||||
i > index then
|
||||
return v.main_si
|
||||
end
|
||||
end
|
||||
|
||||
-- Otherwise return the filter destination target
|
||||
return filter.target
|
||||
end
|
||||
|
||||
function module.get_filter_from_target (direction, si_target)
|
||||
-- Make sure direction and si_target are valid
|
||||
if direction == nil or si_target == nil then
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Find the first filter matching target
|
||||
for i, v in ipairs(module.filters) do
|
||||
if v.direction == direction and
|
||||
v.enabled and
|
||||
v.target ~= nil and
|
||||
v.target.id == target.id then
|
||||
return v.main_si
|
||||
end
|
||||
end
|
||||
|
||||
-- If not found, just return the first filter with nil target
|
||||
for i, v in ipairs(module.filters) do
|
||||
if v.direction == direction and
|
||||
v.enabled and
|
||||
v.target == nil then
|
||||
return v.main_si
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
return module
|
||||
Loading…
Add table
Reference in a new issue