feat: Added audio / video events

This commit is contained in:
florian 2024-05-26 09:23:17 +02:00
parent 1fb2d54191
commit 2286c2c18d
7 changed files with 444 additions and 261 deletions

View File

@ -1,7 +1,7 @@
import { formatFileSize, formatDate } from '../../utils/utils'; import { formatFileSize, formatDate } from '../../utils/utils';
import { ClipboardDocumentIcon, TrashIcon } from '@heroicons/react/24/outline'; import { ClipboardDocumentIcon, TrashIcon } from '@heroicons/react/24/outline';
import { BlobDescriptor } from 'blossom-client-sdk'; import { BlobDescriptor } from 'blossom-client-sdk';
import { fetchId3Tag } from '../../utils/id3'; import { AudioBlob, fetchId3Tag } from '../../utils/id3';
import { useQueries } from '@tanstack/react-query'; import { useQueries } from '@tanstack/react-query';
import { useGlobalContext } from '../../GlobalState'; import { useGlobalContext } from '../../GlobalState';
import { PauseIcon, PlayIcon } from '@heroicons/react/24/solid'; import { PauseIcon, PlayIcon } from '@heroicons/react/24/solid';
@ -17,7 +17,10 @@ const AudioBlobList = ({ audioFiles, onDelete }: AudioBlobListProps) => {
const audioFilesWithId3 = useQueries({ const audioFilesWithId3 = useQueries({
queries: audioFiles.map(af => ({ queries: audioFiles.map(af => ({
queryKey: ['id3', af.sha256], queryKey: ['id3', af.sha256],
queryFn: async () => await fetchId3Tag(af), queryFn: async () => {
const id3Tag = await fetchId3Tag(af.sha256, af.url);
return { ...af, id3: id3Tag?.id3 } as AudioBlob;
},
staleTime: 1000 * 60 * 5, staleTime: 1000 * 60 * 5,
cacheTime: 1000 * 60 * 5, cacheTime: 1000 * 60 * 5,
})), })),

View File

@ -31,7 +31,7 @@ const BlobListTypeMenu = ({ mode, setMode, hasImages, hasAudio, hasDocs, hasVide
}, [hasAudio, hasDocs, hasImages, hasVideo, mode, setMode]); }, [hasAudio, hasDocs, hasImages, hasVideo, mode, setMode]);
return ( return (
<ul className="menu menu-horizontal menu-active bg-base-200 rounded-box"> <ul className="menu menu-horizontal menu-active bg-base-200 rounded-box gap-1">
<li> <li>
<a <a
className={' tooltip ' + (mode == 'list' ? 'bg-primary text-primary-content hover:bg-primary ' : '')} className={' tooltip ' + (mode == 'list' ? 'bg-primary text-primary-content hover:bg-primary ' : '')}

View File

@ -1,12 +1,11 @@
import { NDKEvent, NDKKind, NDKUser, NostrEvent } from '@nostr-dev-kit/ndk'; import { useEffect, useState } from 'react';
import { useNDK } from '../../utils/ndk';
import dayjs from 'dayjs';
import { useEffect, useMemo, useState } from 'react';
import uniq from 'lodash/uniq';
import { formatFileSize } from '../../utils/utils'; import { formatFileSize } from '../../utils/utils';
import useEvents from '../../utils/useEvents'; import { fetchId3Tag } from '../../utils/id3';
import useVideoThumbnailDvm from './dvm';
import { usePublishing } from './usePublishing';
export type FileEventData = { export type FileEventData = {
originalFile: File;
content: string; content: string;
url: string[]; url: string[];
dim?: string; dim?: string;
@ -17,162 +16,53 @@ export type FileEventData = {
thumbnail?: string; thumbnail?: string;
//summary: string; //summary: string;
//alt: string; //alt: string;
artist?: string;
title?: string;
album?: string;
year?: string;
}; };
const ensureDecrypted = async (dvm: NDKUser, event: NDKEvent) => {
if (!event) return undefined;
const encrypted = event.tags.some(t => t[0] == 'encrypted');
if (encrypted) {
const decryptedContent = await event.ndk?.signer?.decrypt(dvm, event.content);
if (decryptedContent) {
return {
...event,
tags: event.tags.filter(t => t[0] !== 'encrypted').concat(JSON.parse(decryptedContent)),
};
}
}
return event;
};
const NPUB_DVM_THUMBNAIL_CREATION = 'npub1q8cv87l47fql2xer2uyw509y5n5s9f53h76hvf9377efdptmsvusxf3n8s';
const FileEventEditor = ({ data }: { data: FileEventData }) => { const FileEventEditor = ({ data }: { data: FileEventData }) => {
const [fileEventData, setFileEventData] = useState(data); const [fileEventData, setFileEventData] = useState(data);
const [thumbnailRequestEventId, setThumbnailRequestEventId] = useState<string | undefined>(); const { createDvmThumbnailRequest, thumbnailRequestEventId } = useVideoThumbnailDvm(setFileEventData);
const { ndk, user } = useNDK(); const { publishAudioEvent, publishFileEvent, publishVideoEvent } = usePublishing();
const dvm = ndk.getUser({ npub: NPUB_DVM_THUMBNAIL_CREATION });
const thumbnailDvmFilter = useMemo(
() => ({ kinds: [6204 as NDKKind], '#e': [thumbnailRequestEventId || ''] }),
[thumbnailRequestEventId]
);
const thumbnailSubscription = useEvents(thumbnailDvmFilter, {
closeOnEose: false,
disable: thumbnailRequestEventId == undefined,
});
useEffect(() => {
const doASync = async () => {
const firstEvent = await ensureDecrypted(dvm, thumbnailSubscription.events[0]);
if (firstEvent) {
const urls = firstEvent.tags.filter(t => t[0] === 'thumb').map(t => t[1]);
const dim = firstEvent.tags.find(t => t[0] === 'dim')?.[1];
setFileEventData(ed => ({ ...ed, thumbnails: urls, dim, thumbnail: urls[0] }));
}
};
doASync();
}, [thumbnailSubscription.events]);
const publishFileEvent = async (data: FileEventData) => {
// TODO REupload selected video thumbnail from DVM
const e: NostrEvent = {
created_at: dayjs().unix(),
content: data.content,
tags: [
...uniq(data.url).map(du => ['url', du]),
['x', data.x],
//['summary', data.summary],
//['alt', data.alt],
],
kind: 1063,
pubkey: user?.pubkey || '',
};
if (data.size) {
e.tags.push(['size', `${data.size}`]);
}
if (data.dim) {
e.tags.push(['dim', data.dim]);
}
if (data.m) {
e.tags.push(['m', data.m]);
}
if (data.thumbnail) {
e.tags.push(['thumb', data.thumbnail]);
e.tags.push(['image', data.thumbnail]);
}
const ev = new NDKEvent(ndk, e);
await ev.sign();
console.log(ev.rawEvent());
// await ev.publish();
};
/*
async function createDvmBlossemAuthToken() {
const pubkey = ndk.activeUser?.pubkey;
if (!ndk.signer || !pubkey) return;
const tenMinutes = () => dayjs().unix() + 10 * 60;
const authEvent = ({
pubkey,
created_at: dayjs().unix(),
kind: 24242,
content: 'Upload thumbail',
tags: [
['t', 'upload'],
['name', `thumb_${Math.random().toString(36).substring(2)}`], // make sure the auth events are unique
['expiration', String(tenMinutes())],
],
});
const ev = new NDKEvent(ndk, authEvent);
await ev.sign();
console.log(JSON.stringify(ev.rawEvent()));
return btoa(JSON.stringify(ev.rawEvent()));
}
*/
const getThumbnails = async (data: FileEventData) => {
if (!ndk.signer) return;
const thumbCount = 3;
/*s
const authTokens = [];
for (let i = 0; i < thumbCount; i++) {
const uploadAuth = await createDvmBlossemAuthToken();
if (uploadAuth) {
authTokens.push(['param', 'authToken', uploadAuth]);
}
}
*/
const e: NostrEvent = {
created_at: dayjs().unix(),
content: await ndk.signer?.encrypt(
dvm,
JSON.stringify([
['i', data.url[0], 'url'],
['output', 'image/jpeg'],
['param', 'thumbnailCount', `${thumbCount}`],
['relays', user?.relayUrls.join(',') || ndk.explicitRelayUrls?.join(',') || ''],
])
),
tags: [
['p', dvm.pubkey],
['encrypted'],
// TODO set expiration
],
kind: 5204,
pubkey: user?.pubkey || '',
};
const ev = new NDKEvent(ndk, e);
await ev.sign();
console.log(ev.rawEvent());
setThumbnailRequestEventId(ev.id);
await ev.publish();
};
useEffect(() => { useEffect(() => {
if (fileEventData.m?.startsWith('video/') && fileEventData.thumbnails == undefined) { if (fileEventData.m?.startsWith('video/') && fileEventData.thumbnails == undefined) {
getThumbnails(fileEventData); createDvmThumbnailRequest(fileEventData);
}
if (
fileEventData.m?.startsWith('audio/') &&
!(
fileEventData.title ||
fileEventData.artist ||
fileEventData.album ||
fileEventData.year ||
fileEventData.thumbnail
)
) {
console.log('getting id3 cover image', fileEventData.x, fileEventData.url[0], fileEventData.originalFile);
fetchId3Tag(fileEventData.x, fileEventData.url[0], fileEventData.originalFile).then(res => {
if (!res) return;
const { id3 } = res;
console.log(res.coverFull);
setFileEventData({
...fileEventData,
artist: id3.artist,
album: id3.album,
title: id3.title,
year: id3.year,
thumbnail: res.coverFull,
thumbnails: res.coverFull ? [res.coverFull] : [],
});
});
} }
}, [fileEventData]); }, [fileEventData]);
return ( return (
<>
<div className=" bg-base-200 rounded-xl p-4 text-neutral-content gap-4 flex flex-row"> <div className=" bg-base-200 rounded-xl p-4 text-neutral-content gap-4 flex flex-row">
{fileEventData.m?.startsWith('video/') && ( {fileEventData.m?.startsWith('video/') && (
<> <>
@ -204,6 +94,11 @@ const FileEventEditor = ({ data }: { data: FileEventData }) => {
))} ))}
</> </>
)} )}
{fileEventData.m?.startsWith('audio/') && fileEventData.thumbnail && (
<div className="w-2/6">
<img src={fileEventData.thumbnail} className="w-full" />
</div>
)}
{fileEventData.m?.startsWith('image/') && ( {fileEventData.m?.startsWith('image/') && (
<div className="p-4 bg-base-300 w-2/6"> <div className="p-4 bg-base-300 w-2/6">
@ -215,6 +110,31 @@ const FileEventEditor = ({ data }: { data: FileEventData }) => {
</div> </div>
)} )}
<div className="grid gap-4 w-4/6" style={{ gridTemplateColumns: '1fr 30em' }}> <div className="grid gap-4 w-4/6" style={{ gridTemplateColumns: '1fr 30em' }}>
{fileEventData.title && (
<>
<span className="font-bold">Title</span>
<span>{fileEventData.title}</span>
</>
)}
{fileEventData.artist && (
<>
<span className="font-bold">Artist</span>
<span>{fileEventData.artist}</span>
</>
)}
{fileEventData.album && (
<>
<span className="font-bold">Album</span>
<span>{fileEventData.album}</span>
</>
)}
{fileEventData.year && (
<>
<span className="font-bold">Year</span>
<span>{fileEventData.year}</span>
</>
)}
<span className="font-bold">Type</span> <span className="font-bold">Type</span>
<span>{fileEventData.m}</span> <span>{fileEventData.m}</span>
@ -242,11 +162,25 @@ const FileEventEditor = ({ data }: { data: FileEventData }) => {
</div> </div>
))} ))}
</div> </div>
</div>
</div>{' '}
<div className="flex gap-2 flex-col">
<div className=" alert alert-warning ">
DEVELOPMENT ZONE! These publish buttons do not work yet. Events are only shown in the browser console.
</div>
<div className="flex gap-2">
<button className="btn btn-primary" onClick={() => publishFileEvent(fileEventData)}> <button className="btn btn-primary" onClick={() => publishFileEvent(fileEventData)}>
Publish Create File Event
</button>
<button className="btn btn-primary" onClick={() => publishAudioEvent(fileEventData)}>
Create Audio Event
</button>
<button className="btn btn-primary" onClick={() => publishVideoEvent(fileEventData)}>
Create Video Event
</button> </button>
</div> </div>
</div> </div>
</>
); );
}; };

