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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
|
/*
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 anyhow::{Context, Result};
use bincode::{Decode, Encode};
use ebml_struct::{
ids::*,
matroska::*,
read::{EbmlReadExt, TagRead},
};
use jellybase::{
assetfed::AssetInner,
cache::{cache_file, cache_memory},
common::Asset,
};
use log::{info, warn};
use std::{
fs::File,
io::{BufReader, ErrorKind, Read, Write},
path::Path,
sync::Arc,
};
pub use ebml_struct::matroska::TrackEntry as MatroskaTrackEntry;
#[derive(Debug, Encode, Decode, Clone)]
pub struct MatroskaMetadata {
pub info: Option<Info>,
pub tracks: Option<Tracks>,
pub cover: Option<Asset>,
pub chapters: Option<Chapters>,
pub tags: Option<Tags>,
pub infojson: Option<Vec<u8>>,
}
pub fn checked_matroska_metadata(path: &Path) -> Result<Arc<Option<MatroskaMetadata>>> {
cache_memory("mkmeta-check-v1", path, || {
let mut magic = [0; 4];
File::open(path)?.read_exact(&mut magic).ok();
if !matches!(magic, [0x1A, 0x45, 0xDF, 0xA3]) {
return Ok(None);
}
Ok(Some((*matroska_metadata(path)?).clone()))
})
}
pub fn matroska_metadata(path: &Path) -> Result<Arc<MatroskaMetadata>> {
cache_memory("mkmeta-v3", path, || {
info!("reading {path:?}");
let mut file = BufReader::new(File::open(path)?);
let mut file = file.by_ref().take(u64::MAX);
let (x, mut ebml) = file.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) = file.read_tag()?;
assert_eq!(x, EL_SEGMENT);
let mut info = None;
let mut infojson = None;
let mut tracks = None;
let mut cover = None;
let mut chapters = None;
let mut tags = None;
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 => info = Some(Info::read(&mut seg).context("info")?),
EL_TRACKS => tracks = Some(Tracks::read(&mut seg).context("tracks")?),
EL_CHAPTERS => chapters = Some(Chapters::read(&mut seg).context("chapters")?),
EL_TAGS => tags = Some(Tags::read(&mut seg).context("tags")?),
EL_ATTACHMENTS => {
let attachments = Attachments::read(&mut seg).context("attachments")?;
for f in attachments.files {
match f.name.as_str() {
"info.json" => {
infojson = Some(f.data);
}
"cover.webp" | "cover.png" | "cover.jpg" | "cover.jpeg"
| "cover.avif" => {
cover = Some(
AssetInner::Cache(cache_file(
"att-cover",
path,
move |mut file| {
file.write_all(&f.data)?;
Ok(())
},
)?)
.ser(),
)
}
_ => (),
}
}
}
EL_VOID | EL_CRC32 | EL_CUES | EL_SEEKHEAD => {
seg.consume()?;
}
EL_CLUSTER => {
break;
}
id => {
warn!("unknown top-level element {id:x}");
seg.consume()?;
}
}
}
Ok(MatroskaMetadata {
chapters,
cover,
info,
infojson,
tags,
tracks,
})
})
}
|