From 5e715a40c62e26b4f6d08b223d02742a3c355714 Mon Sep 17 00:00:00 2001 From: thibaud-leclere Date: Thu, 15 Jan 2026 21:51:35 +0100 Subject: [PATCH] sync actor roles --- .idea/ltbxd-actorle.iml | 1 + migrations/Version20260115201004.php | 39 ++++++++ src/Command/SyncDataCommand.php | 135 +++++++++++++++++++++++++++ src/Command/SyncMoviesCommand.php | 63 ------------- src/Entity/Actor.php | 15 +++ 5 files changed, 190 insertions(+), 63 deletions(-) create mode 100644 migrations/Version20260115201004.php create mode 100644 src/Command/SyncDataCommand.php delete mode 100644 src/Command/SyncMoviesCommand.php diff --git a/.idea/ltbxd-actorle.iml b/.idea/ltbxd-actorle.iml index 0a60166..681bda2 100644 --- a/.idea/ltbxd-actorle.iml +++ b/.idea/ltbxd-actorle.iml @@ -129,6 +129,7 @@ + diff --git a/migrations/Version20260115201004.php b/migrations/Version20260115201004.php new file mode 100644 index 0000000..86860ad --- /dev/null +++ b/migrations/Version20260115201004.php @@ -0,0 +1,39 @@ +addSql('CREATE TABLE actor (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, popularity DOUBLE PRECISION DEFAULT NULL, tmdb_id INT DEFAULT NULL, PRIMARY KEY (id))'); + $this->addSql('CREATE TABLE movie_role (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, character VARCHAR(255) NOT NULL, actor_id INT DEFAULT NULL, movie_id INT DEFAULT NULL, PRIMARY KEY (id))'); + $this->addSql('CREATE INDEX IDX_A40FAB6710DAF24A ON movie_role (actor_id)'); + $this->addSql('CREATE INDEX IDX_A40FAB678F93B6FC ON movie_role (movie_id)'); + $this->addSql('ALTER TABLE movie_role ADD CONSTRAINT FK_A40FAB6710DAF24A FOREIGN KEY (actor_id) REFERENCES actor (id)'); + $this->addSql('ALTER TABLE movie_role ADD CONSTRAINT FK_A40FAB678F93B6FC FOREIGN KEY (movie_id) REFERENCES movie (id)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE movie_role DROP CONSTRAINT FK_A40FAB6710DAF24A'); + $this->addSql('ALTER TABLE movie_role DROP CONSTRAINT FK_A40FAB678F93B6FC'); + $this->addSql('DROP TABLE actor'); + $this->addSql('DROP TABLE movie_role'); + } +} diff --git a/src/Command/SyncDataCommand.php b/src/Command/SyncDataCommand.php new file mode 100644 index 0000000..41a257b --- /dev/null +++ b/src/Command/SyncDataCommand.php @@ -0,0 +1,135 @@ +syncMovies($output); + } catch (\Exception $e) { + $output->writeln('/!\ '.$e->getMessage()); + $output->writeln('/!\ '.$e->getPrevious()->getMessage()); + + return Command::FAILURE; + } + + if (!$skipFilmsSync) { + $this->syncActors($output, $films); + } + + // awards, quotes, complete roles + + return Command::SUCCESS; + } + + /** + * @return Movie[] + * @throws \Exception + */ + private function syncMovies(OutputInterface $output): array + { + $file = file_get_contents('public/files/watched.csv'); + try { + $ltbxdMovies = $this->serializer->deserialize($file, LtbxdMovie::class.'[]', 'csv'); + } catch (ExceptionInterface $e) { + throw new \Exception('Error while deserializing Letterboxd data', previous: $e); + } + + $films = []; + /** @var LtbxdMovie $ltbxdMovie */ + foreach ($ltbxdMovies as $ltbxdMovie) { + // If the movie already exists, skip + if (($film = $this->em->getRepository(Movie::class)->findOneBy(['ltbxdRef' => $ltbxdMovie->getLtbxdRef()])) instanceof Movie) { + $films[] = $film; + continue; + } + + // Search movie on TMDB + $film = $this->TMDBGateway->searchMovie($ltbxdMovie->getName()); + if ($film) { + $output->writeln('* Found '.$ltbxdMovie->getName()); + + $filmEntity = new Movie() + ->setLtbxdRef($ltbxdMovie->getLtbxdRef()) + ->setTitle($ltbxdMovie->getName()) + ->setTmdbId($film->getId()) + ; + $this->em->persist($filmEntity); + $films[] = $film; + } + + if (0 === \count($films) % 50) { + $this->em->flush(); + } + } + + $this->em->flush(); + + return $films; + } + + private function syncActors(OutputInterface $output, array $films): void + { + foreach ($films as $film) { + try { + $creditsContext = $this->TMDBGateway->getMovieCredits($film->getTmdbId()); + } catch (GatewayException $e) { + $output->writeln('/!\ '.$e->getMessage()); + continue; + } + + foreach ($creditsContext->cast as $actorModel) { + // Get existing or create new + $actor = $this->em->getRepository(Actor::class)->findOneBy(['tmdbId' => $actorModel->id]); + if (!$actor instanceof Actor) { + $output->writeln('* New actor found: '.$actorModel->name); + + $actor = new Actor() + ->setPopularity($actorModel->popularity) + ->setName($actorModel->name) + ->setTmdbId($actorModel->id) + ; + + $this->em->persist($actor); + } + + // Get or create the role + if (0 < $this->em->getRepository(MovieRole::class)->count(['actor' => $actor, 'movie' => $film])) { + $actor->addMovieRole(new MovieRole() + ->setMovie($film) + ->setCharacter($actorModel->character) + ); + } + } + + $this->em->flush(); + } + } +} diff --git a/src/Command/SyncMoviesCommand.php b/src/Command/SyncMoviesCommand.php deleted file mode 100644 index ec7accf..0000000 --- a/src/Command/SyncMoviesCommand.php +++ /dev/null @@ -1,63 +0,0 @@ -serializer->deserialize($file, LtbxdMovie::class.'[]', 'csv'); - } catch (ExceptionInterface $e) { - $output->writeln($e->getMessage()); - return Command::FAILURE; - } - - $i = 0; - /** @var LtbxdMovie $ltbxdMovie */ - foreach ($ltbxdMovies as $ltbxdMovie) { - // If the movie already exists, skip - if (0 < $this->em->getRepository(Movie::class)->count(['ltbxdRef' => $ltbxdMovie->getLtbxdRef()])) { - continue; - } - - // Search movie on TMDB - $film = $this->TMDBGateway->searchMovie($ltbxdMovie->getName()); - if ($film) { - $output->writeln('* Found '.$ltbxdMovie->getName()); - - $filmEntity = new Movie() - ->setLtbxdRef($ltbxdMovie->getLtbxdRef()) - ->setTitle($ltbxdMovie->getName()) - ->setTmdbId($film->getId()) - ; - $this->em->persist($filmEntity); - ++$i; - } - - if (0 === $i % 50) { - $this->em->flush(); - } - } - - return Command::SUCCESS; - } -} diff --git a/src/Entity/Actor.php b/src/Entity/Actor.php index e2feca5..4a3f72c 100644 --- a/src/Entity/Actor.php +++ b/src/Entity/Actor.php @@ -27,6 +27,9 @@ class Actor #[ORM\OneToMany(targetEntity: MovieRole::class, mappedBy: 'actor')] private Collection $movieRoles; + #[ORM\Column(nullable: true)] + private ?int $tmdbId = null; + public function __construct() { $this->movieRoles = new ArrayCollection(); @@ -90,4 +93,16 @@ class Actor return $this; } + + public function getTmdbId(): ?int + { + return $this->tmdbId; + } + + public function setTmdbId(?int $tmdbId): static + { + $this->tmdbId = $tmdbId; + + return $this; + } }