View File

@ -0,0 +1,124 @@
/*
async function createDvmBlossemAuthToken() {
const pubkey = ndk.activeUser?.pubkey;
if (!ndk.signer || !pubkey) return;
const tenMinutes = () => dayjs().unix() + 10 * 60;
const authEvent = ({
pubkey,
created_at: dayjs().unix(),
kind: 24242,
content: 'Upload thumbail',
tags: [
['t', 'upload'],
['name', `thumb_${Math.random().toString(36).substring(2)}`], // make sure the auth events are unique
['expiration', String(tenMinutes())],
],
});
const ev = new NDKEvent(ndk, authEvent);
await ev.sign();
console.log(JSON.stringify(ev.rawEvent()));
return btoa(JSON.stringify(ev.rawEvent()));
}
*/
import { NDKEvent, NDKKind, NDKUser, NostrEvent } from '@nostr-dev-kit/ndk';
import { FileEventData } from './FileEventEditor';
import { useEffect, useMemo, useState } from 'react';
import { useNDK } from '../../utils/ndk';
import useEvents from '../../utils/useEvents';
import dayjs from 'dayjs';
const NPUB_DVM_THUMBNAIL_CREATION = 'npub1q8cv87l47fql2xer2uyw509y5n5s9f53h76hvf9377efdptmsvusxf3n8s';
const ensureDecrypted = async (dvm: NDKUser, event: NDKEvent) => {
if (!event) return undefined;
const encrypted = event.tags.some(t => t[0] == 'encrypted');
if (encrypted) {
const decryptedContent = await event.ndk?.signer?.decrypt(dvm, event.content);
if (decryptedContent) {
return {
...event,
tags: event.tags.filter(t => t[0] !== 'encrypted').concat(JSON.parse(decryptedContent)),
};
}
}
return event;
};
const useVideoThumbnailDvm = (setFileEventData: React.Dispatch<React.SetStateAction<FileEventData>>) => {
const [thumbnailRequestEventId, setThumbnailRequestEventId] = useState<string | undefined>();
const { ndk, user } = useNDK();
const dvm = ndk.getUser({ npub: NPUB_DVM_THUMBNAIL_CREATION });
const thumbnailDvmFilter = useMemo(
() => ({ kinds: [6204 as NDKKind], '#e': [thumbnailRequestEventId || ''] }),
[thumbnailRequestEventId]
);
const thumbnailSubscription = useEvents(thumbnailDvmFilter, {
closeOnEose: false,
disable: thumbnailRequestEventId == undefined,
});
useEffect(() => {
const doASync = async () => {
const firstEvent = await ensureDecrypted(dvm, thumbnailSubscription.events[0]);
if (firstEvent) {
const urls = firstEvent.tags.filter(t => t[0] === 'thumb').map(t => t[1]);
const dim = firstEvent.tags.find(t => t[0] === 'dim')?.[1];
setFileEventData(ed => ({ ...ed, thumbnails: urls, dim, thumbnail: urls[0] }));
}
};
doASync();
}, [thumbnailSubscription.events]);
const createDvmThumbnailRequest = async (data: FileEventData) => {
if (!ndk.signer) return;
const thumbCount = 3;
/*s
const authTokens = [];
for (let i = 0; i < thumbCount; i++) {
const uploadAuth = await createDvmBlossemAuthToken();
if (uploadAuth) {
authTokens.push(['param', 'authToken', uploadAuth]);
}
}
*/
const e: NostrEvent = {
created_at: dayjs().unix(),
content: await ndk.signer?.encrypt(
dvm,
JSON.stringify([
['i', data.url[0], 'url'],
['output', 'image/jpeg'],
['param', 'thumbnailCount', `${thumbCount}`],
['relays', user?.relayUrls.join(',') || ndk.explicitRelayUrls?.join(',') || ''],
])
),
tags: [
['p', dvm.pubkey],
['encrypted'],
// TODO set expiration
],
kind: 5204,
pubkey: user?.pubkey || '',
};
const ev = new NDKEvent(ndk, e);
await ev.sign();
console.log(ev.rawEvent());
setThumbnailRequestEventId(ev.id);
await ev.publish();
};
return {
createDvmThumbnailRequest,
thumbnailRequestEventId,
};
};
export default useVideoThumbnailDvm;

