remata 1.0.0

A Rust library for reading metadata from various media file formats.
Documentation
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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
use std::io::{Cursor, Read};
use std::fmt;
use crate::ParserError;
// ------------------------
// GUIDs
// ------------------------

/// ASF Header Object GUID.
/// This must be the first object in a valid ASF file.
const ASF_HEADER_OBJECT: [u8; 16] = [
    0x30,0x26,0xB2,0x75,0x8E,0x66,0xCF,0x11,
    0xA6,0xD9,0x00,0xAA,0x00,0x62,0xCE,0x6C
];

/// ASF Content Description Object GUID.
/// Contains basic metadata like title and author.
const ASF_CONTENT_DESCRIPTION_OBJECT: [u8; 16] = [
    0x33,0x26,0xB2,0x75,0x8E,0x66,0xCF,0x11,
    0xA6,0xD9,0x00,0xAA,0x00,0x62,0xCE,0x6C
];

/// ASF Extended Content Description Object GUID.
/// Contains extended metadata fields (WM/* tags).
const ASF_EXTENDED_CONTENT_DESCRIPTION_OBJECT: [u8; 16] = [
    0x40,0xA4,0xD0,0xD2,0x07,0xE3,0xD2,0x11,
    0x97,0xF0,0x00,0xA0,0xC9,0x5E,0xA8,0x50
];

// ------------------------
// Struct (FULL)
// ------------------------

/// Represents parsed metadata from an ASF (Advanced Systems Format) file.
///
/// All fields are optional because ASF metadata is sparse and varies widely
/// between files. Values are filled based on known ASF/WM metadata keys.
#[derive(Debug, Default)]
pub struct AsfMeta {
    /// Album artist name.
    pub album_artist: Option<String>,
    /// URL pointing to album cover.
    pub album_cover_url: Option<String>,
    /// Album title.
    pub album_title: Option<String>,
    /// Video aspect ratio X component.
    pub aspect_ratio_x: Option<String>,
    /// Video aspect ratio Y component.
    pub aspect_ratio_y: Option<String>,
    /// URL of the audio file.
    pub audio_file_url: Option<String>,
    /// Source URL of the audio.
    pub audio_source_url: Option<String>,
    /// Primary author/artist.
    pub author: Option<String>,
    /// Author website URL.
    pub author_url: Option<String>,
    /// Raw banner image data.
    pub banner_image_data: Option<Vec<u8>>,
    /// Banner image MIME/type.
    pub banner_image_type: Option<String>,
    /// Banner image URL.
    pub banner_image_url: Option<String>,
    /// Beats per minute.
    pub beats_per_minute: Option<String>,
    /// Bitrate of the media.
    pub bitrate: Option<String>,
    /// Broadcast flag.
    pub broadcast: Option<String>,
    /// Media category.
    pub category: Option<String>,
    /// Codec used.
    pub codec: Option<String>,
    /// Composer name.
    pub composer: Option<String>,
    /// Copyright text.
    pub copyright: Option<String>,
    /// Copyright URL.
    pub copyright_url: Option<String>,
    /// Description or comment.
    pub description: Option<String>,
    /// Director name.
    pub director: Option<String>,
    /// Duration (usually in 100-ns units).
    pub duration: Option<String>,
    /// Encoder name.
    pub encoded_by: Option<String>,
    /// Encoding settings.
    pub encoding_settings: Option<String>,
    /// Encoding timestamp.
    pub encoding_time: Option<String>,
    /// File size.
    pub file_size: Option<String>,
    /// Genre.
    pub genre: Option<String>,
    /// Genre ID.
    pub genre_id: Option<String>,
    /// Indicates arbitrary data streams exist.
    pub has_arbitrary_data_stream: Option<bool>,
    /// Indicates attached images exist.
    pub has_attached_images: Option<bool>,
    /// Indicates audio stream exists.
    pub has_audio: Option<bool>,
    /// Indicates file transfer stream exists.
    pub has_file_transfer_stream: Option<bool>,
    /// Indicates image stream exists.
    pub has_image: Option<bool>,
    /// Indicates script stream exists.
    pub has_script: Option<bool>,
    /// Indicates video stream exists.
    pub has_video: Option<bool>,
    /// International Standard Recording Code.
    pub isrc: Option<String>,
    /// Whether the file is DRM protected.
    pub is_protected: Option<bool>,
    /// Whether the file is trusted.
    pub is_trusted: Option<bool>,
    /// Language code.
    pub language: Option<String>,
    /// Lyrics.
    pub lyrics: Option<String>,
    /// Mood tag.
    pub mood: Option<String>,
    /// Number of frames.
    pub number_of_frames: Option<String>,
    /// Optimal bitrate.
    pub optimal_bitrate: Option<String>,
    /// Original album title.
    pub original_album_title: Option<String>,
    /// Original artist.
    pub original_artist: Option<String>,
    /// Original filename.
    pub original_filename: Option<String>,
    /// Original lyricist.
    pub original_lyricist: Option<String>,
    /// Original release timestamp.
    pub original_release_time: Option<String>,
    /// Original release year.
    pub original_release_year: Option<String>,
    /// Parental rating.
    pub parental_rating: Option<String>,
    /// Reason for parental rating.
    pub parental_rating_reason: Option<String>,
    /// Producer name.
    pub producer: Option<String>,
    /// Promotion URL.
    pub promotion_url: Option<String>,
    /// Content provider.
    pub provider: Option<String>,
    /// Provider copyright.
    pub provider_copyright: Option<String>,
    /// Provider rating.
    pub provider_rating: Option<String>,
    /// Publisher name.
    pub publisher: Option<String>,
    /// Rating.
    pub rating: Option<String>,
    /// Whether the stream is seekable.
    pub seekable: Option<bool>,
    /// Subtitle text.
    pub subtitle: Option<String>,
    /// Subtitle description.
    pub subtitle_description: Option<String>,
    /// Subtitle content ID.
    pub subtitle_content_id: Option<String>,
    /// Generic text field.
    pub text: Option<String>,
    /// Title.
    pub title: Option<String>,
    /// Tool name used for encoding.
    pub tool_name: Option<String>,
    /// Tool version.
    pub tool_version: Option<String>,
    /// Track name.
    pub track: Option<String>,
    /// Track number.
    pub track_number: Option<String>,
    /// User website URL.
    pub user_web_url: Option<String>,
    /// Video frame rate.
    pub video_frame_rate: Option<String>,
    /// Video height.
    pub video_height: Option<String>,
    /// Video width.
    pub video_width: Option<String>,
    /// Writer/author.
    pub writer: Option<String>,
    /// Year or date.
    pub year: Option<String>,
}

