« get me outta code hell

hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
path: root/src/data/things
diff options
context:
space:
mode:
Diffstat (limited to 'src/data/things')
-rw-r--r--src/data/things/artist.js8
-rw-r--r--src/data/things/index.js2
-rw-r--r--src/data/things/music-video.js131
-rw-r--r--src/data/things/track.js25
4 files changed, 166 insertions, 0 deletions
diff --git a/src/data/things/artist.js b/src/data/things/artist.js
index 01eb2172..439386f8 100644
--- a/src/data/things/artist.js
+++ b/src/data/things/artist.js
@@ -211,6 +211,14 @@ export class Artist extends Thing {
       },
     ],
 
+    musicVideoArtistContributions: reverseReferenceList({
+      reverse: soupyReverse.input('musicVideoArtistContributionsBy'),
+    }),
+
+    musicVideoContributorContributions: reverseReferenceList({
+      reverse: soupyReverse.input('musicVideoContributorContributionsBy'),
+    }),
+
     totalDuration: [
       withPropertyFromList('musicContributions', V('thing')),
       withPropertyFromList('#musicContributions.thing', V('isMainRelease')),
diff --git a/src/data/things/index.js b/src/data/things/index.js
index 09765fd2..35cd8cf2 100644
--- a/src/data/things/index.js
+++ b/src/data/things/index.js
@@ -21,6 +21,7 @@ import * as flashClasses from './flash.js';
 import * as groupClasses from './group.js';
 import * as homepageLayoutClasses from './homepage-layout.js';
 import * as languageClasses from './language.js';
+import * as musicVideoClasses from './music-video.js';
 import * as newsEntryClasses from './news-entry.js';
 import * as sortingRuleClasses from './sorting-rule.js';
 import * as staticPageClasses from './static-page.js';
@@ -40,6 +41,7 @@ const allClassLists = {
   'group.js': groupClasses,
   'homepage-layout.js': homepageLayoutClasses,
   'language.js': languageClasses,
+  'music-video.js': musicVideoClasses,
   'news-entry.js': newsEntryClasses,
   'sorting-rule.js': sortingRuleClasses,
   'static-page.js': staticPageClasses,
diff --git a/src/data/things/music-video.js b/src/data/things/music-video.js
new file mode 100644
index 00000000..267349e8
--- /dev/null
+++ b/src/data/things/music-video.js
@@ -0,0 +1,131 @@
+import {inspect} from 'node:util';
+
+import {colors} from '#cli';
+import {input, V} from '#composite';
+import find from '#find';
+import Thing from '#thing';
+import {isDate, isStringNonEmpty, isURL} from '#validators';
+import {parseContributors} from '#yaml';
+
+import {exposeConstant, exposeUpdateValueOrContinue}
+  from '#composite/control-flow';
+import {constituteFrom} from '#composite/wiki-data';
+
+import {
+  contributionList,
+  dimensions,
+  directory,
+  fileExtension,
+  soupyFind,
+  soupyReverse,
+  thing,
+  urls,
+} from '#composite/wiki-properties';
+
+export class MusicVideo extends Thing {
+  static [Thing.referenceType] = 'music-video';
+  static [Thing.wikiData] = 'musicVideoData';
+
+  static [Thing.getPropertyDescriptors] = ({ArtTag}) => ({
+    // Update & expose
+
+    thing: thing(),
+
+    label: {
+      flags: {update: true, expose: true},
+      update: {validate: isStringNonEmpty},
+      expose: {transform: value => value ?? 'Music video'},
+    },
+
+    unqualifiedDirectory: directory({name: 'label'}),
+
+    date: [
+      exposeUpdateValueOrContinue({
+        validate: input.value(isDate),
+      }),
+
+      constituteFrom('thing', V('date')),
+    ],
+
+    url: {
+      flags: {update: true, expose: true},
+      update: {validate: isURL},
+    },
+
+    coverArtFileExtension: fileExtension(V('jpg')),
+    coverArtDimensions: dimensions(),
+
+    artistContribs: contributionList({
+      artistProperty: input.value('musicVideoArtistContributions'),
+    }),
+
+    contributorContribs: contributionList({
+      artistProperty: input.value('musicVideoContributorContributions'),
+    }),
+
+    // Update only
+
+    find: soupyFind(),
+  });
+
+  static [Thing.yamlDocumentSpec] = {
+    fields: {
+      'Label': {property: 'label'},
+      'Directory': {property: 'unqualifiedDirectory'},
+      'Date': {property: 'date'},
+      'URL': {property: 'url'},
+
+      'Cover Art File Extension': {property: 'coverArtFileExtension'},
+      'Cover Art Dimensions': {property: 'coverArtDimensions'},
+
+      'Artists': {
+        property: 'artistContribs',
+        transform: parseContributors,
+      },
+
+      'Contributors': {
+        property: 'contributorContribs',
+        transform: parseContributors,
+      },
+    },
+  };
+
+  static [Thing.reverseSpecs] = {
+    musicVideoArtistContributionsBy:
+      soupyReverse.contributionsBy('musicVideoData', 'artistContribs'),
+
+    musicVideoContributorContributionsBy:
+      soupyReverse.contributionsBy('musicVideoData', 'contributorContribs'),
+  };
+
+  get path() {
+    if (!this.thing) return null;
+    if (!this.thing.getOwnMusicVideoCoverPath) return null;
+
+    return this.thing.getOwnMusicVideoCoverPath(this);
+  }
+
+  [inspect.custom](depth, options, inspect) {
+    const parts = [];
+
+    parts.push(Thing.prototype[inspect.custom].apply(this));
+
+    if (this.thing) {
+      if (depth >= 0) {
+        const newOptions = {
+          ...options,
+          depth:
+            (options.depth === null
+              ? null
+              : options.depth - 1),
+        };
+
+        parts.push(` for ${inspect(this.thing, newOptions)}`);
+      } else {
+        parts.push(` for ${colors.blue(Thing.getReference(this.thing))}`);
+      }
+    }
+
+    return parts.join('');
+  }
+}
diff --git a/src/data/things/track.js b/src/data/things/track.js
index 3c4b5409..8652fbdf 100644
--- a/src/data/things/track.js
+++ b/src/data/things/track.js
@@ -31,6 +31,7 @@ import {
   parseDimensions,
   parseDuration,
   parseLyrics,
+  parseMusicVideos,
 } from '#yaml';
 
 import {
@@ -113,6 +114,7 @@ export class Track extends Thing {
     CommentaryEntry,
     CreditingSourcesEntry,
     LyricsEntry,
+    MusicVideo,
     ReferencingSourcesEntry,
     TrackSection,
     WikiInfo,
@@ -488,6 +490,10 @@ export class Track extends Thing {
       }),
     ],
 
+    // > Update & expose - Music videos
+
+    musicVideos: thingList(V(MusicVideo)),
+
     // > Update & expose - Additional files
 
     additionalFiles: thingList(V(AdditionalFile)),
@@ -993,6 +999,13 @@ export class Track extends Thing {
       'Referenced Tracks': {property: 'referencedTracks'},
       'Sampled Tracks': {property: 'sampledTracks'},
 
+      // Music videos
+
+      'Music Videos': {
+        property: 'musicVideos',
+        transform: parseMusicVideos,
+      },
+
       // Additional files
 
       'Additional Files': {
@@ -1216,6 +1229,18 @@ export class Track extends Thing {
     ];
   }
 
+  getOwnMusicVideoCoverPath(musicVideo) {
+    if (!this.album) return null;
+    if (!musicVideo.unqualifiedDirectory) return null;
+
+    return [
+      'media.trackCover',
+      this.album.directory,
+      this.directory + '-' + musicVideo.unqualifiedDirectory,
+      musicVideo.coverArtFileExtension,
+    ];
+  }
+
   countOwnContributionInContributionTotals(_contrib) {
     if (!this.countInArtistTotals) {
       return false;