aboutsummaryrefslogtreecommitdiff
path: root/src/subtitle_extraction/embedded.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/subtitle_extraction/embedded.rs')
-rw-r--r--src/subtitle_extraction/embedded.rs118
1 files changed, 0 insertions, 118 deletions
diff --git a/src/subtitle_extraction/embedded.rs b/src/subtitle_extraction/embedded.rs
deleted file mode 100644
index 0ba6178..0000000
--- a/src/subtitle_extraction/embedded.rs
+++ /dev/null
@@ -1,118 +0,0 @@
-use std::sync::mpsc;
-
-use anyhow::Context;
-
-use crate::subtitle_extraction::*;
-
-pub fn extract_embedded_subtitles(
- // stream index to use when storing extracted subtitles, this index already
- // has to be in TRACKS when this function is called!
- stream_ix: StreamIndex,
- context: ffmpeg::codec::Context,
- time_base: ffmpeg::Rational,
- packet_rx: mpsc::Receiver<ffmpeg::Packet>,
- sender: ComponentSender<SubtitleExtractor>,
-) -> anyhow::Result<()> {
- let mut decoder = context
- .decoder()
- .subtitle()
- .with_context(|| format!("error creating subtitle decoder for stream {}", stream_ix))?;
-
- while let Ok(packet) = packet_rx.recv() {
- let mut subtitle = ffmpeg::Subtitle::new();
- match decoder.decode(&packet, &mut subtitle) {
- Ok(true) => {
- if let Some(cue) = parse_subtitle(&subtitle, &packet, time_base) {
- SUBTITLE_TRACKS
- .write()
- .get_mut(&stream_ix)
- .unwrap()
- .cues
- .push(cue.clone());
- sender
- .output(SubtitleExtractorOutput::NewCue(stream_ix, cue))
- .unwrap();
- } else {
- log::error!("error parsing subtitle at pts {:?}", packet.pts())
- }
- }
- Ok(false) => {
- log::debug!("got empty (?) subtitle, not sure if this should ever happen");
- }
- Err(e) => {
- log::error!("error decoding subtitle: {:?}", e)
- }
- }
- }
-
- Ok(())
-}
-
-fn parse_subtitle(
- subtitle: &ffmpeg::Subtitle,
- packet: &ffmpeg::Packet,
- time_base: Rational,
-) -> Option<SubtitleCue> {
- let pts_to_clock_time = |pts: i64| {
- let nseconds: i64 =
- (pts * time_base.numerator() as i64 * 1_000_000_000) / time_base.denominator() as i64;
- gst::ClockTime::from_nseconds(nseconds as u64)
- };
-
- let text = subtitle
- .rects()
- .into_iter()
- .map(|rect| match rect {
- ffmpeg::subtitle::Rect::Text(text) => text.get().to_string(),
- ffmpeg::subtitle::Rect::Ass(ass) => {
- extract_dialogue_text(ass.get()).unwrap_or(String::new())
- }
- _ => String::new(),
- })
- .collect::<Vec<String>>()
- .join("\n— ");
-
- let start = pts_to_clock_time(packet.pts()?);
- let end = pts_to_clock_time(packet.pts()? + packet.duration());
-
- Some(SubtitleCue { start, end, text })
-}
-
-fn extract_dialogue_text(dialogue_line: &str) -> Option<String> {
- // ASS dialogue format: ReadOrder,Layer,Style,Name,MarginL,MarginR,MarginV,Effect,Text
- // we need the 9th field (Text), so split on comma but only take first 9 splits
- // see also https://github.com/FFmpeg/FFmpeg/blob/a700f0f72d1f073e5adcfbb16f4633850b0ef51c/libavcodec/ass_split.c#L433
- let text = dialogue_line.splitn(9, ',').last()?;
-
- // remove ASS override codes (formatting tags) like {\b1}, {\i1}, {\c&Hffffff&}, etc.
- let mut result = String::new();
- let mut in_tag = false;
- let mut char_iter = text.chars().peekable();
-
- while let Some(c) = char_iter.next() {
- if c == '{' && char_iter.peek() == Some(&'\\') {
- in_tag = true;
- } else if c == '}' {
- in_tag = false;
- } else if !in_tag {
- // process line breaks and hard spaces
- if c == '\\' {
- match char_iter.peek() {
- Some(&'N') => {
- char_iter.next();
- result.push('\n');
- }
- Some(&'n') | Some(&'h') => {
- char_iter.next();
- result.push(' ');
- }
- _ => result.push(c),
- }
- } else {
- result.push(c);
- }
- }
- }
-
- Some(result)
-}