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
//! This library provides access to metadata and libraries written by the [Serato
//! DJ](https://serato.com/dj) software.
//!
//! # Metadata
//!
//! Serato's formats are pretty complex and store redundant data which might lead to situation
//! where that data contradicts each other (e.g. if the data was edited by hand).
//!
//! ## Parsing
//!
//! To make it straightforward to access the metadata, this library provides
//! the [`TagContainer` struct](`tag::TagContainer`), which provides access to all important
//! attributes and implements the same conflict resolution strategies that the Serato DJ software
//! uses.
//!
//! ```
//! use triseratops::tag::{TagContainer, TagFormat};
//!
//! fn parse_and_print_cues(markers_data: &[u8], markers2_data: &[u8]) {
//!     let mut tags = TagContainer::new();
//!     tags.parse_markers(markers_data, TagFormat::ID3).expect("Failed to parse Markers data!");
//!     tags.parse_markers2(markers2_data, TagFormat::ID3).expect("Failed to parse Markers2 data!");
//!
//!     for cue in tags.cues() {
//!         println!("{:?}", cue);
//!     }
//! }
//! ```
//!
//! If you'd rather parse the tag data representation yourself, you can do that by using the
//! individual tag structs directly:
//!
//! ```
//! use triseratops::tag::{Markers, format::id3::ID3Tag};
//!
//! fn parse_and_print_markers(data: &[u8]) {
//!     let markers = Markers::parse_id3(data).expect("Failed to parse data!");
//!     println!("{:?}", markers);
//! }
//! ```
//!
//! **Note:** This library does *not* provide means to read the metadata from music files directly, so you need to
//! use other libraries (e.g. [`id3`](https://lib.rs/crates/id3) to do that. You can check the
//! `examples/` directory for some toy examples.
//!
//! ## Serialization
//!
//! This library aims to provide support for lossless data roundtripping, i.e. parsing and then
//! serializing data results in the exact same bytes as the original input (except for FLAC/MP4
//! data, see last bullet point in the *Caveats* section below).
//!
//! ```
//! use std::io::Write;
//! use triseratops::tag::{Markers, format::id3::ID3Tag};
//!
//! fn write(mut writer: impl Write, markers: &Markers) {
//!     let bytes_written = markers.write_id3(&mut writer).expect("Failed to serialize data!");
//!     println!("Wrote {} bytes", bytes_written);
//! }
//! ```
//!
//! ## Supported File Types
//!
//! Support for the following tags has already been implemented:
//!
//! | Tag          | ID3     | FLAC    | MP4/M4A | Ogg Vorbis | XML (e.g. AAC) | *Description*
//! | ------------ | ------- | ------- | ------- | ---------- | -------------- | ----------
//! | `Analysis`   | **Yes** | **Yes** | **Yes** | **Yes**    | No             | Serato Analysis version
//! | `Autotags`   | **Yes** | **Yes** | **Yes** | No         | No             | BPM and Gain values
//! | `BeatGrid`   | **Yes** | **Yes** | **Yes** | No         | No             | Beatgrid Markers
//! | `Markers_`   | **Yes** | *n/a*   | **Yes** | No         | No             | Hotcues, Saved Loops, etc.
//! | `Markers2`   | **Yes** | **Yes** | **Yes** | **Yes**    | No             | Hotcues, Saved Loops, etc.
//! | `Offsets_`   | No      | No      | No      | No         | No             | ?
//! | `Overview`   | **Yes** | **Yes** | **Yes** | No         | No             | Overview Waveform data
//! | `RelVol`     | *n/a*   | Partial | Partial | *n/a*      | No             | Relative Volume Adjument data (?)
//! | `VideoAssoc` | *n/a*   | Partial | Partial | *n/a*      | No             | Video Association data (?)
//!
//! ## Caveats
//!
//! - Most Ogg tags are currently not supported. Their format is completely different from
//!   the other tag types and need to be reverse-engineered first.
//! - The `Serato Offsets_` tag haven't been reverse engineed yet, and no support has been
//!   implemented.
//! - The `Serato RelVolAd` and the `Serato VidAssoc` tags haven't been reverse engineed yet, but
//!   preliminary support has been added. For now, they just return a tag version and a byte vector.
//! - AAC files (among others) do not store metadata in tags, and use XML files in the
//! `_Serato_/Metadata` directory instead. No support has been added yet.
//! - The cue colors stored in the metadata are *not* the same as displayed in Serato DJ Pro.
//!   Instead, they uses the color palette from Serato DJ Into. Serato then maps them to a new
//!   color palette. Support for converting between the two is currently missing.
//! - The track colors stores in the metadata are those from the color picker, not those shown in
//!   the library table. Support for converting between the two is currently missing.
//! - Unfortunately, full lossless roundtrips  aren't possible for FLAC and MP4, because the data
//!   is wrapped in base64-encoding where the decoded last byte seems to be random junk that change
//!   when writing tags from Serato DJ even if no actual changes were made (possibly an
//!   out-of-bounds read or uninitialized data in Serato DJ). For parsing and using this in Serato
//!   that doesn't make a difference, but the roundtrip tests will ignore those last two bytes.
//!
//! # Library
//!
//! Parsing the Serato library (e.g. the `database V2` file in the `_Serato_` directory) is also
//! possible, but since this feature is still under development, the API is *not* stable yet and
//! might change in the future.

#![warn(unsafe_code)]
#![cfg_attr(not(debug_assertions), deny(warnings))]
#![deny(rust_2018_idioms)]
#![deny(rust_2021_compatibility)]
#![deny(missing_debug_implementations)]
// TODO: Add missing docs
//#![deny(missing_docs)]
#![deny(rustdoc::broken_intra_doc_links)]
#![deny(clippy::all)]
#![deny(clippy::explicit_deref_methods)]
#![deny(clippy::explicit_into_iter_loop)]
#![deny(clippy::explicit_iter_loop)]
#![deny(clippy::must_use_candidate)]
#![cfg_attr(not(test), deny(clippy::panic_in_result_fn))]
#![cfg_attr(not(debug_assertions), deny(clippy::used_underscore_binding))]

pub mod error;
pub mod library;
pub mod tag;
pub(in crate) mod util;