fix: blur and music duration

This commit is contained in:
2025-11-04 14:10:43 +01:00
parent 47187bd6d8
commit af47934477
10 changed files with 323 additions and 81 deletions
+4 -4
View File
@@ -58,7 +58,7 @@ async function buildCoverWithLogo(backgroundUrl, targetPath) {
}); });
return `https://firebasestorage.googleapis.com/v0/b/${bucket.name}/o/${encodeURIComponent( return `https://firebasestorage.googleapis.com/v0/b/${bucket.name}/o/${encodeURIComponent(
targetPath, targetPath
)}?alt=media&token=${token}`; )}?alt=media&token=${token}`;
} }
@@ -131,7 +131,7 @@ async function performCoverGeneration(project) {
coverStatus: "GENERATED", coverStatus: "GENERATED",
updatedAt: admin.firestore.FieldValue.serverTimestamp(), updatedAt: admin.firestore.FieldValue.serverTimestamp(),
}, },
{ merge: true }, { merge: true }
); );
logger.info("✅ [Cover] Saved", { logger.info("✅ [Cover] Saved", {
@@ -280,7 +280,7 @@ exports.onTaskCreateGenerateCover = onDocumentCreated(
}); });
await event.data.ref.set( await event.data.ref.set(
{ status: "ERROR", error: error?.message || "Erreur" }, { status: "ERROR", error: error?.message || "Erreur" },
{ merge: true }, { merge: true }
); );
await refList.projects.doc(projectId).update({ await refList.projects.doc(projectId).update({
coverStatus: "ERROR", coverStatus: "ERROR",
@@ -318,5 +318,5 @@ exports.onTaskCreateGenerateCover = onDocumentCreated(
} }
} }
} }
}, }
); );
+61 -18
View File
@@ -1,4 +1,11 @@
import { View, Text, ScrollView, StyleSheet, Dimensions } from "react-native"; import {
View,
Text,
ScrollView,
StyleSheet,
Dimensions,
TouchableOpacity,
} from "react-native";
import React, { useEffect, useMemo, useState } from "react"; import React, { useEffect, useMemo, useState } from "react";
import Animated, { import Animated, {
runOnJS, runOnJS,
@@ -50,7 +57,6 @@ const SongStructureDragDrop = ({ sourceItems, initialSelected, onChange }) => {
[sourceItems], [sourceItems],
); );
const [leftItems, setLeftItems] = useState(normalizedSource);
const [rightItems, setRightItems] = useState([]); const [rightItems, setRightItems] = useState([]);
// Compare arrays by item id and order to avoid unnecessary state churn // Compare arrays by item id and order to avoid unnecessary state churn
@@ -67,13 +73,16 @@ const SongStructureDragDrop = ({ sourceItems, initialSelected, onChange }) => {
useEffect(() => { useEffect(() => {
const selected = Array.isArray(initialSelected) ? initialSelected : []; const selected = Array.isArray(initialSelected) ? initialSelected : [];
const selectedSet = new Set(selected); const selectedSet = new Set(selected);
const right = normalizedSource.filter((i) => selectedSet.has(i.id)); const nextRight = normalizedSource.filter((i) => selectedSet.has(i.id));
const left = normalizedSource.filter((i) => !selectedSet.has(i.id)); setRightItems((prev) => (sameById(prev, nextRight) ? prev : nextRight));
setLeftItems((prev) => (sameById(prev, left) ? prev : left));
setRightItems((prev) => (sameById(prev, right) ? prev : right));
}, [normalizedSource, initialSelected]); }, [normalizedSource, initialSelected]);
const leftItems = useMemo(() => {
if (!rightItems || rightItems.length === 0) return normalizedSource;
const rightIds = new Set(rightItems.map((item) => item.id));
return normalizedSource.filter((item) => !rightIds.has(item.id));
}, [normalizedSource, rightItems]);
const draggingItem = useSharedValue(null); const draggingItem = useSharedValue(null);
const translateX = useSharedValue(0); const translateX = useSharedValue(0);
const translateY = useSharedValue(0); const translateY = useSharedValue(0);
@@ -83,15 +92,22 @@ const SongStructureDragDrop = ({ sourceItems, initialSelected, onChange }) => {
}; };
const moveItem = (itemId) => { const moveItem = (itemId) => {
setLeftItems((items) => { const item = normalizedSource.find((i) => i.id === itemId);
const found = items.find((i) => i.id === itemId); if (!item) return;
if (!found) return items; setRightItems((current) => {
setRightItems((r) => { if (current.some((i) => i.id === itemId)) return current;
const next = [...r, found]; const next = [...current, item];
onChange?.(next.map((i) => i.value ?? i.label)); onChange?.(next.map((i) => i.value ?? i.label));
return next; return next;
}); });
return items.filter((i) => i.id !== itemId); };
const removeItem = (itemId) => {
setRightItems((current) => {
if (!current.some((i) => i.id === itemId)) return current;
const next = current.filter((i) => i.id !== itemId);
onChange?.(next.map((i) => i.value ?? i.label));
return next;
}); });
}; };
@@ -133,10 +149,19 @@ const SongStructureDragDrop = ({ sourceItems, initialSelected, onChange }) => {
<ScrollView style={{ ...styles.scroll, zIndex: -1 }}> <ScrollView style={{ ...styles.scroll, zIndex: -1 }}>
<Text style={styles.title}>Right</Text> <Text style={styles.title}>Right</Text>
<Text style={styles.helpText}>Appuyez pour retirer un élément</Text>
{rightItems.map((item) => ( {rightItems.map((item) => (
<View key={item.id} style={[styles.item, { backgroundColor: "#bdf" }]}> <TouchableOpacity
key={item.id}
style={styles.selectedItem}
onPress={() => removeItem(item.id)}
activeOpacity={0.7}
accessibilityRole="button"
accessibilityLabel={`Retirer ${item.label ?? String(item)}`}
>
<Text style={styles.text}>{item.label ?? String(item)}</Text> <Text style={styles.text}>{item.label ?? String(item)}</Text>
</View> <Text style={styles.removeHint}>Retirer</Text>
</TouchableOpacity>
))} ))}
</ScrollView> </ScrollView>
</View> </View>
@@ -169,6 +194,24 @@ const styles = StyleSheet.create({
marginVertical: 5, marginVertical: 5,
borderRadius: 10, borderRadius: 10,
}, },
selectedItem: {
backgroundColor: "#bdf",
padding: 15,
marginVertical: 5,
borderRadius: 10,
alignItems: "center",
},
helpText: {
fontSize: 12,
color: "#666",
marginBottom: 8,
textAlign: "center",
},
removeHint: {
fontSize: 12,
color: "#555",
marginTop: 4,
},
text: { text: {
color: "#333", color: "#333",
textAlign: "center", textAlign: "center",
+19 -3
View File
@@ -65,6 +65,7 @@ const useSharedAudioPlayer = (source, options = null) => {
pause, pause,
resume, resume,
seekTo, seekTo,
ensureLoaded,
isPlaying, isPlaying,
positionMs, positionMs,
durationMs, durationMs,
@@ -84,10 +85,16 @@ const useSharedAudioPlayer = (source, options = null) => {
}; };
}, [isPlaying, positionMs, durationMs]); }, [isPlaying, positionMs, durationMs]);
const controlsRef = useRef({ play, pause, resume, seekTo }); const controlsRef = useRef({
play,
pause,
resume,
seekTo,
ensureLoaded,
});
useEffect(() => { useEffect(() => {
controlsRef.current = { play, pause, resume, seekTo }; controlsRef.current = { play, pause, resume, seekTo, ensureLoaded };
}, [play, pause, resume, seekTo]); }, [play, pause, resume, seekTo, ensureLoaded]);
const playerRef = useRef(null); const playerRef = useRef(null);
@@ -138,6 +145,15 @@ const useSharedAudioPlayer = (source, options = null) => {
}, },
}); });
player.load = async ({ startPositionMs = 0 } = {}) => {
if (typeof controlsRef.current.ensureLoaded !== "function") return;
const target = Math.max(0, Number(startPositionMs) || 0);
await controlsRef.current.ensureLoaded({
startPositionMs: target,
autoPlay: false,
});
};
playerRef.current = player; playerRef.current = player;
} }
}, [descriptor]); }, [descriptor]);
+31 -4
View File
@@ -1,4 +1,4 @@
import { useCallback, useMemo } from "react"; import { useCallback, useMemo, useRef } from "react";
import usePlayer from "./usePlayer"; import usePlayer from "./usePlayer";
const useTrackController = (descriptor) => { const useTrackController = (descriptor) => {
@@ -19,10 +19,38 @@ const useTrackController = (descriptor) => {
return descriptor; return descriptor;
}, [descriptor]); }, [descriptor]);
const lastKnownRef = useRef({
descriptorId: null,
positionMs: 0,
durationMs: 0,
});
const trackId = normalizedDescriptor?.id || null; const trackId = normalizedDescriptor?.id || null;
const isCurrent = trackId ? currentTrack?.id === trackId : false; const isCurrent = trackId ? currentTrack?.id === trackId : false;
const effectivePositionMs = isCurrent ? positionMs : 0; const descriptorId = normalizedDescriptor?.id ?? null;
const effectiveDurationMs = isCurrent ? durationMs : 0;
if (lastKnownRef.current.descriptorId !== descriptorId) {
lastKnownRef.current = {
descriptorId,
positionMs: 0,
durationMs: 0,
};
}
if (isCurrent) {
lastKnownRef.current = {
descriptorId,
positionMs,
durationMs,
};
}
const effectivePositionMs = isCurrent
? positionMs
: lastKnownRef.current.positionMs;
const effectiveDurationMs = isCurrent
? durationMs
: lastKnownRef.current.durationMs;
const effectiveIsPlaying = isCurrent && isPlaying; const effectiveIsPlaying = isCurrent && isPlaying;
const ensureLoaded = useCallback( const ensureLoaded = useCallback(
@@ -140,4 +168,3 @@ const useTrackController = (descriptor) => {
}; };
export default useTrackController; export default useTrackController;
+103 -2
View File
@@ -2,7 +2,7 @@
import { useFocusEffect } from "@react-navigation/native"; import { useFocusEffect } from "@react-navigation/native";
import { BlurView } from "expo-blur"; import { BlurView } from "expo-blur";
import React, { useCallback, useEffect, useRef, useState } from "react"; import React, { useCallback, useEffect, useRef, useState } from "react";
import { Image, Platform, Pressable, Text, View } from "react-native"; import { Image, Pressable, Text, View } from "react-native";
import { responsiveHeight } from "react-native-responsive-dimensions"; import { responsiveHeight } from "react-native-responsive-dimensions";
import { background, icons } from "../../assets"; import { background, icons } from "../../assets";
import alert from "../../components/Alert"; import alert from "../../components/Alert";
@@ -35,6 +35,9 @@ const SongReady = () => {
0: { pos: 0, dur: 0 }, 0: { pos: 0, dur: 0 },
1: { pos: 0, dur: 0 }, 1: { pos: 0, dur: 0 },
}); });
console.log("progress info", JSON.stringify(progressInfo, null, 2));
const player0 = useSharedAudioPlayer( const player0 = useSharedAudioPlayer(
musicUrls[0] ? { uri: musicUrls[0] } : undefined, musicUrls[0] ? { uri: musicUrls[0] } : undefined,
{ {
@@ -66,6 +69,10 @@ const SongReady = () => {
} }
); );
const wasPlayingBeforeSeek = useRef({ 0: false, 1: false }); const wasPlayingBeforeSeek = useRef({ 0: false, 1: false });
const prefetchedRef = useRef({
0: { url: null, done: false },
1: { url: null, done: false },
});
// Sync URLs from provider's selectedProject // Sync URLs from provider's selectedProject
useEffect(() => { useEffect(() => {
@@ -75,6 +82,100 @@ const SongReady = () => {
setMusicUrls(urls); setMusicUrls(urls);
}, [selectedProject?.musicUrls]); }, [selectedProject?.musicUrls]);
const firstUrl = musicUrls[0] ?? null;
const secondUrl = musicUrls[1] ?? null;
useEffect(() => {
let isCancelled = false;
const waitForDuration = async (index) => {
const getDurationSeconds = () => {
const targetPlayer = index === 0 ? player0 : player1;
if (!targetPlayer) return 0;
const raw = Number(targetPlayer.duration || 0);
return Number.isFinite(raw) ? raw : 0;
};
const start = Date.now();
while (!isCancelled) {
const durationSeconds = getDurationSeconds();
if (durationSeconds > 0) {
const durationMs = Math.round(durationSeconds * 1000);
setProgressInfo((prev) => {
const current = prev?.[index] || { pos: 0, dur: 0 };
if (Math.abs(current.dur - durationMs) < 5) return prev;
return {
...prev,
[index]: {
...current,
dur: durationMs,
},
};
});
return true;
}
if (Date.now() - start > 6000) {
break;
}
await new Promise((resolve) => setTimeout(resolve, 150));
}
return false;
};
const prefetch = async () => {
const entries = [
[0, firstUrl, player0],
[1, secondUrl, player1],
];
for (const [idx, url, player] of entries) {
const cacheEntry = prefetchedRef.current[idx];
if (!url || !player?.load) {
if (cacheEntry) {
cacheEntry.url = url ?? null;
cacheEntry.done = false;
}
continue;
}
if (cacheEntry && cacheEntry.url === url && cacheEntry.done) {
continue;
}
if (cacheEntry) {
cacheEntry.url = url;
cacheEntry.done = false;
}
try {
await player.load({ startPositionMs: 0 });
const resolved = await waitForDuration(idx);
if (cacheEntry && !isCancelled) {
cacheEntry.done = resolved;
}
} catch (err) {
if (cacheEntry) {
cacheEntry.done = false;
}
if (!isCancelled) {
console.log("SongReady preload error", err?.message);
}
}
if (isCancelled) {
break;
}
}
};
prefetch();
return () => {
isCancelled = true;
};
}, [firstUrl, secondUrl, player0, player1]);
// Sync progression depuis les players // Sync progression depuis les players
useEffect(() => { useEffect(() => {
// Ensure we consistently work with milliseconds whatever the platform // Ensure we consistently work with milliseconds whatever the platform
@@ -264,7 +365,7 @@ const SongReady = () => {
]} ]}
> >
<BlurView <BlurView
intensity={Platform.OS !== "ios" ? 10 : 40} intensity={40}
tint="dark" tint="dark"
style={{ style={{
borderRadius: 18, borderRadius: 18,
+27 -12
View File
@@ -1,5 +1,5 @@
import { View, Text, ScrollView, Pressable } from "react-native"; import { View, Text, ScrollView, Pressable } from "react-native";
import React, { useState } from "react"; import React, { useMemo, useState } from "react";
import Page from "../../layouts/Page"; import Page from "../../layouts/Page";
import { background } from "../../assets"; import { background } from "../../assets";
import GradientButton from "../../components/GradientButton"; import GradientButton from "../../components/GradientButton";
@@ -11,10 +11,15 @@ import { useUser } from "../../providers/UserDataProvider";
import { responsiveHeight } from "react-native-responsive-dimensions"; import { responsiveHeight } from "react-native-responsive-dimensions";
const Studio = () => { const Studio = () => {
const [selected, setSelected] = useState(null); const [selectedProjectId, setSelectedProjectId] = useState(null);
const { userProjects: projects, selectProject } = useUser(); const { userProjects: projects, selectProject } = useUser();
const selectedProject = useMemo(() => {
if (!Array.isArray(projects) || !projects.length) return null;
return projects.find((p) => p?.id === selectedProjectId) ?? null;
}, [projects, selectedProjectId]);
return ( return (
<Page <Page
headerType="NONE" headerType="NONE"
@@ -41,7 +46,7 @@ const Studio = () => {
{projects.map((p) => { {projects.map((p) => {
const couplet = p?.lyrics?.couplet || ""; const couplet = p?.lyrics?.couplet || "";
const preview = couplet.split("\n").slice(0, 2).join(" "); const preview = couplet.split("\n").slice(0, 2).join(" ");
const isSelected = selected === p; const isSelected = selectedProjectId === p?.id;
const createdAt = p?.createdAt?.toDate const createdAt = p?.createdAt?.toDate
? p.createdAt.toDate() ? p.createdAt.toDate()
: p?.createdAt : p?.createdAt
@@ -54,13 +59,20 @@ const Studio = () => {
return ( return (
<Pressable <Pressable
key={p.id} key={p.id}
onPress={() => setSelected(p)} onPress={() => setSelectedProjectId(p?.id ?? null)}
style={{ style={{
backgroundColor: "#0F0C1933", backgroundColor: isSelected
? "rgba(15, 12, 25, 0.75)"
: "rgba(15, 12, 25, 0.2)",
borderRadius: 12, borderRadius: 12,
padding: 12, padding: 12,
borderWidth: isSelected ? 2 : 0, borderWidth: isSelected ? 2 : 0,
borderColor: isSelected ? "#F94697" : "transparent", borderColor: isSelected ? "#F94697" : "transparent",
shadowColor: "rgba(0, 0, 0, 0.55)",
shadowOpacity: isSelected ? 0.6 : 0.35,
shadowRadius: isSelected ? 8 : 4,
shadowOffset: { width: 0, height: 4 },
elevation: isSelected ? 6 : 1,
}} }}
> >
<Text <Text
@@ -100,20 +112,21 @@ const Studio = () => {
<View style={{ justifyContent: "flex-end" }}> <View style={{ justifyContent: "flex-end" }}>
<GradientButton <GradientButton
title="Commencer" title="Commencer"
disabled={!selected} disabled={!selectedProject}
onPress={() => { onPress={() => {
if (selected.musicStatus === "GENERATING") { if (selectedProject?.musicStatus === "GENERATING") {
alert( alert(
"Attention", "Attention",
"La chanson est en cours de génération. Veuillez patienter.", "La chanson est en cours de génération. Veuillez patienter.",
); );
} else { } else {
selectProject(selected.id); if (!selectedProject?.id) return;
selectProject(selectedProject.id);
navigate(Routes.Compose); navigate(Routes.Compose);
} }
}} }}
/> />
{selected?.musicUrls?.length > 0 && ( {selectedProject?.musicUrls?.length > 0 && (
<> <>
<GradientButton <GradientButton
title="Ecouter les audios" title="Ecouter les audios"
@@ -121,18 +134,20 @@ const Studio = () => {
marginTop: responsiveHeight(2), marginTop: responsiveHeight(2),
}} }}
onPress={() => { onPress={() => {
selectProject(selected.id); if (!selectedProject?.id) return;
selectProject(selectedProject.id);
navigate(Routes.SongReady); navigate(Routes.SongReady);
}} }}
/> />
{!selected?.coverUrl && ( {!selectedProject?.coverUrl && (
<GradientButton <GradientButton
title="Générer une pochette" title="Générer une pochette"
containerStyle={{ containerStyle={{
marginTop: responsiveHeight(2), marginTop: responsiveHeight(2),
}} }}
onPress={() => { onPress={() => {
selectProject(selected.id); if (!selectedProject?.id) return;
selectProject(selectedProject.id);
navigate(Routes.PouchReady); navigate(Routes.PouchReady);
}} }}
/> />
+35 -17
View File
@@ -188,6 +188,36 @@ const CreateLyricsWithAi = () => {
} }
}, [structure]); }, [structure]);
const sanitizedCustomStructure = useMemo(() => {
if (!Array.isArray(customStructure) || customStructure.length === 0) {
return [];
}
return sanitizeStructureList(customStructure, CUSTOM_SANITIZE_OPTIONS);
}, [customStructure]);
const sanitizedParsedStructure = useMemo(() => {
if (!Array.isArray(parsedStructure) || parsedStructure.length === 0) {
return [];
}
return sanitizeStructureList(parsedStructure, CUSTOM_SANITIZE_OPTIONS);
}, [parsedStructure]);
const fallbackProjectStructure = useMemo(() => {
const raw = selectedProject?.config?.structure;
if (!Array.isArray(raw) || raw.length === 0) return [];
return sanitizeStructureList(raw, CUSTOM_SANITIZE_OPTIONS);
}, [selectedProject]);
const baseStructureForCustomizer = useMemo(() => {
if (sanitizedCustomStructure.length > 0) return sanitizedCustomStructure;
if (sanitizedParsedStructure.length > 0) return sanitizedParsedStructure;
return fallbackProjectStructure;
}, [
sanitizedCustomStructure,
sanitizedParsedStructure,
fallbackProjectStructure,
]);
const lyricsConfig = useMemo(() => { const lyricsConfig = useMemo(() => {
const resolvedObjective = shouldUseOtherObjective const resolvedObjective = shouldUseOtherObjective
? persistedOtherObjective || undefined ? persistedOtherObjective || undefined
@@ -195,13 +225,8 @@ const CreateLyricsWithAi = () => {
const resolvedStyle = shouldUseOtherStyle const resolvedStyle = shouldUseOtherStyle
? persistedOtherStyle || undefined ? persistedOtherStyle || undefined
: style || undefined; : style || undefined;
const sanitizedCustom = const sanitizedCustom = sanitizedCustomStructure;
Array.isArray(customStructure) && customStructure.length > 0 const sanitizedParsed = sanitizedParsedStructure;
? sanitizeStructureList(customStructure, CUSTOM_SANITIZE_OPTIONS)
: [];
const sanitizedParsed = Array.isArray(parsedStructure)
? sanitizeStructureList(parsedStructure)
: [];
return { return {
objective: resolvedObjective, objective: resolvedObjective,
@@ -230,9 +255,9 @@ const CreateLyricsWithAi = () => {
emotion, emotion,
style, style,
audience, audience,
parsedStructure, sanitizedParsedStructure,
rhymes, rhymes,
customStructure, sanitizedCustomStructure,
]); ]);
const isNextDisabled = React.useMemo(() => { const isNextDisabled = React.useMemo(() => {
@@ -444,14 +469,7 @@ const CreateLyricsWithAi = () => {
}} }}
> >
<CustomizeSongStructure <CustomizeSongStructure
baseStructure={ baseStructure={baseStructureForCustomizer}
parsedStructure ||
(Array.isArray(customStructure)
? customStructure
: Array.isArray(selectedProject?.config?.structure)
? selectedProject.config.structure
: [])
}
onChange={setCustomStructure} onChange={setCustomStructure}
/> />
</View> </View>
+35 -17
View File
@@ -188,6 +188,36 @@ const CreateLyricsWithAi = () => {
} }
}, [structure]); }, [structure]);
const sanitizedCustomStructure = useMemo(() => {
if (!Array.isArray(customStructure) || customStructure.length === 0) {
return [];
}
return sanitizeStructureList(customStructure, CUSTOM_SANITIZE_OPTIONS);
}, [customStructure]);
const sanitizedParsedStructure = useMemo(() => {
if (!Array.isArray(parsedStructure) || parsedStructure.length === 0) {
return [];
}
return sanitizeStructureList(parsedStructure, CUSTOM_SANITIZE_OPTIONS);
}, [parsedStructure]);
const fallbackProjectStructure = useMemo(() => {
const raw = selectedProject?.config?.structure;
if (!Array.isArray(raw) || raw.length === 0) return [];
return sanitizeStructureList(raw, CUSTOM_SANITIZE_OPTIONS);
}, [selectedProject]);
const baseStructureForCustomizer = useMemo(() => {
if (sanitizedCustomStructure.length > 0) return sanitizedCustomStructure;
if (sanitizedParsedStructure.length > 0) return sanitizedParsedStructure;
return fallbackProjectStructure;
}, [
sanitizedCustomStructure,
sanitizedParsedStructure,
fallbackProjectStructure,
]);
const progress = useMemo( const progress = useMemo(
() => 16 + selectedIndex * 9, () => 16 + selectedIndex * 9,
[selectedIndex] [selectedIndex]
@@ -200,13 +230,8 @@ const CreateLyricsWithAi = () => {
const resolvedStyle = shouldUseOtherStyle const resolvedStyle = shouldUseOtherStyle
? persistedOtherStyle || undefined ? persistedOtherStyle || undefined
: style || undefined; : style || undefined;
const sanitizedCustom = const sanitizedCustom = sanitizedCustomStructure;
Array.isArray(customStructure) && customStructure.length > 0 const sanitizedParsed = sanitizedParsedStructure;
? sanitizeStructureList(customStructure, CUSTOM_SANITIZE_OPTIONS)
: [];
const sanitizedParsed = Array.isArray(parsedStructure)
? sanitizeStructureList(parsedStructure)
: [];
return { return {
objective: resolvedObjective, objective: resolvedObjective,
@@ -235,9 +260,9 @@ const CreateLyricsWithAi = () => {
emotion, emotion,
style, style,
audience, audience,
parsedStructure, sanitizedParsedStructure,
rhymes, rhymes,
customStructure, sanitizedCustomStructure,
]); ]);
const isNextDisabled = React.useMemo(() => { const isNextDisabled = React.useMemo(() => {
@@ -358,14 +383,7 @@ const CreateLyricsWithAi = () => {
key: "customStructure", key: "customStructure",
render: () => ( render: () => (
<CustomizeSongStructure <CustomizeSongStructure
baseStructure={ baseStructure={baseStructureForCustomizer}
parsedStructure ||
(Array.isArray(customStructure)
? customStructure
: Array.isArray(selectedProject?.config?.structure)
? selectedProject.config.structure
: [])
}
onChange={setCustomStructure} onChange={setCustomStructure}
/> />
), ),
+6 -2
View File
@@ -77,7 +77,10 @@ const CreatingLyrics = ({ active, config, selections }) => {
console.log("🚀 [CreatingLyrics] Lancement de la génération", { console.log("🚀 [CreatingLyrics] Lancement de la génération", {
platform: Platform.OS, platform: Platform.OS,
}); });
const sanitizedStructure = sanitizeStructureList(config?.structure); const sanitizedStructure = sanitizeStructureList(
config?.structure,
CUSTOM_SANITIZE_OPTIONS
);
const callable = firebase const callable = firebase
.functions() .functions()
.httpsCallable("lyrics-generateLyrics"); .httpsCallable("lyrics-generateLyrics");
@@ -168,7 +171,8 @@ const CreatingLyrics = ({ active, config, selections }) => {
await setIsLoading(true); await setIsLoading(true);
console.log("💾 [CreatingLyrics] Sauvegarde automatique des paroles"); console.log("💾 [CreatingLyrics] Sauvegarde automatique des paroles");
const sanitizedStructure = sanitizeStructureList( const sanitizedStructure = sanitizeStructureList(
result?.structure || config?.structure result?.structure || config?.structure,
CUSTOM_SANITIZE_OPTIONS
); );
const baseConfig = const baseConfig =
config && typeof config === "object" && !Array.isArray(config) config && typeof config === "object" && !Array.isArray(config)
+2 -2
View File
@@ -17,7 +17,7 @@
1. Ouvre cette URL dans un navigateur (mets à jour `redirect_uri` si besoin) : 1. Ouvre cette URL dans un navigateur (mets à jour `redirect_uri` si besoin) :
``` ```
https://accounts.google.com/o/oauth2/v2/auth?client_id=YOUR_CLIENT_ID.apps.googleusercontent.com&redirect_uri=http://localhost:8081&response_type=code&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fyoutube.upload&access_type=offline&prompt=consent https://accounts.google.com/o/oauth2/v2/auth?client_id=305598753437-3mn43cs1phacao2f1ctg1dbur7rct0bt.apps.googleusercontent.com&redirect_uri=http://localhost:8081&response_type=code&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fyoutube.upload&access_type=offline&prompt=consent
``` ```
2. Connecte-toi au compte YouTube cible et accepte les permissions. 2. Connecte-toi au compte YouTube cible et accepte les permissions.
3. Google te redirige vers `http://localhost:8081/?code=...`. Copie la valeur du paramètre `code`. 3. Google te redirige vers `http://localhost:8081/?code=...`. Copie la valeur du paramètre `code`.
@@ -26,7 +26,7 @@
```bash ```bash
curl -X POST https://oauth2.googleapis.com/token \ curl -X POST https://oauth2.googleapis.com/token \
-d client_id=YOUR_CLIENT_ID.apps.googleusercontent.com \ -d client_id=YOUR_CLIENT_ID \
-d client_secret=YOUR_CLIENT_SECRET \ -d client_secret=YOUR_CLIENT_SECRET \
-d code="CODE_RECU" \ -d code="CODE_RECU" \
-d grant_type=authorization_code \ -d grant_type=authorization_code \