diff --git a/migrations/Version20230512222044.php b/migrations/Version20230512222044.php new file mode 100644 index 0000000..c6e28a8 --- /dev/null +++ b/migrations/Version20230512222044.php @@ -0,0 +1,31 @@ +addSql('CREATE SCHEMA public'); + } +} diff --git a/migrations/Version20230512222100.php b/migrations/Version20230512222100.php new file mode 100644 index 0000000..e25f8f6 --- /dev/null +++ b/migrations/Version20230512222100.php @@ -0,0 +1,32 @@ +addSql('ALTER TABLE episode ALTER description DROP NOT NULL'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE SCHEMA public'); + $this->addSql('ALTER TABLE episode ALTER description SET NOT NULL'); + } +} diff --git a/src/Command/DownloadCommand.php b/src/Command/DownloadCommand.php new file mode 100644 index 0000000..56aee40 --- /dev/null +++ b/src/Command/DownloadCommand.php @@ -0,0 +1,86 @@ +addArgument('url', InputArgument::REQUIRED, 'URL to download') + ->addArgument('podcast', InputArgument::REQUIRED, 'Podcast slug') + ->addOption('title', '-t', InputOption::VALUE_REQUIRED, 'Episode title') + ->addOption('description', '-d', InputOption::VALUE_REQUIRED, 'Episode description') + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + $url = $input->getArgument('url'); + $io->info(sprintf('Downloading: %s', $url)); + + $slug = $input->getArgument('podcast'); + $podcast = $this->podcastRepository->findOneBy(['slug' => $slug]); + if ($podcast === null) { + $io->error("No podcast with slug $slug."); + return Command::FAILURE; + } + + $filepath = $this->downloadService->download($url); + if ($filepath === false) { + $io->error('Download failed.'); + return Command::FAILURE; + } + + $filename = basename($filepath); + $publicFilepath = + $this->parameterBag->get('kernel.project_dir') + . '/' + . Constants::BASE_PUBLIC_DIR + . Constants::FILES_BASE_PATH + . $filename; + rename($filepath, $publicFilepath); + + $episode = new Episode(); + $episode->setTitle($input->getOption('title') ?: $filename); + $episode->setDescription($input->getOption('description')); + $episode->setAudioFilename($filename); + $episode->setPodcast($podcast); + $episode->setPublicationDate(date_create()); + $this->entityManager->persist($episode); + $this->entityManager->flush(); + + $io->success("New Episode '{$episode->getTitle()}' added."); + + return Command::SUCCESS; + } +} diff --git a/src/Controller/Admin/PodcastCrudController.php b/src/Controller/Admin/PodcastCrudController.php index 3d05b03..692ba15 100644 --- a/src/Controller/Admin/PodcastCrudController.php +++ b/src/Controller/Admin/PodcastCrudController.php @@ -22,6 +22,7 @@ class PodcastCrudController extends AbstractCrudController { return [ TextField::new('name'), + TextField::new('slug'), UrlField::new('website'), TextEditorField::new('description'), TextField::new('author'), diff --git a/src/Entity/Episode.php b/src/Entity/Episode.php index f06998d..d06372f 100644 --- a/src/Entity/Episode.php +++ b/src/Entity/Episode.php @@ -9,7 +9,6 @@ use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; #[ORM\Entity(repositoryClass: EpisodeRepository::class)] -##[Vich\Uploadable] class Episode { #[ORM\Id] @@ -20,8 +19,8 @@ class Episode #[ORM\Column(length: 255)] private string $title; - #[ORM\Column(type: Types::TEXT)] - private string $description; + #[ORM\Column(type: Types::TEXT, nullable: true)] + private ?string $description; #[ORM\Column(length: 255, nullable: true)] private ?string $audioFilename; @@ -50,12 +49,12 @@ class Episode return $this; } - public function getDescription(): string + public function getDescription(): ?string { return $this->description; } - public function setDescription(string $description): self + public function setDescription(?string $description): self { $this->description = $description; diff --git a/src/Entity/Podcast.php b/src/Entity/Podcast.php index 1f172e2..c8e8c87 100644 --- a/src/Entity/Podcast.php +++ b/src/Entity/Podcast.php @@ -7,10 +7,11 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; +use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Component\String\Slugger\SluggerInterface; #[ORM\Entity(repositoryClass: PodcastRepository::class)] -#[ORM\HasLifecycleCallbacks] +#[UniqueEntity('slug')] class Podcast { #[ORM\Id] @@ -81,7 +82,6 @@ class Podcast return $this; } - #[ORM\PrePersist] public function generateSlug(SluggerInterface $slugger): void { $this->slug = $this->id . '-' . $slugger->slug($this->name)->lower(); diff --git a/src/EntityListener/PodcastEntityListener.php b/src/EntityListener/PodcastEntityListener.php new file mode 100644 index 0000000..0fc9260 --- /dev/null +++ b/src/EntityListener/PodcastEntityListener.php @@ -0,0 +1,26 @@ +generateSlug($this->slugger); + } + + public function preUpdate(Podcast $podcast, LifecycleEventArgs $event) + { + $podcast->generateSlug($this->slugger); + } +} diff --git a/src/Service/DownloadService.php b/src/Service/DownloadService.php new file mode 100644 index 0000000..4bb25f9 --- /dev/null +++ b/src/Service/DownloadService.php @@ -0,0 +1,24 @@ +youtubeService->download($url); + } +} diff --git a/src/Service/FeedService.php b/src/Service/FeedService.php index d6d96ea..66fa1ea 100644 --- a/src/Service/FeedService.php +++ b/src/Service/FeedService.php @@ -13,18 +13,14 @@ use Symfony\Component\Routing\Generator\UrlGeneratorInterface; class FeedService { - protected EntityManagerInterface $entityManager; - protected ParameterBagInterface $parameterBag; protected string $baseFeedUrl; public function __construct( - EntityManagerInterface $entityManager, - ParameterBagInterface $parameterBag, + protected EntityManagerInterface $entityManager, + protected ParameterBagInterface $parameterBag, UrlGeneratorInterface $urlGenerator, ) { - $this->entityManager = $entityManager; - $this->parameterBag = $parameterBag; $this->baseFeedUrl = $urlGenerator->generate( 'app_index', [], diff --git a/src/Service/YoutubeService.php b/src/Service/YoutubeService.php new file mode 100644 index 0000000..90a4206 --- /dev/null +++ b/src/Service/YoutubeService.php @@ -0,0 +1,41 @@ +mustRun(); + } + catch (ProcessFailedException $exception) { + $this->logger->error( + 'yt-dlp process failed: {error}', + ['error' => $exception->getMessage()] + ); + return false; + } + + $filepath = trim($process->getOutput()); + $this->logger->info( + 'Success for URL "{url}": {filepath}', + ['url' => $url, 'filepath' => $filepath] + ); + return $filepath; + } +}