use std::option::Option;
use ffmpeg_next::{codec::Parameters, ffi::avsubtitle_free};
use mediadecode::{
Timebase, decoder::SubtitleDecoder, frame::SubtitleFrame, packet::SubtitlePacket,
};
use crate::{
Error, Ffmpeg, FfmpegBuffer, boundary,
convert::{self, ConvertError},
decoder::build_codec_context,
extras::{SubtitleFrameExtra, SubtitlePacketExtra},
};
struct ScratchSubtitle {
inner: ffmpeg_next::Subtitle,
}
impl ScratchSubtitle {
fn new() -> Self {
Self {
inner: ffmpeg_next::Subtitle::new(),
}
}
fn clear(&mut self) {
unsafe { avsubtitle_free(self.inner.as_mut_ptr()) };
}
}
impl Drop for ScratchSubtitle {
fn drop(&mut self) {
self.clear();
}
}
pub struct FfmpegSubtitleStreamDecoder {
decoder: ffmpeg_next::decoder::Subtitle,
scratch: ScratchSubtitle,
pending: Option<SubtitleFrame<SubtitleFrameExtra, FfmpegBuffer>>,
time_base: Timebase,
}
impl FfmpegSubtitleStreamDecoder {
pub fn open(parameters: Parameters, time_base: Timebase) -> Result<Self, SubtitleDecodeError> {
let ctx = build_codec_context(¶meters).map_err(SubtitleDecodeError::Decode)?;
let decoder = ctx
.decoder()
.subtitle()
.map_err(|e| SubtitleDecodeError::Decode(Error::Ffmpeg(e)))?;
Ok(Self {
decoder,
scratch: ScratchSubtitle::new(),
pending: None,
time_base,
})
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn time_base(&self) -> Timebase {
self.time_base
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn inner(&self) -> &ffmpeg_next::decoder::Subtitle {
&self.decoder
}
}
impl SubtitleDecoder for FfmpegSubtitleStreamDecoder {
type Adapter = Ffmpeg;
type Buffer = FfmpegBuffer;
type Error = SubtitleDecodeError;
fn send_packet(
&mut self,
packet: &SubtitlePacket<SubtitlePacketExtra, Self::Buffer>,
) -> Result<(), Self::Error> {
if self.pending.is_some() {
return Err(SubtitleDecodeError::FramePending);
}
let av_pkt = boundary::ffmpeg_packet_from_subtitle_packet(packet)
.map_err(|e| SubtitleDecodeError::Decode(Error::Ffmpeg(e)))?;
self.scratch.clear();
let got = self
.decoder
.decode(&av_pkt, &mut self.scratch.inner)
.map_err(|e| SubtitleDecodeError::Decode(Error::Ffmpeg(e)))?;
if got {
let result = unsafe {
convert::av_subtitle_to_subtitle_frame(self.scratch.inner.as_ptr(), self.time_base)
};
match result {
Ok(frame) => self.pending = Some(frame),
Err(e) => {
self.scratch.clear();
return Err(SubtitleDecodeError::Convert(e));
}
}
}
Ok(())
}
fn receive_frame(
&mut self,
dst: &mut SubtitleFrame<SubtitleFrameExtra, Self::Buffer>,
) -> Result<(), Self::Error> {
match self.pending.take() {
Some(frame) => {
*dst = frame;
Ok(())
}
None => Err(SubtitleDecodeError::NoFrameReady),
}
}
fn send_eof(&mut self) -> Result<(), Self::Error> {
Ok(())
}
fn flush(&mut self) -> Result<(), Self::Error> {
self.decoder.flush();
self.pending = None;
self.scratch.clear();
Ok(())
}
}
#[derive(thiserror::Error, Debug)]
pub enum SubtitleDecodeError {
#[error(transparent)]
Decode(#[from] Error),
#[error(transparent)]
Convert(#[from] ConvertError),
#[error("no subtitle frame ready; send another packet first")]
NoFrameReady,
#[error("subtitle frame already pending; drain via receive_frame first")]
FramePending,
}