feat: publish av-tools 0.1.0

This commit is contained in:
2026-07-24 14:58:03 +02:00
commit fcc537b024
254 changed files with 63270 additions and 0 deletions

View File

@@ -0,0 +1,83 @@
import type { ImportedMediaAsset } from '../app/application-state';
import { formatBytes, formatDuration } from '../app/application-state';
import { Icon } from './Icon';
export interface MediaBinProps {
assets: readonly ImportedMediaAsset[];
selectedId?: string;
onSelect: (id: string) => void;
onRemove: (id: string) => void;
}
export function MediaBin({
assets,
selectedId,
onSelect,
onRemove,
}: MediaBinProps) {
if (assets.length === 0) {
return (
<div className="media-bin__empty">
<Icon name="video" />
<p>Your source files will appear here.</p>
</div>
);
}
return (
<ul className="media-list" aria-label="Imported media">
{assets.map((asset) => {
const video = asset.probe?.streams.find(
(stream) => stream.type === 'video'
);
const audioCount =
asset.probe?.streams.filter((stream) => stream.type === 'audio')
.length ?? 0;
return (
<li key={asset.id}>
<button
type="button"
className={[
'media-card',
selectedId === asset.id ? 'media-card--selected' : '',
]
.filter(Boolean)
.join(' ')}
onClick={() => onSelect(asset.id)}
>
<span className="media-card__type">
<Icon name={video ? 'video' : 'audio'} />
</span>
<span className="media-card__content">
<strong title={asset.file.name}>{asset.file.name}</strong>
<small>
{formatBytes(asset.file.size)}
{asset.probe?.durationSeconds !== undefined
? ` · ${formatDuration(asset.probe.durationSeconds)}`
: ''}
</small>
<small className={`phase phase--${asset.phase}`}>
{asset.phase === 'probing'
? 'Inspecting locally…'
: asset.phase === 'ready'
? `${video ? `${video.width ?? '?'}×${video.height ?? '?'}` : 'Audio'} · ${audioCount} audio`
: asset.phase === 'error'
? asset.error
: 'Queued'}
</small>
</span>
</button>
<button
className="icon-action"
type="button"
aria-label={`Remove ${asset.file.name}`}
onClick={() => onRemove(asset.id)}
>
<Icon name="trash" />
</button>
</li>
);
})}
</ul>
);
}