arib-cli 0.3.0

Reads the signalling of ARIB broadcasts, as an example of the arib crate
use crate::component::Stream;
use crate::si::{Event, Service};

pub fn or_unknown(value: &str) -> &str {
    if value.is_empty() { "(unknown)" } else { value }
}

pub fn print_service(service: &Service, indent: usize) {
    let pad = " ".repeat(indent);
    print!("{pad}{:#06X}: {}", service.id, or_unknown(&service.name));
    if let Some(service_type) = service.service_type {
        print!(" (type {service_type:#04X})");
    }
    println!();
    if !service.provider.is_empty() {
        println!("{pad}  Provider: {}", service.provider);
    }
}

/// Prints an event, with its genres, its access and its detailed description if `verbose`.
pub fn print_event(event: &Event, indent: usize, verbose: bool) {
    let pad = " ".repeat(indent);
    let time = match (event.start_time, event.duration) {
        (Some(start), Some(duration)) => format!(
            "{} - {} ({} min)",
            start.format("%Y-%m-%d %H:%M"),
            (start + duration).format("%H:%M"),
            duration.num_minutes()
        ),
        (Some(start), None) => format!("{} -", start.format("%Y-%m-%d %H:%M")),
        (None, _) => "(time undecided)".to_owned(),
    };

    println!(
        "{pad}{time}  {:#06X}  {}",
        event.id,
        or_unknown(&event.name)
    );
    if !event.text.is_empty() {
        for line in event.text.lines() {
            println!("{pad}    {line}");
        }
    }
    if !verbose {
        return;
    }

    for genre in &event.genres {
        println!("{pad}    Genre: {genre}");
    }
    let access = if event.scrambled { "scrambled" } else { "free" };
    println!("{pad}    Access: {access}");
    for (description, item) in &event.details {
        println!("{pad}    {}:", or_unknown(description));
        for line in item.lines() {
            println!("{pad}      {line}");
        }
    }
}

/// Parses an identifier given in decimal, or in hexadecimal after `0x`.
pub fn parse_id(value: &str) -> Result<u16, std::num::ParseIntError> {
    match value
        .strip_prefix("0x")
        .or_else(|| value.strip_prefix("0X"))
    {
        Some(hex) => u16::from_str_radix(hex, 16),
        None => value.parse(),
    }
}

/// Prints a stream the way ffprobe does: its ID and component tag, kind, codec, and details.
pub fn print_stream(stream: &Stream, details: &[String], indent: usize) {
    let pad = " ".repeat(indent);
    let tag = stream
        .component_tag
        .map(|tag| format!("[{tag:#04X}]"))
        .unwrap_or_default();
    let details: String = details.iter().map(|detail| format!(", {detail}")).collect();
    println!(
        "{pad}Stream {:#06X}{tag}: {}: {}{details}",
        stream.id, stream.kind, stream.codec
    );
}