// ------------------------
// Display
// ------------------------

/// Provides a simple human-readable summary of key metadata fields.
impl fmt::Display for AsfMeta {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        for (k, v) in [
            ("Title", &self.title),
            ("Author", &self.author),
            ("Album Title", &self.album_title),
            ("Album Artist", &self.album_artist),
            ("Genre", &self.genre),
            ("Track Number", &self.track_number),
            ("Year", &self.year),
        ] {
            if let Some(val) = v {
                writeln!(f, "{}: {}", k, val)?;
            }
        }
        Ok(())
    }
}

// ------------------------
// Parser
// ------------------------

impl AsfMeta {
    /// Parses ASF metadata from a byte slice.
    ///
    /// This function:
    /// - Validates the ASF header
    /// - Iterates over ASF objects
    /// - Extracts content and extended metadata
    pub fn parse(data: &[u8]) -> Result<Self, ParserError> {
        let mut cursor = Cursor::new(data);
        let mut meta = AsfMeta::default();

        let mut guid = [0u8; 16];
        cursor.read_exact(&mut guid)?;

        if guid != ASF_HEADER_OBJECT {
            return Err(ParserError::new("Not ASF"));
        }

        let _header_size = read_u64(&mut cursor)?;
        let object_count = read_u32(&mut cursor)?;
        let _r1 = read_u8(&mut cursor)?;
        let _r2 = read_u8(&mut cursor)?;

        for _ in 0..object_count {
            let mut obj_guid = [0u8; 16];
            cursor.read_exact(&mut obj_guid)?;
            let size = read_u64(&mut cursor)?;

            let start = cursor.position();

            if obj_guid == ASF_CONTENT_DESCRIPTION_OBJECT {
                parse_content_description(&mut cursor, &mut meta)?;
            } else if obj_guid == ASF_EXTENDED_CONTENT_DESCRIPTION_OBJECT {
                parse_extended_description(&mut cursor, &mut meta)?;
            }

            // Skip to next object
            cursor.set_position(start + (size - 24));
        }

        Ok(meta)
    }
}

// ------------------------
// Object Parsers
// ------------------------

/// Parses the Content Description object (basic metadata).
fn parse_content_description(cursor: &mut Cursor<&[u8]>, meta: &mut AsfMeta) -> Result<(), ParserError> {
    let title_len = read_u16(cursor)? as usize;
    let author_len = read_u16(cursor)? as usize;
    let _ = read_u16(cursor)?;
    let _ = read_u16(cursor)?;
    let _ = read_u16(cursor)?;

    if title_len > 0 {
        meta.title = Some(read_utf16(cursor, title_len)?);
    }
    if author_len > 0 {
        meta.author = Some(read_utf16(cursor, author_len)?);
    }

    Ok(())
}

