aboutsummaryrefslogtreecommitdiff
path: root/remuxer/src/matroska_to_webm.rs
blob: b77062b6e478a66a0ca4e3172e6c19f538650a42 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
/*
    This file is part of jellything (https://codeberg.org/metamuffin/jellything)
    which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
    Copyright (C) 2025 metamuffin <metamuffin.org>
*/
use crate::ebml_track_entry;
use anyhow::Context;
use ebml_struct::{
    ids::*,
    matroska::{Cluster, Ebml, Info, Tracks},
    read::{EbmlReadExt, TagRead},
    write::TagWrite,
};
use jellymatroska::{read::EbmlReader, write::EbmlWriter, Master, MatroskaTag};
use log::warn;
use std::io::{BufReader, BufWriter, ErrorKind, Read, Seek, Write};

pub fn matroska_to_webm(
    input: impl Read + Seek + 'static,
    output: impl Write,
) -> anyhow::Result<()> {
    let mut output = EbmlWriter::new(BufWriter::new(output), 0);
    let mut input = EbmlReader::new(BufReader::new(input));

    Ebml {
        ebml_version: 1,
        ebml_read_version: 1,
        ebml_max_id_length: 4,
        ebml_max_size_length: 8,
        doc_type: "webm".to_string(),
        doc_type_version: 4,
        doc_type_read_version: 2,
        doc_type_extensions: vec![],
    }
    .write(&mut output)?;
    output.write_tag(&MatroskaTag::Segment(Master::Start))?;

    let (x, mut ebml) = input.read_tag()?;
    assert_eq!(x, EL_EBML);
    let ebml = Ebml::read(&mut ebml).unwrap();
    assert!(ebml.doc_type == "matroska" || ebml.doc_type == "webm");
    let (x, mut segment) = input.read_tag()?;
    assert_eq!(x, EL_SEGMENT);

    loop {
        let (x, mut seg) = match segment.read_tag() {
            Ok(o) => o,
            Err(e) if e.kind() == ErrorKind::UnexpectedEof => break,
            Err(e) => return Err(e.into()),
        };
        match x {
            EL_INFO => {
                let info = Info::read(&mut seg).context("info")?;
                output.write_tag(&{
                    MatroskaTag::Info(Master::Collected(vec![
                        MatroskaTag::TimestampScale(info.timestamp_scale),
                        MatroskaTag::Duration(info.duration.unwrap_or_default()),
                        MatroskaTag::Title(info.title.unwrap_or_default()),
                        MatroskaTag::MuxingApp("jellyremux".to_string()),
                        MatroskaTag::WritingApp("jellything".to_string()),
                    ]))
                })?;
            }
            EL_TRACKS => {
                let tracks = Tracks::read(&mut seg).context("tracks")?;
                output.write_tag(&MatroskaTag::Tracks(Master::Collected(
                    tracks
                        .entries
                        .into_iter()
                        .map(|t| ebml_track_entry(t.track_number, &t))
                        .collect(),
                )))?;
            }
            EL_VOID | EL_CRC32 | EL_CUES | EL_SEEKHEAD | EL_ATTACHMENTS | EL_TAGS => {
                seg.consume()?;
            }
            EL_CLUSTER => {
                let cluster = Cluster::read(&mut seg).context("cluster")?;
                // TODO mixing both ebml libraries :)))
                cluster.write(&mut output)?;
            }
            id => {
                warn!("unknown top-level element {id:x}");
                seg.consume()?;
            }
        }
    }
    Ok(())
}