├── AudioTransform.py ├── CacheUtils.py ├── Hashprint.py ├── README.md ├── ResultAnalysis.py ├── artist_index.json ├── cleanDatabase.py ├── downloadMusic.py ├── extendQuery.py ├── music_annotation.db ├── music_indexed.db ├── runHashprint.py ├── testMap.py ├── tests.sh └── title_index.json /AudioTransform.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import librosa as la 3 | import CacheUtils as cu 4 | import random 5 | 6 | STANDARD_SAMPLE_RATE = 22050 7 | 8 | class CQT_LIBROSA: 9 | 10 | def __init__(self, sr=STANDARD_SAMPLE_RATE, nbin=96, hopLength=96, 11 | binPerOctave=12, downsample=3, groupSize=1): 12 | 13 | self.sr = sr 14 | self.nbin = nbin 15 | self.hopLength = hopLength 16 | self.binPerOctave = binPerOctave 17 | self.downsample = downsample 18 | self.groupSize = groupSize 19 | 20 | 21 | def fun(self, filename): 22 | y, sr2 = la.load(filename, sr=self.sr) 23 | cqt = la.core.cqt(y, n_bins=self.nbin, 24 | bins_per_octave=self.binPerOctave,real=False) 25 | cqt_log = np.log(1 + 1000000 * cqt) 26 | print("[CQT_LIBROSA] final output spec size:", np.shape(cqt_log)) 27 | return cqt_log 28 | 29 | 30 | 31 | class CQT_LIBROSA_NOLOG: 32 | 33 | def __init__(self, sr=STANDARD_SAMPLE_RATE, nbin=96, hopLength=96, 34 | binPerOctave=12, downsample=3, groupSize=1): 35 | 36 | self.sr = sr 37 | self.nbin = nbin 38 | self.hopLength = hopLength 39 | self.binPerOctave = binPerOctave 40 | self.downsample = downsample 41 | self.groupSize = groupSize 42 | 43 | 44 | def fun(self, filename): 45 | y, sr2 = la.load(filename, sr=self.sr) 46 | cqt = la.core.cqt(y, n_bins=self.nbin, 47 | bins_per_octave=self.binPerOctave, real=False) 48 | print("[CQT_LIBROSA] final output spec size:", np.shape(cqt)) 49 | return cqt 50 | 51 | 52 | 53 | class CQT_TJ: 54 | 55 | def __init__(self, sr=44100, nbin=121, hopLength=96, 56 | binPerOctave=24, downsample=3, groupSize=1): 57 | 58 | self.sr = sr 59 | self.nbin = nbin 60 | self.hopLength = hopLength 61 | self.binPerOctave = binPerOctave 62 | self.downsample = downsample 63 | self.groupSize = groupSize 64 | 65 | def fun(self, filename): 66 | 67 | try: 68 | y, sr2 = la.load(filename, sr=self.sr, mono=False) 69 | y = la.resample(np.sum(y,0), 70 | orig_sr=self.sr, target_sr=STANDARD_SAMPLE_RATE) 71 | cqt = la.core.cqt(y,bins_per_octave=self.binPerOctave, 72 | n_bins=self.nbin, sr=STANDARD_SAMPLE_RATE, 73 | hop_length=self.hopLength, real=False) 74 | except: 75 | y, sr2 = la.load(filename, sr=STANDARD_SAMPLE_RATE) 76 | cqt = la.core.cqt(y,bins_per_octave=self.binPerOctave, 77 | n_bins=self.nbin, sr=STANDARD_SAMPLE_RATE, 78 | hop_length=self.hopLength, real=False) 79 | 80 | nBin = np.size(cqt,0) 81 | nChunk = np.size(cqt,1) // (self.downsample * self.groupSize) 82 | cqt = np.abs(cqt[:,:self.downsample * self.groupSize * nChunk]) 83 | cqt = np.reshape(cqt, (nBin, self.downsample, nChunk * self.groupSize)) 84 | cqt = np.mean(cqt, 1) 85 | cqt = np.reshape(cqt, (nBin, nChunk * self.groupSize)) 86 | cqt = np.reshape(cqt, (nBin * self.groupSize, nChunk)) 87 | cqt_log = np.log(1 + 1000000 * cqt) 88 | 89 | print("[CQT_TJ] final output spec size:", np.shape(cqt_log)) 90 | 91 | return cqt_log 92 | 93 | 94 | class PITCH_SHIFT: 95 | 96 | def __init__(self, nPitch=4, sr=STANDARD_SAMPLE_RATE, binPerOctave=24): 97 | self.nPitch = nPitch 98 | self.sr = sr 99 | self.binPerOctave = binPerOctave 100 | 101 | def fun(self, filename): 102 | audio, sr2 = la.load(filename, sr=self.sr) 103 | changes = list(range(-self.nPitch,0)) + list(range(1, self.nPitch+1)) 104 | specs = [la.effects.pitch_shift(audio, \ 105 | sr=self.sr, n_steps=i, bins_per_octave=self.binPerOctave) \ 106 | for i in changes] 107 | print("[PITCH_SHIFT] generated additional {} samples".format(len(specs))) 108 | 109 | names = list() 110 | randomID = random.getrandbits(32) 111 | for i in range(len(specs)): 112 | name = "temp_PITCH_SHIFT_{}_{}.wav".format(i,randomID) 113 | cu.saveWAV(name, specs[i], self.sr) 114 | names.append(name) 115 | return names 116 | 117 | 118 | 119 | class TIME_STRETCH: 120 | 121 | def __init__(self, maxStretch=2, interval=0.2, 122 | sr=STANDARD_SAMPLE_RATE, binPerOctave=24): 123 | 124 | self.maxStretch = maxStretch 125 | self.interval = interval 126 | self.sr = sr 127 | self.binPerOctave = binPerOctave 128 | 129 | def fun(self, filename): 130 | audio, sr2 = la.load(filename, sr=self.sr) 131 | changes = [float(i) * self.interval for i in \ 132 | range(1,int(self.maxStretch/self.interval))] 133 | specs = [la.effects.time_stretch(audio, rate=i) for i in changes] 134 | 135 | print("[TIME_STRETCH] generated additional {} samples".format(len(specs))) 136 | 137 | names = list() 138 | randomID = random.getrandbits(32) 139 | for i in range(len(specs)): 140 | name = "temp_TIME_STRETCH_{}_{}.wav".format(i, randomID) 141 | cu.saveWAV(name, specs[i], self.sr) 142 | names.append(name) 143 | return names 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | -------------------------------------------------------------------------------- /CacheUtils.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import librosa as la 3 | import os 4 | 5 | NUMPY_SUFFIX = ".npy" 6 | 7 | 8 | def listdir(path): 9 | return [p for p in os.listdir(path) if p[0] != '.'] 10 | 11 | # save given numpy object with the name filePrefix.npy, in the given directory 12 | def saveNumpy(obj, filePrefix, direPath): 13 | print("[saveNumpy] saving numpy shape:", np.shape(obj)) 14 | if not os.path.exists(direPath): 15 | os.makedirs(direPath) 16 | filePath = os.path.join(direPath, filePrefix + NUMPY_SUFFIX) 17 | with open(filePath, "wb") as fd: 18 | np.save(fd, obj) 19 | print("[saveNumpy] saved", filePath) 20 | 21 | # load the saved numpy object at given fileName 22 | def loadNumpy(fileName): 23 | print("[loadNumpy] loading", fileName) 24 | return np.load(fileName) 25 | 26 | # load the database stored in the path 27 | # the path should contain only saved hashprint files 28 | def loadHashprintDB(dbPath): 29 | 30 | print("[loadHashprintDB] loading db at path", dbPath) 31 | database = dict() 32 | for song in listdir(dbPath): 33 | database[song] = [np.load(os.path.join(dbPath,song,p)) \ 34 | for p in os.listdir(os.path.join(dbPath,song))] 35 | 36 | return database 37 | 38 | 39 | 40 | def saveWAV(filePath, audio, sr): 41 | print("[saveWAV] saved", filePath) 42 | la.output.write_wav(filePath, audio, sr) 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /Hashprint.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import librosa as la 3 | import CacheUtils as cu 4 | from runHashprint import MODEL 5 | 6 | 7 | MODEL_HOP = 5 8 | MODEL_TAO = 1 9 | MODEL_FRAME = 20 10 | MODEL_FEATURES = 64 11 | MODEL_DELTADELAY = 16 12 | 13 | class HashprintFunction: 14 | 15 | def __init__(self, transformFun, modelPath): 16 | self.transformFun = transformFun 17 | self.modelPath = modelPath 18 | 19 | def fun(self, audioPath): 20 | print("[hashprintFun] calculating hashprint at:", audioPath) 21 | spec = self.transformFun(audioPath) 22 | model = cu.loadNumpy(self.modelPath) 23 | 24 | tdeMatrix = getHashprintTDE(spec) 25 | features = np.matmul(np.transpose(tdeMatrix), model) 26 | 27 | deltas = features[:,:(np.size(features,1)-MODEL_DELTADELAY)] - \ 28 | features[:,MODEL_DELTADELAY:]; 29 | 30 | print("[hashprintFun] calculated hashprint of shape:", np.shape(deltas)) 31 | 32 | return deltas > 0 33 | 34 | 35 | def learnHashprintModel(specList): 36 | 37 | spectrograms = [cu.loadNumpy(spec) for spec in specList] 38 | 39 | matrixCov = 0 40 | for spec in spectrograms: 41 | matrixCov = np.add(matrixCov, np.cov(getHashprintTDE(spec))) 42 | 43 | matrixCovMean = np.divide(matrixCov, len(spectrograms)) 44 | 45 | eigValue, eigVector = np.linalg.eig(matrixCovMean) 46 | 47 | eigPairs = list(zip(np.abs(eigValue).tolist(), np.transpose(eigVector).tolist())) 48 | sortedPairs = sorted(eigPairs, reverse=True, key=lambda p: p[0]) 49 | features = np.transpose(np.array([pair[1] for pair in sortedPairs[:MODEL_FEATURES]])) 50 | print("[learnHashprintModel] learned model of shape", np.shape(features)) 51 | 52 | return features 53 | 54 | 55 | def getHashprintTDE(spec): 56 | nFrame = np.size(spec,1) 57 | nBin = np.size(spec,0) 58 | tdeSpan = MODEL_TAO * (MODEL_FRAME-1) + 1 59 | endIdx = nFrame - tdeSpan 60 | offsets = range(0, endIdx, MODEL_HOP) 61 | 62 | matrix = np.empty((len(offsets), nBin * MODEL_FRAME)) 63 | 64 | for i in range(MODEL_FRAME): 65 | frameIdxs = [off + i * MODEL_TAO for off in offsets]; 66 | matrix[:,(i*nBin):(i+1)*nBin] = np.transpose(spec[:,frameIdxs]) 67 | rv = np.transpose(matrix) 68 | 69 | print("[getHashprintTDE] calculated TDE of size", np.shape(rv)) 70 | return rv 71 | 72 | def runHashprintQuery(queryPath, hashprintDB): 73 | 74 | queryData = cu.loadNumpy(queryPath) 75 | 76 | print("[runHashprintQuery] receive query data of shape:", np.shape(queryData)) 77 | 78 | queryLen = len(queryData) 79 | 80 | scores = list() 81 | 82 | for song in hashprintDB.keys(): 83 | 84 | print("[runHashprintQuery] run data against song:", song) 85 | 86 | songScores = list() 87 | for hashprint in hashprintDB[song]: 88 | hashLen = len(hashprint) 89 | 90 | if queryLen < hashLen: 91 | songScores.append(hashprintMatchScore(queryData, hashprint)) 92 | else: 93 | songScores.append(hashprintMatchScore(hashprint, queryData)) 94 | 95 | best = min(songScores) 96 | print("[runHashprintQuery] get min score:", best) 97 | scores.append((best, song)) 98 | 99 | scores.sort() 100 | print("[runhashprintQuery] best choice overall:", scores[0]) 101 | return scores[0][1] 102 | 103 | 104 | def hashprintMatchScore(hp1, hp2): 105 | 106 | hpLen = len(hp1) 107 | 108 | if hpLen == len(hp2): 109 | best = np.sum(np.logical_xor(hp1, hp2)) 110 | else: 111 | scoreList = list() 112 | for i in range(len(hp2)-hpLen): 113 | scoreList.append(np.sum(np.logical_xor(hp1, hp2[i:hpLen+i]))) 114 | best = min(scoreList) 115 | print("[hashprintMatchScore] comparing shape {} against {} gets {}".format(\ 116 | np.shape(hp1), np.shape(hp2), best)) 117 | 118 | return best 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cover Song Detection 2 | 3 | This is a music informatics retrieval project based on TJ's paper of live song detection. This proejct changes the focus to cover song detection using K-pop dataset, and tries to achieve the following goals: 4 | 5 | 1. Reconstruct TJ's hashprint framework in Python 6 | 7 | 2. Optimize parameters of hashprint framework for K-pop dataset live song detection and cover song detection 8 | 9 | 3. Find a fast and systematic way to optimize parameters of hashprint framework 10 | 11 | 4. Develop additional methods to compare with and improve this existing framework 12 | 13 | 5. Develop a real world application/platform based on completed design 14 | 15 | 16 | # Database 17 | 18 | ## Database Structure 19 | 20 | Due to the limitation of Korean characters, all singers and music names are preprocessed and converted to index and saved in `artist_index.json` and `title_index.json`, and the `music_annotation.db` file is converted to `music_indexed.db`. These are done by `cleanDatabase.py`. 21 | 22 | | column | description | 23 | |-----------|-----------------------------------------------| 24 | | title | title of the song | 25 | | title_id | title_id of the song | 26 | | artist | artist of the song | 27 | | artist_id | artist_id of the song | 28 | | cover | 1 if is cover song, else 0 | 29 | | vid | youtube video download link | 30 | | soundsrc | content of the song, specified by index below | 31 | (Only relevant columns are listed) 32 | 33 | | index | meaning | 34 | |-------|--------------------------------------| 35 | | 0 | master | 36 | | 1 | master with little noise | 37 | | 2 | noisy track | 38 | | 3 | live performance/non-live instrument | 39 | | 4 | live performance/live instrument | 40 | | 5 | MR | 41 | | 6 | Karaoke | 42 | | 7 | Instrumental | 43 | | 8 | master+instrument | 44 | | 9 | remix | 45 | | 10 | unrelated | 46 | 47 | ## Downloaded Music File Directory Structure 48 | 49 | All the files are downloaded into the following directory structure by file `downloadMusic.py`, the audio files are in .mp3 format downloaded by `youtube-dl` and converted to mp3 by `ffmpeg` and `ffprobe` 50 | 51 | ``` 52 | > pwd 53 | /home/jackye/hashprint 54 | 55 | > tree 56 | . 57 | ├── music_annotation.db 58 | │  59 | ├── music_indexed.db 60 | │  61 | ├── artist_index.json 62 | │  63 | ├── title_index.json 64 | │  65 | ├── master (content == 0 or 1) 66 | │   ├── singer1 67 | │   │   ├── music1.mp3 68 | │   │   ├── music2.mp3 69 | │   │   ├── music... 70 | │   ├── singer2 71 | │   │   ├── music1.mp3 72 | │   │   ├── music2.mp3 73 | │   │   ├── music... 74 | │   ├── singer... 75 | ├── live (content == 3 or 4) 76 | │   ├── singer1 77 | │   │   ├── music1.mp3 78 | │   │   ├── music2.mp3 79 | │   │   ├── music... 80 | │   ├── singer2 81 | │   │   ├── music1.mp3 82 | │   │   ├── music2.mp3 83 | │   │   ├── music... 84 | │   ├── singer... 85 | ├── cover (cover == 1 and title.matchOriginalSinger()) 86 | │   ├── singer1 87 | │   │   ├── music1.mp3 88 | │   │   ├── music2.mp3 89 | │   │   ├── music... 90 | │   ├── singer2 91 | │   │   ├── music1.mp3 92 | │   │   ├── music2.mp3 93 | │   │   ├── music... 94 | │   ├── singer... 95 | 96 | ``` 97 | 98 | # Task 1: Reconstruct TJ's hashprint framework in Python 99 | 100 | Original framework is in MATLAB, and this new Python framework is implemented using `librosa` audio analysis library and standard `numpy` libraries. 101 | 102 | ## Documentation 103 | 104 | * `runHashprint.py` 105 | 106 | Main function for whole testing program, use `python3 runHashprint.py` to start 107 | 108 | * `testMap.py` 109 | 110 | Test configuration file, control number and types of tests involved in the run 111 | 112 | * `AudioTransform.py` 113 | 114 | Contains classes that produce audio transformation functions, such as producing spectrograms, pitch-shift, time-stretch effects 115 | 116 | * `Hashprint.py` 117 | 118 | Contains core functions that implement the hashprint algorithm 119 | 120 | * `CacheUtils.py` 121 | 122 | Contains caching functions for easy use 123 | 124 | * `ResultAnalysis.py` 125 | 126 | Contains analysis classes and saves results to `results` folder `csv` files 127 | 128 | * `extendQuery.py` 129 | 130 | Contains functions that extend existing database files by transformations 131 | 132 | ## Cache Format 133 | 134 | In testMap, there're two maps specifying (1) types of audio transformation to produce spectrogram (referred as `audioTransFun`) and (2) types of audio effects applied for hashprint sets of each song (referred as `hashprintTransFun`). For example, the cached files are in the format: 135 | ``` 136 | CQT_TJ_HOP64/ 137 | ├── PITCH_SHIFT_3 138 | │   ├── 11 139 | │   │   ├── 1.mp3 140 | │   │   │   ├── hashprint_CQT_TJ_HOP64_PITCH_SHIFT_3_11_1.mp3_0.npy 141 | │   │   │   ├── hashprint_CQT_TJ_HOP64_PITCH_SHIFT_3_11_1.mp3_1.npy 142 | ├── master 143 | │   ├── 11 144 | │   │   ├── 1.mp3.npy (spectrogram) 145 | │   │   └── 2.mp3.npy 146 | │   └── 22 147 | │   └── 3.mp3.npy 148 | ├── model 149 | │  ├── 11.npy 150 | │  └── 22.npy 151 | ├── live 152 | │   ├── 11 153 | │   │   ├── hashprint_CQT_TJ_HOP64_PITCH_SHIFT_3_11_1.mp3_0.npy 154 | │   │   └── hashprint_CQT_TJ_HOP64_PITCH_SHIFT_3_11_2.mp3_0.npy 155 | │   └── 22 156 | │   └── hashprint_CQT_TJ_HOP64_PITCH_SHIFT_3_11_3.mp3_0.npy 157 | 158 | ``` 159 | 160 | where `CQT_TJ_HOP64` is a type of `audioTransFun`, and `PITCH_SHIFT_3` is a type of `hashprintTransFun`. Because for each `audioTransFun`, the model and query hashprint does not change, and for each `hashprintTransFun` underneath, we can define one database for matching. 161 | 162 | ## Result Format 163 | 164 | All analysis results are stored in `results` folder. File `analysis.csv` gives aggregated result for comparison, and each individual analysis file gives detailed decomposition score of each artist. 165 | 166 | `analysis.csv` has columns `audioTransName`, `hashprintTransName`, `testName`, `total accuracy`, `mean accuracy of artists`, `number queries`, `number artists` 167 | 168 | Each specific analysis file has columns `artistID`, `accuracy`, `number queries`, `number masters` 169 | 170 | 171 | # Task 2: Optimize parameters of hashprint framework for K-pop dataset live song detection and cover song detection 172 | 173 | ## Improvements of the testing framework 174 | 175 | From September to mid November, all tests are done using MATLAB framework with small size K-pop database. The Result was 72.3% accuracy in general, and by increasing pitch shifts the accuracy was increased to 75.0%. However, the testing framework had the following flaws: 176 | 177 | 1. database contain too few songs, and most artists only have 2 songs in database, so even a random guess can reach 50% accuracy. By examing some artists in detail, even for artists with more than 2 songs, lots of them are duplicates (having the same title name), which further decreases the reliability of the accuracy. 178 | 179 | 2. pitch shift was done using SOX framework, which has a fast but badly implemented pitch shift algorithm 180 | 181 | 3. it was hard to find an open framework to extend pitch shift to more variations in timbre domian 182 | 183 | 4. the whole live/cover song is passed in as query, which is not really possible for real world applications, and also not fit TJ's query setting that used 6-second duplicates with wihte noise of one live song. 184 | 185 | 5. in TJ's MATLAB hashprint algorithm, the raw audio is loaded as steoro, and then the two channels are added together to form mono, and then audio is mannually downsampled using a parameter. The mono and downsample conversion might not yield expected spectrogram due to the limitation of MATLAB CQT framework 186 | 187 | From mid November to early December, the python testing framework is designed, and these problems are fixed in the following ways: 188 | 189 | 1. by obtaining a bigger database, only artists with at least 5 distinct songs are selected for testing 190 | 191 | 2. the framework compared pitch shift of SOX, Librosa, Audacity, the later ones have same algorithm and produces pitch shift closer to original timbre, so are used in testing. 192 | 193 | 3. it was still a problem finding a good timbre change framework, so the test right now only have pitch shift and time stretch extensions tested 194 | 195 | 4. the tests are designed to test both whole song as query, and clips of 10, 20, 30, 40 seconds to see how it affects the result 196 | 197 | 5. in Python Librosa framework, the mono and downsample can be done with a simple function call, which directly downsample through raw audio read during raw audio sampling phase, so the result should be more reliable, but the tests test both ways and compare results. 198 | 199 | 200 | ## Testing results (keep updating as tests progress) 201 | 202 | Current highest test accuracy: 203 | 204 | `Live`: **80.2%** 205 | 206 | `Cover`: **72.3%** 207 | 208 | 209 | ### Audio transformation tuning 210 | 211 | The accuracy is highest when pitch shift = 3 for both live and cover. the value 3 means 3 up shift and 3 down shift (6 variation copies in total). The time stretch is added further to pitch shift, for example, with 6 variation of pitch shifts, 3 time stretches (half speed, original speed, double speed) are calculated for each song, resulting 18 copies in total. However, too many copies seem not increase the accuracy at all. 212 | 213 | 214 | ### Audio query transformation tuning 215 | 216 | The accuracy is highest with 40 second clips, which both increases the accuracy and also increases the query speed. Surprisingly, the accuracy is not very low even with a 10-20 second query (which gives 72% accuracy for live and 67% accuracy for cover). By taking advantage of the this prediction accuracy with small duration audio, the speed of query can be significantly improved, and further real-time stream analysis algorithm can be developed based on this idea. 217 | 218 | 219 | ### Spectrogram parameters tuning 220 | 221 | The accuracy of use of Librosa library is indeed much higher than TJ's original manual mono and downsample conversion, as (76.6% vs 46.0%), and (72.3% vs 22.3%) difference for live and cover. The use of Librosa facilitates the tuning of parameters and also significantly increased the database construction speed. Currently the best parameter uses 12 bins each octive, with 96 pitchs per frame. 222 | 223 | During TJ's spectrogram to covariance transformation, he did not take care about the cast from complex number spectrogram values to real covariance values, and system automatically discard the imaginary part which may loose information and need to be tested. 224 | 225 | 226 | ### Voting algorithm accuracy boost 227 | 228 | Voting algorithm is constructed as: clip audio to X second pieces, run query on each single piece of clip, get the mode of choice as the final query output. This method takes advantage of the accuracy of small clips and try to boost the accuracy. The result shows that this algorithm works for live to boost its accuracy by 5%, but did not work for cover (decrease of 0.03%). 229 | 230 | By investigating the detailed distribution of accuracy, this algorithm turns out to work well for artists with high accuracy but poorly for ones not. Because cover song artists has a more sparse distribution of prediction accuracy, this method did not help boost accuracy after all. Therefore, live song boost proves this is a feasiable algorithm, but more focus will be put to first increase individual artist accuracy for cover songs. 231 | 232 | 233 | 234 | ### Possible further speed improvement 235 | 236 | 1. lots of processes can be processed in parallel which is not yet implemented 237 | 238 | 2. currently uses grid search method to find optimum, should develop better methods after analyzing the behavior of accuracy in Task 2 239 | 240 | 3. currently hashprint increases its accuracy by duplicating variations of raw audio and compare against query audio, but this consumes lots of time and space to calculate. One better way might be to standardize the raw audio and query audio (eg. all to C pitch) so comparison only happen once 241 | 242 | ### Possible further accuracy improvement 243 | 244 | 1. current model only addresses query against known single artist, but not very practical especially regarding cover song case, because if user knows the artist and the coversong, it's unlikely he/she does not know the original song. TJ states that, different artists have different features so formulate different hashprints, and the features (eigenvectors) are his key tool to compute hashprints. Using this idea, the concept of "artist" can be changed to "genre", or any other categorization methods that can formulate distinct features. Because user might not know the artist, but usually can supply information like what type of music it is, which country, etc. 245 | 246 | 2. This testing algorithm does not test the hyperparameters for hashprint eigenvector calculation, there're in total 5 parameters (which I assume already optimized by TJ according to his paper) but can still try tuning. 247 | 248 | 3. try other transformations (STFT, MFCC, etc.) rather than CQT to compute hashprint 249 | 250 | 4. Currently only top 64 eigenvector features are selected which is not sure enough or too much, and if suitable for all artists. Try develop learning methods like LSTM or one-shot learning neural networks might get better features for computing hashprint, which might be a better fit to use as feature vectors 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | -------------------------------------------------------------------------------- /ResultAnalysis.py: -------------------------------------------------------------------------------- 1 | import sqlite3, os 2 | import CacheUtils as cu 3 | 4 | COMMON_ANALYSIS_FILENAME = "results/analysis.csv" 5 | DB = "music_indexed.db" 6 | TABLE = "annotation" 7 | 8 | 9 | class ResultAnalyzer: 10 | 11 | def __init__(self, resultDict, audioTransName, hashprintTransName, testName): 12 | 13 | self.resultDict = resultDict 14 | self.audioTransName = audioTransName 15 | self.hashprintTransName = hashprintTransName 16 | 17 | 18 | self.testName = testName 19 | self.accuracy = [self.individualAccuracy(a) for a in self.resultDict.keys()] 20 | 21 | # self.testName = testName + "_vote" 22 | # self.accuracy = [self.voteAnalysis(a) for a in self.resultDict.keys()] 23 | 24 | self.totalQuery = sum([i[2] for i in self.accuracy]) 25 | self.totalMaster = sum([i[3] for i in self.accuracy]) 26 | 27 | # accuracy per query 28 | def calculateTotalAccuracy(self): 29 | if not self.totalQuery: 30 | return "NA" 31 | return sum([i[1]*i[2] for i in self.accuracy]) / float(self.totalQuery) 32 | 33 | # accuracy per artist 34 | def calculateMeanAccuracy(self): 35 | if not len(self.accuracy): 36 | return "NA" 37 | return sum([i[1] for i in self.accuracy]) / float(len(self.accuracy)) 38 | 39 | 40 | def individualAccuracy(self, artist): 41 | 42 | results = self.resultDict[artist] 43 | total = len(results) 44 | correct = 0 45 | 46 | masterPath = os.path.join(self.audioTransName, \ 47 | self.hashprintTransName, artist) 48 | nMaster = len(cu.listdir(masterPath)) 49 | 50 | conn = sqlite3.connect(DB) 51 | c = conn.cursor() 52 | for song in results.keys(): 53 | 54 | songID = song.split(".")[0].split("_")[0] 55 | best = results[song].split(".")[0].split("_")[0] 56 | print("[individualAccuracy] hashprint algorithm gets best match song id", \ 57 | best, "for song", songID) 58 | 59 | sql = "select title_id from {tn} where id={sid};".\ 60 | format(tn=TABLE, sid=best) 61 | print("[individualAccuracy] start query 1:", sql) 62 | c.execute(sql) 63 | bestTitleID = c.fetchall()[0][0] 64 | print("[individualAccuracy] best title id", bestTitleID) 65 | 66 | 67 | sql = "select title_id from {tn} where id={sid};".\ 68 | format(tn=TABLE, sid=songID) 69 | print("[individualAccuracy] start query 2:", sql) 70 | c.execute(sql) 71 | titleID = c.fetchall()[0][0] 72 | print("[individualAccuracy] actual title id", titleID) 73 | if titleID == bestTitleID: 74 | correct += 1 75 | 76 | conn.close() 77 | 78 | if not total: 79 | accuracy = 0 80 | else: 81 | accuracy = float(correct) / total 82 | return artist, accuracy, total, nMaster 83 | 84 | def voteAnalysis(self, artist): 85 | 86 | results = self.resultDict[artist] 87 | correct = 0 88 | 89 | masterPath = os.path.join(self.audioTransName, \ 90 | self.hashprintTransName, artist) 91 | nMaster = len(cu.listdir(masterPath)) 92 | 93 | conn = sqlite3.connect(DB) 94 | c = conn.cursor() 95 | 96 | finalResults = dict() 97 | for song in results.keys(): 98 | 99 | songID, ClipIndex = song.split(".")[0].split("_") 100 | best = results[song].split(".")[0].split("_")[0] 101 | print("[voteAnalysis] hashprint algorithm gets best match song id", \ 102 | best, "for song", songID) 103 | 104 | if not finalResults.get(songID): 105 | votes = list() 106 | finalResults[songID] = votes 107 | else: 108 | votes = finalResults[songID] 109 | 110 | 111 | sql = "select title_id from {tn} where id={sid};".\ 112 | format(tn=TABLE, sid=best) 113 | print("[voteAnalysis] start query 1:", sql) 114 | c.execute(sql) 115 | bestTitleID = c.fetchall()[0][0] 116 | print("[voteAnalysis] best title id", bestTitleID) 117 | 118 | votes.append(bestTitleID) 119 | 120 | for song in finalResults.keys(): 121 | votes = finalResults[song] 122 | print("[voteAnalysis] song", song, "gets votes", votes) 123 | bestVote = max(votes,key=votes.count) 124 | print("[voteAnalysis] gets vote mode as", bestVote) 125 | 126 | sql = "select title_id from {tn} where id={sid};".\ 127 | format(tn=TABLE, sid=song) 128 | print("[voteAnalysis] start query 2:", sql) 129 | c.execute(sql) 130 | titleID = c.fetchall()[0][0] 131 | print("[voteAnalysis] actual title id", titleID) 132 | if titleID == bestVote: 133 | correct += 1 134 | 135 | conn.close() 136 | 137 | total = len(finalResults) 138 | if not total: 139 | accuracy = 0 140 | else: 141 | accuracy = float(correct) / total 142 | return artist, accuracy, total, nMaster 143 | 144 | 145 | 146 | # save all information to path analysis_[audioTransName]_[hashprintTransName]_[testName].txt 147 | def saveAll(self): 148 | 149 | fileName = "results/analysis_{}_{}_{}.csv".format(\ 150 | self.audioTransName, self.hashprintTransName, self.testName) 151 | with open(fileName, "w") as fd: 152 | fd.write("{},{},{},{}\n".format("total", self.calculateTotalAccuracy(), \ 153 | self.totalQuery, self.totalMaster)) 154 | fd.write("{},{},{},{}\n".format("total", self.calculateMeanAccuracy(), \ 155 | len(self.accuracy), "NA")) 156 | for artist, acc, total, total2 in self.accuracy: 157 | fd.write("{},{},{},{}\n".format(artist, acc, total, total2)) 158 | 159 | print("[saveAll] saved all analysis to", fileName) 160 | 161 | # save to a common csv file for comparison 162 | def saveToCommonCSV(self): 163 | 164 | with open(COMMON_ANALYSIS_FILENAME, "a") as fd: 165 | fd.write("{},{},{},{},{},{},{},{}\n".format(\ 166 | self.audioTransName, self.hashprintTransName, self.testName, \ 167 | self.calculateTotalAccuracy(), self.calculateMeanAccuracy(), 168 | self.totalQuery, self.totalMaster, len(self.accuracy))) 169 | print("[saveToCommonCSV] saved to common csv file", \ 170 | self.audioTransName, self.hashprintTransName, self.testName) 171 | 172 | 173 | 174 | 175 | 176 | 177 | -------------------------------------------------------------------------------- /artist_index.json: -------------------------------------------------------------------------------- 1 | { 2 | "0": "\uc18c\uc720", 3 | "1": "\ud718\uc131", 4 | "2": "\uc9c0\uc544(Zia)", 5 | "3": "\uba85\uce74\ub4dc\ub77c\uc774\ube0c", 6 | "4": "\uc608\uc778", 7 | "5": "\uc5d0\ud53d \ud558\uc774(Epik High)", 8 | "6": "\uc2a4\ud0a4\uc870(Schizo)", 9 | "7": "\uc81c\uc544 [\ube0c\ub77c\uc6b4 \uc544\uc774\ub4dc \uac78\uc2a4]", 10 | "8": "\uc774\uc18c\uc740", 11 | "9": "\ub4dc\ub801\ud070 \ud0c0\uc774\uac70(Drunken Tiger)", 12 | "10": "\ud06c\ub9ac\uc2a4\ud0c8 [f(x)]", 13 | "11": "\ud06c\ub77c\uc6b4 \uc81c\uc774(Crown J)", 14 | "12": "\uc720\ud0a4\uc2a4(U-KISS)", 15 | "13": "\uc120\ubbf8 [\uc6d0\ub354\uac78\uc2a4]", 16 | "14": "\uce74\ub77c(Kara)", 17 | "15": "\ub9c8\uc774\ud2f0 \ub9c8\uc6b0\uc2a4(Mighty Mouth)", 18 | "16": "\ubc15\uc815\ud604", 19 | "17": "\ud0dc\ubbfc(TAEMIN)", 20 | "18": "\ubc15\uc9c4\uc601", 21 | "19": "\uae40\ud604\uc815", 22 | "20": "\uae40\ub0a8\uae38", 23 | "21": "\uc81c\uad6d\uc758\uc544\uc774\ub4e4[ZE:A]", 24 | "22": "\ubc14\ub2e4", 25 | "23": "\uc2e0\ud654", 26 | "24": "\ud22c\ube45(2BiC)", 27 | "25": "\ub7fc\ube14 \ud53c\uc26c(Rumble Fish)", 28 | "26": "\ub9b0\uc560", 29 | "27": "\uc740\uc9c0\uc6d0", 30 | "28": "\uc190\ub2f4\ube44", 31 | "29": "\uae40\uc885\uad6d", 32 | "30": "\uc6a9\uac10\ud55c \ub140\uc11d\ub4e4", 33 | "31": "\uc0b0\uc774(San E)", 34 | "32": "\uc560\uc988 \uc6d0(As One)", 35 | "33": "\uac00\uc778 [\ube0c\ub77c\uc6b4\uc544\uc774\ub4dc\uac78\uc2a4]", 36 | "34": "\ub514\uc148\ubc84(December)", 37 | "35": "M(\uc774\ubbfc\uc6b0)", 38 | "36": "\ucc44\uc5f0", 39 | "37": "\uc641\uc2a4(Wax)", 40 | "38": "\uae40\uc218\ud604 [\ubc30\uc6b0]", 41 | "39": "2AM", 42 | "40": "\ub9ac\uc0ac(Lisa)", 43 | "41": "\ud604\uc544(Hyun A) [\ud3ec\ubbf8\ub2db]", 44 | "42": "\uae40\ubc94\uc218", 45 | "43": "\uc288\ud37c\uc8fc\ub2c8\uc5b4(Super Junior)", 46 | "44": "Idina Menzel(\uc774\ub514\ub098 \uba58\uc824)", 47 | "45": "\ud604\uc601", 48 | "46": "\uae40\uc7ac\ud76c", 49 | "47": "\ub864\ub7ec\ucf54\uc2a4\ud130(Rollercoaster)", 50 | "48": "\uac78\uc2a4\ub370\uc774(Girl's Day)", 51 | "49": "\uac70\ubd81\uc774(Turtles)", 52 | "50": "\ub3d9\ubc29\uc2e0\uae30(TVXQ!)", 53 | "51": "t\uc724\ubbf8\ub798", 54 | "52": "\uc5c5\ud0c0\uc6b4(UPT)", 55 | "53": "\uc5d0\uc774\ud551\ud06c(Apink)", 56 | "54": "\uae40\uc6a9\uc900 [SG \uc6cc\ub108\ube44]", 57 | "55": "\uc774\ub8e8(Eru)", 58 | "56": "\ud22c\uac1c\uc6d4", 59 | "57": "\uc774\uc801", 60 | "58": "\ud558\ub3d9\uade0", 61 | "59": "\uc11c\uc601\uc740", 62 | "60": "\ud6c8(Hun)", 63 | "61": "\ud5c8\uac01", 64 | "62": "\ucf54\uc694\ud0dc", 65 | "63": "\ub098\ube44(Navi)", 66 | "64": "\uc784\uc815\ud76c", 67 | "65": "\ub514\ubc14(Diva)", 68 | "66": "FTISLAND(\uc5d0\ud504\ud2f0 \uc544\uc77c\ub79c\ub4dc)", 69 | "67": "\uc724\ud558(Younha/\u30e6\u30f3\u30ca)", 70 | "68": "\uc0e4\uc774\ub2c8(SHINee)", 71 | "69": "\uc774\ud604 [\uc5d0\uc774\ud2b8]", 72 | "70": "10cm(\uc2ed\uc13c\uce58)", 73 | "71": "\uc774\uc740\ubbf8", 74 | "72": "S.M. THE BALLAD", 75 | "73": "\ud6a8\ub9b0 [\uc528\uc2a4\ud0c0]", 76 | "74": "\uae40\uc9c4\ud45c(JP)", 77 | "75": "\uc774\uc2b9\uae30", 78 | "76": "\uc774\ud6a8\ub9ac", 79 | "77": "\ube14\ub77d\ube44(Block B)", 80 | "78": "\ud654\uc694\ube44", 81 | "79": "\ud06c\ub7ec\uc26c(Crush)", 82 | "80": "EXO", 83 | "81": "\ub354\ube14\uc5d0\uc2a4501(SS501)", 84 | "82": "\ubc94\ud0a4(Bumkey)", 85 | "83": "\uc544\uc774\uc720(IU)", 86 | "84": "\ud06c\ub808\uc6a9\ud31d(Crayon Pop)", 87 | "85": "\ud4e8\ucc98\ub77c\uc774\uac70", 88 | "86": "\uc7a5\uc724\uc815", 89 | "87": "\ub098\ubab0\ub77c\ud328\ubc00\ub9ac JW", 90 | "88": "WINNER", 91 | "89": "\uc2a4\ud154\ub77c(Stellar)", 92 | "90": "\ubc84\uc2a4\ucee4 \ubc84\uc2a4\ucee4(Busker Busker)", 93 | "91": "\uc774\uc2b9\ucca0", 94 | "92": "\uc5e0\uc528 \ub354 \ub9e5\uc2a4(M.C the Max)", 95 | "93": "\ubc15\ubcf4\ub78c", 96 | "94": "\uc138\ube10(SE7EN)", 97 | "95": "\uc774\uc7ac\uc740", 98 | "96": "\ubc31\uc9c0\uc601", 99 | "97": "\ub354 \uc6d0(The One)", 100 | "98": "\ube0c\uc774\uc6d0 (V.One)", 101 | "99": "\uc96c\uc5bc\ub9ac(Jewelry)", 102 | "100": "\ud504\ub9ac \uc2a4\ud0c0\uc77c(Free Style)", 103 | "101": "\ubb38\uadfc\uc601", 104 | "102": "\uac70\ubbf8", 105 | "103": "\ud2f0\uc544\ub77c(T-ara)", 106 | "104": "\uc5d0\uc774\ud2b8(8Eight)", 107 | "105": "\ub9ac\uce58(Rich)", 108 | "106": "\uc194\ube44", 109 | "107": "\uc18c\ub140\uc2dc\ub300", 110 | "108": "\uc720\uc2b9\uc6b0", 111 | "109": "G-DRAGON", 112 | "110": "\uc528\uc57c(SeeYa)", 113 | "111": "\uc560\ud504\ud130 \uc2a4\ucfe8(After School) ", 114 | "112": "\uc9c0\ub098(G.NA)", 115 | "113": "BIGBANG", 116 | "114": "\ub2e4\ube44\uce58", 117 | "115": "\ub8f0\ub77c(Roo'Ra)", 118 | "116": "\uc564(Ann)", 119 | "117": "\ubc84\uc988(Buzz)", 120 | "118": "\uae40\uc724\uc544", 121 | "119": "\uc2a4\uc717\uc18c\ub85c\uc6b0(SWEET SORROW)", 122 | "120": "\uc774\uc120\ud76c", 123 | "121": "\uc784\uc7ac\ubc94", 124 | "122": "\uc11c\uc778\uc601", 125 | "123": "\ubc15\uc7ac\ubc94(Jay Park)", 126 | "124": "\uc2a4\ud53c\uce74(SPICA)", 127 | "125": "\ud14c\uc774(Tei)", 128 | "126": "\ub514\uce74\ud504\ub9ac\uc624(D.Caprio)", 129 | "127": "\uae40\uacbd\ub85d [V.O.S]", 130 | "128": "\ucfe8(COOL)", 131 | "129": "\ub809\uc2dc(Lexy)", 132 | "130": "\uc131\uc2dc\uacbd", 133 | "131": "\uae38\uac74", 134 | "132": "\ube44\uc2a4\ud2b8(Beast)", 135 | "133": "CL", 136 | "134": "\ud604\uc9c4\uc601", 137 | "135": "\uc6d0\ub354\uac78\uc2a4(Wonder Girls)", 138 | "136": "\uba54\uc774\ube44(MayBee)", 139 | "137": "\ud22c\uc560\ub2c8\uc6d0(2NE1)", 140 | "138": "\uc528\uc5d4\ube14\ub8e8(CNBLUE)", 141 | "139": "\uc870\uc131\ubaa8", 142 | "140": "\uac15\uc131\ud6c8", 143 | "141": "\uc815\uc7ac\uc6b1", 144 | "142": "\ubc15\uc7a5\uadfc", 145 | "143": "\ubc30\ub2e4\ud574", 146 | "144": "\uc870pd(ZoPD)", 147 | "145": "\uc625\ud0dd\uc5f0 [2PM]", 148 | "146": "\ud50c\ub77c\uc774 \ud22c \ub354 \uc2a4\uce74\uc774(Fly To The Sky)", 149 | "147": "\ud0dc\uc5f0 (Taeyeon)", 150 | "148": "\ud2f0\ub9e5\uc2a4(T-Max)", 151 | "149": "\uc528\uc2a4\ud0c0(Sistar)", 152 | "150": "\ub354\ud544\ub984(The Film)", 153 | "151": "\uc2a4\uc719\uc2a4(Swings)", 154 | "152": "\uc774\uc694\uc6d0", 155 | "153": "\ud55c\uacbd\uc77c", 156 | "154": "\ub9ac\uc30d(Leessang)", 157 | "155": "\uc544\uc774\ube44(IVY)", 158 | "156": "\ud658\ud76c", 159 | "157": "\uc804\ud61c\ube48", 160 | "158": "\uc815\uc77c\uc601", 161 | "159": "\ubc15\ud61c\uacbd", 162 | "160": "\uc11c\ud0dc\uc9c0", 163 | "161": "G.\uace0\ub9b4\ub77c", 164 | "162": "\uba5c\ub85c\ub514\ub370\uc774(Melody Day)", 165 | "163": "\uc2b9\ub9ac", 166 | "164": "MC \uc2a4\ub098\uc774\ud37c(MC Sniper)", 167 | "165": "Santino Fontana", 168 | "166": "\ub2e4\uc774\ub098\ubbf9 \ub4c0\uc624(Dynamic Duo)", 169 | "167": "\ud3ec\ubbf8\ub2db(4minute)", 170 | "168": "\uac00\ud76c", 171 | "169": "\ub098\uc724\uad8c", 172 | "170": "\uc5d0\uc77c\ub9ac(Ailee)", 173 | "171": "\uc2f8\uc774(Psy)", 174 | "172": "\uae40\uc608\ub9bc [\ud22c\uac1c\uc6d4]", 175 | "173": "\uc2e0\uc2b9\ud6c8", 176 | "174": "\ud5c8\ubc0d \uc5b4\ubc18 \uc2a4\ud14c\ub808\uc624(Humming Urban Stereo)", 177 | "175": "\uaddc\ud604(KYUHYUN)", 178 | "176": "\ubcc4", 179 | "177": "\ud3ec\ub9e8", 180 | "178": "\uae40\ubcf4\uacbd", 181 | "179": "\ube0c\ub77c\uc6b4 \uc544\uc774\ub4dc \uac78\uc2a4", 182 | "180": "MC \ubabd", 183 | "181": "\ucd5c\uc815\ucca0", 184 | "182": "\ube0c\ub77c\uc6b4 \uc544\uc774\ub4dc \uc18c\uc6b8(Brown Eyed Soul)", 185 | "183": "\ucc3d\ubbfc", 186 | "184": "Lady GaGa(\ub808\uc774\ub514 \uac00\uac00)", 187 | "185": "\uae31\uc2a4(Geeks) [\ud799\ud569]", 188 | "186": "\ube0c\ub77c\uc774\uc5b8(Brian)", 189 | "187": "\ubc14\ube44(Bobby)", 190 | "188": "Adam Levine (Maroon 5)", 191 | "189": "\uae40\ud0dc\uc6b0", 192 | "190": "\ub808\uc774\ub514\uc2a4 \ucf54\ub4dc(LADIES' CODE)", 193 | "191": "\ubc15\uae30\uc601", 194 | "192": "\ucf00\uc774\uc70c(K.Will)", 195 | "193": "\ubc14\uc774\ube0c(Vibe)", 196 | "194": "\uc591\ud30c", 197 | "195": "\uc190\uc2b9\uc5f0", 198 | "196": "\uc5e0\ud22c\uc5e0(M To M)", 199 | "197": "\ud0c0\uc774\ud47c", 200 | "198": "\uc7a5\ubc94\uc900", 201 | "199": "SG\uc6cc\ub108\ube44", 202 | "200": "\uc6d0\ud0c0\uc784(1TYM)", 203 | "201": "Keira Knightley(\ud0a4\uc774\ub77c \ub098\uc774\ud2c0\ub9ac)", 204 | "202": "2PM", 205 | "203": "\uc784\ucc3d\uc815", 206 | "204": "\ub098\ubab0\ub77c \ud328\ubc00\ub9ac TKO" 207 | } -------------------------------------------------------------------------------- /cleanDatabase.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import sqlite3 4 | import json 5 | 6 | DB = "music_indexed.db" 7 | TABLE = "annotation" 8 | conn = sqlite3.connect(DB) 9 | c = conn.cursor() 10 | 11 | for term in ["title", "artist"]: 12 | c.execute("select id, {term} from {tn}".format(term=term, tn=TABLE)) 13 | rows = c.fetchall() 14 | row_id = [r[0] for r in rows] 15 | row_term = [r[1] for r in rows] 16 | terms = set(row_term) 17 | 18 | term_index_dict = dict() 19 | term_index_dict_reverse = dict() 20 | index = 0 21 | for t in terms: 22 | term_index_dict[index] = t 23 | term_index_dict_reverse[t] = index 24 | index += 1 25 | 26 | c.execute("ALTER TABLE {tn} ADD COLUMN '{cn}' {ct}".\ 27 | format(tn=TABLE, cn="{}_id".format(term), ct="INTEGER")) 28 | 29 | for i in range(len(rows)): 30 | index = rows[i][0] 31 | t = rows[i][1] 32 | print("Updating index {}, term {} to {}".format(index, t, term_index_dict_reverse[t])) 33 | c.execute("UPDATE {tn} SET {cn}={cv} WHERE {rn}={rv}".\ 34 | format(tn=TABLE, cn="{}_id".format(term), 35 | cv=term_index_dict_reverse[t], rn="id", rv=index)) 36 | 37 | with open('{}_index.json'.format(term), 'w') as fp: 38 | json.dump(term_index_dict, fp, sort_keys=True, indent=4) 39 | 40 | conn.commit() 41 | conn.close() 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /downloadMusic.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import sqlite3, os 4 | 5 | DB = "music_indexed.db" 6 | TABLE = "annotation" 7 | LEAST_MASTER_SIZE = 5 8 | 9 | conn = sqlite3.connect(DB) 10 | c = conn.cursor() 11 | c.execute("select distinct artist_id from {}".format(TABLE)) 12 | artists = [r[0] for r in c.fetchall()] 13 | 14 | for artist in artists: 15 | 16 | # first download master pieces 17 | # if no master piece, no need to download live and cover ones 18 | # otherwise record all master titles, later downloads need to be in this set 19 | constraint = ("soundsrc in (0,1)", "master") 20 | print("[downloadMusic] Start download MASTER music for", artist) 21 | 22 | artistDir = constraint[1] + "/" + str(artist) 23 | coverDir = "cover" + "/" + str(artist) 24 | 25 | if not os.path.exists(artistDir): 26 | os.makedirs(artistDir) 27 | os.makedirs(coverDir) 28 | 29 | sql = '''select vid,title_id,id from {tn} where artist_id={an} and relevant=1 30 | and medley=0 and cover=0 and {cn};'''.\ 31 | format(tn=TABLE, an=artist, cn=constraint[0]) 32 | 33 | c.execute(sql) 34 | tracks = c.fetchall() 35 | 36 | if len(tracks) == 0: 37 | print("[remove] Empty master data for artist", artist) 38 | os.system("rm -r -f master/{}".format(artist)) 39 | os.system("rm -r -f cover/{}".format(artist)) 40 | continue 41 | 42 | allMastersTrack = set([row[1] for row in tracks]) 43 | print("[downloadMusic] Found {} masters".format(len(allMastersTrack))) 44 | 45 | if len(allMastersTrack) < LEAST_MASTER_SIZE: 46 | print("[remove] No enough master data for artist", artist) 47 | os.system("rm -r -f master/{}".format(artist)) 48 | os.system("rm -r -f cover/{}".format(artist)) 49 | continue 50 | 51 | downloaded = set() 52 | 53 | for row in tracks: 54 | vid = row[0] 55 | title_id = row[1] 56 | true_id = row[2] 57 | 58 | if title_id not in downloaded: 59 | 60 | audioPath = artistDir + "/" + str(true_id) + ".mp3" 61 | os.system("youtube-dl --id -x --audio-format mp3 http://youtu.be/{}".format(vid)) 62 | print("[move] mv master {}.mp3 {}".format(vid, audioPath)) 63 | os.system("mv -- {}.mp3 {}".format(vid, audioPath)) 64 | downloaded.add(title_id) 65 | 66 | for title_id in allMastersTrack: 67 | # download cover songs 68 | # go to master and find the title_id, find all cover=1 songs 69 | print("[downloadMusic] Start download COVER music for title_id", title_id) 70 | cover_sql = '''select vid,id from {tn} where title_id = {tid} and 71 | relevant=1 and medley=0 and cover=1;'''.\ 72 | format(tn=TABLE, an=artist, tid=title_id) 73 | c.execute(cover_sql) 74 | rows = c.fetchall() 75 | print("[downloadMusic] Found {} covers".format(len(rows))) 76 | 77 | for i in range(len(rows)): 78 | vid = rows[i][0] 79 | true_id = rows[i][1] 80 | coverPath = coverDir + "/" + str(true_id) + ".mp3" 81 | os.system("youtube-dl --id -x --audio-format mp3 http://youtu.be/{}".format(vid)) 82 | print("[move] mv cover {}.mp3 {}".format(vid, coverPath)) 83 | os.system("mv -- {}.mp3 {}".format(vid, coverPath)) 84 | allMastersTrack.add(title_id) 85 | 86 | 87 | 88 | # download live songs 89 | # basically same algorithm as master, add check if in master 90 | constraint = ("soundsrc in (3,4)", "live") 91 | print("[downloadMusic] Start download LIVE music for", artist) 92 | 93 | artistDir = constraint[1] + "/" + str(artist) 94 | 95 | if not os.path.exists(artistDir): 96 | os.makedirs(artistDir) 97 | 98 | sql = '''select vid,title_id,id from {tn} where artist_id={an} and relevant=1 99 | and medley=0 and cover=0 and {cn};'''.\ 100 | format(tn=TABLE, an=artist, cn=constraint[0]) 101 | 102 | c.execute(sql) 103 | tracks = c.fetchall() 104 | 105 | print("[downloadMusic] Found {} lives".format(len(tracks))) 106 | 107 | for row in tracks: 108 | vid = row[0] 109 | title_id = row[1] 110 | true_id = row[2] 111 | if title_id in allMastersTrack: 112 | audioPath = artistDir + "/" + str(true_id) + ".mp3" 113 | os.system("youtube-dl --id -x --audio-format mp3 http://youtu.be/{}".format(vid)) 114 | print("[move] mv live {}.mp3 {}".format(vid, audioPath)) 115 | os.system("mv -- {}.mp3 {}".format(vid, audioPath)) 116 | else: 117 | print("[downloadMusic] Cannot find master for live", true_id) 118 | 119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /extendQuery.py: -------------------------------------------------------------------------------- 1 | import audioread as ar 2 | import CacheUtils as cu 3 | import os 4 | 5 | def clipAudio(dirPath, fileName, clip_len, max_clip): 6 | filePath = os.path.join(dirPath, fileName) 7 | audioID = fileName.split(".")[0] 8 | 9 | with ar.audio_open(filePath) as a: 10 | seconds = a.duration 11 | max_duration = int(min(seconds//clip_len, max_clip) * clip_len) 12 | 13 | 14 | for i in range(0,max_duration,clip_len): 15 | newFilePath = os.path.join(dirPath, "{}_{}.mp3".format(audioID, i)) 16 | print("[clipAudio]",filePath,"from",i,"to",i+clip_len) 17 | os.system("ffmpeg -i {} -ss {} -t {} -acodec copy {}".\ 18 | format(filePath, i, clip_len, newFilePath)) 19 | os.system("rm {}".format(filePath)) 20 | 21 | 22 | def extendQueryByClip(test, version, clip_len=20, max_clip=10): 23 | print("[extendQueryByClip] extending", test, "to version", version) 24 | newTestName = "{}_{}".format(test, version) 25 | os.system("cp -r {} {}".format(test, newTestName)) 26 | 27 | for artist in cu.listdir(newTestName): 28 | dirPath = os.path.join(newTestName, artist) 29 | for song in cu.listdir(dirPath): 30 | clipAudio(dirPath, song, clip_len, max_clip) 31 | 32 | 33 | extendQueryByClip("live", "clip10", 10, 10) 34 | extendQueryByClip("live", "clip20", 20, 10) 35 | extendQueryByClip("live", "clip30", 30, 10) 36 | extendQueryByClip("live", "clip40", 40, 10) 37 | 38 | 39 | extendQueryByClip("cover", "clip10", 10, 10) 40 | extendQueryByClip("cover", "clip20", 20, 10) 41 | extendQueryByClip("cover", "clip30", 30, 10) 42 | extendQueryByClip("cover", "clip40", 40, 10) 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /music_annotation.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multimedia-berkeley/deep_hashing_coverSongDetection/a6a6c5e8d342656ae2a6428ad25e483a0287662a/music_annotation.db -------------------------------------------------------------------------------- /music_indexed.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multimedia-berkeley/deep_hashing_coverSongDetection/a6a6c5e8d342656ae2a6428ad25e483a0287662a/music_indexed.db -------------------------------------------------------------------------------- /runHashprint.py: -------------------------------------------------------------------------------- 1 | import AudioTransform as at 2 | import Hashprint as hp 3 | import CacheUtils as cu 4 | import testMap as tm 5 | import ResultAnalysis as ra 6 | import os, sqlite3, sys, random 7 | 8 | MASTER = "master" 9 | MODEL = "model" 10 | 11 | artistList = cu.listdir(MASTER) 12 | 13 | # transform the addios in given folder using the given audio transform function 14 | # the transform function should take in a audio and output a numpy spectrogram 15 | # the numpy will be saved in the same structure in transformName/folder 16 | def transformAll(folder, audioTransName, audioTransFun): 17 | print("[transformAll] start at folder:", folder) 18 | removed = list() 19 | for artist in artistList: 20 | print("[transformAll] artist:", artist) 21 | for song in cu.listdir(os.path.join(folder, artist)): 22 | print("[transformAll] song:", song) 23 | try: 24 | songPath = os.path.join(folder, artist, song) 25 | spectrogram = audioTransFun(songPath) 26 | cu.saveNumpy(spectrogram, song, os.path.join(audioTransName, folder, artist)) 27 | except: 28 | print("[transformAll][ERROR] trnasformation failed for song:", song) 29 | print("[transformAll][ERROR] imcomplete master, remove artist", artist) 30 | removed.append(artist) 31 | break 32 | 33 | # if not os.path.exists(os.path.join(audioTransName, folder, artist)): 34 | 35 | for r in removed: 36 | artistList.remove(r) 37 | if os.path.exists(os.path.join(audioTransName, folder, artist)): 38 | os.system("rm -rf {}".format(os.path.join(audioTransName, folder, artist))) 39 | print("[transformAll][ERROR] removed artist", r) 40 | 41 | 42 | 43 | 44 | # calculate all hashprints in a given folder 45 | def calculateHashprintAll(folder, key, audioTransFun): 46 | 47 | print("[calculateHashprintAll] start at folder:", folder) 48 | for artist in artistList: 49 | modelPath = os.path.join(key, MODEL, artist + cu.NUMPY_SUFFIX) 50 | hashprintFun = hp.HashprintFunction(audioTransFun, modelPath).fun 51 | print("[calculateHashprintAll] artist:", artist) 52 | for song in cu.listdir(os.path.join(folder, artist)): 53 | try: 54 | print("[calculateHashprintAll] song:", song) 55 | songPath = os.path.join(folder, artist, song) 56 | hashprint = hashprintFun(songPath) 57 | cu.saveNumpy(hashprint, song, os.path.join(key, folder, artist)) 58 | except: 59 | print("[calculateHashprintAll][ERROR] hashprint calculation failed for song:", song) 60 | 61 | # make hashprint models given the transformation and associated model generating function 62 | # each model is an array of eigenvectors, and will be saved in transformName/model 63 | def makeModels(audioTransName): 64 | 65 | transMaster = os.path.join(audioTransName, MASTER) 66 | print("[makeModels] start at folder:", transMaster) 67 | 68 | for artist in artistList: 69 | print("[makeModels] artist:", artist) 70 | specList = [os.path.join(transMaster, artist, spec) \ 71 | for spec in cu.listdir(os.path.join(transMaster, artist))] 72 | 73 | model = hp.learnHashprintModel(specList) 74 | cu.saveNumpy(model, artist, os.path.join(audioTransName, MODEL)) 75 | 76 | 77 | # make complete hashprint databases given audio transformation and hashprint transformation 78 | # each hashprint transform funciton takes a spectrogram and output a series of transformations 79 | # then hash function is applied to each of them to compute a list of hashprints for each audio 80 | def makeDatabases(audioTransName, hashprintTransName): 81 | 82 | print("[makeDatabases] start at folder:", MASTER) 83 | 84 | for artist in artistList: 85 | 86 | print("[makeDatabases] artist:", artist) 87 | 88 | modelPath = os.path.join(audioTransName, MODEL, artist + cu.NUMPY_SUFFIX) 89 | hashprintFun = hp.HashprintFunction(\ 90 | tm.AUDIO_TRANS_MAP[audioTransName], modelPath).fun 91 | 92 | for song in cu.listdir(os.path.join(MASTER, artist)): 93 | 94 | print("[makeDatabases] song:", song) 95 | songPath = os.path.join(MASTER, artist, song) 96 | savePath = os.path.join(audioTransName, hashprintTransName, artist, song) 97 | 98 | try: 99 | songList = tm.HP_TRANS_MAP[hashprintTransName](songPath) 100 | hashprintList = [hashprintFun(s) for s in songList] 101 | 102 | for s in songList: 103 | os.system("rm -f {}".format(s)) 104 | 105 | for i in range(len(hashprintList)): 106 | saveName = "hashprint_{}_{}_{}_{}_{}".format(\ 107 | audioTransName, hashprintTransName, artist, song, i) 108 | cu.saveNumpy(hashprintList[i], saveName, savePath) 109 | except: 110 | print("[makeDatabases][ERROR] cannot make hashprint series for song",song) 111 | print("[makeDatabases][ERROR] remove artist {} in database {}".\ 112 | format(artist, song)) 113 | artistPath = os.path.join(audioTransName,hashprintTransName,artist) 114 | if os.path.exists(artistPath): 115 | os.system("rm -rf {}".format(artistPath)) 116 | break 117 | 118 | 119 | 120 | 121 | 122 | # run query of each query hashprint against a precomputed database 123 | # returns a dictionary of results corresponding to each artist 124 | # each artist produces results of their corresponding queries 125 | # the two level dict is returned for detailed analysis 126 | def runQueryAnalysis(audioTransName, hashprintTransName, testName): 127 | 128 | print("[runQueryAnalysis] start analysis of:", \ 129 | audioTransName, hashprintTransName, testName) 130 | 131 | testPath = os.path.join(audioTransName, testName) 132 | dbsPath = os.path.join(audioTransName, hashprintTransName) 133 | 134 | resultDict = dict() 135 | 136 | for artist in cu.listdir(os.path.join(audioTransName, hashprintTransName)): 137 | 138 | print("[runQueryAnalysis] start artist:", artist) 139 | 140 | artistResult = dict() 141 | hashprintDB = cu.loadHashprintDB(os.path.join(dbsPath, artist)) 142 | 143 | for songHashprint in cu.listdir(os.path.join(testPath, artist)): 144 | 145 | songHashprintPath = os.path.join(testPath, artist, songHashprint) 146 | artistResult[songHashprint] = hp.runHashprintQuery(songHashprintPath, hashprintDB) 147 | 148 | resultDict[artist] = artistResult 149 | 150 | analysis = ra.ResultAnalyzer(resultDict, audioTransName, hashprintTransName, testName) 151 | analysis.saveAll() 152 | analysis.saveToCommonCSV() 153 | 154 | 155 | def mainForEachKey(key): 156 | 157 | # for each type of audio transformation, if have not computed hashprint database: 158 | # 1. compute given transformation for each artist each song 159 | # 2. compute model using the computed transformation 160 | # 3. for each type of hashprint transformation, compute database 161 | 162 | print("[main] start constructing hashprint databases for:", key) 163 | 164 | if not os.path.exists(key): 165 | 166 | masterPath = os.path.join(key, MASTER) 167 | os.makedirs(masterPath) 168 | print("[main] add path:", masterPath) 169 | 170 | transformAll(MASTER, key, tm.AUDIO_TRANS_MAP[key]) 171 | 172 | modelPath = os.path.join(key, MODEL) 173 | os.makedirs(modelPath) 174 | print("[main] add path:", modelPath) 175 | makeModels(key) 176 | 177 | for hpkey in tm.HP_TRANS_MAP.keys(): 178 | 179 | hashprintPath = os.path.join(key, hpkey) 180 | os.makedirs(hashprintPath) 181 | print("[main] add path:", hashprintPath) 182 | 183 | makeDatabases(key, hpkey) 184 | 185 | 186 | else: 187 | print("[main] audio transfer type db detected for:", 188 | key, ", start search") 189 | 190 | # compute query result: 191 | # 1. if have not compute the hashprint of test set, compute hashprint 192 | # * this only depends on the audio transformation calculated model 193 | # 2. for each hashprint transformation, conduct query and analyze result 194 | print("[main] start querying databases") 195 | for test in tm.TESTS: 196 | print("[main] test for type:", test) 197 | testPath = os.path.join(key, test) 198 | modelPath = os.path.join(key, MODEL) 199 | 200 | if not os.path.exists(testPath): 201 | 202 | calculateHashprintAll(test, key, tm.AUDIO_TRANS_MAP[key]) 203 | 204 | for hpkey in tm.HP_TRANS_MAP.keys(): 205 | runQueryAnalysis(key, hpkey, test) 206 | 207 | # master cache is too big to store and useless, so delete when complete 208 | print("[main] deleting extra cache for databases for:", key) 209 | os.system("rm -rf {}/{}".format(key,MASTER)) 210 | 211 | # except: 212 | # print("[ERROR] encounters error during trnasformation", key) 213 | # print("[ERROR] deleting all data") 214 | # os.system("rm -rf {}".format(key)) 215 | 216 | 217 | def main(): 218 | try: 219 | testID = int(sys.argv[1]) 220 | testKey = tm.TEST_MAP[testID] 221 | mainForEachKey(testKey) 222 | 223 | except Exception as e: 224 | print("[ERROR] Unexpected exception occured:") 225 | print(e) 226 | 227 | 228 | 229 | if __name__ == '__main__': 230 | main() 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | -------------------------------------------------------------------------------- /testMap.py: -------------------------------------------------------------------------------- 1 | import AudioTransform as at 2 | 3 | 4 | TESTS = [ 5 | # "live", 6 | "live_clip10", 7 | # "live_clip20", 8 | # "live_clip30", 9 | "live_clip40", 10 | 11 | # "cover", 12 | "cover_clip10", 13 | # "cover_clip20", 14 | # "cover_clip30", 15 | "cover_clip40", 16 | 17 | ] 18 | 19 | TEST_MAP = { 20 | 21 | 1:"CQT_LIBROSA_NBIN96", 22 | 2:"CQT_LIBROSA_NOLOG_NBIN96", 23 | 3:"CQT_LIBROSA_BO24", 24 | 4:"CQT_LIBROSA_NOLOG_BO24", 25 | 26 | 27 | } 28 | 29 | 30 | AUDIO_TRANS_MAP = { 31 | 32 | 33 | "CQT_LIBROSA_NBIN96":at.CQT_LIBROSA(nbin=96).fun, 34 | "CQT_LIBROSA_NOLOG_NBIN96":at.CQT_LIBROSA_NOLOG(nbin=96).fun, 35 | "CQT_LIBROSA_BO24":at.CQT_LIBROSA(binPerOctave=24).fun, 36 | "CQT_LIBROSA_NOLOG_BO24":at.CQT_LIBROSA_NOLOG(binPerOctave=24).fun, 37 | # "CQT_LIBROSA_NBIN72":at.CQT_LIBROSA(nbin=72).fun, 38 | 39 | # "CQT_LIBROSA_HOP64":at.CQT_LIBROSA(hopLength=64).fun, 40 | # "CQT_LIBROSA_HOP128":at.CQT_LIBROSA(hopLength=128).fun, 41 | 42 | # "CQT_LIBROSA_BIN12":at.CQT_LIBROSA(binPerOctave=12).fun, 43 | 44 | # "CQT_LIBROSA_DOWN4":at.CQT_LIBROSA(downsample=4).fun, 45 | # "CQT_LIBROSA_DOWN2":at.CQT_LIBROSA(downsample=2).fun, 46 | 47 | # "CQT_LIBROSA_GRP2":at.CQT_LIBROSA(groupSize=2).fun, 48 | # "CQT_LIBROSA_GRP3":at.CQT_LIBROSA(groupSize=3).fun, 49 | } 50 | 51 | 52 | HP_TRANS_MAP = { 53 | 54 | "PITCH_SHIFT_3":at.PITCH_SHIFT(nPitch=3).fun, 55 | # "PITCH_SHIFT_4":at.PITCH_SHIFT(nPitch=4).fun, 56 | # "PITCH_SHIFT_5":at.PITCH_SHIFT(nPitch=5).fun, 57 | # "PITCH_SHIFT_6":at.PITCH_SHIFT(nPitch=6).fun, 58 | # "PITCH_SHIFT_7":at.PITCH_SHIFT(nPitch=7).fun, 59 | 60 | # "TIME_STRETCH_2_0.2":at.TIME_STRETCH(maxStretch=2, interval=0.2).fun, 61 | # "TIME_STRETCH_2_0.5":at.TIME_STRETCH(maxStretch=2, interval=0.5).fun, 62 | # "TIME_STRETCH_2_1":at.TIME_STRETCH(maxStretch=2, interval=1).fun, 63 | # "TIME_STRETCH_3_0.2":at.TIME_STRETCH(maxStretch=3, interval=0.2).fun, 64 | # "TIME_STRETCH_3_0.5":at.TIME_STRETCH(maxStretch=3, interval=0.5).fun, 65 | } 66 | 67 | 68 | -------------------------------------------------------------------------------- /tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # python3 downloadMusic.py > log/downloadMusic.log 4 | 5 | for i in {2..4} 6 | do 7 | python3 runHashprint.py $i > log/runHashprint_$i.log 8 | done 9 | 10 | 11 | -------------------------------------------------------------------------------- /title_index.json: -------------------------------------------------------------------------------- 1 | { 2 | "0": "Let's Dance (\uc720\uc7ac\uc11d, \ud0c0\uc774\uac70 JK, \uc724\ubbf8\ub798)", 3 | "1": "\uc78a\uc9c0 \ub9d0\uc544\uc694", 4 | "2": "\uc0ac\ub791\ud574\uc694", 5 | "3": "GG Be (\uc9c0\uc9c0\ubca0) (feat. Jennie Kim Of YG New Artist)", 6 | "4": "I Love U, Oh Thank U (feat. \uae40\ud0dc\uc6b0)", 7 | "5": "Voice (feat. Swings)", 8 | "6": "\ub098\ub97c \uc0ac\ub791\ud588\ub358 \uc0ac\ub78c\uc544", 9 | "7": "\ub3cc\uc544\uc640 \ub098\uc05c \ub108", 10 | "8": "\uadf8\ub0a8\uc790 \uadf8\uc5ec\uc790 (feat. \uc7a5\ud61c\uc9c4)", 11 | "9": "\ud3ed\ud48d", 12 | "10": "\uc0bc\ud0a4\uc9c0\ub9c8", 13 | "11": "\ucc98\uc74c\ucc98\ub7fc \uadf8\ub54c\ucc98\ub7fc (feat. \uac15\ubbfc\uacbd From Davichi)", 14 | "12": "\ub85c\ub9e8\ud2f1 \uaca8\uc6b8 (feat. \uae40\uc9c4\ud638 Of SG\uc6cc\ub108\ube44)", 15 | "13": "Hot Girl", 16 | "14": "\uac16\uace0\ub180\ub798 (feat. \ub2e4\uc774\ub098\ubbf9 \ub4c0\uc624)", 17 | "15": "\uc644\uc18c \uadf8\ub300", 18 | "16": "\uadf8\ub798\uc11c...", 19 | "17": "\uc785\uc220\uc774 \ubc09\ub2e4", 20 | "18": "\ub0b4 \uc5ec\uc790 (feat. \uc288\ud504\ub9bc\ud300)", 21 | "19": "Girls (feat. Lil Kim)", 22 | "20": "\ubc14\ubcf8\uac00\ubd10", 23 | "21": "\uc2f8\uc774\ub80c (Siren) (feat. \ubbf8\uc2a4\ud130\ud0c0\uc774\ud47c)", 24 | "22": "\uc0ac\ub791\uc740 \ub108 \ud558\ub098\ubfd0 (With \uc9c0\uc624, \uc1fc\uad70)", 25 | "23": "My Valentine (feat. \ubc15\uc9c4\uc601)", 26 | "24": "No No No", 27 | "25": "7\ub144\uac04\uc758 \uc0ac\ub791", 28 | "26": "Candy Man", 29 | "27": "\uad1c\ucc2e\uc544", 30 | "28": "\uc0ac\ub791\uc5d0 \uc5b4\ub5a4 \ub9d0\ub85c\ub3c4 (\uc0ac\ub9d0\ub85c\ub3c4)", 31 | "29": "\ub458 \uc911\uc5d0 \ud558\ub098 (Runaway)", 32 | "30": "\uc544\ub9ac\ub791", 33 | "31": "\uc774\ubcc4\uc740 \uba40\uc5c8\uc8e0", 34 | "32": "HER", 35 | "33": "\uac19\uc774 \uac78\uc744\uae4c", 36 | "34": "\uc774\ub7f4\uac70\uba74", 37 | "35": "\uc6b0\ub9ac\uac00 \uc0ac\ub791\ud558\ub294 \ubc29\ubc95", 38 | "36": "Bye", 39 | "37": "\uc9c4\uc2e4 \ud639\uc740 \ub300\ub2f4", 40 | "38": "\uc3d8\ub9ac \uc3d8\ub9ac (Sorry, Sorry)", 41 | "39": "\ub0b4\uac00 \ub2f9\uc2e0\uc744 \uc0ac\ub791\ud558\ub294 \uc774\uc720", 42 | "40": "Talk To Me", 43 | "41": "Insomnia (\ubd88\uba74\uc99d)", 44 | "42": "\uad1c\ucc2e\uc544 \uc0ac\ub791\uc774\uc57c", 45 | "43": "\uc090\ub531\ud558\uac8c (Crooked)", 46 | "44": "\uc2ec\uc7a5\uc774 \uc5c6\uc5b4", 47 | "45": "\uacb0\ud63c\ud574 \uc904\ub798 (feat. Bizniz)", 48 | "46": "\uace0\ub9d9\uc2b5\ub2c8\ub2e4", 49 | "47": "\uc7a0\uc2dc \uc548\ub155\ucc98\ub7fc", 50 | "48": "\ub099\uc778", 51 | "49": "Mystery", 52 | "50": "\ud558\ub298\uc704\ub85c (B.Remix) (feat. \uae40\uc9c0\uc740)", 53 | "51": "So Wonderful", 54 | "52": "\uadf8\uac8c \uc544\ub2c8\uace0", 55 | "53": "Rainy Day", 56 | "54": "Change (feat. \uc6a9\uc900\ud615 From Beast)", 57 | "55": "\ub098\ube44\ud6a8\uacfc (feat. SG \uc6cc\ub108\ube44)", 58 | "56": "\ub2e4\uc2dc \ud0dc\uc5b4\ub098\ub3c4", 59 | "57": "\uc18c\ub144\uc774\uc5ec", 60 | "58": "\uc6c3\uc5b4\uc694", 61 | "59": "\ubbf8\uce5c\uac8c \uc544\ub2c8\ub77c\uad6c\uc694 (feat. MC \uc2a4\ub098\uc774\ud37c)", 62 | "60": "\uc9d1\uc5d0 \uc548\uac08\ub798 (feat. \uae31\uc2a4)", 63 | "61": "\ub0b4 \uadc0\uc5d0 \uce94\ub514 (feat. \ud0dd\uc5f0 Of 2PM)", 64 | "62": "Body Language (feat. \ubc94\ud0a4)", 65 | "63": "\uaf43\uc1a1\uc774\uac00", 66 | "64": "\uc560\uc778 \ub9cc\ub4e4\uae30", 67 | "65": "Fly Boy", 68 | "66": "\uc0ac\ub791 \ucc38...", 69 | "67": "Pass Me By", 70 | "68": "\uccab\uc0ac\ub791", 71 | "69": "\ub0c9\uba74 (\ubc15\uba85\uc218, \uc81c\uc2dc\uce74, \uc774\ud2b8\ub77c\uc774\ube0c)", 72 | "70": "\ud560\ub9d0 \uc788\uc5b4\uc694 (Gotta Talk To U)", 73 | "71": "\u60b2\u8ac7(\ube44\ub2f4) (\uc2ac\ud508 \uc774\uc57c\uae30)", 74 | "72": "\uadf8 \uc911\uc5d0 \uadf8\ub300\ub97c \ub9cc\ub098", 75 | "73": "Juliet ", 76 | "74": "Lost Stars", 77 | "75": "Falling In Love", 78 | "76": "All Right", 79 | "77": "\ub108\uc758 \ubaa8\ub4e0 \uc21c\uac04", 80 | "78": "Tik Tok (feat. \uc724\uc740\ud61c)", 81 | "79": "\uadf8\ub550 \ubbf8\ucc98 \uc54c\uc9c0 \ubabb\ud588\uc9c0", 82 | "80": "\ud558\ubaa8\ub2c8", 83 | "81": "\uc228\uc18c\ub9ac(Breath) (Sung by \ud0dc\uc5f0(\uc18c\ub140\uc2dc\ub300) & \uc885\ud604(\uc0e4\uc774\ub2c8))", 84 | "82": "Paradise", 85 | "83": "\uc5b4\ub5bb\uac8c \ub2c8\uac00", 86 | "84": "\ub180\ub9cc\ud07c \ub180\uc544\ubd24\uc5b4 (Had Enough Parties)", 87 | "85": "\uc5b4\ub824\uc6b4 \uc5ec\uc790", 88 | "86": "\ub0a8\uc790\ub97c \ubab0\ub77c", 89 | "87": "\uc608\ubed0 \uc608\ubed0", 90 | "88": "\uc804\uad6d\ubbfc \ub7ec\ube0c\uc1a1 (feat. MC\ubabd, \uc720\ub9ac Of Cool)", 91 | "89": "\ub2e4\uc2dc \uc2dc\uc791\uc774\uc57c (Start All Over Again)", 92 | "90": "\ub208\ubb3c\uc18c\ub9ac", 93 | "91": "Gloomy Sunday", 94 | "92": "Hey You", 95 | "93": "Bo Peep Bo Peep", 96 | "94": "\uc73c\ub974\ub801 (Growl)", 97 | "95": "\ud63c\uc7a3\ub9d0", 98 | "96": "\ub514\uc2a4\ucf54\uc655", 99 | "97": "NoNoNo", 100 | "98": "\uc544\ubb34\ub9ac \uc0dd\uac01\ud574\ub3c4 \ub09c \ub108\ub97c", 101 | "99": "Heartbreaker", 102 | "100": "Indian Boy (feat. \uc7a5\uadfc\uc774, B.I)", 103 | "101": "Love Story", 104 | "102": "\ubcf4\uace0\ubcf4\uace0", 105 | "103": "\ud3b8\uc9c0", 106 | "104": "\uae30\ub2e4\ub824 \uadf8\ub9ac\uace0 \uc900\ube44\ud574", 107 | "105": "\ub0b4 \uc0b6\uc758 \ubc18", 108 | "106": "\ucc29\ud574 \ube60\uc84c\uc5b4 (Stupid In Love)", 109 | "107": "\ub07c\ubd80\ub9ac\uc9c0\ub9c8", 110 | "108": "Once In A Lifetime", 111 | "109": "\ub208\ubb3c\uc740 \ubaa8\ub974\uac8c", 112 | "110": "\uac00\uc2b4 \uc544\ud30c\ub3c4", 113 | "111": "\uc219\ub140\uac00 \ubabb \ub3fc (Damaged Lady)", 114 | "112": "\uc0ac\ub791\ub208 (feat. \ub4dc\ub85c\ud50c\ub81b, \uc2ac\ub77c\uc784)", 115 | "113": "\uadf8\ub7f0\uac00\ubd10\uc694...", 116 | "114": "\uc8fd\ub3c4\ub85d \uc0ac\ub791\ud574 2 (feat. \uc870\uc131\ubaa8)", 117 | "115": "I Like 2 Party", 118 | "116": "\uadf8\ub140\ub97c \ubbff\uc9c0 \ub9c8\uc138\uc694", 119 | "117": "\uc0ac\ub791\uc774\ub780 \ub9d0\uc774 \uc5b4\uc6b8\ub9ac\ub294 \uc0ac\ub78c (\uc0ac\ub9d0\uc5b4\uc0ac)", 120 | "118": "\ud1b5\uc99d", 121 | "119": "\ub208\ubb3c\uc774 \ub098\uc11c (New ver.)", 122 | "120": "Hot Stuff", 123 | "121": "\uc624\ub298 \ud5e4\uc5b4\uc84c\uc5b4\uc694", 124 | "122": "\ud5e4\uc5b4\uc9c4 \ub450\uc0ac\ub78c (feat. \ud0dc\uc778)", 125 | "123": "Gee", 126 | "124": "\ub099\uc5fd \uc5d4\ub529", 127 | "125": "\uaf43", 128 | "126": "\ubbf8\uce58\uac8c \ubcf4\uace0\uc2f6\uc740", 129 | "127": "\uc0ac\ub791\ud588\uc5b4\uc694 (feat. \ub370\ub2c8 \uc548)", 130 | "128": "Luv is (feat. \ubc30\uce58\uae30)", 131 | "129": "\uc88b\uc558\ub358 \uac74, \uc544\ud320\ub358 \uac74 (When I Was... When U Were...) (Sung by f(KRYSTAL) & CHEN(EXO))", 132 | "130": "\uadf8\ub300 \ub098\ub97c \ubcf4\ub098\uc694", 133 | "131": "Dreams Come True", 134 | "132": "Dreaming", 135 | "133": "\ubabb \uc78a\uc740 \uac70\uc8e0 (If)", 136 | "134": "\uace0\uc789\uace0\uc789 (Going Going)", 137 | "135": "\uc8fc\ubb38 - MIROTIC (Clean ver.)", 138 | "136": "\ub204\ub098\uc758 \uafc8", 139 | "137": "\ubc14\ub78c\uc758 \ub178\ub798", 140 | "138": "\ubbf8\uc6cc\ub3c4 \uc88b\uc544", 141 | "139": "\uc0ac\ub791\uc740...\ud5a5\uae30\ub97c \ub0a8\uae30\uace0", 142 | "140": "\uc624\ub298\ub530\ub77c \ubcf4\uace0\uc2f6\uc5b4\uc11c \uadf8\ub798", 143 | "141": "My Style", 144 | "142": "\ub10c \ub0b4\uaebc\ub77c\ub294\uac78 (feat. \uc2a4\uc719\uc2a4)", 145 | "143": "\ubc14\ub78c\uc758 \uc720\ub839", 146 | "144": "Let Me Dance (feat. Teddy)", 147 | "145": "\ub108\ub3c4 \ub098\ucc98\ub7fc", 148 | "146": "\uc794\ud639\ub3d9\ud654", 149 | "147": "Never Again", 150 | "148": "\ud56b \uc564 \ucf5c\ub4dc (Hot \uff06 Cold)", 151 | "149": "\uc0ac\ub791\ud558\uba74 \uc548\ub418\ub098\uc694", 152 | "150": "\uc138\uae00\uc790", 153 | "151": "\uc0ac\ub791\ud569\ub2c8\ub2e4", 154 | "152": "\ub0a8\uc790\ub97c \ubbff\uc9c0\ub9c8 (feat. Big Tone)", 155 | "153": "\uc544\uc2dc\ub098\uc694", 156 | "154": "Reds, Go Together", 157 | "155": "\ud754\ub4e4\uc5b4\ubd10 (feat. MC \ud0c0\uc774\ud47c)", 158 | "156": "Cry", 159 | "157": "U & I", 160 | "158": "Feel Good", 161 | "159": "\ub4e3\uc8e0... \uadf8\ub300\ub97c", 162 | "160": "\uccab\ub208\uc774 \uc624\uba74 \uc124\ub808\uc600\ub358 \uaf2c\ub9c8\uc544\uc774 (Time Travel)", 163 | "161": "\ub0b4 \ub9c8\uc74c\uc758 \ubcf4\uc11d\uc0c1\uc790", 164 | "162": "Let It Go (\uaca8\uc6b8\uc655\uad6d OST \ud6a8\ub9b0 \ubc84\uc804)", 165 | "163": "\uafc8\uc744 \uafb8\ub2e4 (Main ver.)", 166 | "164": "\ud558\ub8e8 \ud558\ub8e8", 167 | "165": "1\ubd84 1\ucd08 (feat. \ud0c0\ub8e8)", 168 | "166": "Monologue", 169 | "167": "Hot Issue", 170 | "168": "24\uc2dc\uac04\uc774 \ubaa8\uc790\ub77c", 171 | "169": "\uc5ec\uc778\uc758 \ud5a5\uae30", 172 | "170": "\uc0ac\ub791\ud574 \uc0ac\ub791\ud574 \uc0ac\ub791\ud574", 173 | "171": "\ud55c\ub3d9\uc548 \ub738\ud588\uc5c8\uc9c0", 174 | "172": "\uace8\ubaa9\uae38 \uc5b4\uadc0\uc5d0\uc11c", 175 | "173": "\ub3cc\uc544\uc62c \uc21c \uc5c6\ub098\uc694", 176 | "174": "Beautiful Life", 177 | "175": "Abracadabra", 178 | "176": "No One Else Like You", 179 | "177": "\uc378\ub0a8\uc378\ub140 (feat. \ud718\uc131)", 180 | "178": "8282", 181 | "179": "\uc2dc\uacc4", 182 | "180": "\ube60\ube60\ube60", 183 | "181": "Fly", 184 | "182": "\ub208 \ub0b4\ub9ac\ub294 \ub9c8\uc744 (With \ube0c\ub77c\uc6b4 \uc544\uc774\ub4dc \uac78\uc2a4, K.Will, \uac04\ubbf8\uc5f0, \ud14c\uc774, \uc219\ud76c, \uc548\uc601\ubbfc)", 185 | "183": "\uc0ac\ub791\uc2a4\ub7ec\uc6cc", 186 | "184": "\ub0b4\uc0ac\ub78c: Partner For Life", 187 | "185": "\ub4e4\ub9ac\ub098\uc694...", 188 | "186": "\uc0ac\ub791\ud558\uba74 \uc548\ub420\uae4c", 189 | "187": "\uc6b0\ub9ac \uc0ac\ub791\ud558\uac8c \ub410\uc5b4\uc694", 190 | "188": "\uc624\ub79c\ub9cc\uc774\uc57c", 191 | "189": "\uc8fc\ud64d\ube5b \uac70\ub9ac", 192 | "190": "\ud63c\uc790\ud558\ub294 \uc0ac\ub791", 193 | "191": "\uc0ac\ub791 \uc548\ud574", 194 | "192": "\ub108\ub97c \uc0ac\ub791\ud574", 195 | "193": "\ud5e4\uc5b4\uc9c0\ub294 \uc911\uc785\ub2c8\ub2e4", 196 | "194": "V.I.P (Volume Instead Pause)", 197 | "195": "\ubcf4\uc774\uc9c0 \uc54a\ub294 \uc778\uc0ac (\ud574\uae08 ver.)", 198 | "196": "My Love", 199 | "197": "FANTASTIC BABY", 200 | "198": "No Love No More", 201 | "199": "Tell Me If You Wanna Go Home", 202 | "200": "Do It Do It (feat. Maniac)", 203 | "201": "\uadf8\ub300\ub77c\uc11c", 204 | "202": "\ubdf0\ud2f0\ud480 \uac78 (feat. \uc77c\ub809\ud2b8\ub85c \ubcf4\uc774\uc988)", 205 | "203": "\ube44\ubc00", 206 | "204": "\ub10c \uadf8\ub0a0 (feat. \uc720\uc778\ub098)", 207 | "205": "\uc5b4\uca4c\uba74...", 208 | "206": "\ub2ec\ub77c ", 209 | "207": "\uc0ac\ub791\uc544 \uac00\uc9c0\ub9c8", 210 | "208": "\uc2dc\uc791\ubcf4\ub2e4 \ub05d\uc774 \uc544\ub984\ub2e4\uc6b4 \uc0ac\ub791 (duet with \uc774\uc2b9\ucca0)", 211 | "209": "Touch Love", 212 | "210": "Hold The Line", 213 | "211": "My Color", 214 | "212": "\ub4e4\ub9ac\ub098\uc694", 215 | "213": "Good-Bye Luv..", 216 | "214": "You (\ub300\ub2e8\ud55c \uc6b0\uc5f0)", 217 | "215": "\uc544\ubab0\ub808\ub4dc (Amoled)", 218 | "216": "\uc5ec\uc790 \ub300\ud1b5\ub839", 219 | "217": "\uc5ec\uc218 \ubc24\ubc14\ub2e4", 220 | "218": "\uc544\uc774\ucc98\ub7fc (feat. \uac15\ubbfc\ud76c)", 221 | "219": "\uc798 \uc9c0\ub0b4\ub2c8 (feat. \ub10b\uc5c5\uc0e8)", 222 | "220": "\uc2f8\uc774 \uc751\uc6d0\uac00 - We Are The One", 223 | "221": "\uace8\ubaa9\uae38", 224 | "222": "\ucc98\uc74c\ucc98\ub7fc", 225 | "223": "Please Don't Go", 226 | "224": "Wanna", 227 | "225": "2 Am (2:00 Am)", 228 | "226": "Venus", 229 | "227": "\ub2e4\ud589\uc774\ub2e4", 230 | "228": "\ubbf8\uc2a4\ud130", 231 | "229": "\ubbf8\uce58GO (GO)", 232 | "230": "BAAAM (feat. Muzie Of UV)", 233 | "231": "Sherlock\u318d\uc15c\ub85d (Clue + Note)", 234 | "232": "Set Me Free (Sung by \ud0dc\uc5f0(\uc18c\ub140\uc2dc\ub300))", 235 | "233": "\uc548\ub155", 236 | "234": "\uc774\uc0c1\ud615", 237 | "235": "\ubc9a\uaf43 \uc5d4\ub529", 238 | "236": "My Boyfriend", 239 | "237": "\uc220 \ud55c\uc794 \ud574\uc694", 240 | "238": "Born This Way", 241 | "239": "Nobody", 242 | "240": "\ub0b4 \ub9c8\uc74c\uc774 \uadf8\ub300\uac00 \ub418\uc5b4 (\ub0b4\ub9c8\uadf8)", 243 | "241": "Let It Go", 244 | "242": "\ub9c8\ub9ac\uc624\ub124\ud2b8", 245 | "243": "\uc0ac\ub791\uc744 \uc78a\ub2e4", 246 | "244": "\uc678\ub85c\uc6c0\uc99d\ud3ed\uc7a5\uce58 (\ube0c\ub798\ub4dc \ub4dc\ub7fc \ud55c\ud310 \uc26c\uae30)", 247 | "245": "Let's Talk About Love (feat. G-Dragon & \ud0dc\uc591 Of Bigbang)", 248 | "246": "Tonight", 249 | "247": "\uadf8 \ubc14\ub78c\uc18c\ub9ac", 250 | "248": "\uc18c\uc6d0\uc744 \ub9d0\ud574\ubd10 (Genie)", 251 | "249": "\uad6c\uc18d", 252 | "250": "1,2,3,4. (\uc6d0,\ud22c,\uc4f0\ub9ac,\ud3ec.)", 253 | "251": "\uc57c\uc0c1\uace1(\u591c\u60f3\u66f2)", 254 | "252": "\ub0a0\uac1c\ubf08 (Hot Wings) (feat. \ud6a8\ub9b0 Of Sistar)", 255 | "253": "\uc5b4\uca4c\ub2e4", 256 | "254": "\ud658\ud76c", 257 | "255": "\ub108\uc5d0\uac90 \ub4e4\ub9ac\uc9c0 \uc54a\ub294 \uadf8 \ub9d0", 258 | "256": "\ud5a5\uc218", 259 | "257": "\ubc18\ucabd", 260 | "258": "\uac70\uc9d3\ub9d0 (Part.1)", 261 | "259": "\uc0ac\ub791\ud558\uace0 \uc788\uc2b5\ub2c8\ub2e4", 262 | "260": "I Don't Care", 263 | "261": "Black & White", 264 | "262": "\ud1a1\ud1a1 (Tok Tok) (feat. Soya)", 265 | "263": "\uad34\ub3c4 (Danger)", 266 | "264": "\uceec\ub7ec\ub9c1", 267 | "265": "You", 268 | "266": "\ub208\ubb3c\uc0d8", 269 | "267": "All About", 270 | "268": "Hey DJ (70's Remix)", 271 | "269": "\ub2c8\uac00 \ubb54\ub370 (Who You?)", 272 | "270": "Love Love Love", 273 | "271": "L4L (Lookin' For Luv) (feat. Dok2 & The Quiett)", 274 | "272": "\ub098\ub9cc \ubab0\ub790\ub358 \uc774\uc57c\uae30", 275 | "273": "\ub0b4\uaebc\uc911\uc5d0 \ucd5c\uace0", 276 | "274": "1004 (\ub108\ub294 \ub0b4\uc6b4\uba85 2) (feat. \ub0af\uc120)", 277 | "275": "\uc0ac\ub791\uc740 \ub2e4 \uadf8\ub7f0\uac70\ub798\uc694", 278 | "276": "\ubbf8\uce58\uac8c \ub9cc\ub4e4\uc5b4", 279 | "277": "\uc0ac\ub791\ud558\uba74 \uc548 \ub418\ub2c8", 280 | "278": "Sexy Girl", 281 | "279": "\uadf8\ub300\uc5d0\uac8c \ud558\ub294 \ub9d0", 282 | "280": "\uadf8\ub140\ucc98\ub7fc", 283 | "281": "\ubd04\ubc14\ub78c", 284 | "282": "\ud558\ub8e8 (A Day Without You) (Sung by \uc885\ud604(\uc0e4\uc774\ub2c8) & CHEN(EXO))", 285 | "283": "\uc608\ubed0\uc84c\ub2e4 (feat. \uc9c0\ucf54 Of \ube14\ub77d\ube44)", 286 | "284": "\ub9cc\ub9cc\ud558\ub2c8", 287 | "285": "\uc804\ud65c \uac70\ub124", 288 | "286": "\uc0ac\ub791\uc7c1\uc774 (feat. \uae40\uc740\uc815 Of Jewelry)", 289 | "287": "\ube44\uac00\uc640", 290 | "288": "\ud55c\uc5ec\ub984\ubc24\uc758 \uafc0", 291 | "289": "\uc65c\uc774\ub798", 292 | "290": "Do You Love Me", 293 | "291": "Love Is An Open Door", 294 | "292": "\ubbf8\ucce4\uc5b4 (feat. \uc5d0\ub9ad)", 295 | "293": "\ubc84\uc2a4 \uc548\uc5d0\uc11c (feat. \uae40\ud604\uc219)", 296 | "294": "\ubb34\uc9c0\uac1c", 297 | "295": "Heartbeat", 298 | "296": "\uc0ac\ub791\uc740 (Song \uc815\uc778)", 299 | "297": "\ub108 \ub54c\ubb38\uc5d0", 300 | "298": "I Swear", 301 | "299": "Black (feat. Jennie Kim Of YG New Artist)", 302 | "300": "Touch My Body", 303 | "301": "\ud558\ub8e8\ud558\ub8e8", 304 | "302": "\uc6d0\ub354\uc6b0\uba3c", 305 | "303": "Darling", 306 | "304": "\ubb3c \uc88b\uc544?", 307 | "305": "\uc0ac\uace0\ucce4\uc5b4\uc694", 308 | "306": "Hey Mr. Big", 309 | "307": "\uc624\ub298 \uac19\uc740 \ub208\ubb3c\uc774", 310 | "308": "\uc9e0\uc9dc\ub77c", 311 | "309": "\uc378 (feat. \ub9b4\ubcf4\uc774 Of \uae31\uc2a4)", 312 | "310": "\ubc14\ub798", 313 | "311": "\ud55c\uc0ac\ub78c\uc744 \uc704\ud55c \ub9c8\uc74c", 314 | "312": "\uc0ac\ub791\ud588\uc5c8\ub2e4\uba74", 315 | "313": "A Real Man", 316 | "314": "\uc0b4\uc790 (The Cure)", 317 | "315": "Honey (\ud5c8\ub2c8)", 318 | "316": "Sign", 319 | "317": "\ub098 \ub54c\ubb38\uc5d0", 320 | "318": "\ud798\ub4e4\uc5b4", 321 | "319": "JoJo", 322 | "320": "\ud589\ubcf5\ubcd1 (feat. Shorry J Of MM)", 323 | "321": "\uc7a0 \ubabb\ub4dc\ub294 \ubc24 (feat. \ud380\uce58(Punch))", 324 | "322": "Ghost (\uace0\uc2a4\ud2b8)", 325 | "323": "\uacf5\ud5c8\ud574", 326 | "324": "\ud55c\ubc88\ucbe4\uc740", 327 | "325": "\uc544\ub294\uc0ac\ub78c \uc598\uae30", 328 | "326": "Break It", 329 | "327": "\uc5ec\uc790\ub294", 330 | "328": "\ub0ae\uacfc \ubc24", 331 | "329": "\ub808\uc2dc\ud53c" 332 | } --------------------------------------------------------------------------------