Skip to content

Commit

Permalink
feat: import single works
Browse files Browse the repository at this point in the history
works both from work and release editors
  • Loading branch information
dvirtz committed Dec 16, 2024
1 parent 7867409 commit b19cffa
Show file tree
Hide file tree
Showing 25 changed files with 561 additions and 207 deletions.
3 changes: 1 addition & 2 deletions rollup.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export default defineConfig(
minimize: false,
extensions: ['.ts', '.tsx', '.mjs', '.js', '.jsx'],
postcss: {
inject: name === 'acum-work-import',
inject: false,
minimize: true,
},
aliases: {
Expand All @@ -38,7 +38,6 @@ export default defineConfig(
'solid-js/store': 'VM.solid.store',
'@violentmonkey/ui': 'VM',
},
indent: false,
},
}))
);
20 changes: 15 additions & 5 deletions src/acum-work-import/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ This scripts allows you to import works from the database of the Israeli rights

![ACUM importer](assets/acum-work-import.png?raw=1)

To import works for a release:
To import a whole medium:

1. Open the release relationship editor
2. Find the album ID, this will be in the end of the album URL in ACUM, e.g. https://nocs.acum.org.il/acumsitesearchdb/album?albumid=011820
3. Insert the ACUM album ID in the input box
4. Select the recordings whose works you want to import
1. Open the release relationship editor.
2. Find the album ID, this will be in the end of the album URL in ACUM, e.g. https://nocs.acum.org.il/acumsitesearchdb/album?albumid=011820.
3. Insert the ACUM album ID in the input box.
4. Select the recordings whose works you want to import.
5. Click the import button.
6. New works (green background links) will be created and exitsting works (yellow background) will be updated with links to the selected recordings and any writer and arranger as well as ISWCs and ACUM ID work attribute.
![updated works](assets/updated-works.png)
Expand All @@ -23,6 +23,16 @@ To import works for a release:

Since ACUM database treats every medium as a separate album all selected recordings should be under a single medium.

To import a single work:

1. Open a work editor.
2. Find the work ID, this will be in the end of the work URL in ACUM, e.g. https://nocs.acum.org.il/acumsitesearchdb/work?workid=1005566.
3. Insert the ACUM work ID in the input box.
4. Click the import button.
5. The work will be updated with links writers, ISWCs and ACUM ID work attribute.
6. Review the changes.
7. Submit the work.

## Release Notes

See [CHANGELOG.md](CHANGELOG.md).
8 changes: 6 additions & 2 deletions src/acum-work-import/acum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ type WorkInfoResponse = Response<{
workAlbums: ReadonlyArray<AlbumBean>;
}>;

