1 #pragma once
2 
3 #include <QByteArray>
4 #include <QColor>
5 #include <QDataStream>
6 #include <QList>
7 #include <memory>
8 
9 #include "track/cueinfo.h"
10 #include "track/taglib/trackmetadata_file.h"
11 #include "util/types.h"
12 
13 namespace mixxx {
14 
15 class SeratoMarkersEntry;
16 typedef std::shared_ptr<SeratoMarkersEntry> SeratoMarkersEntryPointer;
17 
18 class SeratoMarkersEntry {
19   public:
20     /// We didn't encounter other type IDs as those listed here (e.g. "2") yet.
21     /// Apparently these are not used.
22     enum class TypeId : quint8 {
23         /// Used for unset cue points
24         Unknown = 0,
25         /// Used for set cue points
26         Cue = 1,
27         /// Used for saved loops (both set and unset ones)
28         Loop = 3,
29     };
30 
SeratoMarkersEntry(bool hasStartPosition,int startPosition,bool hasEndPosition,int endPosition,RgbColor color,quint8 type,bool isLocked)31     SeratoMarkersEntry(
32             bool hasStartPosition,
33             int startPosition,
34             bool hasEndPosition,
35             int endPosition,
36             RgbColor color,
37             quint8 type,
38             bool isLocked)
39             : m_color(color),
40               m_hasStartPosition(hasStartPosition),
41               m_hasEndPosition(hasEndPosition),
42               m_isLocked(isLocked),
43               m_startPosition(startPosition),
44               m_endPosition(endPosition),
45               m_type(type) {
46     }
47     ~SeratoMarkersEntry() = default;
48 
49     QByteArray dumpID3() const;
50     QByteArray dumpMP4() const;
51 
52     static SeratoMarkersEntryPointer parseID3(const QByteArray& data);
53     static SeratoMarkersEntryPointer parseMP4(const QByteArray& data);
54 
type()55     quint8 type() const {
56         return m_type;
57     }
58 
typeId()59     SeratoMarkersEntry::TypeId typeId() const {
60         SeratoMarkersEntry::TypeId typeId = SeratoMarkersEntry::TypeId::Unknown;
61         switch (static_cast<SeratoMarkersEntry::TypeId>(type())) {
62         case SeratoMarkersEntry::TypeId::Unknown:
63             // This seems to be an unset Hotcue (i.e. without a position)
64         case SeratoMarkersEntry::TypeId::Cue:
65             typeId = SeratoMarkersEntry::TypeId::Cue;
66             break;
67         case SeratoMarkersEntry::TypeId::Loop:
68             typeId = SeratoMarkersEntry::TypeId::Loop;
69             break;
70         }
71         return typeId;
72     }
73 
getColor()74     RgbColor getColor() const {
75         return m_color;
76     }
77 
isLocked()78     bool isLocked() const {
79         return m_isLocked;
80     }
81 
hasStartPosition()82     bool hasStartPosition() const {
83         return m_hasStartPosition;
84     }
85 
getStartPosition()86     quint32 getStartPosition() const {
87         return m_startPosition;
88     }
89 
hasEndPosition()90     bool hasEndPosition() const {
91         return m_hasEndPosition;
92     }
93 
getEndPosition()94     quint32 getEndPosition() const {
95         return m_endPosition;
96     }
97 
98   private:
99     RgbColor m_color;
100     bool m_hasStartPosition;
101     bool m_hasEndPosition;
102     ;
103     bool m_isLocked;
104     quint32 m_startPosition;
105     quint32 m_endPosition;
106     quint8 m_type;
107 };
108 
109 inline bool operator==(const SeratoMarkersEntry& lhs, const SeratoMarkersEntry& rhs) {
110     return (lhs.dumpID3() == rhs.dumpID3());
111 }
112 
113 inline bool operator!=(const SeratoMarkersEntry& lhs, const SeratoMarkersEntry& rhs) {
114     return !(lhs == rhs);
115 }
116 
117 inline QDebug operator<<(QDebug dbg, const SeratoMarkersEntry& arg) {
118     dbg << "type =" << arg.type();
119     if (arg.hasStartPosition()) {
120         dbg << "startPosition =" << arg.getStartPosition();
121     }
122     if (arg.hasEndPosition()) {
123         dbg << "endPosition =" << arg.getEndPosition();
124     }
125     return dbg << "color =" << arg.getColor()
126                << "isLocked =" << arg.isLocked();
127 }
128 
129 /// DTO for storing information from the SeratoMarkers_ tags used by the Serato
130 /// DJ Pro software.
131 ///
132 /// This class includes functions for formatting and parsing SeratoMarkers_
133 /// metadata according to the specification:
134 /// https://github.com/Holzhaus/serato-tags/blob/master/docs/serato_markers_.md
135 class SeratoMarkers final {
136   public:
137     SeratoMarkers() = default;
138 
139     /// Parse a binary Serato representation of the "Markers_" data from a
140     /// `QByteArray` and write the results to the `SeratoMarkers` instance.
141     /// The `fileType` parameter determines the exact format of the data being
142     /// used.
143     static bool parse(
144             SeratoMarkers* seratoMarkers,
145             const QByteArray& data,
146             taglib::FileType fileType);
147 
148     /// Create a binary Serato representation of the "Markers_" data suitable
149     /// for `fileType` and dump it into a `QByteArray`. The content of that
150     /// byte array can be used for round-trip tests or written to the
151     /// appropriate tag to make it accessible to Serato.
152     QByteArray dump(taglib::FileType fileType) const;
153 
isEmpty()154     bool isEmpty() const {
155         return m_entries.isEmpty() && !m_trackColor;
156     }
157 
getEntries()158     const QList<SeratoMarkersEntryPointer>& getEntries() const {
159         return m_entries;
160     }
setEntries(const QList<SeratoMarkersEntryPointer> & entries)161     void setEntries(const QList<SeratoMarkersEntryPointer>& entries) {
162         m_entries = entries;
163     }
164 
getTrackColor()165     RgbColor::optional_t getTrackColor() const {
166         return m_trackColor;
167     }
setTrackColor(RgbColor::optional_t color)168     void setTrackColor(RgbColor::optional_t color) {
169         m_trackColor = color;
170     }
171 
172     QList<CueInfo> getCues() const;
173     void setCues(const QList<CueInfo>& cueInfos);
174 
175   private:
176     static bool parseID3(
177             SeratoMarkers* seratoMarkers,
178             const QByteArray& data);
179     static bool parseMP4(
180             SeratoMarkers* seratoMarkers,
181             const QByteArray& base64EncodedData);
182 
183     QByteArray dumpID3() const;
184     QByteArray dumpMP4() const;
185 
186     QList<SeratoMarkersEntryPointer> m_entries;
187     RgbColor::optional_t m_trackColor;
188 };
189 
190 inline bool operator==(const SeratoMarkers& lhs, const SeratoMarkers& rhs) {
191     return (lhs.getEntries() == rhs.getEntries());
192 }
193 
194 inline bool operator!=(const SeratoMarkers& lhs, const SeratoMarkers& rhs) {
195     return !(lhs == rhs);
196 }
197 
198 inline QDebug operator<<(QDebug dbg, const SeratoMarkers& arg) {
199     return dbg << "entries =" << arg.getEntries().length();
200 }
201 
202 } // namespace mixxx
203 
204 Q_DECLARE_TYPEINFO(mixxx::SeratoMarkers, Q_MOVABLE_TYPE);
205 Q_DECLARE_METATYPE(mixxx::SeratoMarkers)
206