[{"data":1,"prerenderedAt":311},["ShallowReactive",2],{"project-\u002Fprojects\u002Fpeliculle-fr":3,"related-\u002Fprojects\u002Fpeliculle-fr":289,"legal-link-fr":309},{"id":4,"title":5,"body":6,"description":267,"extension":268,"git":269,"git_soon":272,"meta":273,"navigation":274,"order":254,"path":275,"seo":276,"stack":277,"stem":282,"type":283,"updated":284,"web":285,"__hash__":288},"projects_fr\u002Fprojects\u002Fpeliculle.md","Peliculle",{"type":7,"value":8,"toc":252},"minimark",[9,17,22,25,29,105,116,120,194,198,201,204,208,213,219,221,225,230,232,236,241,243,247],[10,11,12,16],"p",{},[13,14,15],"strong",{},"Trier des RAW de 45 Mpx directement depuis la carte SD, avec le ressenti de Photos.app"," — sans import, sans abonnement : seuls les keepers rejoignent la pellicule, la carte reste en lecture seule stricte.",[18,19,21],"h2",{"id":20},"quel-est-le-contexte-de-peliculle","Quel est le contexte de Peliculle ?",[10,23,24],{},"Quand on branche une carte SD d'appareil photo sur un iPhone USB-C, iOS n'offre aucun vrai workflow de tri : le dialogue d'import natif n'a pas de plein écran navigable, l'app Fichiers gère mal les RAW, et les alternatives (Lightroom, Raw Power) imposent un import préalable ou un abonnement. Peliculle comble ce manque : un viewer plein écran fluide qui travaille directement sur la carte, un tri par gestes (garder \u002F rejeter), et seuls les keepers sont enregistrés dans la pellicule — la carte reste intacte, en lecture seule stricte. Il n'y a pas de version précédente : l'app est une v1 née d'un PRD complet, puis étendue (photothèque et albums comme sources, vidéos, Mode Voyage, suppression confirmée des rejets).",[18,26,28],{"id":27},"quelle-stack-technique","Quelle stack technique ?",[30,31,32,47,61,71,81,87,93,99],"ul",{},[33,34,35,42,43,46],"li",{},[13,36,37,38],{},"Swift, strict concurrency ",[39,40,41],"code",{},"complete"," — tout le pipeline (décodage, indexation EXIF, analyse Vision) est vérifié par le compilateur : acteurs pour les index partagés, état UI isolé au main actor, chaque ",[39,44,45],{},"Sendable"," non trivial justifié en commentaire.",[33,48,49,52,53,56,57,60],{},[13,50,51],{},"SwiftUI + Observation, cible iOS 26"," — 100 % natif pour rivaliser avec Photos.app : transition hero grille → viewer (",[39,54,55],{},".navigationTransition(.zoom)","), pagination ",[39,58,59],{},"TabView"," avec l'inertie système, haptique native.",[33,62,63,66,67,70],{},[13,64,65],{},"ImageIO"," — aperçus générés par downsampling borné (",[39,68,69],{},"kCGImageSourceThumbnailMaxPixelSize",") : jamais de décodage pleine résolution au scroll ; pour un RAW, ImageIO renvoie le preview JPEG embarqué, quasi instantané.",[33,72,73,80],{},[13,74,75,76,79],{},"Core Image (",[39,77,78],{},"CIRAWFilter",")"," — décodage RAW plein format uniquement à la demande (zoom pour vérifier le piqué), routé par type de fichier pour ne jamais construire un pipeline Core Image sur un simple JPEG.",[33,82,83,86],{},[13,84,85],{},"PhotoKit"," — deuxième famille de sources (photothèque, albums) et écriture des keepers dans la pellicule.",[33,88,89,92],{},[13,90,91],{},"Vision"," — score esthétique calculé sur l'appareil, en tâche de fond basse priorité, jamais dans le cloud.",[33,94,95,98],{},[13,96,97],{},"AVFoundation"," — vignettes et lecture des clips vidéo de la carte.",[33,100,101,104],{},[13,102,103],{},"Zéro dépendance tierce"," — aucun package SPM : ~11 500 lignes de Swift sur les seuls frameworks Apple. Localisation FR\u002FEN par String Catalogs (318 clés), manifeste de confidentialité, tests unitaires sur la logique pure (rafales, tri, persistance, Mode Voyage).",[10,106,107,108,111,112,115],{},"Toute la différence entre les sources tient dans l'enum ",[39,109,110],{},"PhotoBacking"," : le reste de l'app (grille, viewer, tri rapide, filtres, Mode Voyage) manipule des ",[39,113,114],{},"PhotoItem"," sans savoir s'ils viennent d'une carte ou de la photothèque. Une session peut même combiner plusieurs sources, chacune avec sa propre persistance.",[18,117,119],{"id":118},"points-techniques-notables","Points techniques notables",[30,121,122,135,149,155,184],{},[33,123,124,127,128,130,131,134],{},[13,125,126],{},"Pipeline d'images à trois niveaux, dimensionné pour la RAM d'un iPhone."," Aperçus ~400 px pour la grille et ~2048 px pour le viewer via downsampling ImageIO ; pleine résolution (",[39,129,78],{},") seulement au zoom. Le cache d'aperçus est un ",[39,132,133],{},"NSCache"," borné en octets (un huitième de la RAM physique, plafonné à 300 Mo) ; la pleine résolution a son cache dédié de 2 entrées : un RAW 45 Mpx décodé pèse ~180 Mo de pixels et aurait évincé toute la grille du cache partagé.",[33,136,137,140,141,144,145,148],{},[13,138,139],{},"Annulation coopérative des décodages."," Chaque chargement hérite de l'annulation du ",[39,142,143],{},".task"," SwiftUI de sa cellule : au scroll rapide, les décodages des cellules sorties de l'écran s'arrêtent au lieu de tourner jusqu'au bout. Une version antérieure en ",[39,146,147],{},"Task.detached"," laissait des centaines de décodages hors écran voler CPU et batterie aux cellules visibles.",[33,150,151,154],{},[13,152,153],{},"Identité stable des photos entre deux montages de carte."," L'URL absolue d'une carte SD change à chaque branchement ; la persistance (décisions, notes) et le cache d'analyse sont donc keyés par nom + taille de fichier, jamais par URL. Résultat : débrancher la carte, la rebrancher trois jours plus tard, et reprendre le tri exactement là où il s'était arrêté.",[33,156,157,160,161,164,165,168,169,172,173,176,177,180,181,183],{},[13,158,159],{},"Concurrence stricte de bout en bout."," ",[39,162,163],{},"VisionAnalyzer"," et ",[39,166,167],{},"ExifIndexer"," sont des acteurs mutualisés avec déduplication des requêtes en vol (deux cellules demandant la même photo attendent la même tâche), calcul hors acteur en priorité basse, et ",[39,170,171],{},"allowNetwork: false"," pour les passes de fond — analyser 10 000 photos ne doit jamais déclencher 10 000 téléchargements iCloud. À l'inverse, ",[39,174,175],{},"ImageCache"," reste une simple classe ",[39,178,179],{},"@unchecked Sendable"," : ",[39,182,133],{}," est déjà thread-safe, un acteur n'aurait ajouté qu'un saut de contexte par vignette.",[33,185,186,189,190,193],{},[13,187,188],{},"Le tri comme machine à gestes."," Swipe garder \u002F rejeter \u002F plus tard avec retour haptique, piles de rafales détectées par chaînage temporel (",[39,191,192],{},"BurstGrouper",", testé unitairement), duel A\u002FB en tournoi avec zoom synchronisé sur les deux photos (un seul pincement, la même transformation appliquée aux deux panneaux — le moyen le plus fiable de comparer le piqué au même endroit), et journal d'annulation où chaque geste, même en masse, ne fait qu'une entrée.",[18,195,197],{"id":196},"ce-que-jai-appris-apporté","Ce que j'ai appris \u002F apporté",[10,199,200],{},"Le plus difficile a été de tenir la promesse d'un ressenti Photos.app sur des RAW de 45 Mpx lus depuis une carte SD : chaque choix du pipeline (niveau de résolution, borne de cache, annulation, clé d'identité) découle d'un problème concret rencontré en usage réel. Le passage à la vérification stricte de concurrence du compilateur Swift m'a forcé à rendre explicite qui possède quel état, et à documenter dans le code pourquoi chaque exception est sûre.",[202,203],"hr",{},[18,205,207],{"id":206},"schémas","Schémas",[209,210,212],"h3",{"id":211},"architecture-globale","Architecture globale",[10,214,215],{},[216,217],"img",{"alt":212,"src":218},"\u002Fdiagrams\u002Fpeliculle\u002F01-architecture.svg",[202,220],{},[209,222,224],{"id":223},"pipeline-dimages-à-trois-niveaux","Pipeline d'images à trois niveaux",[10,226,227],{},[216,228],{"alt":224,"src":229},"\u002Fdiagrams\u002Fpeliculle\u002F02-image-pipeline.svg",[202,231],{},[209,233,235],{"id":234},"annulation-coopérative-des-décodages","Annulation coopérative des décodages",[10,237,238],{},[216,239],{"alt":235,"src":240},"\u002Fdiagrams\u002Fpeliculle\u002F03-cancellation.svg",[202,242],{},[209,244,246],{"id":245},"identité-stable-des-photos-entre-montages","Identité stable des photos entre montages",[10,248,249],{},[216,250],{"alt":246,"src":251},"\u002Fdiagrams\u002Fpeliculle\u002F04-stable-identity.svg",{"title":253,"searchDepth":254,"depth":254,"links":255},"",2,[256,257,258,259,260],{"id":20,"depth":254,"text":21},{"id":27,"depth":254,"text":28},{"id":118,"depth":254,"text":119},{"id":196,"depth":254,"text":197},{"id":206,"depth":254,"text":207,"children":261},[262,264,265,266],{"id":211,"depth":263,"text":212},3,{"id":223,"depth":263,"text":224},{"id":234,"depth":263,"text":235},{"id":245,"depth":263,"text":246},"App iOS de culling : trier ses photos RAW directement depuis la carte SD, sans import, et n'enregistrer que les meilleures dans la pellicule.","md",[270,271],"Répertoire Git","https:\u002F\u002Fgithub.com\u002FOwenLB\u002Fpeliculle",false,{},true,"\u002Fprojects\u002Fpeliculle",{"title":5,"description":267},[278,279,280,65,281,85,91],"Swift","SwiftUI","Swift Concurrency","Core Image","projects\u002Fpeliculle","App iOS","2026-07-17",[286,287],"Voir la landing","https:\u002F\u002Fpeliculle.netlify.app","M8cU8AQR22UA7hdkNQj8R7EkLwGNNiXkvdDFTKjTiZE",[290,295,299,304],{"title":291,"type":292,"path":293,"order":294},"Finixa","App web & mobile","\u002Fprojects\u002Ffinixa",1,{"title":296,"type":297,"path":298,"order":263},"Scan Auto","App web","\u002Fprojects\u002Fscanauto",{"title":300,"type":301,"path":302,"order":303},"Portfolio Développeur","Site","\u002Fprojects\u002Fportfolio-dev",4,{"title":305,"type":306,"path":307,"order":308},"Portfolio Photo","Site full-stack","\u002Fprojects\u002Fportfolio-photo",5,{"title":310},"Mentions légales",1784470460523]