● Korben
📅 25/03/2026 à 15:22
Sora ferme - Comment sauvegarder vos vidéos IA avant la coupure - Korben
Cybersécurité
👤 Korben
Sora ferme - Comment sauvegarder vos vidéos IA avant la coupure25 mars 2026 / PAR KORBEN ✨ / 12 MIN DE LECTURE / À lire plus tard SauvegardéCe qu’il faut retenirOpenAI ferme Sora (plateforme vidéo IA) sans date précise, les utilisateurs doivent sauvegarder leurs créations avant la coupure définitiveUn script JavaScript gratuit télécharge automatiquement toutes vos vidéos Sora + prompts + métadonnées en ZIP, sans installation ni compte externeLes téléchargements de Sora ont plongé de 32% entre novembre et décembre 2025 après un lancement en fanfare, OpenAI abandonne pour se concentrer sur la robotique 3DRésumé généré par IASora, c’est fini les amis !Hé oui, cest chacals d'OpenAI ferment leur plateforme de vidéos IA, et franchement, ça me rend un peu triste. À vrai dire, même si c’était que de la vidéo générée à partir de prompts, moi je me marrais bien. C'était fun de regarder le produit de ses prompts mais aussi de regarder les conneries des autres. Les versions québécoises, aïe aïe aïe, c’était quelque chose quand même !Mais bon, le plus urgent maintenant, c’est de sauvegarder vos vidéos avant que tout disparaisse. OpenAI n’a pas encore communiqué de date précise pour la coupure, juste un vague « on vous dira bientôt ». Du coup, autant ne pas traîner, parce que quand ce genre de service cloud ferme, en général c’est pas 6 mois de préavis qu’on vous file... Votre navigateur ne supporte pas la lecture de vidéos HTML5. Voici un lien vers la vidéo.Depuis la fuite du modèle jusqu’à aujourd’hui, Sora aura fait parler de lui. Côté raisons, c’est Fidji Simo (la patronne de la division Applications) qui a lâché le morceau : ils éparpillent leurs efforts sur trop d’apps, d’API et de stacks serveur différents, et ça les ralentit. En gros, entre préparer une entrée en bourse pour fin 2026 et cramer du GPU H100 sur des vidéos de chats en IA, le choix est vite fait. L’équipe de recherche Sora, elle, continuera à bosser sur la simulation de mondes 3D... mais pour la robotique. Et le fameux deal à 1 milliard de dollars avec Disney pour des films et séries ? Pouf, magie magie, c'est envolé !!Faut dire que les chiffres n’étaient pas glorieux non plus. Après un lancement en fanfare fin 2024 (et une app iOS lancée à l’automne 2025 qui avait cartonné dans les charts), les téléchargements sur l’App Store avaient plongé de 32% entre novembre et décembre 2025. La hype, ça dure qu’un temps.Mais maintenant les gens, on passe aux choses sérieuses ! Votre navigateur ne supporte pas la lecture de vidéos HTML5. Voici un lien vers la vidéo.Sora Backup - le script qui sauve vos vidéosJe n'avais absolument pas de temps aujourd'hui, mais j'ai quand même taffé pour vous développer un petit script JavaScript qui récupère TOUTES vos vidéos Sora d’un coup, avec les prompts et les métadonnées, et qui vous génère un joli ZIP prêt à archiver. Pas besoin d’installer quoi que ce soit, pas d’extension louche. Vous avez juste besoin d'être connecté à votre profil Sora et d'un navigateur. Votre navigateur ne supporte pas la lecture de vidéos HTML5. Voici un lien vers la vidéo.Comment ça marcheAllez sur sora.com , connectez-vous à votre compte, puis ouvrez la console JavaScript de votre navigateur (F12 sur Chrome ou Firefox, onglet Console). Ensuite, glissez-déplacez ou collez le script ci-dessous dedans et appuyez sur Entrée.Le script va automatiquement récupérer votre token d’authentification (pas besoin de le chercher vous-même), puis il va paginer sur votre profil Sora pour récupérer tous vos posts publiés. Pour chaque post, il extrait les vidéos attachées (MP4), les télécharge, et empaquette le tout dans un fichier ZIP directement dans votre navigateur. Votre navigateur ne supporte pas la lecture de vidéos HTML5. Voici un lien vers la vidéo.Y’a même un fichier manifest.json dans le ZIP qui contient tous vos prompts, les dimensions, les durées, les permalinks, les dates de création... bref, tout ce qu’il faut pour retrouver vos petits. Le ZIP est généré en format STORE (pas compressé, parce que compresser du MP4 ça sert à rien), avec un calcul CRC32 maison et sans aucune librairie externe.Le script completVoici le code à coller dans la console :// ========================================================== // SORA BACKUP - Sauvegarde complète vidéos + images + prompts par Korben // ========================================================== // Usage : Ouvrir https://sora.com, F12 > Console, coller ce script // Les fichiers sont téléchargés via le navigateur (dossier Downloads) // Un fichier manifest.json récapitule tout (prompts, metadata, URLs) // ========================================================== (async () => { // --- Mini ZIP builder (STORE, pas de lib externe) --- const crc32table = new Uint32Array(256); for (let i = 0; i < 256; i++) { let c = i; for (let j = 0; j < 8; j++) c = (c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1); crc32table[i] = c; } function crc32(buf) { let c = 0xFFFFFFFF; for (let i = 0; i < buf.length; i++) c = crc32table[(c ^ buf[i]) & 0xFF] ^ (c >>> 8); return (c ^ 0xFFFFFFFF) >>> 0; } const zipFiles = []; // {name, data (Uint8Array), crc, size} const PAGE_SIZE = 50; const DELAY_MS = 1500; const manifest = []; let totalDownloaded = 0; let totalErrors = 0; // --- Auth : récupérer le Bearer token --- // OPTION 1 : Coller ton token ici (Network tab > Authorization header) // OPTION 2 : Laisser vide, le script tentera de le récupérer auto let AUTH_TOKEN = ''; async function getAuthToken() { if (AUTH_TOKEN) return AUTH_TOKEN; // Auto-detect : endpoint session ChatGPT for (const path of ['/api/auth/session', '/backend-api/auth/session']) { try { const r = await fetch(path, { credentials: 'include' }); if (r.ok) { const json = await r.json(); if (json.accessToken) { AUTH_TOKEN = json.accessToken; console.log(' 🔑 Token récupéré automatiquement'); return AUTH_TOKEN; } } } catch(e) {} } // Fallback : demander à l'utilisateur const input = prompt( 'Token non trouvé automatiquement.\n\n' + 'Pour le récupérer :\n' + '1. F12 > onglet Réseau\n' + '2. Rafraîchis la page\n' + '3. Clique sur une requête /backend/...\n' + '4. Copie le header Authorization\n\n' + 'Colle le token ici (Bearer eyJ...):' ); if (input) { AUTH_TOKEN = input.replace(/^Bearer\s+/i, '').trim(); return AUTH_TOKEN; } console.error(' ❌ Pas de token. Annulation.'); return null; } // --- Fetch API avec auth --- async function apiFetch(url) { const token = await getAuthToken(); const headers = {}; if (token) headers['Authorization'] = 'Bearer ' + token; // oai-device-id requis par certains endpoints const deviceId = localStorage.getItem('oai-did') || ''; if (deviceId) headers['oai-device-id'] = deviceId; const resp = await fetch(url, { method: 'GET', credentials: 'include', headers }); if (!resp.ok) throw new Error(`HTTP ${resp.status} for ${url}`); return resp.json(); } // --- Pagination générique --- async function fetchAllPages(baseUrl, dataField = 'data', cursorParam = 'after', cursorField = 'last_id') { let allItems = []; let cursor = ''; let page = 0; while (true) { let url = baseUrl; if (cursor) url += `&${cursorParam}=${cursor}`; console.log(` 📄 Page ${++page} (${allItems.length} items so far)...`); const json = await apiFetch(url); const items = json[dataField]; if (!Array.isArray(items) || items.length === 0) break; allItems = allItems.concat(items); cursor = json[cursorField] || ''; if (!json.has_more && !cursor) break; await sleep(DELAY_MS); } return allItems; } // Variante pour les endpoints project_y (cursor-based) async function fetchAllPagesCursor(baseUrl) { let allItems = []; let cursor = ''; let page = 0; while (true) { let url = baseUrl; if (cursor) url += `&cursor=${cursor}`; console.log(` 📄 Page ${++page} (${allItems.length} items so far)...`); const json = await apiFetch(url); const items = json.items; if (!Array.isArray(items) || items.length === 0) break; allItems = allItems.concat(items); cursor = json.cursor || ''; if (!cursor) break; await sleep(DELAY_MS); } return allItems; } function sleep(ms) { return new Promise(r => setTimeout(r, ms)); } // --- Extraire URL du média depuis une generation --- function getMediaUrl(gen) { return gen?.encodings?.source?.path || gen?.downloadable_url || gen?.url || ''; } // --- Extraire le prompt (peut être dans actions, prompt, ou input_text) --- function getPrompt(item, gen) { // Prompt direct if (gen?.prompt) return gen.prompt; if (item?.prompt) return item.prompt; if (item?.input_text) return item.input_text; // Storyboard : les actions sont les descriptions des scènes if (item?.actions && typeof item.actions === 'object') { return Object.entries(item.actions) .sort((a,b) => Number(a[0]) - Number(b[0])) .map(([frame, desc]) => `[frame ${frame}] ${desc}`) .join(' | '); } if (gen?.actions && typeof gen.actions === 'object') { return Object.entries(gen.actions) .sort((a,b) => Number(a[0]) - Number(b[0])) .map(([frame, desc]) => `[frame ${frame}] ${desc}`) .join(' | '); } return ''; } // --- Dérouler les items du profil Sora en items plats --- function flattenProfileItems(items) { const flat = []; for (const item of items) { const post = item.post || item; const attachments = post.attachments || []; if (attachments.length === 0) continue; for (const att of attachments) { const url = att.encodings?.source?.path || att.downloadable_url || att.url || ''; if (!url) continue; flat.push({ id: post.id || att.generation_id || '', generation_id: att.generation_id || '', task_id: att.task_id || '', title: att.title || post.discovery_phrase || '', prompt: post.text || '', emoji: post.emoji || '', type: att.generation_type || att.kind || '', width: att.width || 0, height: att.height || 0, duration_s: att.duration_s || 0, is_public: !!post.posted_to_public, created_at: post.posted_at ? new Date(post.posted_at * 1000).toISOString() : '', url: url, permalink: post.permalink || '', username: item.profile?.username || '', }); } } return flat; } // --- Sanitize filename --- function sanitize(name) { return name.replace(/[:"\/\\|?*\x00-\x1f]/g, '_').substring(0, 100); } // --- Ajouter un fichier au ZIP --- async function addToZip(url, filename) { try { const resp = await fetch(url); if (!resp.ok) throw new Error(`HTTP ${resp.status}`); const buf = await resp.arrayBuffer(); const data = new Uint8Array(buf); zipFiles.push({ name: filename, data, crc: crc32(data), size: data.length }); totalDownloaded++; return true; } catch(e) { console.warn(` ⚠️ Erreur ${filename}:`, e.message); totalErrors++; return false; } } // --- Déduire l'extension --- function getExt(url, type) { if (!url) return type === 'video' ? '.mp4' : '.png'; const m = url.match(/\.(mp4|webm|mov|png|jpg|jpeg|webp|gif)/i); return m ? '.' + m[1].toLowerCase() : (type === 'video' ? '.mp4' : '.png'); } // ========================================================== // MAIN // ========================================================== const origin = window.location.origin; console.log('🎬 SORA BACKUP - Démarrage'); console.log('='.repeat(50)); // 1. Mes posts Sora (profil) console.log('\n📦 1/2 - Récupération de mes posts Sora...'); let myPosts = []; try { myPosts = await fetchAllPagesCursor( `${origin}/backend/project_y/profile_feed/me?limit=${PAGE_SIZE}&cut=nf2` ); console.log(` ✅ ${myPosts.length} posts de profil`); // Debug premier item if (myPosts.length > 0) { const first = myPosts[0]; console.log(' 🔍 Premier item - clés:', Object.keys(first).join(', ')); console.log(' 🔍 URL:', first.url?.substring(0, 80) || 'none'); console.log(' 🔍 DL:', first.downloadable_url?.substring(0, 80) || 'none'); console.log(' 🔍 ENC:', first.encodings?.source?.path?.substring(0, 80) || 'none'); console.log(' 🔍 GENS:', first.generations?.length || 'none'); console.log(' 🔍 TITLE:', first.title || 'none'); } } catch(e) { console.warn(' ⚠️ profil failed:', e.message); } // 2. Mes likes sur Sora console.log('\n📦 2/2 - Récupération de mes likes Sora...'); let myLikes = []; try { myLikes = await fetchAllPagesCursor( `${origin}/backend/project_y/profile_feed/me?limit=${PAGE_SIZE}&cut=appearances` ); if (myCameos.length) console.log(` ✅ ${myCameos.length} cameos trouvés`); } catch(e) {} // --- Dérouler les generations et dédupliquer --- console.log('\n🔄 Extraction des vidéos...'); const rawAll = [...myPosts, ...myLikes]; const flatItems = flattenProfileItems(rawAll); const seen = new Set(); const allItems = []; for (const item of flatItems) { if (item.id && seen.has(item.id)) continue; // Filtrer : vidéos uniquement const isVideo = item.type === 'video_gen' || item.url.includes('/videos/') || item.url.includes('.mp4'); if (!isVideo) continue; if (item.id) seen.add(item.id); allItems.push(item); } console.log(`📊 Total unique: ${allItems.length} vidéos à télécharger`); console.log('='.repeat(50)); // --- Construire le manifest et télécharger --- console.log('\n⬇️ Téléchargement en cours...'); console.log('(Les fichiers arrivent dans ton dossier Downloads)'); for (let i = 0; i < allItems.length; i++) { const meta = allItems[i]; const url = meta.url; if (!url) { console.log(` ⏭️ [${i+1}/${allItems.length}] ${meta.id} - pas d'URL, skip`); meta.downloaded = false; manifest.push(meta); continue; } const type = (meta.task_type === 'image_gen' || url.match(/\.(png|jpg|jpeg|webp|gif)/i)) ? 'image' : 'video'; const ext = getExt(url, type); const nameBase = meta.title ? sanitize(meta.title) : (meta.prompt ? sanitize(meta.prompt.substring(0, 60)) : meta.id); const filename = `sora_${String(i+1).padStart(4,'0')}_${nameBase}${ext}`; console.log(` ⬇️ [${i+1}/${allItems.length}] ${filename}`); meta.filename = filename; meta.downloaded = await addToZip(url, filename); manifest.push(meta); // Pause entre downloads pour pas surcharger if (i < allItems.length - 1) await sleep(800); } // --- Ajouter le manifest au ZIP --- console.log('\n📝 Ajout du manifest au ZIP...'); const manifestData = new TextEncoder().encode(JSON.stringify(manifest, null, 2)); zipFiles.push({ name: 'manifest.json', data: manifestData, crc: crc32(manifestData), size: manifestData.length }); // --- Générer le ZIP (format STORE, pas de compression) --- console.log('\n📦 Génération du ZIP...'); const enc = new TextEncoder(); const blobParts = []; const centralParts = []; let offset = 0; for (const f of zipFiles) { const nameBytes = enc.encode(f.name); // Local file header (30 bytes + name) const lh = new ArrayBuffer(30); const lv = new DataView(lh); lv.setUint32(0, 0x04034b50, true); lv.setUint16(4, 20, true); lv.setUint16(8, 0, true); // STORE lv.setUint32(14, f.crc, true); lv.setUint32(18, f.size, true); lv.setUint32(22, f.size, true); lv.setUint16(26, nameBytes.length, true); blobParts.push(new Uint8Array(lh), nameBytes, f.data); // Central directory entry (46 bytes + name) const ch = new ArrayBuffer(46); const cv = new DataView(ch); cv.setUint32(0, 0x02014b50, true); cv.setUint16(4, 20, true); cv.setUint16(6, 20, true); cv.setUint16(10, 0, true); // STORE cv.setUint32(16, f.crc, true); cv.setUint32(20, f.size, true); cv.setUint32(24, f.size, true); cv.setUint16(28, nameBytes.length, true); cv.setUint32(42, offset, true); centralParts.push(new Uint8Array(ch), nameBytes); offset += 30 + nameBytes.length + f.size; } const centralSize = centralParts.reduce((s, p) => s + p.length, 0); const eocd = new ArrayBuffer(22); const ev = new DataView(eocd); ev.setUint32(0, 0x06054b50, true); ev.setUint16(8, zipFiles.length, true); ev.setUint16(10, zipFiles.length, true); ev.setUint32(12, centralSize, true); ev.setUint32(16, offset, true); const zipBlob = new Blob([...blobParts, ...centralParts, new Uint8Array(eocd)], { type: 'application/zip' }); const zipName = `sora_backup_${new Date().toISOString().split('T')[0]}.zip`; const a = document.createElement('a'); a.href = URL.createObjectURL(zipBlob); a.download = zipName; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(a.href); // --- Résumé --- const sizeMB = (zipBlob.size / 1024 / 1024).toFixed(1); console.log('\n' + '='.repeat(50)); console.log('🎬 SORA BACKUP TERMINÉ'); console.log(` ✅ Vidéos dans le ZIP : ${totalDownloaded}`); console.log(` ❌ Erreurs : ${totalErrors}`); console.log(` 📦 Fichier : ${zipName} (${sizeMB} MB)`); console.log(` 📝 manifest.json inclus dans le ZIP`); console.log('='.repeat(50)); })(); Quelques précisionsSi le token n’est pas récupéré automatiquement (ça peut arriver selon votre config), le script vous demandera de le coller manuellement. Pour le trouver, c’est simple : F12 > onglet Réseau > rafraîchissez la page > cliquez sur n’importe quelle requête vers /backend/... > copiez le header Authorization.D’ailleurs, si la vidéo IA vous branche toujours, Higgsfield propose des séries entièrement générées par IA. C’est pas la même approche que Sora, mais c’est un signe que la vidéo IA ne meurt pas avec la fermeture d’un seul service. Votre navigateur ne supporte pas la lecture de vidéos HTML5. Voici un lien vers la vidéo.Bon, bref, c’est la fin d’un truc sympa. Moi je préférais largement scroller sur Sora sur d'aller sur TikTok ou Instagram parce qu'au moins c'était drôle !Merci à mes Patreons qui me permettent de prendre le temps de développer ce genre de petits outils pour vous. Sans eux, j’aurais jamais pu me poser une après-midi pour coder ça.SourceRéférenceshttps://sora.comhttps://www.engadget.com/ai/openai-is-shutting-down-its-sora-video-generation-app-211023358.htmlCet article peut contenir des images générées à l'aide de l'IA - J'apporte le plus grand soin à chaque article, toutefois, si vous repérez une boulette, faites-moi signe !Vous avez aimé cet article ?Alors rejoignez ma communauté sur Patreon et accédez à des articles exclusifs, des tutos avancés et plein d'autres surprises que je réserve à mes soutiens. C'est grâce à vous que je peux continuer à partager ma passion depuis 20 ans !Rejoindre l'aventure La clé de la réussite pour votre TPE/PME : les nouvelles offres o2switchContenu partenaireVous cherchez un hébergement web professionnel pour propulser votre entreprise ? Ne cherchez plus.Avec les nouvelles offres de o2switch, offrez à votre TPE/PME l'hébergement qu'elle mérite pour viser les sommets.Choisissez l'offre qui vous convient : Cloud avec 12 CPU et 48 Go de RAM à 1,86 € HT/mois, ou Pro avec 24 CPU et 64 Go de RAM à 6,25 € HT/mois. Stockez sans compter grâce à l'espace disque illimité en NVMe. Soyez serein avec des sauvegardes jusqu'à 90 jours (selon l'offre) et un support prioritaire 24/7 (N2 à N2+3 selon l'offre).Pilotez votre activité en ligne du bout des doigts, sans connaissances techniques, via l'interface cPanel. Site web, outils, emails... tout est centralisé !Le meilleur dans tout ça ? Les offres démarrent à seulement 1,86 € HT/mois. C'est le moment d'offrir à votre entreprise l'hébergement qu'elle mérite pour décoller. Avec o2switch, dites adieu aux problèmes techniques et bonjour à la croissance !Découvrez les nouvelles offres o2switch
🔗 Lire l'article original
👁️ 1 lecture