feat(playlists): библиотека плейлистов приложения
CRUD в редакторе, read-only окно на пульте, отдельный аудиоканал, экспорт/импорт и маппинг из Foundry. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -279,10 +279,17 @@ function collectSceneAudioPaths(
|
||||
return paths;
|
||||
}
|
||||
|
||||
export type FoundryPendingAppPlaylist = {
|
||||
name: string;
|
||||
tracks: { title: string; absPath: string; mime: string }[];
|
||||
};
|
||||
|
||||
export type FoundryBuiltProject = {
|
||||
project: Project;
|
||||
/** Абсолютные пути исходников превью для последующей генерации thumb (sceneId → abs path). */
|
||||
previewSources: { sceneId: SceneId; absPath: string }[];
|
||||
/** Неиспользуемые на сценах Foundry-плейлисты → локальная библиотека. */
|
||||
pendingAppPlaylists: FoundryPendingAppPlaylist[];
|
||||
};
|
||||
|
||||
export async function buildProjectFromFoundryDocuments(
|
||||
@@ -406,15 +413,28 @@ export async function buildProjectFromFoundryDocuments(
|
||||
importScenes.map((s) => s.playlist).filter((id): id is string => typeof id === 'string'),
|
||||
);
|
||||
const campaignAudios: Project['campaignAudios'] = [];
|
||||
const pendingAppPlaylists: FoundryPendingAppPlaylist[] = [];
|
||||
for (const pl of docs.playlists) {
|
||||
if (usedPlaylistIds.has(pl._id)) continue;
|
||||
const sounds = [...(pl.sounds ?? [])].sort((a, b) => (a.sort ?? 0) - (b.sort ?? 0));
|
||||
const tracks: FoundryPendingAppPlaylist['tracks'] = [];
|
||||
for (const s of sounds) {
|
||||
if (typeof s.path !== 'string' || !s.path.trim()) continue;
|
||||
const assetId = await resolveAndImport(ctx, manifest, s.path);
|
||||
if (!assetId || ctx.assets[assetId]?.type !== 'audio') continue;
|
||||
campaignAudios.push({ assetId, autoplay: false, loop: true });
|
||||
const abs = await resolveFoundryAssetPath(manifest, s.path);
|
||||
if (!abs) continue;
|
||||
const kind = classifyMediaPath(abs);
|
||||
if (!kind || kind.type !== 'audio') continue;
|
||||
const title =
|
||||
(typeof s.name === 'string' && s.name.trim()) ||
|
||||
path.basename(s.path).replace(/\.[^.]+$/u, '') ||
|
||||
'Track';
|
||||
tracks.push({ title, absPath: abs, mime: kind.mime });
|
||||
}
|
||||
if (tracks.length === 0) continue;
|
||||
pendingAppPlaylists.push({
|
||||
name: (typeof pl.name === 'string' && pl.name.trim()) || 'Playlist',
|
||||
tracks,
|
||||
});
|
||||
}
|
||||
|
||||
const { npcGroups, foundryFolderToGroupId } = buildNpcGroupsFromFoundryFolders(docs.folders);
|
||||
@@ -538,7 +558,7 @@ export async function buildProjectFromFoundryDocuments(
|
||||
sceneGraphEdges,
|
||||
};
|
||||
|
||||
return { project, previewSources };
|
||||
return { project, previewSources, pendingAppPlaylists };
|
||||
}
|
||||
|
||||
/** Actor folders Foundry → дерево групп НПС. */
|
||||
|
||||
Reference in New Issue
Block a user