View File

@ -0,0 +1,125 @@
import { NDKEvent, NostrEvent } from '@nostr-dev-kit/ndk';
import dayjs from 'dayjs';
import { FileEventData } from './FileEventEditor';
import { uniq } from 'lodash';
import { useNDK } from '../../utils/ndk';
export const usePublishing = () => {
const { ndk, user } = useNDK();
const publishFileEvent = async (data: FileEventData) => {
// TODO REupload selected video thumbnail from DVM
const e: NostrEvent = {
created_at: dayjs().unix(),
content: data.content,
tags: [
...uniq(data.url).map(du => ['url', du]),
['x', data.x],
//['summary', data.summary],
//['alt', data.alt],
],
kind: 1063,
pubkey: user?.pubkey || '',
};
if (data.size) {
e.tags.push(['size', `${data.size}`]);
}
if (data.dim) {
e.tags.push(['dim', data.dim]);
}
if (data.m) {
e.tags.push(['m', data.m]);
}
if (data.thumbnail) {
// TODO upload thumbnail to own storage
e.tags.push(['thumb', data.thumbnail]);
e.tags.push(['image', data.thumbnail]);
}
const ev = new NDKEvent(ndk, e);
await ev.sign();
console.log(ev.rawEvent());
// await ev.publish();
};
const publishAudioEvent = async (data: FileEventData) => {
const e: NostrEvent = {
created_at: dayjs().unix(),
content: `${data.artist} - ${data.title}`,
tags: [
['d', data.x],
...uniq(data.url).map(du => ['media', du]),
['x', data.x],
...uniq(data.url).map(du => ['imeta', `url ${du}`, `m ${data.m}`]),
],
kind: 31337,
pubkey: user?.pubkey || '',
};
if (data.title) {
e.tags.push(['title', `${data.title}`]);
e.tags.push(['subject', `${data.title}`]);
}
if (data.artist) {
e.tags.push(['creator', `${data.artist}`]);
e.tags.push(['creator', `${data.artist}`, 'Artist']);
}
if (data.album) {
e.tags.push(['album', `${data.album}`]);
}
const ev = new NDKEvent(ndk, e);
await ev.sign();
console.log(ev.rawEvent());
// await ev.publish();
};
const publishVideoEvent = async (data: FileEventData) => {
const e: NostrEvent = {
created_at: dayjs().unix(),
content: data.content,
tags: [
['d', data.x],
['x', data.x],
['url', data.url[0]],
['title', data.content],
// ['summary', data.], TODO add summary
['published_at', `${dayjs().unix()}`],
['client', 'bouquet'],
],
kind: 31337,
pubkey: user?.pubkey || '',
};
if (data.size) {
e.tags.push(['size', `${data.size}`]);
}
if (data.dim) {
e.tags.push(['dim', data.dim]);
}
if (data.m) {
e.tags.push(['m', data.m]);
}
if (data.thumbnail) {
// TODO upload to own blossom instance
e.tags.push(['thumb', data.thumbnail]);
e.tags.push(['preview', data.thumbnail]);
}
// TODO add tags ("t")
const ev = new NDKEvent(ndk, e);
await ev.sign();
console.log(ev.rawEvent());
// await ev.publish();
};
return {
publishAudioEvent,
publishFileEvent,
publishVideoEvent,
};
};

