use core::fmt::{self, Debug};
use derive_more::{IsVariant, TryUnwrap, Unwrap};
use crate::{
Timebase, Timestamp,
adapter::{AudioAdapter, SubtitleAdapter, VideoAdapter},
packet::{AudioPacket, PacketFlags, SubtitlePacket, VideoPacket},
};
#[cfg(any(feature = "std", feature = "alloc"))]
extern crate alloc;
#[repr(transparent)]
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct TrackIndex(usize);
impl TrackIndex {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new(index: usize) -> Self {
Self(index)
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn get(self) -> usize {
self.0
}
}
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, IsVariant)]
pub enum TrackKind {
Video,
Audio,
Subtitle,
Data,
Attachment,
#[default]
Unknown,
}
pub trait DemuxAdapter {
type CodecId: Copy + Eq + Debug;
type Video: VideoAdapter<CodecId = Self::CodecId>;
type Audio: AudioAdapter<CodecId = Self::CodecId>;
type Subtitle: SubtitleAdapter<CodecId = Self::CodecId>;
type DataExtra;
type AttachmentExtra;
type TrackExtra;
type Text: AsRef<str> + Debug;
}
pub type DemuxVideoPacket<E, D> =
VideoPacket<<<E as DemuxAdapter>::Video as VideoAdapter>::PacketExtra, D>;
pub type DemuxAudioPacket<E, D> =
AudioPacket<<<E as DemuxAdapter>::Audio as AudioAdapter>::PacketExtra, D>;
pub type DemuxSubtitlePacket<E, D> =
SubtitlePacket<<<E as DemuxAdapter>::Subtitle as SubtitleAdapter>::PacketExtra, D>;
pub type DemuxDataPacket<E, D> = DataPacket<<E as DemuxAdapter>::DataExtra, D>;
pub type DemuxAttachmentPacket<E, D> = AttachmentPacket<<E as DemuxAdapter>::AttachmentExtra, D>;
#[derive(Clone, Debug)]
pub struct DataPacket<E, D> {
pts: Option<Timestamp>,
duration: Option<Timestamp>,
flags: PacketFlags,
data: D,
extra: E,
}
impl<E, D> DataPacket<E, D> {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new(data: D, extra: E) -> Self {
Self {
pts: None,
duration: None,
flags: PacketFlags::empty(),
data,
extra,
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn pts(&self) -> Option<Timestamp> {
self.pts
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn duration(&self) -> Option<Timestamp> {
self.duration
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn flags(&self) -> PacketFlags {
self.flags
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn data(&self) -> &D {
&self.data
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn extra(&self) -> &E {
&self.extra
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn extra_mut(&mut self) -> &mut E {
&mut self.extra
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn into_data(self) -> D {
self.data
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn into_parts(self) -> (D, E) {
(self.data, self.extra)
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub const fn with_pts(mut self, v: Option<Timestamp>) -> Self {
self.pts = v;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub const fn with_duration(mut self, v: Option<Timestamp>) -> Self {
self.duration = v;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub const fn with_flags(mut self, v: PacketFlags) -> Self {
self.flags = v;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_pts(&mut self, v: Option<Timestamp>) -> &mut Self {
self.pts = v;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_duration(&mut self, v: Option<Timestamp>) -> &mut Self {
self.duration = v;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_flags(&mut self, v: PacketFlags) -> &mut Self {
self.flags = v;
self
}
}
#[derive(Clone, Debug)]
pub struct AttachmentPacket<E, D> {
flags: PacketFlags,
data: D,
extra: E,
}
impl<E, D> AttachmentPacket<E, D> {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new(data: D, extra: E) -> Self {
Self {
flags: PacketFlags::empty(),
data,
extra,
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn flags(&self) -> PacketFlags {
self.flags
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn data(&self) -> &D {
&self.data
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn extra(&self) -> &E {
&self.extra
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn extra_mut(&mut self) -> &mut E {
&mut self.extra
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn into_data(self) -> D {
self.data
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn into_parts(self) -> (D, E) {
(self.data, self.extra)
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub const fn with_flags(mut self, v: PacketFlags) -> Self {
self.flags = v;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_flags(&mut self, v: PacketFlags) -> &mut Self {
self.flags = v;
self
}
}
pub struct VideoTrackParams<E: DemuxAdapter> {
codec: E::CodecId,
width: u32,
height: u32,
pixel_format: <E::Video as VideoAdapter>::PixelFormat,
frame_rate: Option<Timebase>,
}
impl<E: DemuxAdapter> VideoTrackParams<E> {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new(
codec: E::CodecId,
width: u32,
height: u32,
pixel_format: <E::Video as VideoAdapter>::PixelFormat,
frame_rate: Option<Timebase>,
) -> Self {
Self {
codec,
width,
height,
pixel_format,
frame_rate,
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn codec(&self) -> E::CodecId {
self.codec
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn width(&self) -> u32 {
self.width
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn height(&self) -> u32 {
self.height
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn pixel_format(&self) -> &<E::Video as VideoAdapter>::PixelFormat {
&self.pixel_format
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn frame_rate(&self) -> Option<Timebase> {
self.frame_rate
}
}
impl<E: DemuxAdapter> Debug for VideoTrackParams<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("VideoTrackParams")
.field("codec", &self.codec)
.field("width", &self.width)
.field("height", &self.height)
.field("pixel_format", &self.pixel_format)
.field("frame_rate", &self.frame_rate)
.finish()
}
}
pub struct AudioTrackParams<E: DemuxAdapter> {
codec: E::CodecId,
sample_rate: u32,
channel_count: u8,
sample_format: <E::Audio as AudioAdapter>::SampleFormat,
channel_layout: <E::Audio as AudioAdapter>::ChannelLayout,
}
impl<E: DemuxAdapter> AudioTrackParams<E> {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new(
codec: E::CodecId,
sample_rate: u32,
channel_count: u8,
sample_format: <E::Audio as AudioAdapter>::SampleFormat,
channel_layout: <E::Audio as AudioAdapter>::ChannelLayout,
) -> Self {
Self {
codec,
sample_rate,
channel_count,
sample_format,
channel_layout,
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn codec(&self) -> E::CodecId {
self.codec
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn sample_rate(&self) -> u32 {
self.sample_rate
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn channel_count(&self) -> u8 {
self.channel_count
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn sample_format(&self) -> <E::Audio as AudioAdapter>::SampleFormat {
self.sample_format
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn channel_layout(&self) -> &<E::Audio as AudioAdapter>::ChannelLayout {
&self.channel_layout
}
}
impl<E: DemuxAdapter> Debug for AudioTrackParams<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("AudioTrackParams")
.field("codec", &self.codec)
.field("sample_rate", &self.sample_rate)
.field("channel_count", &self.channel_count)
.field("sample_format", &self.sample_format)
.field("channel_layout", &self.channel_layout)
.finish()
}
}
pub struct SubtitleTrackParams<E: DemuxAdapter> {
codec: E::CodecId,
}
impl<E: DemuxAdapter> SubtitleTrackParams<E> {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new(codec: E::CodecId) -> Self {
Self { codec }
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn codec(&self) -> E::CodecId {
self.codec
}
}
impl<E: DemuxAdapter> Debug for SubtitleTrackParams<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SubtitleTrackParams")
.field("codec", &self.codec)
.finish()
}
}
pub struct DataTrackParams<E: DemuxAdapter> {
codec: E::CodecId,
}
impl<E: DemuxAdapter> DataTrackParams<E> {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new(codec: E::CodecId) -> Self {
Self { codec }
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn codec(&self) -> E::CodecId {
self.codec
}
}
impl<E: DemuxAdapter> Debug for DataTrackParams<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("DataTrackParams")
.field("codec", &self.codec)
.finish()
}
}
pub struct AttachmentTrackParams<E: DemuxAdapter> {
codec: E::CodecId,
}
impl<E: DemuxAdapter> AttachmentTrackParams<E> {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new(codec: E::CodecId) -> Self {
Self { codec }
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn codec(&self) -> E::CodecId {
self.codec
}
}
impl<E: DemuxAdapter> Debug for AttachmentTrackParams<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("AttachmentTrackParams")
.field("codec", &self.codec)
.finish()
}
}
pub struct UnknownTrackParams<E: DemuxAdapter> {
codec: E::CodecId,
}
impl<E: DemuxAdapter> UnknownTrackParams<E> {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new(codec: E::CodecId) -> Self {
Self { codec }
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn codec(&self) -> E::CodecId {
self.codec
}
}
impl<E: DemuxAdapter> Debug for UnknownTrackParams<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("UnknownTrackParams")
.field("codec", &self.codec)
.finish()
}
}
#[derive(IsVariant, Unwrap, TryUnwrap)]
#[unwrap(ref, ref_mut)]
#[try_unwrap(ref, ref_mut)]
pub enum TrackParams<E: DemuxAdapter> {
Video(VideoTrackParams<E>),
Audio(AudioTrackParams<E>),
Subtitle(SubtitleTrackParams<E>),
Data(DataTrackParams<E>),
Attachment(AttachmentTrackParams<E>),
Unknown(UnknownTrackParams<E>),
}
impl<E: DemuxAdapter> TrackParams<E> {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn kind(&self) -> TrackKind {
match self {
Self::Video(_) => TrackKind::Video,
Self::Audio(_) => TrackKind::Audio,
Self::Subtitle(_) => TrackKind::Subtitle,
Self::Data(_) => TrackKind::Data,
Self::Attachment(_) => TrackKind::Attachment,
Self::Unknown(_) => TrackKind::Unknown,
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn codec(&self) -> E::CodecId {
match self {
Self::Video(p) => p.codec(),
Self::Audio(p) => p.codec(),
Self::Subtitle(p) => p.codec(),
Self::Data(p) => p.codec(),
Self::Attachment(p) => p.codec(),
Self::Unknown(p) => p.codec(),
}
}
}
impl<E: DemuxAdapter> Debug for TrackParams<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Video(p) => f.debug_tuple("Video").field(p).finish(),
Self::Audio(p) => f.debug_tuple("Audio").field(p).finish(),
Self::Subtitle(p) => f.debug_tuple("Subtitle").field(p).finish(),
Self::Data(p) => f.debug_tuple("Data").field(p).finish(),
Self::Attachment(p) => f.debug_tuple("Attachment").field(p).finish(),
Self::Unknown(p) => f.debug_tuple("Unknown").field(p).finish(),
}
}
}
pub struct TrackInfo<E: DemuxAdapter> {
timebase: Timebase,
duration: Option<Timestamp>,
params: TrackParams<E>,
filename: Option<E::Text>,
mime_type: Option<E::Text>,
extra: E::TrackExtra,
}
impl<E: DemuxAdapter> TrackInfo<E> {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new(timebase: Timebase, params: TrackParams<E>, extra: E::TrackExtra) -> Self {
Self {
timebase,
duration: None,
params,
filename: None,
mime_type: None,
extra,
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn kind(&self) -> TrackKind {
self.params.kind()
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn timebase(&self) -> Timebase {
self.timebase
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn duration(&self) -> Option<Timestamp> {
self.duration
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn params(&self) -> &TrackParams<E> {
&self.params
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn filename(&self) -> Option<&E::Text> {
self.filename.as_ref()
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn mime_type(&self) -> Option<&E::Text> {
self.mime_type.as_ref()
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn extra(&self) -> &E::TrackExtra {
&self.extra
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn extra_mut(&mut self) -> &mut E::TrackExtra {
&mut self.extra
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub const fn with_duration(mut self, v: Option<Timestamp>) -> Self {
self.duration = v;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub fn with_filename(mut self, v: Option<E::Text>) -> Self {
self.filename = v;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub fn with_mime_type(mut self, v: Option<E::Text>) -> Self {
self.mime_type = v;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_duration(&mut self, v: Option<Timestamp>) -> &mut Self {
self.duration = v;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn set_filename(&mut self, v: Option<E::Text>) -> &mut Self {
self.filename = v;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn set_mime_type(&mut self, v: Option<E::Text>) -> &mut Self {
self.mime_type = v;
self
}
}
impl<E: DemuxAdapter> Debug for TrackInfo<E>
where
E::TrackExtra: Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TrackInfo")
.field("timebase", &self.timebase)
.field("duration", &self.duration)
.field("params", &self.params)
.field("filename", &self.filename)
.field("mime_type", &self.mime_type)
.field("extra", &self.extra)
.finish()
}
}
pub struct VideoTrackPacket<E: DemuxAdapter, D> {
track: TrackIndex,
packet: DemuxVideoPacket<E, D>,
}
impl<E: DemuxAdapter, D> VideoTrackPacket<E, D> {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new(track: TrackIndex, packet: DemuxVideoPacket<E, D>) -> Self {
Self { track, packet }
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn track(&self) -> TrackIndex {
self.track
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn packet(&self) -> &DemuxVideoPacket<E, D> {
&self.packet
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn into_packet(self) -> DemuxVideoPacket<E, D> {
self.packet
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn into_parts(self) -> (TrackIndex, DemuxVideoPacket<E, D>) {
(self.track, self.packet)
}
}
impl<E, D> Clone for VideoTrackPacket<E, D>
where
E: DemuxAdapter,
D: Clone,
<E::Video as VideoAdapter>::PacketExtra: Clone,
{
fn clone(&self) -> Self {
Self {
track: self.track,
packet: self.packet.clone(),
}
}
}
impl<E, D> Debug for VideoTrackPacket<E, D>
where
E: DemuxAdapter,
D: Debug,
<E::Video as VideoAdapter>::PacketExtra: Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("VideoTrackPacket")
.field("track", &self.track)
.field("packet", &self.packet)
.finish()
}
}
pub struct AudioTrackPacket<E: DemuxAdapter, D> {
track: TrackIndex,
packet: DemuxAudioPacket<E, D>,
}
impl<E: DemuxAdapter, D> AudioTrackPacket<E, D> {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new(track: TrackIndex, packet: DemuxAudioPacket<E, D>) -> Self {
Self { track, packet }
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn track(&self) -> TrackIndex {
self.track
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn packet(&self) -> &DemuxAudioPacket<E, D> {
&self.packet
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn into_packet(self) -> DemuxAudioPacket<E, D> {
self.packet
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn into_parts(self) -> (TrackIndex, DemuxAudioPacket<E, D>) {
(self.track, self.packet)
}
}
impl<E, D> Clone for AudioTrackPacket<E, D>
where
E: DemuxAdapter,
D: Clone,
<E::Audio as AudioAdapter>::PacketExtra: Clone,
{
fn clone(&self) -> Self {
Self {
track: self.track,
packet: self.packet.clone(),
}
}
}
impl<E, D> Debug for AudioTrackPacket<E, D>
where
E: DemuxAdapter,
D: Debug,
<E::Audio as AudioAdapter>::PacketExtra: Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("AudioTrackPacket")
.field("track", &self.track)
.field("packet", &self.packet)
.finish()
}
}
pub struct SubtitleTrackPacket<E: DemuxAdapter, D> {
track: TrackIndex,
packet: DemuxSubtitlePacket<E, D>,
}
impl<E: DemuxAdapter, D> SubtitleTrackPacket<E, D> {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new(track: TrackIndex, packet: DemuxSubtitlePacket<E, D>) -> Self {
Self { track, packet }
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn track(&self) -> TrackIndex {
self.track
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn packet(&self) -> &DemuxSubtitlePacket<E, D> {
&self.packet
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn into_packet(self) -> DemuxSubtitlePacket<E, D> {
self.packet
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn into_parts(self) -> (TrackIndex, DemuxSubtitlePacket<E, D>) {
(self.track, self.packet)
}
}
impl<E, D> Clone for SubtitleTrackPacket<E, D>
where
E: DemuxAdapter,
D: Clone,
<E::Subtitle as SubtitleAdapter>::PacketExtra: Clone,
{
fn clone(&self) -> Self {
Self {
track: self.track,
packet: self.packet.clone(),
}
}
}
impl<E, D> Debug for SubtitleTrackPacket<E, D>
where
E: DemuxAdapter,
D: Debug,
<E::Subtitle as SubtitleAdapter>::PacketExtra: Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SubtitleTrackPacket")
.field("track", &self.track)
.field("packet", &self.packet)
.finish()
}
}
pub struct DataTrackPacket<E: DemuxAdapter, D> {
track: TrackIndex,
packet: DemuxDataPacket<E, D>,
}
impl<E: DemuxAdapter, D> DataTrackPacket<E, D> {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new(track: TrackIndex, packet: DemuxDataPacket<E, D>) -> Self {
Self { track, packet }
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn track(&self) -> TrackIndex {
self.track
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn packet(&self) -> &DemuxDataPacket<E, D> {
&self.packet
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn into_packet(self) -> DemuxDataPacket<E, D> {
self.packet
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn into_parts(self) -> (TrackIndex, DemuxDataPacket<E, D>) {
(self.track, self.packet)
}
}
impl<E, D> Clone for DataTrackPacket<E, D>
where
E: DemuxAdapter,
D: Clone,
E::DataExtra: Clone,
{
fn clone(&self) -> Self {
Self {
track: self.track,
packet: self.packet.clone(),
}
}
}
impl<E, D> Debug for DataTrackPacket<E, D>
where
E: DemuxAdapter,
D: Debug,
E::DataExtra: Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("DataTrackPacket")
.field("track", &self.track)
.field("packet", &self.packet)
.finish()
}
}
pub struct AttachmentTrackPacket<E: DemuxAdapter, D> {
track: TrackIndex,
packet: DemuxAttachmentPacket<E, D>,
}
impl<E: DemuxAdapter, D> AttachmentTrackPacket<E, D> {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new(track: TrackIndex, packet: DemuxAttachmentPacket<E, D>) -> Self {
Self { track, packet }
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn track(&self) -> TrackIndex {
self.track
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn packet(&self) -> &DemuxAttachmentPacket<E, D> {
&self.packet
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn into_packet(self) -> DemuxAttachmentPacket<E, D> {
self.packet
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn into_parts(self) -> (TrackIndex, DemuxAttachmentPacket<E, D>) {
(self.track, self.packet)
}
}
impl<E, D> Clone for AttachmentTrackPacket<E, D>
where
E: DemuxAdapter,
D: Clone,
E::AttachmentExtra: Clone,
{
fn clone(&self) -> Self {
Self {
track: self.track,
packet: self.packet.clone(),
}
}
}
impl<E, D> Debug for AttachmentTrackPacket<E, D>
where
E: DemuxAdapter,
D: Debug,
E::AttachmentExtra: Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("AttachmentTrackPacket")
.field("track", &self.track)
.field("packet", &self.packet)
.finish()
}
}
#[derive(IsVariant, Unwrap, TryUnwrap)]
#[unwrap(ref, ref_mut)]
#[try_unwrap(ref, ref_mut)]
pub enum DemuxedPacket<E: DemuxAdapter, D> {
Video(VideoTrackPacket<E, D>),
Audio(AudioTrackPacket<E, D>),
Subtitle(SubtitleTrackPacket<E, D>),
Data(DataTrackPacket<E, D>),
Attachment(AttachmentTrackPacket<E, D>),
}
impl<E: DemuxAdapter, D> DemuxedPacket<E, D> {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn track(&self) -> TrackIndex {
match self {
Self::Video(p) => p.track(),
Self::Audio(p) => p.track(),
Self::Subtitle(p) => p.track(),
Self::Data(p) => p.track(),
Self::Attachment(p) => p.track(),
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn kind(&self) -> TrackKind {
match self {
Self::Video(_) => TrackKind::Video,
Self::Audio(_) => TrackKind::Audio,
Self::Subtitle(_) => TrackKind::Subtitle,
Self::Data(_) => TrackKind::Data,
Self::Attachment(_) => TrackKind::Attachment,
}
}
}
impl<E, D> Clone for DemuxedPacket<E, D>
where
E: DemuxAdapter,
D: Clone,
<E::Video as VideoAdapter>::PacketExtra: Clone,
<E::Audio as AudioAdapter>::PacketExtra: Clone,
<E::Subtitle as SubtitleAdapter>::PacketExtra: Clone,
E::DataExtra: Clone,
E::AttachmentExtra: Clone,
{
fn clone(&self) -> Self {
match self {
Self::Video(p) => Self::Video(p.clone()),
Self::Audio(p) => Self::Audio(p.clone()),
Self::Subtitle(p) => Self::Subtitle(p.clone()),
Self::Data(p) => Self::Data(p.clone()),
Self::Attachment(p) => Self::Attachment(p.clone()),
}
}
}
impl<E, D> Debug for DemuxedPacket<E, D>
where
E: DemuxAdapter,
D: Debug,
<E::Video as VideoAdapter>::PacketExtra: Debug,
<E::Audio as AudioAdapter>::PacketExtra: Debug,
<E::Subtitle as SubtitleAdapter>::PacketExtra: Debug,
E::DataExtra: Debug,
E::AttachmentExtra: Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Video(p) => f.debug_tuple("Video").field(p).finish(),
Self::Audio(p) => f.debug_tuple("Audio").field(p).finish(),
Self::Subtitle(p) => f.debug_tuple("Subtitle").field(p).finish(),
Self::Data(p) => f.debug_tuple("Data").field(p).finish(),
Self::Attachment(p) => f.debug_tuple("Attachment").field(p).finish(),
}
}
}
pub trait Demuxer {
type Adapter: DemuxAdapter;
type Buffer: AsRef<[u8]>;
type Error;
fn tracks(&self) -> &[TrackInfo<Self::Adapter>];
#[cfg(any(feature = "std", feature = "alloc"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))]
fn take_tracks(&mut self) -> alloc::vec::Vec<TrackInfo<Self::Adapter>>;
fn next_packet(
&mut self,
) -> Result<Option<DemuxedPacket<Self::Adapter, Self::Buffer>>, Self::Error>;
fn seek(&mut self, target: Timestamp) -> Result<(), Self::Error>;
}
#[cfg(test)]
mod tests {
use core::num::NonZeroI32;
use super::*;
#[cfg(any(feature = "std", feature = "alloc"))]
use alloc::{format, vec, vec::Vec};
struct VLoop;
impl VideoAdapter for VLoop {
type CodecId = u32;
type PixelFormat = u32;
type PacketExtra = ();
type FrameExtra = ();
}
struct ALoop;
impl AudioAdapter for ALoop {
type CodecId = u32;
type SampleFormat = u32;
type ChannelLayout = u32;
type PacketExtra = ();
type FrameExtra = ();
}
struct SLoop;
impl SubtitleAdapter for SLoop {
type CodecId = u32;
type PacketExtra = ();
type FrameExtra = ();
}
struct Loopback;
impl DemuxAdapter for Loopback {
type CodecId = u32;
type Video = VLoop;
type Audio = ALoop;
type Subtitle = SLoop;
type DataExtra = ();
type AttachmentExtra = ();
type TrackExtra = ();
type Text = &'static str;
}
fn ms_tb() -> Timebase {
Timebase::new(1, NonZeroI32::new(1000).expect("non-zero"))
}
#[test]
fn track_index_round_trips() {
assert_eq!(TrackIndex::new(3).get(), 3);
assert_eq!(TrackIndex::default(), TrackIndex::new(0));
}
#[test]
fn the_kind_is_read_off_the_params_arm() {
let rows: [(TrackParams<Loopback>, TrackKind); 6] = [
(
TrackParams::Video(VideoTrackParams::new(1, 1920, 1080, 7, None)),
TrackKind::Video,
),
(
TrackParams::Audio(AudioTrackParams::new(2, 48_000, 2, 3, 4)),
TrackKind::Audio,
),
(
TrackParams::Subtitle(SubtitleTrackParams::new(3)),
TrackKind::Subtitle,
),
(TrackParams::Data(DataTrackParams::new(4)), TrackKind::Data),
(
TrackParams::Attachment(AttachmentTrackParams::new(5)),
TrackKind::Attachment,
),
(
TrackParams::Unknown(UnknownTrackParams::new(0)),
TrackKind::Unknown,
),
];
for (params, expected) in rows {
let codec = params.codec();
let info = TrackInfo::<Loopback>::new(ms_tb(), params, ());
assert_eq!(info.kind(), expected);
assert_eq!(info.params().kind(), expected);
assert_eq!(info.params().codec(), codec);
}
}
#[test]
fn attachment_identity_lives_on_the_track() {
let info = TrackInfo::<Loopback>::new(
ms_tb(),
TrackParams::Attachment(AttachmentTrackParams::new(9)),
(),
)
.with_filename(Some("Arial.ttf"))
.with_mime_type(Some("application/x-truetype-font"));
assert_eq!(info.filename().copied(), Some("Arial.ttf"));
assert_eq!(
info.mime_type().copied(),
Some("application/x-truetype-font")
);
assert_eq!(info.duration(), None);
}
#[test]
fn data_packet_follows_the_house_shape() {
let pts = Timestamp::new(1500, ms_tb());
let p: DataPacket<(), &[u8]> = DataPacket::new(&b"klv"[..], ())
.with_pts(Some(pts))
.with_duration(Some(Timestamp::new(40, ms_tb())))
.with_flags(PacketFlags::KEY);
assert_eq!(p.pts(), Some(pts));
assert_eq!(p.duration(), Some(Timestamp::new(40, ms_tb())));
assert!(p.flags().contains(PacketFlags::KEY));
let (data, _) = p.into_parts();
assert_eq!(data, b"klv");
}
#[cfg(any(feature = "std", feature = "alloc"))]
#[test]
fn data_packet_clone_matches_the_original() {
let pts = Timestamp::new(1500, ms_tb());
let original: DataPacket<(), &[u8]> = DataPacket::new(&b"klv"[..], ())
.with_pts(Some(pts))
.with_duration(Some(Timestamp::new(40, ms_tb())))
.with_flags(PacketFlags::KEY);
let cloned = original.clone();
assert_eq!(cloned.pts(), original.pts());
assert_eq!(cloned.duration(), original.duration());
assert_eq!(cloned.flags(), original.flags());
assert_eq!(cloned.data(), original.data());
assert!(format!("{cloned:?}").contains("DataPacket"));
}
#[test]
fn an_attachment_packet_has_no_timestamp_seat() {
let mut p: AttachmentPacket<(), &[u8]> = AttachmentPacket::new(&b"\x00\x01TTF"[..], ());
assert_eq!(p.flags(), PacketFlags::empty());
p.set_flags(PacketFlags::CORRUPT);
assert!(p.flags().contains(PacketFlags::CORRUPT));
assert_eq!(p.data(), &&b"\x00\x01TTF"[..]);
}
#[cfg(any(feature = "std", feature = "alloc"))]
#[test]
fn attachment_packet_clone_matches_the_original() {
let mut original: AttachmentPacket<(), &[u8]> = AttachmentPacket::new(&b"\x00\x01TTF"[..], ());
original.set_flags(PacketFlags::CORRUPT);
let cloned = original.clone();
assert_eq!(cloned.flags(), original.flags());
assert_eq!(cloned.data(), original.data());
assert!(format!("{cloned:?}").contains("AttachmentPacket"));
}
#[test]
fn the_envelope_carries_the_coordinate_and_the_kind() {
let track = TrackIndex::new(2);
let packets: [(DemuxedPacket<Loopback, &[u8]>, TrackKind); 5] = [
(
DemuxedPacket::Video(VideoTrackPacket::new(track, VideoPacket::new(&[][..], ()))),
TrackKind::Video,
),
(
DemuxedPacket::Audio(AudioTrackPacket::new(track, AudioPacket::new(&[][..], ()))),
TrackKind::Audio,
),
(
DemuxedPacket::Subtitle(SubtitleTrackPacket::new(
track,
SubtitlePacket::new(&[][..], ()),
)),
TrackKind::Subtitle,
),
(
DemuxedPacket::Data(DataTrackPacket::new(track, DataPacket::new(&[][..], ()))),
TrackKind::Data,
),
(
DemuxedPacket::Attachment(AttachmentTrackPacket::new(
track,
AttachmentPacket::new(&[][..], ()),
)),
TrackKind::Attachment,
),
];
for (packet, expected) in packets {
assert_eq!(packet.track(), track);
assert_eq!(packet.kind(), expected);
}
}
#[cfg(any(feature = "std", feature = "alloc"))]
#[test]
fn demuxed_packet_clone_matches_the_original() {
let track = TrackIndex::new(2);
let packets: [DemuxedPacket<Loopback, &[u8]>; 5] = [
DemuxedPacket::Video(VideoTrackPacket::new(track, VideoPacket::new(&[][..], ()))),
DemuxedPacket::Audio(AudioTrackPacket::new(track, AudioPacket::new(&[][..], ()))),
DemuxedPacket::Subtitle(SubtitleTrackPacket::new(
track,
SubtitlePacket::new(&[][..], ()),
)),
DemuxedPacket::Data(DataTrackPacket::new(track, DataPacket::new(&[][..], ()))),
DemuxedPacket::Attachment(AttachmentTrackPacket::new(
track,
AttachmentPacket::new(&[][..], ()),
)),
];
for packet in packets {
let cloned = packet.clone();
assert_eq!(cloned.track(), packet.track());
assert_eq!(cloned.kind(), packet.kind());
assert!(!format!("{cloned:?}").is_empty());
}
}
#[test]
fn demuxed_packet_carries_the_derived_accessor_face() {
let track = TrackIndex::new(0);
let video: DemuxedPacket<Loopback, &[u8]> =
DemuxedPacket::Video(VideoTrackPacket::new(track, VideoPacket::new(&[][..], ())));
assert!(video.is_video());
assert!(!video.is_audio());
assert_eq!(video.unwrap_video_ref().track(), track);
assert!(video.try_unwrap_audio().is_err());
}
#[cfg(any(feature = "std", feature = "alloc"))]
struct LoopDemuxer {
tracks: Vec<TrackInfo<Loopback>>,
drained: bool,
}
#[cfg(any(feature = "std", feature = "alloc"))]
#[derive(Debug)]
struct LoopError;
#[cfg(any(feature = "std", feature = "alloc"))]
impl Demuxer for LoopDemuxer {
type Adapter = Loopback;
type Buffer = &'static [u8];
type Error = LoopError;
fn tracks(&self) -> &[TrackInfo<Loopback>] {
&self.tracks
}
fn take_tracks(&mut self) -> Vec<TrackInfo<Loopback>> {
core::mem::take(&mut self.tracks)
}
fn next_packet(&mut self) -> Result<Option<DemuxedPacket<Loopback, &'static [u8]>>, LoopError> {
if self.drained {
return Ok(None);
}
self.drained = true;
Ok(Some(DemuxedPacket::Audio(AudioTrackPacket::new(
TrackIndex::new(0),
AudioPacket::new(&[][..], ()),
))))
}
fn seek(&mut self, _target: Timestamp) -> Result<(), LoopError> {
self.drained = false;
Ok(())
}
}
#[cfg(any(feature = "std", feature = "alloc"))]
#[test]
fn the_session_face_is_implementable_and_none_means_eof() {
fn _accepts<D: Demuxer>() {}
_accepts::<LoopDemuxer>();
let mut d = LoopDemuxer {
tracks: vec![TrackInfo::new(
ms_tb(),
TrackParams::Audio(AudioTrackParams::new(1, 48_000, 2, 0, 0)),
(),
)],
drained: false,
};
assert_eq!(d.tracks().len(), 1);
assert_eq!(d.tracks()[0].kind(), TrackKind::Audio);
assert!(matches!(
d.next_packet().expect("pull"),
Some(DemuxedPacket::Audio(_))
));
assert!(d.next_packet().expect("pull").is_none());
assert!(d.next_packet().expect("pull").is_none(), "EOF is sticky");
d.seek(Timestamp::new(0, ms_tb())).expect("seek");
assert!(d.next_packet().expect("pull").is_some());
}
#[cfg(any(feature = "std", feature = "alloc"))]
#[test]
fn take_tracks_moves_every_row_out_once_and_leaves_the_table_empty() {
let mut d = LoopDemuxer {
tracks: vec![
TrackInfo::new(
ms_tb(),
TrackParams::Audio(AudioTrackParams::new(1, 48_000, 2, 0, 0)),
(),
),
TrackInfo::new(
ms_tb(),
TrackParams::Subtitle(SubtitleTrackParams::new(2)),
(),
),
],
drained: false,
};
let expected: Vec<TrackKind> = d.tracks().iter().map(|t| t.kind()).collect();
let taken = d.take_tracks();
assert_eq!(
taken.iter().map(|t| t.kind()).collect::<Vec<_>>(),
expected,
"every row comes out, in table order",
);
assert!(
d.tracks().is_empty(),
"the table is empty after the first take"
);
assert!(d.take_tracks().is_empty(), "a second take yields nothing");
}
}