cuekit and cuenorm: three subtitle formats, one cue stream

This commit is contained in:
funkpower
2026-09-19 11:54:36 -04:00
parent 33c2e40a1a
commit c7d0767376
42 changed files with 1433 additions and 68 deletions
+4 -10
View File
@@ -26,18 +26,12 @@ pub struct Chunk {
/// What part of the work the passage belongs to.
pub role: Role,
/// Seconds from the start of the recording, for timed sources.
#[serde(
default,
serialize_with = "crate::float::serialize_option",
deserialize_with = "crate::float::deserialize_option"
)]
#[serde(default, with = "crate::finite::option")]
#[schemars(with = "Option<f64>")]
pub start_time: Option<f64>,
/// Seconds from the start of the recording, for timed sources.
#[serde(
default,
serialize_with = "crate::float::serialize_option",
deserialize_with = "crate::float::deserialize_option"
)]
#[serde(default, with = "crate::finite::option")]
#[schemars(with = "Option<f64>")]
pub end_time: Option<f64>,
/// Set tags; see [`flags`]. Sorted and deduplicated on the way in and on
/// the way out, so the same set of flags is always the same bytes
+57
View File
@@ -0,0 +1,57 @@
//! Serde plumbing for floats that JSON can actually carry.
//!
//! Use it as `#[serde(with = "corpus_core::finite")]` on an `f64`, or
//! `#[serde(default, with = "corpus_core::finite::option")]` on an
//! `Option<f64>`.
//!
//! JSON has no spelling for NaN or infinity, and `serde_json` writes them as
//! `null`. That is a silent corruption in both directions: a score written as
//! `null` fails to read back and kills the stream at the consumer, and a
//! timestamp written as `null` reads back as "no timestamp" with nothing to
//! say it was ever anything else. So a non-finite number is refused where it
//! is produced, which is the only place anyone can still fix it.
use serde::{Deserialize, Deserializer, Serializer, de::Error as _, ser::Error as _};
/// Write a float, refusing one JSON cannot carry.
pub fn serialize<S: Serializer>(value: &f64, out: S) -> Result<S::Ok, S::Error> {
if !value.is_finite() {
return Err(S::Error::custom(format!(
"{value} cannot be written as JSON"
)));
}
out.serialize_f64(*value)
}
/// Read a float, refusing one that is not finite.
pub fn deserialize<'de, D: Deserializer<'de>>(input: D) -> Result<f64, D::Error> {
let value = f64::deserialize(input)?;
if !value.is_finite() {
return Err(D::Error::custom(format!("{value} is not a finite number")));
}
Ok(value)
}
/// The same, for a field that may be absent.
pub mod option {
use serde::{Deserialize, Deserializer, Serializer, de::Error as _};
/// Write an optional float, refusing one JSON cannot carry.
pub fn serialize<S: Serializer>(value: &Option<f64>, out: S) -> Result<S::Ok, S::Error> {
match value {
Some(value) => super::serialize(value, out),
None => out.serialize_none(),
}
}
/// Read an optional float, refusing one that is not finite.
pub fn deserialize<'de, D: Deserializer<'de>>(input: D) -> Result<Option<f64>, D::Error> {
let value = Option::<f64>::deserialize(input)?;
match value {
Some(value) if !value.is_finite() => {
Err(D::Error::custom(format!("{value} is not a finite number")))
}
value => Ok(value),
}
}
}
-49
View File
@@ -1,49 +0,0 @@
//! Serde plumbing for floats that JSON can actually carry.
//!
//! JSON has no spelling for NaN or infinity, and `serde_json` writes them as
//! `null`. That is a silent corruption in both directions: a score written as
//! `null` fails to read back and kills the stream at the consumer, and a
//! timestamp written as `null` reads back as "no timestamp" with nothing to
//! say it was ever anything else. So a non-finite number is refused where it
//! is produced, which is the only place anyone can still fix it.
use serde::{Deserialize, Deserializer, Serializer, de::Error as _, ser::Error as _};
pub(crate) fn serialize<S: Serializer>(value: &f64, out: S) -> Result<S::Ok, S::Error> {
if !value.is_finite() {
return Err(S::Error::custom(format!(
"{value} cannot be written as JSON"
)));
}
out.serialize_f64(*value)
}
pub(crate) fn serialize_option<S: Serializer>(
value: &Option<f64>,
out: S,
) -> Result<S::Ok, S::Error> {
match value {
Some(value) => serialize(value, out),
None => out.serialize_none(),
}
}
pub(crate) fn deserialize<'de, D: Deserializer<'de>>(input: D) -> Result<f64, D::Error> {
let value = f64::deserialize(input)?;
if !value.is_finite() {
return Err(D::Error::custom(format!("{value} is not a finite number")));
}
Ok(value)
}
pub(crate) fn deserialize_option<'de, D: Deserializer<'de>>(
input: D,
) -> Result<Option<f64>, D::Error> {
let value = Option::<f64>::deserialize(input)?;
match value {
Some(value) if !value.is_finite() => {
Err(D::Error::custom(format!("{value} is not a finite number")))
}
value => Ok(value),
}
}
+2 -4
View File
@@ -22,10 +22,8 @@ pub struct Hit {
/// Position within the channel's own ranking, from one.
pub rank: u32,
/// The channel's raw score, on whatever scale that channel uses.
#[serde(
serialize_with = "crate::float::serialize",
deserialize_with = "crate::float::deserialize"
)]
#[serde(with = "crate::finite")]
#[schemars(with = "f64")]
pub score: f64,
/// Anything the producer wants to carry through fusion untouched.
///
+1 -1
View File
@@ -11,7 +11,7 @@
pub mod chunk;
pub mod doc;
pub mod exit;
mod float;
pub mod finite;
pub mod hit;
pub mod ndjson;