Skip to content

Commit 8830616

Browse files
committed
wip merge guessfeats
1 parent 810a961 commit 8830616

File tree

5 files changed

+113
-328
lines changed

5 files changed

+113
-328
lines changed

root/static/scripts/edit/utility/guessFeat.js

Lines changed: 106 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
/*
2+
* @flow
23
* Copyright (C) 2015 MetaBrainz Foundation
34
*
45
* This file is part of MusicBrainz, the open internet music database,
@@ -8,9 +9,11 @@
89

910
import balanced from 'balanced-match';
1011
import $ from 'jquery';
12+
import mutate from 'mutate-cow';
1113

1214
import '../../common/entity.js';
1315

16+
import {expect} from '../../../../utility/invariant.js';
1417
import {
1518
BRACKET_PAIRS,
1619
MIN_NAME_SIMILARITY,
@@ -30,8 +33,30 @@ import {
3033
import getRelatedArtists from './getRelatedArtists.js';
3134
import getSimilarity from './similarity.js';
3235

36+
type GuessFeatEntityT = {
37+
+artistCredit: IncompleteArtistCreditT,
38+
+name: string,
39+
+relationships?: $ReadOnlyArray<RelationshipT>,
40+
};
41+
42+
type GuessFeatResultT = {
43+
artistCreditNames: $ReadOnlyArray<IncompleteArtistCreditNameT>,
44+
name: string,
45+
};
46+
47+
type ExpandedArtistCreditNameT = {
48+
...IncompleteArtistCreditNameT,
49+
similarity: number,
50+
};
51+
52+
type ExtractedCreditsT = {
53+
+artistCredit: Array<ExpandedArtistCreditNameT>,
54+
+joinPhrase: string,
55+
+name: string,
56+
};
57+
3358
/* eslint-disable sort-keys */
34-
export const featRegex = /(?:^\s*|[,-]\s*|\s+)((?:ft|feat||)(?:[.]|(?=\s))|(?:featuring|)(?=\s))\s*/i;
59+
export const featRegex: RegExp = /(?:^\s*|[,-]\s*|\s+)((?:ft|feat||)(?:[.]|(?=\s))|(?:featuring|)(?=\s))\s*/i;
3560
/*
3661
* `featQuickTestRegex` is used to quickly test whether a title *might*
3762
* contain featured artists. It's fine if it returns false-positives.
@@ -40,10 +65,13 @@ export const featRegex = /(?:^\s*|[,,--]\s*|\s+)((?:ft|feat|ft|fea
4065
const featQuickTestRegex = /ft|feat||/i;
4166
const collabRegex = /([,]?\s+(?:&|and|et|||)\s+||[,;]\s+|\s*[/]\s*|\s+(?:vs|)[.]\s+)/i;
4267

43-
function extractNonBracketedFeatCredits(str, artists) {
68+
function extractNonBracketedFeatCredits(
69+
str: string,
70+
artists: Array<ArtistT>,
71+
): ExtractedCreditsT {
4472
const parts = str.split(featRegex).map(clean);
4573

46-
function fixFeatJoinPhrase(existing) {
74+
function fixFeatJoinPhrase(existing: string) {
4775
const joinPhrase = existing ? (
4876
' ' +
4977
fromFullwidthLatin(existing)
@@ -75,7 +103,10 @@ function extractNonBracketedFeatCredits(str, artists) {
75103
};
76104
}
77105

78-
function extractBracketedFeatCredits(str, artists) {
106+
function extractBracketedFeatCredits(
107+
str: string,
108+
artists: Array<ArtistT>,
109+
): ExtractedCreditsT {
79110
return BRACKET_PAIRS.reduce(function (accum, pair) {
80111
let name = '';
81112
let joinPhrase = accum.joinPhrase;
@@ -122,16 +153,18 @@ function extractBracketedFeatCredits(str, artists) {
122153
}
123154

124155
export function extractFeatCredits(
125-
str, artists, allowEmptyName,
126-
) {
127-
if (!featQuickTestRegex.test(str)) {
128-
return {name: str, joinPhrase: '', artistCredit: []};
156+
name: string,
157+
artists: Array<ArtistT>,
158+
allowEmptyName: boolean,
159+
): ExtractedCreditsT {
160+
if (!featQuickTestRegex.test(name)) {
161+
return {name, joinPhrase: '', artistCredit: []};
129162
}
130163

131-
const m1 = extractBracketedFeatCredits(str, artists);
164+
const m1 = extractBracketedFeatCredits(name, artists);
132165

133166
if (!m1.name && !allowEmptyName) {
134-
return {name: str, joinPhrase: '', artistCredit: []};
167+
return {name, joinPhrase: '', artistCredit: []};
135168
}
136169

137170
const m2 = extractNonBracketedFeatCredits(
@@ -149,24 +182,30 @@ export function extractFeatCredits(
149182
};
150183
}
151184

152-
function bestArtistMatch(artists, name) {
185+
function bestArtistMatch(
186+
artists: Array<ArtistT> | null,
187+
name: string,
188+
): ExpandedArtistCreditNameT | null {
153189
if (!artists) {
154190
return null;
155191
}
156-
let match = null;
192+
let match: ExpandedArtistCreditNameT | null = null;
157193
for (const artist of artists) {
158194
const similarity = getSimilarity(name, artist.name);
159195
if (
160196
similarity >= MIN_NAME_SIMILARITY &&
161197
(match == null || similarity > match.similarity)
162198
) {
163-
match = {similarity, artist, name};
199+
match = {similarity, artist, name, joinPhrase: ''};
164200
}
165201
}
166202
return match;
167203
}
168204

169-
function expandCredit(fullName, artists) {
205+
function expandCredit(
206+
fullName: string,
207+
artists: Array<ArtistT>,
208+
): Array<ExpandedArtistCreditNameT> {
170209
/*
171210
* See which produces a better match to an existing artist: the full
172211
* credit, or the individual credits as split by collabRegex. Some artist
@@ -176,7 +215,7 @@ function expandCredit(fullName, artists) {
176215
*/
177216
const bestFullMatch = bestArtistMatch(artists, fullName);
178217

