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