diff options
Diffstat (limited to 'src/data/things/track.js')
-rw-r--r-- | src/data/things/track.js | 460 |
1 files changed, 327 insertions, 133 deletions
diff --git a/src/data/things/track.js b/src/data/things/track.js index 725b1bb7..bcf84aa8 100644 --- a/src/data/things/track.js +++ b/src/data/things/track.js @@ -3,14 +3,15 @@ import {inspect} from 'node:util'; import CacheableObject from '#cacheable-object'; import {colors} from '#cli'; import {input} from '#composite'; -import find from '#find'; import Thing from '#thing'; -import {isColor, isContributionList, isDate, isFileExtension} +import {isBoolean, isColor, isContributionList, isDate, isFileExtension} from '#validators'; import { parseAdditionalFiles, parseAdditionalNames, + parseAnnotatedReferences, + parseArtwork, parseContributors, parseDate, parseDimensions, @@ -18,63 +19,117 @@ import { } from '#yaml'; import {withPropertyFromObject} from '#composite/data'; -import {withResolvedContribs} from '#composite/wiki-data'; import { - exitWithoutDependency, exposeConstant, exposeDependency, exposeDependencyOrContinue, exposeUpdateValueOrContinue, + exposeWhetherDependencyAvailable, } from '#composite/control-flow'; import { + withRecontextualizedContributionList, + withRedatedContributionList, + withResolvedContribs, +} from '#composite/wiki-data'; + +import { additionalFiles, additionalNameList, commentary, commentatorArtists, + constitutibleArtworkList, contentString, contributionList, dimensions, directory, duration, flag, + lyrics, name, referenceList, + referencedArtworkList, reverseReferenceList, simpleDate, - singleReference, simpleString, + singleReference, + soupyFind, + soupyReverse, + thing, urls, wikiData, } from '#composite/wiki-properties'; import { exitWithoutUniqueCoverArt, - inferredAdditionalNameList, - inheritFromOriginalRelease, - sharedAdditionalNameList, - trackReverseReferenceList, - withAlbum, + inheritContributionListFromMainRelease, + inheritFromMainRelease, + withAllReleases, withAlwaysReferenceByDirectory, withContainingTrackSection, + withCoverArtistContribs, + withDate, + withDirectorySuffix, withHasUniqueCoverArt, + withMainRelease, withOtherReleases, withPropertyFromAlbum, + withSuffixDirectoryFromAlbum, + withTrackArtDate, + withTrackNumber, } from '#composite/things/track'; export class Track extends Thing { static [Thing.referenceType] = 'track'; - static [Thing.getPropertyDescriptors] = ({Album, ArtTag, Artist, Flash}) => ({ + static [Thing.getPropertyDescriptors] = ({ + Album, + ArtTag, + Artwork, + Flash, + TrackSection, + WikiInfo, + }) => ({ // Update & expose name: name('Unnamed Track'), - directory: directory(), + + directory: [ + withDirectorySuffix(), + + directory({ + suffix: '#directorySuffix', + }), + ], + + suffixDirectoryFromAlbum: [ + { + dependencies: [ + input.updateValue({validate: isBoolean}), + ], + + compute: (continuation, { + [input.updateValue()]: value, + }) => continuation({ + ['#flagValue']: value ?? false, + }), + }, + + withSuffixDirectoryFromAlbum({ + flagValue: '#flagValue', + }), + + exposeDependency({ + dependency: '#suffixDirectoryFromAlbum', + }) + ], + + album: thing({ + class: input.value(Album), + }), additionalNames: additionalNameList(), - sharedAdditionalNames: sharedAdditionalNameList(), - inferredAdditionalNames: inferredAdditionalNameList(), bandcampTrackIdentifier: simpleString(), bandcampArtworkIdentifier: simpleString(), @@ -137,67 +192,57 @@ export class Track extends Thing { }), ], - // Date of cover art release. Like coverArtFileExtension, this represents - // only the track's own unique cover artwork, if any. This exposes only as - // the track's own coverArtDate or its album's trackArtDate, so if neither - // is specified, this value is null. coverArtDate: [ - withHasUniqueCoverArt(), - - exitWithoutDependency({ - dependency: '#hasUniqueCoverArt', - mode: input.value('falsy'), + withTrackArtDate({ + from: input.updateValue({ + validate: isDate, + }), }), - exposeUpdateValueOrContinue({ - validate: input.value(isDate), - }), + exposeDependency({dependency: '#trackArtDate'}), + ], + + coverArtDimensions: [ + exitWithoutUniqueCoverArt(), + + exposeUpdateValueOrContinue(), withPropertyFromAlbum({ - property: input.value('trackArtDate'), + property: input.value('trackDimensions'), }), - exposeDependency({dependency: '#album.trackArtDate'}), - ], + exposeDependencyOrContinue({dependency: '#album.trackDimensions'}), - coverArtDimensions: [ - exitWithoutUniqueCoverArt(), dimensions(), ], commentary: commentary(), + creditSources: commentary(), lyrics: [ - inheritFromOriginalRelease(), - contentString(), + inheritFromMainRelease(), + lyrics(), ], additionalFiles: additionalFiles(), sheetMusicFiles: additionalFiles(), midiProjectFiles: additionalFiles(), - originalReleaseTrack: singleReference({ + mainReleaseTrack: singleReference({ class: input.value(Track), - find: input.value(find.track), - data: 'trackData', - }), - - // Internal use only - for directly identifying an album inside a track's - // util.inspect display, if it isn't indirectly available (by way of being - // included in an album's track list). - dataSourceAlbum: singleReference({ - class: input.value(Album), - find: input.value(find.album), - data: 'albumData', + find: soupyFind.input('track'), }), artistContribs: [ - inheritFromOriginalRelease({ - notFoundValue: input.value([]), - }), + inheritContributionListFromMainRelease(), + + withDate(), withResolvedContribs({ from: input.updateValue({validate: isContributionList}), + thingProperty: input.thisProperty(), + artistProperty: input.value('trackArtistContributions'), + date: '#date', }).outputs({ '#resolvedContribs': '#artistContribs', }), @@ -211,67 +256,71 @@ export class Track extends Thing { property: input.value('artistContribs'), }), - exposeDependency({dependency: '#album.artistContribs'}), - ], + withRecontextualizedContributionList({ + list: '#album.artistContribs', + artistProperty: input.value('trackArtistContributions'), + }), - contributorContribs: [ - inheritFromOriginalRelease({ - notFoundValue: input.value([]), + withRedatedContributionList({ + list: '#album.artistContribs', + date: '#date', }), - contributionList(), + exposeDependency({dependency: '#album.artistContribs'}), ], - // Cover artists aren't inherited from the original release, since it - // typically varies by release and isn't defined by the musical qualities - // of the track. - coverArtistContribs: [ - exitWithoutUniqueCoverArt({ - value: input.value([]), - }), + contributorContribs: [ + inheritContributionListFromMainRelease(), - withResolvedContribs({ - from: input.updateValue({validate: isContributionList}), - }).outputs({ - '#resolvedContribs': '#coverArtistContribs', - }), + withDate(), - exposeDependencyOrContinue({ - dependency: '#coverArtistContribs', - mode: input.value('empty'), + contributionList({ + date: '#date', + artistProperty: input.value('trackContributorContributions'), }), + ], - withPropertyFromAlbum({ - property: input.value('trackCoverArtistContribs'), + coverArtistContribs: [ + withCoverArtistContribs({ + from: input.updateValue({ + validate: isContributionList, + }), }), - exposeDependency({dependency: '#album.trackCoverArtistContribs'}), + exposeDependency({dependency: '#coverArtistContribs'}), ], referencedTracks: [ - inheritFromOriginalRelease({ + inheritFromMainRelease({ notFoundValue: input.value([]), }), referenceList({ class: input.value(Track), - find: input.value(find.track), - data: 'trackData', + find: soupyFind.input('track'), }), ], sampledTracks: [ - inheritFromOriginalRelease({ + inheritFromMainRelease({ notFoundValue: input.value([]), }), referenceList({ class: input.value(Track), - find: input.value(find.track), - data: 'trackData', + find: soupyFind.input('track'), }), ], + trackArtworks: [ + exitWithoutUniqueCoverArt({ + value: input.value([]), + }), + + constitutibleArtworkList.fromYAMLFieldSpec + .call(this, 'Track Artwork'), + ], + artTags: [ exitWithoutUniqueCoverArt({ value: input.value([]), @@ -279,50 +328,50 @@ export class Track extends Thing { referenceList({ class: input.value(ArtTag), - find: input.value(find.artTag), - data: 'artTagData', + find: soupyFind.input('artTag'), }), ], - // Update only + referencedArtworks: [ + exitWithoutUniqueCoverArt({ + value: input.value([]), + }), - albumData: wikiData({ - class: input.value(Album), - }), + referencedArtworkList(), + ], - artistData: wikiData({ - class: input.value(Artist), - }), + // Update only - artTagData: wikiData({ - class: input.value(ArtTag), - }), + find: soupyFind(), + reverse: soupyReverse(), - flashData: wikiData({ - class: input.value(Flash), + // used for referencedArtworkList (mixedFind) + artworkData: wikiData({ + class: input.value(Artwork), }), + // used for withAlwaysReferenceByDirectory (for some reason) trackData: wikiData({ class: input.value(Track), }), + // used for withMatchingContributionPresets (indirectly by Contribution) + wikiInfo: thing({ + class: input.value(WikiInfo), + }), + // Expose only commentatorArtists: commentatorArtists(), - album: [ - withAlbum(), - exposeDependency({dependency: '#album'}), - ], - date: [ - exposeDependencyOrContinue({dependency: 'dateFirstReleased'}), - - withPropertyFromAlbum({ - property: input.value('date'), - }), + withDate(), + exposeDependency({dependency: '#date'}), + ], - exposeDependency({dependency: '#album.date'}), + trackNumber: [ + withTrackNumber(), + exposeDependency({dependency: '#trackNumber'}), ], hasUniqueCoverArt: [ @@ -330,22 +379,49 @@ export class Track extends Thing { exposeDependency({dependency: '#hasUniqueCoverArt'}), ], + isMainRelease: [ + withMainRelease(), + + exposeWhetherDependencyAvailable({ + dependency: '#mainRelease', + negate: input.value(true), + }), + ], + + isSecondaryRelease: [ + withMainRelease(), + + exposeWhetherDependencyAvailable({ + dependency: '#mainRelease', + }), + ], + + // Only has any value for main releases, because secondary releases + // are never secondary to *another* secondary release. + secondaryReleases: reverseReferenceList({ + reverse: soupyReverse.input('tracksWhichAreSecondaryReleasesOf'), + }), + + allReleases: [ + withAllReleases(), + exposeDependency({dependency: '#allReleases'}), + ], + otherReleases: [ withOtherReleases(), exposeDependency({dependency: '#otherReleases'}), ], - referencedByTracks: trackReverseReferenceList({ - list: input.value('referencedTracks'), + referencedByTracks: reverseReferenceList({ + reverse: soupyReverse.input('tracksWhichReference'), }), - sampledByTracks: trackReverseReferenceList({ - list: input.value('sampledTracks'), + sampledByTracks: reverseReferenceList({ + reverse: soupyReverse.input('tracksWhichSample'), }), featuredInFlashes: reverseReferenceList({ - data: 'flashData', - list: input.value('featuredTracks'), + reverse: soupyReverse.input('flashesWhichFeature'), }), }); @@ -353,6 +429,7 @@ export class Track extends Thing { fields: { 'Track': {property: 'name'}, 'Directory': {property: 'directory'}, + 'Suffix Directory': {property: 'suffixDirectoryFromAlbum'}, 'Additional Names': { property: 'additionalNames', @@ -406,6 +483,7 @@ export class Track extends Thing { 'Lyrics': {property: 'lyrics'}, 'Commentary': {property: 'commentary'}, + 'Credit Sources': {property: 'creditSources'}, 'Additional Files': { property: 'additionalFiles', @@ -422,10 +500,15 @@ export class Track extends Thing { transform: parseAdditionalFiles, }, - 'Originally Released As': {property: 'originalReleaseTrack'}, + 'Main Release': {property: 'mainReleaseTrack'}, 'Referenced Tracks': {property: 'referencedTracks'}, 'Sampled Tracks': {property: 'sampledTracks'}, + 'Referenced Artworks': { + property: 'referencedArtworks', + transform: parseAnnotatedReferences, + }, + 'Franchises': {ignore: true}, 'Inherit Franchises': {ignore: true}, @@ -444,34 +527,48 @@ export class Track extends Thing { transform: parseContributors, }, + 'Track Artwork': { + property: 'trackArtworks', + transform: + parseArtwork({ + dimensionsFromThingProperty: 'coverArtDimensions', + fileExtensionFromThingProperty: 'coverArtFileExtension', + dateFromThingProperty: 'coverArtDate', + artTagsFromThingProperty: 'artTags', + referencedArtworksFromThingProperty: 'referencedArtworks', + artistContribsFromThingProperty: 'coverArtistContribs', + artistContribsArtistProperty: 'trackCoverArtistContributions', + }), + }, + 'Art Tags': {property: 'artTags'}, 'Review Points': {ignore: true}, }, invalidFieldCombinations: [ - {message: `Rereleases inherit references from the original`, fields: [ - 'Originally Released As', + {message: `Secondary releases inherit references from the main one`, fields: [ + 'Main Release', 'Referenced Tracks', ]}, - {message: `Rereleases inherit samples from the original`, fields: [ - 'Originally Released As', + {message: `Secondary releases inherit samples from the main one`, fields: [ + 'Main Release', 'Sampled Tracks', ]}, - {message: `Rereleases inherit artists from the original`, fields: [ - 'Originally Released As', + {message: `Secondary releases inherit artists from the main one`, fields: [ + 'Main Release', 'Artists', ]}, - {message: `Rereleases inherit contributors from the original`, fields: [ - 'Originally Released As', + {message: `Secondary releases inherit contributors from the main one`, fields: [ + 'Main Release', 'Contributors', ]}, - {message: `Rereleases inherit lyrics from the original`, fields: [ - 'Originally Released As', + {message: `Secondary releases inherit lyrics from the main one`, fields: [ + 'Main Release', 'Lyrics', ]}, @@ -492,6 +589,7 @@ export class Track extends Thing { static [Thing.findSpecs] = { track: { referenceTypes: ['track'], + bindTo: 'trackData', getMatchableNames: track => @@ -500,12 +598,12 @@ export class Track extends Thing { : [track.name]), }, - trackOriginalReleasesOnly: { + trackMainReleasesOnly: { referenceTypes: ['track'], bindTo: 'trackData', include: track => - !CacheableObject.getUpdateValue(track, 'originalReleaseTrack'), + !CacheableObject.getUpdateValue(track, 'mainReleaseTrack'), // It's still necessary to check alwaysReferenceByDirectory here, since // it may be set manually (with `Always Reference By Directory: true`), @@ -516,32 +614,128 @@ export class Track extends Thing { ? [] : [track.name]), }, + + trackWithArtwork: { + referenceTypes: [ + 'track', + 'track-referencing-artworks', + 'track-referenced-artworks', + ], + + bindTo: 'trackData', + + include: track => + track.hasUniqueCoverArt, + + getMatchableNames: track => + (track.alwaysReferenceByDirectory + ? [] + : [track.name]), + }, + + trackPrimaryArtwork: { + [Thing.findThisThingOnly]: false, + + referenceTypes: [ + 'track', + 'track-referencing-artworks', + 'track-referenced-artworks', + ], + + bindTo: 'artworkData', + + include: (artwork, {Artwork, Track}) => + artwork instanceof Artwork && + artwork.thing instanceof Track && + artwork === artwork.thing.trackArtworks[0], + + getMatchableNames: ({thing: track}) => + (track.alwaysReferenceByDirectory + ? [] + : [track.name]), + + getMatchableDirectories: ({thing: track}) => + [track.directory], + }, + }; + + static [Thing.reverseSpecs] = { + tracksWhichReference: { + bindTo: 'trackData', + + referencing: track => track.isMainRelease ? [track] : [], + referenced: track => track.referencedTracks, + }, + + tracksWhichSample: { + bindTo: 'trackData', + + referencing: track => track.isMainRelease ? [track] : [], + referenced: track => track.sampledTracks, + }, + + tracksWhoseArtworksFeature: { + bindTo: 'trackData', + + referencing: track => [track], + referenced: track => track.artTags, + }, + + trackArtistContributionsBy: + soupyReverse.contributionsBy('trackData', 'artistContribs'), + + trackContributorContributionsBy: + soupyReverse.contributionsBy('trackData', 'contributorContribs'), + + trackCoverArtistContributionsBy: + soupyReverse.artworkContributionsBy('trackData', 'trackArtworks'), + + tracksWithCommentaryBy: { + bindTo: 'trackData', + + referencing: track => [track], + referenced: track => track.commentatorArtists, + }, + + tracksWhichAreSecondaryReleasesOf: { + bindTo: 'trackData', + + referencing: track => track.isSecondaryRelease ? [track] : [], + referenced: track => [track.mainReleaseTrack], + }, }; // Track YAML loading is handled in album.js. static [Thing.getYamlLoadingSpec] = null; + getOwnArtworkPath(artwork) { + if (!this.album) return null; + + return [ + 'media.trackCover', + this.album.directory, + + (artwork.unqualifiedDirectory + ? this.directory + '-' + artwork.unqualifiedDirectory + : this.directory), + + artwork.fileExtension, + ]; + } + [inspect.custom](depth) { const parts = []; parts.push(Thing.prototype[inspect.custom].apply(this)); - if (CacheableObject.getUpdateValue(this, 'originalReleaseTrack')) { - parts.unshift(`${colors.yellow('[rerelease]')} `); + if (CacheableObject.getUpdateValue(this, 'mainReleaseTrack')) { + parts.unshift(`${colors.yellow('[secrelease]')} `); } let album; if (depth >= 0) { - try { - album = this.album; - } catch (_error) { - // Computing album might crash for any reason, which we don't want to - // distract from another error we might be trying to work out at the - // moment (for which debugging might involve inspecting this track!). - } - - album ??= this.dataSourceAlbum; + album = this.album; } if (album) { |