View File

@ -105,7 +105,11 @@ function Upload() {
// for image resizing // for image resizing
const fileDimensions: { [key: string]: FileEventData } = {}; const fileDimensions: { [key: string]: FileEventData } = {};
for (const file of filesToUpload) { for (const file of filesToUpload) {
let data = { content: file.name.replace(/\.[a-zA-Z0-9]{3,4}$/, ''), url: [] as string[] } as FileEventData; let data = {
content: file.name.replace(/\.[a-zA-Z0-9]{3,4}$/, ''),
url: [] as string[],
originalFile: file,
} as FileEventData;
if (file.type.startsWith('image/')) { if (file.type.startsWith('image/')) {
const dimensions = await getImageSize(file); const dimensions = await getImageSize(file);
data = { ...data, dim: `${dimensions.width}x${dimensions.height}` }; data = { ...data, dim: `${dimensions.width}x${dimensions.height}` };
@ -199,23 +203,6 @@ function Upload() {
useEffect(() => { useEffect(() => {
clearTransfers(); clearTransfers();
/*
setFileEventsToPublish([
{
content: '_DSF3852.jpg',
dim: '1365x2048',
m: 'image/jpeg',
size: 599988,
url: [
'https://test-store.slidestr.net/d32b7eff53919bc38b59e05b2fe4bda3067c46589eeee743a46649ae71f4b659',
'https://media-server.slidestr.net/d32b7eff53919bc38b59e05b2fe4bda3067c46589eeee743a46649ae71f4b659',
'https://cdn.satellite.earth/d32b7eff53919bc38b59e05b2fe4bda3067c46589eeee743a46649ae71f4b659.jpg',
],
x: 'd32b7eff53919bc38b59e05b2fe4bda3067c46589eeee743a46649ae71f4b659',
},
]);*/
}, [servers]); }, [servers]);
const handleFileChange = (event: ChangeEvent<HTMLInputElement>) => { const handleFileChange = (event: ChangeEvent<HTMLInputElement>) => {

View File

@ -67,11 +67,8 @@ function saveID3TagToDB(db: IDBDatabase, key: string, id3Tag: ID3Tag): Promise<v
} }
// Function to resize image // Function to resize image
function resizeImage(imageArray: ArrayBuffer, maxWidth: number, maxHeight: number): Promise<string> { function resizeImage(imageBlobUrl: string, maxWidth: number, maxHeight: number): Promise<string> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const blob = new Blob([imageArray], { type: 'image/jpeg' });
const url = URL.createObjectURL(blob);
const img = new Image(); const img = new Image();
img.onload = () => { img.onload = () => {
let width = img.width; let width = img.width;
@ -108,24 +105,29 @@ function resizeImage(imageArray: ArrayBuffer, maxWidth: number, maxHeight: numbe
reject(new Error('Canvas context could not be retrieved')); reject(new Error('Canvas context could not be retrieved'));
} }
URL.revokeObjectURL(url); // Clean up // URL.revokeObjectURL(url); // Clean up
}; };
img.onerror = () => { img.onerror = () => {
reject(new Error('Image could not be loaded')); reject(new Error('Image could not be loaded'));
URL.revokeObjectURL(url); // Clean up // URL.revokeObjectURL(url); // Clean up
}; };
img.src = url; img.src = imageBlobUrl;
}); });
} }
export const fetchId3Tag = async (blob: BlobDescriptor): Promise<AudioBlob> => { export const fetchId3Tag = async (
blobHash: string,
blobUrl?: string,
localFile?: File
): Promise<{ id3: ID3Tag; coverFull?: string } | undefined> => {
const db = await openIndexedDB(); const db = await openIndexedDB();
const cachedID3Tag = await getID3TagFromDB(db, blob.sha256); const cachedID3Tag = await getID3TagFromDB(db, blobHash);
if (cachedID3Tag) { // Don't cache the ID3 tag if we have a local file
return { ...blob, id3: cachedID3Tag } as AudioBlob; if (!localFile && cachedID3Tag) {
return { id3: cachedID3Tag };
} }
function arrayBufferToFile(arrayBuffer: ArrayBuffer, fileName: string, mimeType: string) { function arrayBufferToFile(arrayBuffer: ArrayBuffer, fileName: string, mimeType: string) {
@ -141,10 +143,18 @@ export const fetchId3Tag = async (blob: BlobDescriptor): Promise<AudioBlob> => {
// const id3Tag = await id3.fromUrl(blob.url).catch(e => console.warn(e)); // const id3Tag = await id3.fromUrl(blob.url).catch(e => console.warn(e));
// download the whole song, convert to blob and file to read mp3 tag // download the whole song, convert to blob and file to read mp3 tag
const response = await fetch(blob.url); let file = localFile;
if (!file) {
if (!blobUrl) return undefined;
// if we don't have a local file, download from blob url
const response = await fetch(blobUrl);
const buffer = await response.arrayBuffer(); const buffer = await response.arrayBuffer();
const file = arrayBufferToFile(buffer, `${blob.sha256}.mp3`, blob.type || 'audio/mpeg'); file = arrayBufferToFile(buffer, `${blobHash}.mp3`, 'audio/mpeg');
}
const id3Tag = await id3.fromFile(file).catch(e => console.warn(e)); const id3Tag = await id3.fromFile(file).catch(e => console.warn(e));
let imageBlobUrl: string | undefined;
if (id3Tag) { if (id3Tag) {
const tagResult: ID3Tag = { const tagResult: ID3Tag = {
@ -157,16 +167,16 @@ export const fetchId3Tag = async (blob: BlobDescriptor): Promise<AudioBlob> => {
if (id3Tag.kind == 'v2') { if (id3Tag.kind == 'v2') {
const id3v2 = id3Tag as ID3TagV2; const id3v2 = id3Tag as ID3TagV2;
if (id3v2.images[0].data) { if (id3v2.images[0].data) {
tagResult.cover = await resizeImage(id3v2.images[0].data, 128, 128); const blob = new Blob([id3v2.images[0].data], { type: 'image/jpeg' });
imageBlobUrl = URL.createObjectURL(blob);
tagResult.cover = await resizeImage(imageBlobUrl, 128, 128);
} }
} }
// console.log(blob.sha256, tagResult); console.log(blobHash, blobUrl, tagResult);
await saveID3TagToDB(db, blob.sha256, tagResult); await saveID3TagToDB(db, blobHash, tagResult);
return { ...blob, id3: tagResult }; return { id3: tagResult, coverFull: imageBlobUrl };
} }
console.log('No ID3 tag found for ' + blob.sha256); console.log('No ID3 tag found for ' + blobHash);
return blob; // only when ID3 fails completely
}; };