async function getWorkVersions(workId: string): Promise<ReadonlyArray<WorkVersion> | undefined> {
export async function workVersions(workId: string): Promise<ReadonlyArray<WorkVersion> | undefined> {
const result = await tryFetchJSON<WorkInfoResponse>(
`https://nocs.acum.org.il/acumsitesearchdb/getworkinfo?workId=${workId}`
);
Expand All @@ -93,6 +93,10 @@ function albumApiUrl(albumId: string) {
return `https://nocs.acum.org.il/acumsitesearchdb/getalbuminfo?albumId=${albumId}`;
}

export function workUrl(workId: string) {
return `https://nocs.acum.org.il/acumsitesearchdb/work?workId=${workId}`;
}

export async function getAlbumInfo(albumId: string): Promise<AlbumBean | undefined> {
const result = await tryFetchJSON<AlbumInfoResponse>(albumApiUrl(albumId));
if (result) {
Expand All @@ -107,7 +111,7 @@ export async function getAlbumInfo(albumId: string): Promise<AlbumBean | undefin
export async function workISWCs(workID: string) {
const formatISWC = (iswc: string) => iswc.replace(/T(\d{3})(\d{3})(\d{3})(\d)/, 'T-$1.$2.$3-$4');

return (await getWorkVersions(workID))
return (await workVersions(workID))
?.map(albumVersion => albumVersion.versionIswcNumber)
.filter(iswc => iswc.length > 0)
.map(formatISWC);
Expand Down
23 changes: 16 additions & 7 deletions src/acum-work-import/app.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
import {createUI} from './ui/ui';
import {createReleaseEditorUI, releaseEditorContainerId} from './ui/release-editor-ui';
import {createWorkEditorUI} from './ui/work-editor-ui';

main();

function main() {
VM.observe(document.body, () => {
const recordingCheckboxes = document.querySelectorAll<HTMLInputElement>(
'input[type=checkbox].recording, input[type=checkbox].medium-recordings, input[type=checkbox].all-recordings'
);
if (recordingCheckboxes.length > 0) {
createUI(recordingCheckboxes);
return true;
if (location.pathname.startsWith('/release/')) {
const recordingCheckboxes = document.querySelectorAll<HTMLInputElement>(
'input[type=checkbox].recording, input[type=checkbox].medium-recordings, input[type=checkbox].all-recordings'
);
if (recordingCheckboxes.length > 0 && !document.getElementById(releaseEditorContainerId)) {
createReleaseEditorUI(recordingCheckboxes);
return true;
}
} else {
const workForm = document.querySelector<HTMLFormElement>('form.edit-work');
if (workForm) {
createWorkEditorUI(workForm);
return true;
}
}
});
}
30 changes: 28 additions & 2 deletions src/acum-work-import/artists.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {filter, from, lastValueFrom, mergeMap, tap} from 'rxjs';
import {tryFetchJSON} from 'src/common/musicbrainz/fetch';
import {CreatorFull, Creators, IPBaseNumber} from './acum';
import {Creator, CreatorFull, Creators, IPBaseNumber} from './acum';
import {AddWarning} from './ui/warnings';

function nameMatch(creator: CreatorFull, artist: ArtistSearchResultsT['artists'][number]): boolean {
Expand All @@ -9,7 +10,7 @@ function nameMatch(creator: CreatorFull, artist: ArtistSearchResultsT['artists']
);
}

export async function findArtist(
async function findArtist(
ipBaseNumber: IPBaseNumber,
creators: Creators,
addWarning: AddWarning
Expand All @@ -35,3 +36,28 @@ export async function findArtist(

return artistMBID ? await tryFetchJSON<ArtistT>(`/ws/js/entity/${artistMBID}`) : null;
}

export async function linkArtists(
artistCache: Map<string, Promise<ArtistT | null>>,
writers: readonly Creator[] | undefined,
creators: Creators,
doLink: (artist: ArtistT) => void,
addWarning: (message: string) => Set<string>
) {
await lastValueFrom(
from(writers || []).pipe(
mergeMap(
async author =>
await (artistCache.get(author.creatorIpBaseNumber) ||
artistCache
.set(author.creatorIpBaseNumber, findArtist(author.creatorIpBaseNumber, creators, addWarning))
.get(author.creatorIpBaseNumber))
),
filter((artist): artist is ArtistT => artist !== null),
tap(doLink)
),
{
defaultValue: null,
}
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
filter,
from,
ignoreElements,
isEmpty,
lastValueFrom,
map,
merge,
Expand All @@ -22,14 +21,14 @@ import {addEditNote} from 'src/common/musicbrainz/edit-note';
import {trackRecordingState} from 'src/common/musicbrainz/track-recording-state';
import {albumUrl, Creator, Creators, IPBaseNumber, searchName, WorkVersion} from './acum';
import {albumInfo} from './albums';
import {findArtist} from './artists';
import {linkArtists} from './artists';
import {addArrangerRelationship, addWriterRelationship} from './relationships';
import {AddWarning, ClearWarnings} from './ui/warnings';
import {workEditDataEqual} from './ui/work-edit-data';
import {WorkStateWithEditDataT} from './work-state';
import {addWork} from './works';

export async function importWorks(
export async function importAlbum(
albumId: string,
addWarning: AddWarning,
clearWarnings: ClearWarnings,
Expand All @@ -46,36 +45,14 @@ export async function importWorks(
// map of promises so that we don't fetch the same artist multiple times
const artistCache = new Map<IPBaseNumber, Promise<ArtistT | null>>();

const linkArtists = async (
writers: ReadonlyArray<Creator> | undefined,
creators: Creators,
doLink: (artist: ArtistT) => void,
addWarning: AddWarning
) => {
await lastValueFrom(
from(writers || []).pipe(
mergeMap(
async author =>
await (artistCache.get(author.creatorIpBaseNumber) ||
artistCache
.set(author.creatorIpBaseNumber, findArtist(author.creatorIpBaseNumber, creators, addWarning))
.get(author.creatorIpBaseNumber))
),
filter((artist): artist is ArtistT => artist !== null),
tap(doLink),
isEmpty()
)
);
};

const linkWriters = async (
work: WorkT,
writers: ReadonlyArray<Creator> | undefined,
creators: Creators,
linkTypeId: number,
addWarning: AddWarning
linkTypeId: number
) => {
await linkArtists(
artistCache,
writers,
creators,
(artist: ArtistT) => addWriterRelationship(work, artist, linkTypeId),
Expand All @@ -86,10 +63,15 @@ export async function importWorks(
const linkArrangers = async (
recording: RecordingT,
arrangers: ReadonlyArray<Creator> | undefined,
creators: Creators,
addWarning: AddWarning
creators: Creators
) => {
await linkArtists(arrangers, creators, (artist: ArtistT) => addArrangerRelationship(recording, artist), addWarning);
await linkArtists(
artistCache,
arrangers,
creators,
(artist: ArtistT) => addArrangerRelationship(recording, artist),
addWarning
);
};

const selectedRecordings = await lastValueFrom(
Expand All @@ -108,17 +90,16 @@ export async function importWorks(
)
);

const linkCreators = async ([track, recording, workState, addWarning]: readonly [
const linkCreators = async ([track, recording, workState]: readonly [
WorkVersion,
RecordingT,
WorkStateWithEditDataT,
AddWarning,
]): Promise<WorkStateWithEditDataT> => {
const work = workState.work;
await linkWriters(work, track.authors, track.creators, LYRICIST_LINK_TYPE_ID, addWarning);
await linkWriters(work, track.composers, track.creators, COMPOSER_LINK_TYPE_ID, addWarning);
await linkWriters(work, track.translators, track.creators, TRANSLATOR_LINK_TYPE_ID, addWarning);
await linkArrangers(recording, track.arrangers, track.creators, addWarning);
await linkWriters(work, track.authors, track.creators, LYRICIST_LINK_TYPE_ID);
await linkWriters(work, track.composers, track.creators, COMPOSER_LINK_TYPE_ID);
await linkWriters(work, track.translators, track.creators, TRANSLATOR_LINK_TYPE_ID);
await linkArrangers(recording, track.arrangers, track.creators);
return workState;
};

Expand Down Expand Up @@ -152,7 +133,7 @@ export async function importWorks(
}),
mergeMap(
async ([track, recordingState, addWarning]) =>
[track, recordingState.recording, await addWork(track, recordingState, addWarning), addWarning] as const
[track, recordingState.recording, await addWork(track, recordingState, addWarning)] as const
),
mergeMap(linkCreators),
connect(shared => merge(shared.pipe(maybeSetEditNote), shared.pipe(updateProgress, ignoreElements())))
Expand Down
Loading

0 comments on commit b19cffa

Please sign in to comment.