-
Notifications
You must be signed in to change notification settings - Fork 0
/
cues.py
executable file
·110 lines (89 loc) · 4.8 KB
/
cues.py
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
#!/usr/bin/env python3
limit=''
#rating='library.rating > 2 and'
rating=''
# remove the limit='limit 5' line in order to process your whole library, else it will only process the first five tracks
#limit='limit 5'
import sqlite3
import mutagen
from mutagen.id3 import ID3, error, delete, ID3FileType
import struct
import textwrap
import base64
import argparse
def try_to_open(filename):
try:
audio = mutagen.File(filename)
return audio
except mutagen.MutagenError:
print(f"File {filename} not found!")
return None
def determine_filetype(inputfile):
audio = try_to_open(inputfile)
if audio:
return audio.mime[0]
def mixxx_cuepos_to_ms(cuepos,samplerate,channels):
return int(float(cuepos) / (int(samplerate) * int(channels)) * 1000)
def serato_cues_for_track(con, path, samplerate, channels):
countcur = con.cursor()
serato_cues = []
for cue in countcur.execute(f'select cues.position, cues.hotcue, cues.label from track_locations inner join cues on cues.track_id = track_locations.id inner join library on library.id = track_locations.id where track_locations.location = (?) and cues.type = 1 and cues.hotcue >= 0 order by track_locations.location', (path,)):
serato_cues.append((mixxx_cuepos_to_ms(cue[0], samplerate, channels), cue[1], cue[2]))
return serato_cues
def gen_serato_markers(con, file, samplerate, channels):
# https://github.com/Holzhaus/serato-tags/blob/master/docs/serato_markers2.md
cues = serato_cues_for_track(con, file, samplerate, channels)
# print(cues)
markers = b'\x01\x01' + b'COLOR\x00' + b'\x00\x00\x00\x04' + b'\x00\xff\xff\xff'
for cue in cues:
markers += b'CUE\x00' + struct.pack('>i', len(cue[2]) + 13) # label length can be zero or more bytes, the rest is always 13 bytes
markers += b'\x00' # Magic value
markers += struct.pack('B', cue[1]) # Cue index
markers += struct.pack('>i', cue[0]) # Position in track of this cue in milliseconds
markers += b'\x00' # Magic value
markers += b'\xcc\x00\x00' # Red colour for everything
markers += b'\x00\x00' # Magic value
markers += cue[2].encode('utf8') # Insert any label for this cue
markers += b'\x00' # Magic value
markers += b'BPMLOCK\x00' + b'\x00\x00\x00\x01' + b'\x00'
markers += b'\x00'
return markers
def write_flac(con, flacfile, samplerate, channels):
audio = try_to_open(flacfile)
markers = b'application/octet-stream' + b'\x00\x00' + b'Serato Markers2' + b'\x00\x01\x01'
markers += textwrap.fill(base64.b64encode(gen_serato_markers(con, flacfile, samplerate, channels)).decode('ascii'), width=72).encode('utf8')
markers += b'\x00' * 470
audio["SERATO_MARKERS_V2"] = textwrap.fill(base64.b64encode(markers).decode('ascii'), width=72)
audio.save()
def write_id3(con, id3file, samplerate, channels):
audio = try_to_open(id3file)
markers = b'\x01\x01'
markers += textwrap.fill(base64.b64encode(gen_serato_markers(con, id3file, samplerate, channels)).decode('ascii'), width=72).encode('utf8')
markers += b'\x00' * 470
audio['GEOB:Serato Markers2'] = mutagen.id3.GEOB(
encoding=0,
mime='application/octet-stream',
desc='Serato Markers2',
data=markers,
)
# Serato would prefer this older format if it already existed in the file
if 'GEOB:Serato Markers_' in audio:
del(audio['GEOB:Serato Markers_'])
audio.save()
parser = argparse.ArgumentParser()
parser.add_argument('mixxx_database', metavar='INFILE', help="path to the mixxxdb.sqlite database file")
args = parser.parse_args()
con = sqlite3.connect(args.mixxx_database)
cur = con.cursor()
#cur.execute(f'select track_locations.location, library.rating, library.artist, library.title, library.datetime_added, library.comment, library.album, library.samplerate, library.channels from track_locations inner join library on library.id = track_locations.id where library.rating > 2 and mixxx_deleted = 0 and UPPER(track_locations.location) like "%.MP3" {limit}')
cur.execute(f'select track_locations.location, library.rating, library.artist, library.title, library.datetime_added, library.comment, library.album, library.samplerate, library.channels from track_locations inner join library on library.id = track_locations.id where {rating} mixxx_deleted = 0 {limit}')
tracks = cur.fetchall()
for track in tracks:
print(track[0])
filetype = determine_filetype(track[0])
if filetype == 'audio/flac':
write_flac(con, track[0], track[7], track[8])
elif filetype == 'audio/mp3':
write_id3(con, track[0], track[7], track[8])
else:
print(f'Sorry, {filetype} files are not supported')