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;