179-
function fixJoinPhrase(existing) {
218+
function fixJoinPhrase(existing: string) {
180219
const joinPhrase = (existing || ' & ');
181220

182221
return hasFullwidthLatin(existing)
@@ -185,12 +224,12 @@ function expandCredit(fullName, artists) {
185224
}
186225

187226
const splitParts = fullName.split(collabRegex);
188-
const splitMatches = [];
189-
let bestSplitMatch;
227+
const splitMatches: Array<ExpandedArtistCreditNameT> = [];
228+
let bestSplitMatch: ExpandedArtistCreditNameT;
190229

191230
for (let i = 0; i < splitParts.length; i += 2) {
192231
const name = splitParts[i];
193-
const match = {
232+
const match: ExpandedArtistCreditNameT = {
194233
similarity: -1,
195234
artist: null,
196235
name,
@@ -203,15 +242,60 @@ function expandCredit(fullName, artists) {
203242
}
204243
}
205244

206-
if (bestFullMatch && bestFullMatch.similarity > bestSplitMatch.similarity) {
207-
bestFullMatch.joinPhrase = fixJoinPhrase();
245+
if (bestFullMatch && bestSplitMatch &&
246+
bestFullMatch.similarity > bestSplitMatch.similarity) {
247+
bestFullMatch.joinPhrase = fixJoinPhrase('');
208248
return [bestFullMatch];
209249
}
210250

211251
return splitMatches;
212252
}
213253

214-
export default function guessFeat(entity) {
254+
export default function guessFeat(
255+
entity: GuessFeatEntityT,
256+
): GuessFeatResultT | null {
257+
const name = entity.name;
258+
259+
if (empty(name)) {
260+
// Nothing to guess from an empty name
261+
return null;
262+
}
263+
264+
const relatedArtists = getRelatedArtists(entity.relationships);
265+
266+
const match = extractFeatCredits(
267+
name, relatedArtists, false,
268+
);
269+
270+
if (!match.name || !match.artistCredit.length) {
271+
return null;
272+
}
273+
274+
const artistCreditNamesCtx = mutate(entity.artistCredit.names);
275+
artistCreditNamesCtx.set(
276+
entity.artistCredit.names.length - 1,
277+
'joinPhrase',
278+
match.joinPhrase,
279+
);
280+
expect(last(match.artistCredit)).joinPhrase = '';
281+
282+
for (const name of match.artistCredit) {
283+
artistCreditNamesCtx.write().push({
284+
artist: name.artist,
285+
joinPhrase: name.joinPhrase,
286+
name: name.name,
287+
});
288+
}
289+
290+
return {
291+
name: match.name,
292+
artistCreditNames: artistCreditNamesCtx.final(),
293+
};
294+
}
295+
296+
export function guessFeatForReleaseEditor(
297+
entity: any,
298+
): any {
215299
const name = entity.name();
216300

217301
if (empty(name)) {
@@ -250,7 +334,7 @@ export default function guessFeat(entity) {
250334
}
251335

252336
// For use outside of the release editor.
253-
export function initGuessFeatButton(formName) {
337+
export function initGuessFeatButton(formName: any): any {
254338
const source = MB.getSourceEntityInstance();
255339
const augmentedEntity = Object.assign(
256340
Object.create(source),

0 commit comments

Comments
 (0)