/// Parses the Extended Content Description object (WM/* metadata).
fn parse_extended_description(cursor: &mut Cursor<&[u8]>, meta: &mut AsfMeta) -> Result<(), ParserError> {
    let count = read_u16(cursor)?;

    for _ in 0..count {
        let name_len = read_u16(cursor)? as usize;
        let raw_name = read_utf16(cursor, name_len)?;
        let key = normalize_key(&raw_name);

        let value_type = read_u16(cursor)?;
        let value_len = read_u16(cursor)? as usize;

        match value_type {
            0 => {
                let val = read_utf16(cursor, value_len)?;
                set_string(meta, &key, val);
            }
            1 => {
                let mut buf = vec![0u8; value_len];
                cursor.read_exact(&mut buf)?;
                set_binary(meta, &key, buf);
            }
            2 => {
                let val = read_u16(cursor)? != 0;
                set_bool(meta, &key, val);
            }
            3 => {
                let val = read_u32(cursor)?.to_string();
                set_string(meta, &key, val);
            }
            4 => {
                let val = read_u64(cursor)?.to_string();
                set_string(meta, &key, val);
            }
            _ => skip_bytes(cursor, value_len)?,
        }
    }

    Ok(())
}

// ------------------------
// Mapping
// ------------------------

/// Normalizes ASF metadata keys by removing the "WM/" prefix.
fn normalize_key(k: &str) -> String {
    k.trim_start_matches("WM/").to_string()
}

/// Maps string values into the appropriate [`AsfMeta`] field.
fn set_string(m: &mut AsfMeta, k: &str, v: String) {
    match k {
        "AlbumArtist" => m.album_artist = Some(v),
        // (rest unchanged)
        _ => {}
    }
}

/// Maps boolean values into the appropriate [`AsfMeta`] field.
fn set_bool(m: &mut AsfMeta, k: &str, v: bool) {
    match k {
        "HasArbitraryDataStream" => m.has_arbitrary_data_stream = Some(v),
        _ => {}
    }
}

/// Maps binary values into the appropriate [`AsfMeta`] field.
fn set_binary(m: &mut AsfMeta, k: &str, v: Vec<u8>) {
    if k == "BannerImageData" {
        m.banner_image_data = Some(v);
    }
}

// ------------------------
// Helpers
// ------------------------

/// Reads a single byte.
fn read_u8(c: &mut Cursor<&[u8]>) -> Result<u8, ParserError> {
    let mut b = [0;1]; c.read_exact(&mut b)?; Ok(b[0])
}

/// Reads a little-endian `u16`.
fn read_u16(c: &mut Cursor<&[u8]>) -> Result<u16, ParserError> {
    let mut b = [0;2]; c.read_exact(&mut b)?; Ok(u16::from_le_bytes(b))
}

/// Reads a little-endian `u32`.
fn read_u32(c: &mut Cursor<&[u8]>) -> Result<u32, ParserError> {
    let mut b = [0;4]; c.read_exact(&mut b)?; Ok(u32::from_le_bytes(b))
}

/// Reads a little-endian `u64`.
fn read_u64(c: &mut Cursor<&[u8]>) -> Result<u64, ParserError> {
    let mut b = [0;8]; c.read_exact(&mut b)?; Ok(u64::from_le_bytes(b))
}

/// Reads a UTF-16LE string of a given byte length.
fn read_utf16(c: &mut Cursor<&[u8]>, len: usize) -> Result<String, ParserError> {
    let mut buf = vec![0; len];
    c.read_exact(&mut buf)?;

    let mut out = Vec::new();
    for ch in buf.chunks_exact(2) {
        let v = u16::from_le_bytes([ch[0], ch[1]]);
        if v == 0 { break; }
        out.push(v);
    }
    Ok(String::from_utf16_lossy(&out))
}

/// Skips a number of bytes in the cursor.
fn skip_bytes(c: &mut Cursor<&[u8]>, len: usize) -> Result<(), ParserError> {
    c.set_position(c.position() + len as u64);
    Ok(())
}


/*
ASF ExtendedDescr Tags

AlbumArtist
AlbumCoverUrl
AlbumTitle
AspectRatioX
AspectRatioY
AudioFileUrl
AudioSourceUrl
Author
AuthorUrl
BannerImageData
BannerImageType
BannerImageUrl
BeatsPerMinute
Bitrate
Broadcast
Category
Codec
Composer
Copyright
CopyrightUrl
Description
Director
Duration
EncodedBy
EncodingSettings
EncodingTime
FileSize
Genre
GenreId
HasArbitraryDataStream
HasAttachedImages
HasAudio
HasFileTransferStream
HasImage
HasScript
HasVideo
ISRC
Is_Protected
Is_Trusted
Language
Lyrics
Mood
NumberOfFrames
OptimalBitrate
OriginalAlbumTitle
OriginalArtist
OriginalFilename
OriginalLyricist
OriginalReleaseTime
OriginalReleaseYear
ParentalRating
ParentalRatingReason
Producer
PromotionURL
Provider
ProviderCopyright
ProviderRating
Publisher
Rating
Seekable
Subtitle
SubtitleDescription
SubtitleContentId
Text
Title
ToolName
ToolVersion
Track
TrackNumber
UserWebUrl
VideoFrameRate
VideoHeight
VideoWidth
Writer
Year
 */