arib-cli 0.2.0

Reads the signalling of ARIB broadcasts and descrambles them, as an example of the arib crate
use std::collections::BTreeMap;
use std::ops::ControlFlow;
use std::time::Duration;

use clap::Args;

use crate::input::InputArgs;
use crate::json;
use crate::print::{or_unknown, print_service};
use crate::si::{self, Network, Services, Si};

/// Lists the services of the network: those of the stream being read, and those of the other
/// streams it tells about.
#[derive(Clone, Debug, Args)]
pub struct Options {
    #[command(flatten)]
    input: InputArgs,

    /// Seconds to read the stream for, if it does not end before.
    #[arg(long, default_value_t = 30)]
    timeout: u64,

    /// Prints what was found as JSON.
    #[arg(long)]
    json: bool,
}

pub fn run(options: &Options) -> anyhow::Result<()> {
    let mut network: Option<Network> = None;
    let mut streams = BTreeMap::<(u16, u16), Services>::new();

    si::read(
        options.input.open()?,
        Duration::from_secs(options.timeout),
        |si| {
            match si {
                Si::Network(found) => network = Some(found),
                Si::Services(services) => {
                    streams.insert((services.original_network_id, services.stream_id), services);
                }
                Si::Events(_) | Si::Streams(_) => {}
            }
            ControlFlow::Continue(())
        },
    )?;

    if options.json {
        let streams: Vec<_> = streams
            .values()
            .map(|services| {
                serde_json::json!({
                    "id": services.stream_id,
                    "original_network_id": services.original_network_id,
                    "actual": services.actual,
                    "services": services.services.iter().map(json::service).collect::<Vec<_>>(),
                })
            })
            .collect();
        println!(
            "{}",
            serde_json::json!({
                "network": network.as_ref().map(json::network),
                "streams": streams,
            })
        );
        return Ok(());
    }

    if let Some(network) = &network {
        println!("Network: {:#06X} {}", network.id, or_unknown(&network.name));
    }

    for ((original_network_id, stream_id), services) in &streams {
        let actual = if services.actual {
            " (this stream)"
        } else {
            ""
        };
        println!("Stream: {stream_id:#06X} of network {original_network_id:#06X}{actual}");
        for service in &services.services {
            print_service(service, 2);
        }
    }

    // What the NIT names but no SDT was read for.
    for stream in network.iter().flat_map(|network| &network.streams) {
        if !streams.contains_key(&(stream.original_network_id, stream.id)) {
            println!(
                "Stream: {:#06X} of network {:#06X} (services not found)",
                stream.id, stream.original_network_id
            );
            for service_id in &stream.service_ids {
                println!("  {service_id:#06X}");
            }
        }
    }

    Ok(())
}