1use super::color::Color;
16use super::format::{enveloped, id3, mp4, Tag};
17use super::generic::{Position, Version};
18use super::serato32;
19use super::util::{take_color, take_version, write_color, write_version};
20use crate::error::Error;
21use crate::util::{Res, NULL};
22use nom::error::ParseError;
23use std::io;
24use std::io::Cursor;
25
26#[derive(Debug, Clone)]
28pub struct Marker {
29 pub start_position: Option<Position>,
31
32 pub end_position: Option<Position>,
34
35 pub color: Color,
39
40 pub marker_type: MarkerType,
42
43 pub is_locked: bool,
47}
48
49#[derive(Debug, Clone)]
69pub struct Markers {
70 pub version: Version,
72
73 pub entries: Vec<Marker>,
75
76 pub track_color: Color,
78}
79
80impl Markers {
81 #[must_use]
82 pub fn cues(&self) -> Vec<(u8, &Marker)> {
83 let mut index: u8 = 0;
84 let mut cues = Vec::new();
85 for marker in &self.entries {
86 if marker.marker_type != MarkerType::Invalid && marker.marker_type != MarkerType::Cue {
87 continue;
88 }
89
90 cues.push((index, marker));
91 index += 1;
92 }
93 cues
94 }
95
96 #[must_use]
97 pub fn loops(&self) -> Vec<(u8, &Marker)> {
98 let mut index: u8 = 0;
99 let mut loops = Vec::new();
100 for marker in &self.entries {
101 if marker.marker_type != MarkerType::Loop {
102 continue;
103 }
104
105 loops.push((index, marker));
106 index += 1;
107 }
108 loops
109 }
110
111 #[must_use]
112 pub fn track_color(&self) -> Color {
113 self.track_color
114 }
115}
116
117impl Tag for Markers {
118 const NAME: &'static str = "Serato Markers_";
119
120 fn parse(input: &[u8]) -> Result<Self, Error> {
121 let (_, autotags) = nom::combinator::all_consuming(take_markers)(input)?;
122 Ok(autotags)
123 }
124
125 fn write(&self, writer: &mut impl io::Write) -> Result<usize, Error> {
126 write_markers(writer, self)
127 }
128}
129
130impl id3::ID3Tag for Markers {}
131impl enveloped::EnvelopedTag for Markers {}
132impl mp4::MP4Tag for Markers {
133 const MP4_ATOM_FREEFORM_NAME: &'static str = "markers";
134
135 fn parse_mp4(input: &[u8]) -> Result<Self, Error> {
136 let (_, encoded) = nom::combinator::all_consuming(
137 super::format::enveloped::take_base64_with_newline,
138 )(input)?;
139 let content = super::format::enveloped::envelope_decode_with_name(encoded, Self::NAME)?;
140 let (_, markers) = nom::combinator::all_consuming(take_markers_mp4)(&content)?;
141 Ok(markers)
142 }
143
144 fn write_mp4(&self, writer: &mut impl io::Write) -> Result<usize, Error> {
145 let mut buffer = Cursor::new(vec![]);
146 write_markers_mp4(&mut buffer, self)?;
147 let plain_data = &buffer.get_ref()[..];
148 enveloped::envelope_encode_with_name(writer, plain_data, Self::NAME)
149 }
150}
151
152#[derive(Debug, Clone, Copy, PartialEq, Eq)]
154pub enum MarkerType {
155 Invalid,
161 Cue,
165 Loop,
169}
170
171pub fn take_bool(input: &[u8]) -> Res<&[u8], bool> {
187 let (input, position_prefix) = nom::number::complete::u8(input)?;
188 match position_prefix {
189 0x00 => Ok((input, false)),
190 0x01 => Ok((input, true)),
191 _ => Err(nom::Err::Error(nom::error::VerboseError::from_error_kind(
192 input,
193 nom::error::ErrorKind::Tag,
194 ))),
195 }
196}
197
198fn take_has_position(input: &[u8]) -> Res<&[u8], bool> {
210 let (input, position_prefix) = nom::number::complete::u8(input)?;
211 match position_prefix {
212 0x00 => Ok((input, true)),
213 0x7F => Ok((input, false)),
214 _ => Err(nom::Err::Error(nom::error::VerboseError::from_error_kind(
215 input,
216 nom::error::ErrorKind::Tag,
217 ))),
218 }
219}
220
221#[test]
222fn test_take_has_position() {
223 assert_eq!(take_has_position(&[0x00]), Ok((&[][..], true)));
224 assert_eq!(take_has_position(&[0x7F]), Ok((&[][..], false)));
225 assert_eq!(take_has_position(&[0x00, 0x05]), Ok((&[0x05][..], true)));
226 assert!(take_has_position(&[0xAB, 0x00, 0x01]).is_err());
227 assert!(take_has_position(&[]).is_err());
228}
229
230pub fn take_position(input: &[u8]) -> Res<&[u8], Option<Position>> {
235 if input.len() < 5 {
236 return Err(nom::Err::Error(nom::error::VerboseError::from_error_kind(
237 input,
238 nom::error::ErrorKind::Digit,
239 )));
240 }
241 let (input, has_position) = nom::error::context("take has_position", take_has_position)(input)?;
242 if has_position {
243 let (input, millis) = serato32::take_u32(input)?;
244 let position = Position { millis };
245 Ok((input, Some(position)))
246 } else {
247 let (input, _) = nom::bytes::complete::tag(b"\x7f\x7f\x7f\x7f")(input)?;
248 Ok((input, None))
249 }
250}
251
252#[test]
253fn test_take_position() {
254 assert_eq!(
255 take_position(&[0x00, 0x00, 0x00, 0x00, 0x00]),
256 Ok((&[][..], Some(Default::default())))
257 );
258 assert_eq!(
259 take_position(&[0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x00]),
260 Ok((&[0x00][..], None))
261 );
262}
263
264fn take_marker_type(input: &[u8]) -> Res<&[u8], MarkerType> {
266 let (next_input, position_prefix) = nom::number::complete::u8(input)?;
267 match position_prefix {
268 0x00 => Ok((next_input, MarkerType::Invalid)),
269 0x01 => Ok((next_input, MarkerType::Cue)),
270 0x03 => Ok((next_input, MarkerType::Loop)),
271 _ => Err(nom::Err::Error(nom::error::VerboseError::from_error_kind(
272 input,
273 nom::error::ErrorKind::Tag,
274 ))),
275 }
276}
277
278#[test]
279fn test_take_marker_type() {
280 assert_eq!(
281 take_marker_type(&[0x00]),
282 Ok((&[][..], MarkerType::Invalid))
283 );
284 assert_eq!(
285 take_marker_type(&[0x03, 0x01]),
286 Ok((&[0x01][..], MarkerType::Loop))
287 );
288 assert!(take_marker_type(&[0xAB]).is_err());
289}
290
291fn take_marker(input: &[u8]) -> Res<&[u8], Marker> {
293 let (input, start_position) =
294 nom::error::context("marker start position", take_position)(input)?;
295 let (input, end_position) = nom::error::context("marker end position", take_position)(input)?;
296 let (input, _) = nom::error::context(
297 "marker unknown bytes",
298 nom::bytes::complete::tag(b"\x00\x7F\x7F\x7F\x7F\x7F"),
299 )(input)?;
300 let (input, color) = nom::error::context("marker color", serato32::take_color)(input)?;
301 let (input, marker_type) = nom::error::context("marker type", take_marker_type)(input)?;
302 let (input, is_locked) = nom::error::context("marker locked state", take_bool)(input)?;
303 Ok((
304 input,
305 Marker {
306 start_position,
307 end_position,
308 color,
309 marker_type,
310 is_locked,
311 },
312 ))
313}
314
315fn take_markers(input: &[u8]) -> Res<&[u8], Markers> {
317 let (input, version) = take_version(input)?;
318 let (input, entries) =
319 nom::multi::length_count(nom::number::complete::be_u32, take_marker)(input)?;
320 let (input, track_color) = nom::combinator::all_consuming(serato32::take_color)(input)?;
321
322 let markers = Markers {
323 version,
324 entries,
325 track_color,
326 };
327 Ok((input, markers))
328}
329
330fn take_marker_mp4(input: &[u8]) -> Res<&[u8], Marker> {
332 let (input, start_position_raw) =
333 nom::error::context("marker start position", nom::number::complete::be_u32)(input)?;
334 let (input, end_position_raw) =
335 nom::error::context("marker end position", nom::number::complete::be_u32)(input)?;
336 let (input, _) =
337 nom::error::context("marker unknown bytes", nom::bytes::complete::take(6usize))(input)?;
338 let (input, color) = nom::error::context("marker color", take_color)(input)?;
339 let (input, marker_type) = nom::error::context("marker type", take_marker_type)(input)?;
340 let (input, is_locked) = nom::error::context("marker locked state", take_bool)(input)?;
341
342 let start_position = if start_position_raw != 0xFFFFFFFF {
343 Some(Position {
344 millis: start_position_raw,
345 })
346 } else {
347 None
348 };
349 let end_position = if end_position_raw != 0xFFFFFFFF && marker_type == MarkerType::Loop {
350 Some(Position {
351 millis: end_position_raw,
352 })
353 } else {
354 None
355 };
356
357 Ok((
358 input,
359 Marker {
360 start_position,
361 end_position,
362 color,
363 marker_type,
364 is_locked,
365 },
366 ))
367}
368
369fn take_markers_mp4(input: &[u8]) -> Res<&[u8], Markers> {
371 let (input, version) = take_version(input)?;
372 let (input, entries) =
373 nom::multi::length_count(nom::number::complete::be_u32, take_marker_mp4)(input)?;
374 let (input, _) = nom::bytes::complete::tag(b"\0")(input)?;
375 let (input, track_color) = nom::combinator::all_consuming(take_color)(input)?;
376
377 let markers = Markers {
378 version,
379 entries,
380 track_color,
381 };
382 Ok((input, markers))
383}
384
385fn write_position(writer: &mut impl io::Write, position: Option<Position>) -> Result<usize, Error> {
386 match position {
387 Some(Position { millis }) => {
388 let mut bytes_written = writer.write(NULL)?;
389 bytes_written += serato32::write_u32(writer, millis)?;
390 Ok(bytes_written)
391 }
392 None => Ok(writer.write(b"\x7F\x7F\x7F\x7F\x7F")?),
393 }
394}
395
396fn write_position_mp4(
397 writer: &mut impl io::Write,
398 position: Option<Position>,
399) -> Result<usize, Error> {
400 let data = match position {
402 Some(Position { millis }) => millis.to_be_bytes(),
403 None => *b"\xFF\xFF\xFF\xFF",
404 };
405 Ok(writer.write(&data)?)
406}
407
408fn write_marker_type(writer: &mut impl io::Write, marker_type: MarkerType) -> Result<usize, Error> {
409 let byte: u8 = match marker_type {
410 MarkerType::Invalid => 0x00,
411 MarkerType::Cue => 0x01,
412 MarkerType::Loop => 0x03,
413 };
414 Ok(writer.write(&[byte])?)
415}
416
417fn write_bool(writer: &mut impl io::Write, value: bool) -> Result<usize, Error> {
418 let byte: u8 = match value {
419 true => 0x01,
420 false => 0x00,
421 };
422 Ok(writer.write(&[byte])?)
423}
424
425fn write_marker(writer: &mut impl io::Write, marker: &Marker) -> Result<usize, Error> {
426 let &Marker {
427 start_position,
428 end_position,
429 color,
430 marker_type,
431 is_locked,
432 } = marker;
433 let mut bytes_written = write_position(writer, start_position)?;
434 bytes_written += write_position(writer, end_position)?;
435 bytes_written += writer.write(b"\x00\x7F\x7F\x7F\x7F\x7F")?;
436 bytes_written += serato32::write_color(writer, color)?;
437 bytes_written += write_marker_type(writer, marker_type)?;
438 bytes_written += write_bool(writer, is_locked)?;
439 Ok(bytes_written)
440}
441
442fn write_marker_mp4(writer: &mut impl io::Write, marker: &Marker) -> Result<usize, Error> {
443 let &Marker {
444 start_position,
445 end_position,
446 color,
447 marker_type,
448 is_locked,
449 } = marker;
450 let mut bytes_written = write_position_mp4(writer, start_position)?;
451 bytes_written += write_position_mp4(writer, end_position)?;
452 bytes_written += writer.write(b"\x00\xFF\xFF\xFF\xFF\x00")?;
453 bytes_written += write_color(writer, color)?;
454 bytes_written += write_marker_type(writer, marker_type)?;
455 bytes_written += write_bool(writer, is_locked)?;
456 Ok(bytes_written)
457}
458
459pub fn write_markers(writer: &mut impl io::Write, markers: &Markers) -> Result<usize, Error> {
460 let Markers {
461 version,
462 entries,
463 track_color,
464 } = markers;
465 let mut bytes_written = write_version(writer, *version)?;
466 let num_markers = markers.entries.len() as u32;
467 bytes_written += writer.write(&num_markers.to_be_bytes())?;
468 for marker in entries {
469 bytes_written += write_marker(writer, marker)?;
470 }
471 bytes_written += serato32::write_color(writer, *track_color)?;
472 Ok(bytes_written)
473}
474
475pub fn write_markers_mp4(writer: &mut impl io::Write, markers: &Markers) -> Result<usize, Error> {
476 let mut bytes_written = write_version(writer, markers.version)?;
477 let num_markers = markers.entries.len() as u32;
478 bytes_written += writer.write(&num_markers.to_be_bytes())?;
479 for marker in &markers.entries {
480 bytes_written += write_marker_mp4(writer, marker)?;
481 }
482 bytes_written += writer.write(NULL)?;
483 bytes_written += write_color(writer, markers.track_color)?;
484 Ok(bytes_written)
485}