diff --git a/app/internal/liquidsoap_config_utils.py b/app/internal/liquidsoap_config_utils.py new file mode 100644 index 0000000..389d0d0 --- /dev/null +++ b/app/internal/liquidsoap_config_utils.py @@ -0,0 +1,34 @@ +import os +from internal.op25.liq_template import liquidsoap_config_template +from models import IcecastConfig + +def generate_liquid_script(config: IcecastConfig): + """ + Generates the "*.liq" file that's run by OP25 on startup. + + Placeholders in the template must be formatted as ${VARIABLE_NAME}. + + Args: + config (dict): A dictionary of key-value pairs for substitution. + Keys should match the variable names in the template (e.g., 'icecast_host'). + """ + try: + content = liquidsoap_config_template + # Replace variables + for key, value in config.items(): + placeholder = f"${{{key}}}" + # Ensure the value is converted to string for replacement + content = content.replace(placeholder, str(value)) + print(f" - Replaced placeholder {placeholder}") + + # Write the processed content to the output path + output_path = "/op25/op25/gr-op25_repeater/apps/op25.liq" + with open(output_path, 'w') as f: + f.write(content) + + print(f"\nSuccessfully wrote processed configuration to: {output_path}") + + except FileNotFoundError: + print(f"Error: Template file not found at {template_path}") + except Exception as e: + print(f"An unexpected error occurred: {e}") \ No newline at end of file diff --git a/op25.liq b/app/internal/op25.liq_template.py similarity index 72% rename from op25.liq rename to app/internal/op25.liq_template.py index f7da183..da27d63 100644 --- a/op25.liq +++ b/app/internal/op25.liq_template.py @@ -1,4 +1,4 @@ -#!/usr/bin/liquidsoap +liquidsoap_config_template = """#!/usr/bin/liquidsoap # Example liquidsoap streaming from op25 to icecast # (c) 2019-2021 gnorbury@bondcar.com, wllmbecks@gmail.com @@ -10,17 +10,15 @@ set("log.level", 1) # Make the native sample rate compatible with op25 set("frame.audio.samplerate", 8000) +set("init.allow_root", true) # ========================================================== -# 📢 ICECAST CONFIGURATION USING ENVIRONMENT VARIABLES -# Use getenv("VAR_NAME", "default_value") -# ========================================================== -ICE_HOST = getenv("ICE_HOST", "ic2.vpn.cusano.net") -ICE_PORT = int_of_string(getenv("ICE_PORT", "8000")) -ICE_MOUNT = getenv("ICE_MOUNT", "mountpoint") -ICE_PASSWORD = getenv("ICE_PASSWORD", "hackme") -ICE_DESCRIPTION = getenv("ICE_DESCRIPTION", "op25") -ICE_GENRE = getenv("ICE_GENRE", "Public Safety") +ICE_HOST = "${icecast_host}" +ICE_PORT = ${icecast_port} +ICE_MOUNT = "${icecast_mountpoint}" +ICE_PASSWORD = "${icecast_password}" +ICE_DESCRIPTION = "${icecast_description}" +ICE_GENRE = "${icecast_genre}" # ========================================================== input = mksafe(input.external(buffer=0.25, channels=2, samplerate=8000, restart_on_error=false, "./audio.py -x 2.5 -s")) @@ -46,4 +44,5 @@ output.icecast( mount=ICE_MOUNT, password=ICE_PASSWORD, mean(input) -) \ No newline at end of file +) +""" \ No newline at end of file diff --git a/app/internal/op25_config_utls.py b/app/internal/op25_config_utls.py index 23e9018..3919779 100644 --- a/app/internal/op25_config_utls.py +++ b/app/internal/op25_config_utls.py @@ -17,7 +17,7 @@ def save_talkgroup_tags(talkgroup_tags: List[TalkgroupTag]) -> None: writer = csv.writer(file, delimiter='\t', lineterminator='\n') # Write rows for tag in talkgroup_tags: - writer.writerow([tag.talkgroup, tag.tagDec]) + writer.writerow([tag.tagDec, tag.talkgroup]) def save_whitelist(talkgroup_tags: List[int]) -> None: """ diff --git a/app/models.py b/app/models.py index 15fb54e..88c0701 100644 --- a/app/models.py +++ b/app/models.py @@ -17,7 +17,7 @@ class ConfigGenerator(BaseModel): channels: List[Union[int, str]] tags: Optional[List[TalkgroupTag]] whitelist: Optional[List[int]] - icecastConfig: Optional[MetadataStreamConfig] + icecastConfig: Optional[IcecastConfig] class DemodType(str, Enum): CQPSK = "cqpsk" @@ -97,3 +97,15 @@ class TerminalConfig(BaseModel): http_plot_directory: Optional[str] = "../www/images" tuning_step_large: Optional[int] = 1200 tuning_step_small: Optional[int] = 100 + + + +### ====================================================== +# Icecast models +class IcecastConfig: + icecast_host: str + icecast_port: int + icecast_mountpoint: str + icecast_password: str + icecast_description: Optional[str] = "OP25" + icecast_genre: Optional[str] = "Public Safety" \ No newline at end of file diff --git a/app/routers/op25_controller.py b/app/routers/op25_controller.py index 6c1575c..67b36f4 100644 --- a/app/routers/op25_controller.py +++ b/app/routers/op25_controller.py @@ -6,6 +6,7 @@ import json from models import ConfigGenerator, DecodeMode, ChannelConfig, DeviceConfig, TrunkingConfig, TrunkingChannelConfig, TerminalConfig, MetadataConfig, MetadataStreamConfig from internal.logger import create_logger from internal.op25_config_utls import save_talkgroup_tags, save_whitelist, del_none_in_dict, get_current_system_from_config +from internal.liquidsoap_config_utils import generate_liquid_script LOGGER = create_logger(__name__) @@ -57,7 +58,7 @@ def create_op25_router(): demod_type="cqpsk", cqpsk_tracking=True, filter_type="rc", - meta_stream_name=generator.icecastConfig.stream_name + meta_stream_name="stream_0" )] devices = [DeviceConfig()] save_talkgroup_tags(generator.tags) @@ -73,9 +74,20 @@ def create_op25_router(): ) metadata = MetadataConfig( - streams=[generator.icecastConfig] + streams=[ + MetadataStreamConfig( + stream_name="stream_0", + icecastServerAddress = f"{generator.icecastConfig.icecast_host}:{generator.icecastConfig.icecast_port}", + icecastMountpoint = generator.icecastConfig.icecast_mountpoint, + icecastPass = generator.icecastConfig.icecast_password + ) + ] ) + # Generate the op25.liq file + generate_liquid_script(generator.icecastConfig) + + terminal = TerminalConfig() config_dict = {