From 5b2d493ae4d5faf2a0fac12e5b1a299891a55b39 Mon Sep 17 00:00:00 2001 From: Pablo Ogando Ferreira Date: Tue, 23 Jan 2024 09:06:59 +0100 Subject: [PATCH] TTK-26792 add Youtube Downloader videos --- ...ImportDownloadVideosFromYoutubeChannel.php | 186 ++++++++++++++++++ Resources/config/pumukit_youtube.yaml | 1 + composer.json | 3 +- 3 files changed, 189 insertions(+), 1 deletion(-) create mode 100644 Command/ImportDownloadVideosFromYoutubeChannel.php diff --git a/Command/ImportDownloadVideosFromYoutubeChannel.php b/Command/ImportDownloadVideosFromYoutubeChannel.php new file mode 100644 index 0000000..8f79c70 --- /dev/null +++ b/Command/ImportDownloadVideosFromYoutubeChannel.php @@ -0,0 +1,186 @@ +documentManager = $documentManager; + $this->googleAccountService = $googleAccountService; + $this->tempDir = $tempDir; + $this->qualities = []; + parent::__construct(); + } + + protected function configure(): void + { + $this + ->setName('pumukit:youtube:import:videos:from:channel') + ->addOption( + 'account', + null, + InputOption::VALUE_REQUIRED, + 'Account' + ) + ->addOption( + 'channel', + null, + InputOption::VALUE_REQUIRED, + 'Channel ID' + ) + ->addOption( + 'limit', + null, + InputOption::VALUE_OPTIONAL, + 'limit' + ) + ->setDescription('Import all videos from Youtube channel') + ->setHelp( + <<<'EOT' +Import all videos from Youtube channel + +Limit is optional to test the command. If you don't set it, all videos will be downloaded. + +Usage: php bin/console pumukit:youtube:import:videos:from:channel --account={ACCOUNT} --channel={CHANNEL_ID} --limit={LIMIT} + +EOT + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $channel = $input->getOption('channel'); + $youtubeAccount = $this->documentManager->getRepository(Tag::class)->findOneBy([ + 'properties.login' => $input->getOption('account'), + ]); + + if(!$youtubeAccount) { + throw new \Exception('Account not found'); + } + + $service = $this->googleAccountService->googleServiceFromAccount($youtubeAccount); + $queryParams = [ + 'id' => $channel + ]; + + $channels = $service->channels->listChannels('snippet', $queryParams); + $channelId = $channels->getItems()[0]->getId(); + + $nextPageToken = null; + $count = 0; + do { + $queryParams = [ + 'channelId' => $channelId, + 'maxResults' => 50, + 'order' => 'date', + 'type' => 'video' + ]; + + if($input->getOption('limit') !== null && $count >= $input->getOption('limit')) { + break; + } + + if($nextPageToken !== null) { + $queryParams['pageToken'] = $nextPageToken; + } + + $response = $service->search->listSearch('snippet', $queryParams); + $nextPageToken = $response->getNextPageToken(); + foreach($response->getItems() as $item) { + + if($input->getOption('limit') !== null && $count >= $input->getOption('limit')) { + break; + } + $count++; + $videoId = $item->getId()->getVideoId(); + $youtubeDownloader = new YouTubeDownloader(); + try { + $youtubeURL = self::BASE_URL_YOUTUBE_VIDEO.$videoId; + $downloadOptions = $youtubeDownloader->getDownloadLinks($youtubeURL); + + if (empty($downloadOptions->getAllFormats())) { + $output->writeln('URL: '. $youtubeURL . ' no formats found.'); + continue; + } + + $url = $this->selectBestStreamFormat($downloadOptions); + try { + $this->moveFileToStorage($item, $url, $downloadOptions, $channelId); + } catch (\Exception $exception) { + $output->writeln('Error moving file to storage: ' . $exception->getMessage()); + } + + } catch (YouTubeException $e) { + echo 'Something went wrong: ' . $e->getMessage(); + } + } + } while(null !== $nextPageToken); + + return 0; + } + + private function selectBestStreamFormat(DownloadOptions $downloadOptions): ?StreamFormat + { + $quality720p = Utils::arrayFilterReset($downloadOptions->getAllFormats(), function ($format) { + return str_starts_with($format->mimeType, 'video') && !empty($format->audioQuality) && $format->qualityLabel === '720p'; + }); + + $quality360p = Utils::arrayFilterReset($downloadOptions->getAllFormats(), function ($format) { + return str_starts_with($format->mimeType, 'video') && !empty($format->audioQuality) && $format->qualityLabel === '360p'; + }); + + $quality240p = Utils::arrayFilterReset($downloadOptions->getAllFormats(), function ($format) { + return str_starts_with($format->mimeType, 'video') && !empty($format->audioQuality) && $format->qualityLabel === '240p'; + }); + + return $quality720p[0] ?? $quality360p[0] ?? $quality240p[0] ?? null; + } + + private function moveFileToStorage($item, $url, DownloadOptions $downloadOptions, string $channelId): void + { + $videoId = $item->getId()->getVideoId(); + $mimeType = explode('video/', $this->selectBestStreamFormat($downloadOptions)->getCleanMimeType())[1]; + $this->createChannelDir($channelId); + $file = $this->tempDir.'/'.$channelId.'/'.$videoId.'.'.$mimeType; + + if(file_exists($file)) { + return; + } + + $content = file_get_contents($url->url); + + file_put_contents($file, $content); + } + + private function createChannelDir(string $channelId): void + { + if (!is_dir($this->tempDir.'/'.$channelId)) { + mkdir($this->tempDir.'/'.$channelId, 0775, true); + } + } + +} diff --git a/Resources/config/pumukit_youtube.yaml b/Resources/config/pumukit_youtube.yaml index c1f54c4..f5cbef8 100644 --- a/Resources/config/pumukit_youtube.yaml +++ b/Resources/config/pumukit_youtube.yaml @@ -5,6 +5,7 @@ services: public: true bind: $pumukitLocales: "%pumukit.locales%" + $tempDir: "%pumukit.tmp%" Pumukit\YoutubeBundle\Controller\: resource: '../../Controller' diff --git a/composer.json b/composer.json index e3016ac..ac37839 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,8 @@ "require": { "php": "^7.4 || ^8.2", "pumukit/pumukit": ">4.0", - "google/apiclient" : "^2.0" + "google/apiclient" : "^2.0", + "athlon1600/youtube-downloader": "^4.0" }, "autoload": { "psr-4": {