diff --git a/src/components/media/AMediaSourceEditor.vue b/src/components/media/AMediaSourceEditor.vue
index cfeef94aa4ce7cfbbc7cd2a6dfdd320abb454963..260267a80bb3e9a83f57e9cb480f5bca51146367 100644
--- a/src/components/media/AMediaSourceEditor.vue
+++ b/src/components/media/AMediaSourceEditor.vue
@@ -91,6 +91,7 @@
       :title="t('media.editor.editMediaSource')"
       :save="save"
       class="tw-w-[90vw] md:tw-w-min"
+      @close="resetData"
     >
       <FormTable>
         <template v-if="type === 'file'">
@@ -105,7 +106,13 @@
           </FormGroup>
         </template>
 
-        <FormGroup v-if="type !== 'file'" v-slot="attrs" :label="t('file.duration')" center>
+        <FormGroup
+          v-if="type !== 'file'"
+          v-slot="attrs"
+          :label="t('file.duration')"
+          center
+          :errors="errors.forField('duration')"
+        >
           <input
             autofocus
             type="text"
@@ -119,6 +126,12 @@
         </FormGroup>
       </FormTable>
 
+      <hr />
+
+      <section>
+        <AAttributionEditor v-model="licensing" :errors="errors.subtree('licensing')" />
+      </section>
+
       <template #footer-buttons-extra="{ isSaving }">
         <div class="tw-ml-auto">
           <button
@@ -159,11 +172,11 @@
 import { useObjectFromStore } from '@rokoli/bnb/drf'
 import { computed, ref } from 'vue'
 
-import { useCopy } from '@/form'
+import { useCopy, useSaveBehaviour } from '@/form'
 import { useI18n } from '@/i18n'
 import { useFilesStore, useMediaSourceStore } from '@/stores'
 import { useInput } from '@/stores/playout'
-import { FileMetadata, MediaSource } from '@/types'
+import { FileMetadata, MediaSource, MediaSourceUpdateData } from '@/types'
 import { parseTime, secondsToDurationString } from '@/util'
 import SafeHTML from '@/components/generic/SafeHTML'
 import AEditDialog from '@/components/generic/AEditDialog.vue'
@@ -171,6 +184,8 @@ import FormTable from '@/components/generic/FormTable.vue'
 import FormGroup from '@/components/generic/FormGroup.vue'
 import AFileImportLog from '@/components/media/AFileImportLog.vue'
 import AStatus from '@/components/generic/AStatus.vue'
+import { isEqual } from 'lodash'
+import AAttributionEditor from '@/components/license/AAttributionEditor.vue'
 
 const props = withDefaults(
   defineProps<{
@@ -201,6 +216,8 @@ const title = useCopy(() => file.value?.metadata?.title)
 const artist = useCopy(() => file.value?.metadata?.artist)
 const album = useCopy(() => file.value?.metadata?.album)
 const duration = useCopy(() => props.mediaSource.duration)
+const licensing = useCopy(() => props.mediaSource.licensing)
+
 const uri = computed(() =>
   props.mediaSource.uri ? new URL(props.mediaSource.uri as string) : null,
 )
@@ -221,6 +238,14 @@ const artistInfo = computed(() => {
   }
 })
 
+function resetData() {
+  title.reset()
+  artist.reset()
+  album.reset()
+  duration.reset()
+  licensing.reset()
+}
+
 function setDuration(event: Event) {
   const value = (event.target as HTMLInputElement).value
   // A duration of zero should be treated as null so that the
@@ -228,9 +253,16 @@ function setDuration(event: Event) {
   duration.value = parseTime(value) || null
 }
 
-async function save() {
+const { save, errors } = useSaveBehaviour(async () => {
+  const mediaSourceData: MediaSourceUpdateData = {}
   if (duration.value !== props.mediaSource.duration) {
-    await mediaSourceStore.partialUpdate(props.mediaSource.id, { duration: duration.value })
+    mediaSourceData.duration = duration.value
+  }
+  if (!isEqual(licensing.value, props.mediaSource.licensing)) {
+    mediaSourceData.licensing = licensing.value
+  }
+  if (Object.keys(mediaSourceData).length > 0) {
+    await mediaSourceStore.partialUpdate(props.mediaSource.id, mediaSourceData)
   }
 
   if (file.value) {
@@ -245,7 +277,7 @@ async function save() {
   }
 
   editDialog.value.close()
-}
+})
 
 async function remove() {
   isDeleted.value = true
diff --git a/src/stores/media-manager.ts b/src/stores/media-manager.ts
index f19f96bfed6f8710c9a59b34fd7385b0db2f4994..2f3ccdcb0955c5dfb753820d31d21e58aa621419 100644
--- a/src/stores/media-manager.ts
+++ b/src/stores/media-manager.ts
@@ -132,9 +132,9 @@ const importFile: MediaSourceJobProcessor = async (job: MediaSourceJob, next: ()
 async function makeMediaSourceFromJob(job: MediaSourceJob): Promise<MediaSourceCreateData> {
   const mediaId = (await job.getMedia()).id
   if (job.tankFile) {
-    return { mediaId, duration: job.tankFile.duration, fileId: job.tankFile.id }
+    return { mediaId, duration: job.tankFile.duration, licensing: null, fileId: job.tankFile.id }
   } else if ('url' in job.data) {
-    return { mediaId, duration: null, uri: job.data.url }
+    return { mediaId, duration: null, licensing: null, uri: job.data.url }
   } else {
     throw new TypeError('Invalid job type. Cannot convert to media source.')
   }
diff --git a/src/types.ts b/src/types.ts
index e00239bfc74c0ef119973fc4df990d12ad0a8ac9..85b48ecca2acf711fc60b28120a0298606651d2e 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -55,8 +55,8 @@ export type ImageUpdateData = Omit<Image, _ImageReadonlyAttrs>
 type _MediaSource = steeringComponents['schemas']['MediaSource']
 export type MediaSource = Required<_MediaSource>
 export type MediaSourceCreateData =
-  | Pick<MediaSource, 'mediaId' | 'duration' | 'fileId'>
-  | Pick<MediaSource, 'mediaId' | 'duration' | 'uri'>
+  | Pick<MediaSource, 'mediaId' | 'duration' | 'licensing' | 'fileId'>
+  | Pick<MediaSource, 'mediaId' | 'duration' | 'licensing' | 'uri'>
 export type MediaSourceUpdateData = Partial<MediaSourceCreateData>
 
 type _Media = steeringComponents['schemas']['Media']