import { ALL_FORMATS, BlobSource, Input, UrlSource } from 'mediabunny'; import SampleFileUrl from '../../docs/assets/big-buck-bunny-trimmed.mp4'; (document.querySelector('#sample-file-download') as HTMLAnchorElement).href = SampleFileUrl; const selectMediaButton = document.querySelector('#select-file') as HTMLButtonElement; const loadUrlButton = document.querySelector('#load-url') as HTMLButtonElement; const fileNameElement = document.querySelector('#file-name') as HTMLParagraphElement; const horizontalRule = document.querySelector('hr') as HTMLHRElement; const bytesReadElement = document.querySelector('#bytes-read') as HTMLParagraphElement; const metadataContainer = document.querySelector('#metadata-container') as HTMLDivElement; const extractMetadata = (resource: File | string) => { // Create a new input from the resource const input = new Input({ source: typeof resource === 'string' ? new UrlSource(resource) : new BlobSource(resource), formats: ALL_FORMATS, // Accept all formats }); let bytesRead = 0; let fileSize: number | null = null; let obtainedSources = 0; const updateBytesRead = () => { if (obtainedSources > 1) { bytesReadElement.textContent = `Bytes read: ${bytesRead} across ${obtainedSources} files`; } else { bytesReadElement.textContent = `Bytes read: ${bytesRead} / ${fileSize === null ? '?' : fileSize}`; if (fileSize !== null) { bytesReadElement.textContent += ` (${(100 * bytesRead / fileSize).toPrecision(3)}% of entire file)`; } } }; input.on('source', ({ source, isRoot }) => { if (isRoot) { // Get the input's size void source.getSize().then((size) => { fileSize = size; updateBytesRead(); }); } obtainedSources++; updateBytesRead(); source.on('read', ({ start, end }) => { bytesRead += end - start; updateBytesRead(); }); }); // This object contains all the data that gets displayed: const object = { 'Format': input.getFormat().then(format => format.name), 'Full MIME type': input.getMimeType(), 'Starts at': input.getFirstTimestamp().then(start => `${start} seconds`), 'Ends at': input.computeDuration().then(duration => `${duration} seconds`), 'Tracks': input.getTracks().then(tracks => tracks.map(track => ({ 'Type': track.type, 'Codec': track.getCodec(), 'Full codec string': track.getCodecParameterString(), 'Starts at': track.getFirstTimestamp().then(start => `${start} seconds`), 'Ends at': track.computeDuration().then(duration => `${duration} seconds`), 'Language code': track.getLanguageCode(), ...(track.isVideoTrack() ? { 'Coded width': track.getCodedWidth().then(w => `${w} pixels`), 'Coded height': track.getCodedHeight().then(h => `${h} pixels`), 'Rotation': track.getRotation().then(rot => `${rot}° clockwise`), 'Pixel aspect ratio': track.getPixelAspectRatio().then(par => `${par.num}:${par.den}`), 'Display width': track.getDisplayWidth().then(w => `${w} pixels`), 'Display height': track.getDisplayHeight().then(h => `${h} pixels`), 'Transparency': track.canBeTransparent(), } : track.isAudioTrack() ? { 'Number of channels': track.getNumberOfChannels(), 'Sample rate': track.getSampleRate().then(rate => `${rate} Hz`), } : {}), 'Packet statistics': shortDelay().then(() => track.computePacketStats()).then(stats => ({ 'Packet count': stats.packetCount, 'Average packet rate': `${stats.averagePacketRate} Hz${track.isVideoTrack() ? ' (FPS)' : ''}`, 'Average bitrate': `${stats.averageBitrate} bps`, })), ...(track.isVideoTrack() ? { 'Color space': track.getColorSpace().then(colorSpace => ({ 'Color primaries': colorSpace.primaries ?? 'Unknown', 'Transfer characteristics': colorSpace.transfer ?? 'Unknown', 'Matrix coefficients': colorSpace.matrix ?? 'Unknown', 'Full range': colorSpace.fullRange ?? 'Unknown', 'HDR': track.hasHighDynamicRange(), })), } : {} ), }))), 'Metadata tags': input.getMetadataTags().then((tags) => { const result = { 'Title': tags.title, 'Description': tags.description, 'Artist': tags.artist, 'Album': tags.album, 'Album artist': tags.albumArtist, 'Track number': tags.trackNumber, 'Tracks total': tags.tracksTotal, 'Disc number': tags.discNumber, 'Discs total': tags.discsTotal, 'Genre': tags.genre, 'Date': tags.date?.toISOString().slice(0, 10), 'Lyrics': tags.lyrics, 'Comment': tags.comment, 'Images': tags.images?.map((image) => { const blob = new Blob([image.data.slice()], { type: image.mimeType }); const element = new Image(); element.src = URL.createObjectURL(blob); return element; }), 'Raw tag count': tags.raw && Object.keys(tags.raw).length, }; if (Object.values(result).some(x => x !== undefined)) { return result; } else { return undefined; } }), }; fileNameElement.textContent = resource instanceof File ? resource.name : resource; horizontalRule.style.display = ''; bytesReadElement.innerHTML = ''; metadataContainer.innerHTML = ''; const htmlElement = renderValue(object); metadataContainer.append(bytesReadElement, htmlElement); }; // Creates an HTML element to display any given value const renderValue = (value: unknown) => { if (value instanceof HTMLElement) { return value; } else if (Array.isArray(value)) { const arrayAsObject: Record = Object.fromEntries( value.map((item, index) => [(index + 1).toString(), item]), ); return renderObject(arrayAsObject); } else if (typeof value === 'object' && value !== null) { return renderObject(value as Record); } else { const spanElement = document.createElement('span'); spanElement.textContent = String(value); return spanElement; } }; // Returns a