use crate::disc::{
AudioStream, Codec, ColorSpace, DiscTitle, HdrFormat, Stream, SubtitleStream, VideoStream,
};
use serde::{Deserialize, Serialize};
use std::io::{self, Read, Seek, SeekFrom, Write};
const MAGIC: [u8; 8] = [b'F', b'M', b'K', b'V', 0x00, 0x01, 0x00, 0x00];
const PACKET_SIZE: usize = 192;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct M2tsMeta {
pub v: u8,
#[serde(default)]
pub title: String,
#[serde(default)]
pub duration: f64,
pub streams: Vec<MetaStream>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum MetaStream {
#[serde(rename = "video")]
Video {
pid: u16,
codec: String,
#[serde(default)]
resolution: String,
#[serde(default)]
frame_rate: String,
#[serde(default)]
hdr: String,
#[serde(default)]
label: String,
#[serde(default)]
secondary: bool,
},
#[serde(rename = "audio")]
Audio {
pid: u16,
codec: String,
#[serde(default)]
channels: String,
#[serde(default)]
language: String,
#[serde(default)]
sample_rate: String,
#[serde(default)]
label: String,
#[serde(default)]
secondary: bool,
},
#[serde(rename = "subtitle")]
Subtitle {
pid: u16,
codec: String,
#[serde(default)]
language: String,
#[serde(default)]
forced: bool,
},
}
impl M2tsMeta {
pub fn from_title(title: &DiscTitle) -> Self {
let streams = title
.streams
.iter()
.map(|s| match s {
Stream::Video(v) => MetaStream::Video {
pid: v.pid,
codec: codec_to_str(v.codec),
resolution: v.resolution.clone(),
frame_rate: v.frame_rate.clone(),
hdr: hdr_to_str(v.hdr),
label: v.label.clone(),
secondary: v.secondary,
},
Stream::Audio(a) => MetaStream::Audio {
pid: a.pid,
codec: codec_to_str(a.codec),
channels: a.channels.clone(),
language: a.language.clone(),
sample_rate: a.sample_rate.clone(),
label: a.label.clone(),
secondary: a.secondary,
},
Stream::Subtitle(s) => MetaStream::Subtitle {
pid: s.pid,
codec: codec_to_str(s.codec),
language: s.language.clone(),
forced: s.forced,
},
})
.collect();
Self {
v: 1,
title: title.playlist.clone(),
duration: title.duration_secs,
streams,
}
}
pub fn to_title(&self) -> DiscTitle {
let streams = self
.streams
.iter()
.map(|s| match s {
MetaStream::Video {
pid,
codec,
resolution,
frame_rate,
hdr,
label,
secondary,
} => Stream::Video(VideoStream {
pid: *pid,
codec: str_to_codec(codec),
resolution: resolution.clone(),
frame_rate: frame_rate.clone(),
hdr: str_to_hdr(hdr),
color_space: ColorSpace::Bt709,
secondary: *secondary,
label: label.clone(),
}),
MetaStream::Audio {
pid,
codec,
channels,
language,
sample_rate,
label,
secondary,
} => Stream::Audio(AudioStream {
pid: *pid,
codec: str_to_codec(codec),
channels: channels.clone(),
language: language.clone(),
sample_rate: sample_rate.clone(),
secondary: *secondary,
label: label.clone(),
}),
MetaStream::Subtitle {
pid,
codec,
language,
forced,
} => Stream::Subtitle(SubtitleStream {
pid: *pid,
codec: str_to_codec(codec),
language: language.clone(),
forced: *forced,
codec_data: None,
}),
})
.collect();
DiscTitle {
playlist: self.title.clone(),
playlist_id: 0,
duration_secs: self.duration,
size_bytes: 0,
clips: Vec::new(),
streams,
chapters: Vec::new(),
extents: Vec::new(),
content_format: crate::disc::ContentFormat::BdTs,
}
}
}
pub fn write_header(w: &mut impl Write, meta: &M2tsMeta) -> io::Result<()> {
let json = serde_json::to_vec(meta).map_err(io::Error::other)?;
let json_len = json.len() as u32;
let raw_len = 8 + 4 + json.len(); let padded_len = raw_len.div_ceil(PACKET_SIZE) * PACKET_SIZE;
let padding = padded_len - raw_len;
w.write_all(&MAGIC)?;
w.write_all(&json_len.to_be_bytes())?;
w.write_all(&json)?;
if padding > 0 {
w.write_all(&vec![0u8; padding])?;
}
Ok(())
}
pub fn read_header<R: Read + Seek>(r: &mut R) -> io::Result<Option<M2tsMeta>> {
let start = r.stream_position()?;
let mut magic = [0u8; 8];
if r.read_exact(&mut magic).is_err() {
r.seek(SeekFrom::Start(start))?;
return Ok(None);
}
if magic[..4] != MAGIC[..4] {
r.seek(SeekFrom::Start(start))?;
return Ok(None);
}
let mut len_buf = [0u8; 4];
r.read_exact(&mut len_buf)?;
let json_len = u32::from_be_bytes(len_buf) as usize;
let mut json_buf = vec![0u8; json_len];
r.read_exact(&mut json_buf)?;
let meta: M2tsMeta = serde_json::from_slice(&json_buf)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
let raw_len = 8 + 4 + json_len;
let padded_len = raw_len.div_ceil(PACKET_SIZE) * PACKET_SIZE;
let padding = padded_len - raw_len;
if padding > 0 {
r.seek(SeekFrom::Current(padding as i64))?;
}
Ok(Some(meta))
}
pub fn read_header_from_stream(r: &mut impl Read) -> io::Result<Option<M2tsMeta>> {
let mut magic = [0u8; 8];
r.read_exact(&mut magic)?;
if magic[..4] != MAGIC[..4] {
return Ok(None);
}
let mut len_buf = [0u8; 4];
r.read_exact(&mut len_buf)?;
let json_len = u32::from_be_bytes(len_buf) as usize;
let mut json_buf = vec![0u8; json_len];
r.read_exact(&mut json_buf)?;
let meta: M2tsMeta = serde_json::from_slice(&json_buf)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
let raw_len = 8 + 4 + json_len;
let padded_len = raw_len.div_ceil(PACKET_SIZE) * PACKET_SIZE;
let padding = padded_len - raw_len;
if padding > 0 {
let mut skip = vec![0u8; padding];
r.read_exact(&mut skip)?;
}
Ok(Some(meta))
}
fn codec_to_str(c: Codec) -> String {
match c {
Codec::Hevc => "hevc",
Codec::H264 => "h264",
Codec::Vc1 => "vc1",
Codec::Mpeg2 => "mpeg2",
Codec::TrueHd => "truehd",
Codec::DtsHdMa => "dtshd_ma",
Codec::DtsHdHr => "dtshd_hr",
Codec::Dts => "dts",
Codec::Ac3 => "ac3",
Codec::Ac3Plus => "eac3",
Codec::Lpcm => "lpcm",
Codec::Pgs => "pgs",
Codec::DvdSub => "dvdsub",
Codec::Unknown(_) => "unknown",
}
.into()
}
fn str_to_codec(s: &str) -> Codec {
match s {
"hevc" => Codec::Hevc,
"h264" => Codec::H264,
"vc1" => Codec::Vc1,
"mpeg2" => Codec::Mpeg2,
"truehd" => Codec::TrueHd,
"dtshd_ma" => Codec::DtsHdMa,
"dtshd_hr" => Codec::DtsHdHr,
"dts" => Codec::Dts,
"ac3" => Codec::Ac3,
"eac3" => Codec::Ac3Plus,
"lpcm" => Codec::Lpcm,
"pgs" => Codec::Pgs,
_ => Codec::Unknown(0),
}
}
fn hdr_to_str(h: HdrFormat) -> String {
match h {
HdrFormat::Sdr => "sdr",
HdrFormat::Hdr10 => "hdr10",
HdrFormat::DolbyVision => "dv",
}
.into()
}
fn str_to_hdr(s: &str) -> HdrFormat {
match s {
"hdr10" => HdrFormat::Hdr10,
"dv" => HdrFormat::DolbyVision,
_ => HdrFormat::Sdr,
}
}