How to add a video to a product programmatically?

Problem is, I want to add (via MediaGallery attribute?) to product some videos, as it can be done in case of images. It can be done via adminhtml product edit form and works like a charm. So far I have found module-product-video in vendor. I’m not sure if that I should use

I think maybe I should use then VideoEntryFactory and bind it with product instance but I am not sure. In Model of Product i haven’t found method similar to addImageToMediaGallery.

If anyone have faced it, please give me some advices.

So far I was trying to use following method:

/** @var \Magento\ProductVideo\Model\Product\Attribute\Media\VideoEntryFactory  */
    $product_preview = $this->productVideoFactory->create();

    //print_r($product_preview);

    //$product_preview->setMediaType('external_video');
    $product_preview->setEntityId($product_id);
Without _setResourceModel I had error 'Resource isn't set.
    $product_preview->_setResourceModel('Magento\ProductVideo\Model\ResourceModel\Video');
    $product_preview->setMediaType('external-video');
    $product_preview->setTypes('image');
    $product_preview->setVideoUrl('https://vimeo.com/channels/mercedesbenz/156395271');
    $product_preview->setVideoTitle('example');
    $product_preview->setVideoDescription('Dynamic HD preview for product');
    print_r($product_preview->getData());
    $product_preview_result = $product_preview->save();

    return $product_preview_result;

And after running it I had an error:

SQLSTATE[23000]: Integrity constraint violation: 1452 Cannot add or update a child row: a foreign key constraint fails (`magento2`.`catalog_product_entity_media_gallery_value_video`, CONSTRAINT `FK_6FDF205946906B0E653E60AA769899F8` FOREIGN KEY (`value_id`) REFERENCES `catalog_product_entity_media_gallery` (`val), query was: INSERT INTO `catalog_product_entity_media_gallery_value_video` () VALUES ()

It seems for me that Magento can’t add video because entry to Media Gallery can’t be binded with specific Media Gallery.

1 Like

follow the following simple steps to add youtube video in Magento2

Step 1: Get Your YouTube API Key
Log in to your Google account, and visit the Google Developers Console. Then, follow these:
Under Use Google APIs, click Youtube Data APIs.In the panel on the left choose Credentials, click on Create Credentials and choose API key.When prompted to create a new key, choose Server key. Enter a name for the key and IP address, and click on Create.
After you get the key, copy the key to the clipboard.

Step 2: Configure Magento
On the Admin Sidebar, Stores > Settings > Configuration.In the panel, under Catalog, choose Catalog.Expand the Product Videos section, paste Youtube API key into the required field.

Click Save Config.Go to Cache Management to refresh the cache.

Step 3: Link to Video
From the Poduct Detail, click on Add Video in the Images and Videos.

I was asking how to do it programatically…

$valueId = $this->galleryProductResource->insertGallery([
                "attribute_id" => $this->getMediaGalleryAttribute()->getId(), //code media_gallery
                "media_type" => \Magento\ProductVideo\Model\Product\Attribute\Media\ExternalVideoEntryConverter::MEDIA_TYPE_CODE
            ]);

            $this->videoResourceModel->insertOnDuplicate([
                "value_id" => $valueId,
                "store_id" => 0,
                "url" => $productVideoUrl,
            ]);
            $this->galleryProductResource->bindValueToEntity($valueId, $product->getId());
            $this->galleryProductResource->insertGalleryValueInStore([
                "value_id" => $valueId,
                "entity_id" => $product->getId(),
                "store_id" => 0,
            ]);

\Magento\Catalog\Model\Product\Gallery\CreateHandler can be used to save video programmatically to database.

You also need to download and save the thumbnail image for product. To do both, you can extend class \Magento\Catalog\Model\Product\Gallery\Processor and add new function similar to “addImage” except the end part is bit different:

// This part differs
$mediaGalleryData['images'][] = array_merge([
            'file' => $fileName,
            'label' => $videoData['video_title'],
            'position' => $position,
            'disabled' => (int)$exclude
        ], $videoData);
        
        $product->setData($attrCode, $mediaGalleryData);

        if ($mediaAttribute !== null) {
            $product->setMediaAttribute($product, $mediaAttribute, $fileName);
        }
		
		/** @var \Magento\Catalog\Model\Product\Gallery\CreateHandler */
        $this->createHandler->execute($product); // Video is saved to db here
        
        return $fileName;

Here videoData is an array with following values (these fields are required):

$videoData = [
            'video_title' => "Title here",
            'video_description' => "Description here",
            'thumbnail' => "Thumbnail url here",
            'video_provider' => "youtube",
            'video_metadata' => null,
            'video_url' => "URL here",
            'media_type' => Magento\ProductVideo\Model\Product\Attribute\Media\ExternalVideoEntryConverter::MEDIA_TYPE_CODE
        ]

Also note that createHandler takes store code from the product, so you may need to temporarily set it to zero (0) or otherwise video desciption, title, etc. gets saved for default store (usually store 1) instead of store 0. Unless you have store specific products, you don’t want that to happen.

This did not worked for me, can you update the code here.

  1. Fetch video data from youtube/vimeo API. Magento 2 has no PHP code for this, so you need to implement this yourself.
  2. Download thumbnail image and save it temporarily somewhere. You can use Magento\ProductVideo\Controller\Adminhtml\Product\Gallery\RetrieveImage code as an example of how to do this.

Tester class implementation:
<?php
namespace Yourcompany\Yourmodule\Controller\Test;

class Test extends \Magento\Framework\App\Action\Action
{
    protected $videoGalleryProcessor;

    public function __construct(
        \Magento\Framework\App\Action\Context $context,
        \Yourcompany\Yourmodule\Model\Product\Gallery\Video\Processor $videoGalleryProcessor
    ){
        parent::__construct($context);
        $this->videoGalleryProcessor = $videoGalleryProcessor;
    }

    public function execute()
    {
        // Load sample product
        $objectManager = \Magento\Framework\App\ObjectManager::getInstance();
        $productId = 1; // Id of product here
        $product = $objectManager->create('Magento\Catalog\Model\Product')->load($productId);

        // Hack, but we need to save data for admin store
        $product->setStoreId(0);

        // Sample video data
        $videoData = [
            'video_id' => "sGF6bOi1NfA",
            'video_title' => "Cute pandas playing on the slide",
            'video_description' => "What's more entertaining than watching a panda playing on a slide?",
            'thumbnail' => "https://i.ytimg.com/vi/sGF6bOi1NfA/hqdefault.jpg",
            'video_provider' => "youtube",
            'video_metadata' => null,
            'video_url' => "https://www.youtube.com/watch?v=sGF6bOi1NfA",
            'media_type' => \Magento\ProductVideo\Model\Product\Attribute\Media\ExternalVideoEntryConverter::MEDIA_TYPE_CODE,
        ];

        // TODO: download thumbnail image and save locally under pub/media
        $videoData['file'] = $videoData['video_id'] . '_hqdefault.jpg';

        // Add video to the product
        if ($product->hasGalleryAttribute()) {
            $this->videoGalleryProcessor->addVideo(
                $product,
                $videoData,
                ['image', 'small_image', 'thumbnail'],
                false,
                true
            );
        }
        $product->save();
    }
}

Video processor implementation:

<?php
namespace Yourcompany\Yourmodule\Model\Product\Gallery\Video;

use Magento\Framework\Exception\LocalizedException;

class Processor extends \Magento\Catalog\Model\Product\Gallery\Processor
{
    /**
     * @var \Magento\Catalog\Model\Product\Gallery\CreateHandler
     */
    protected $createHandler;

    /**
     * Processor constructor.
     * @param \Magento\Catalog\Api\ProductAttributeRepositoryInterface $attributeRepository
     * @param \Magento\MediaStorage\Helper\File\Storage\Database $fileStorageDb
     * @param \Magento\Catalog\Model\Product\Media\Config $mediaConfig
     * @param \Magento\Framework\Filesystem $filesystem
     * @param \Magento\Catalog\Model\ResourceModel\Product\Gallery $resourceModel
     * @param \Magento\Catalog\Model\Product\Gallery\CreateHandler $createHandler
     */
    public function __construct(
        \Magento\Catalog\Api\ProductAttributeRepositoryInterface $attributeRepository,
        \Magento\MediaStorage\Helper\File\Storage\Database $fileStorageDb,
        \Magento\Catalog\Model\Product\Media\Config $mediaConfig,
        \Magento\Framework\Filesystem $filesystem,
        \Magento\Catalog\Model\ResourceModel\Product\Gallery $resourceModel,
        \Magento\Catalog\Model\Product\Gallery\CreateHandler $createHandler
    )
    {
        parent::__construct($attributeRepository, $fileStorageDb, $mediaConfig, $filesystem, $resourceModel);
        $this->createHandler = $createHandler;
    }

    /**
     * @param \Magento\Catalog\Model\Product $product
     * @param array $videoData
     * @param array $mediaAttribute
     * @param bool $move
     * @param bool $exclude
     * @return string
     * @throws LocalizedException
     */
    public function addVideo(
        \Magento\Catalog\Model\Product $product,
        array $videoData,
        $mediaAttribute = null,
        $move = false,
        $exclude = true
    ) {
        $file = $this->mediaDirectory->getRelativePath($videoData['file']);
        if (!$this->mediaDirectory->isFile($file)) {
            throw new LocalizedException(__('The image does not exist.'));
        }

        $pathinfo = pathinfo($file);
        $imgExtensions = ['jpg', 'jpeg', 'gif', 'png'];
        if (!isset($pathinfo['extension']) || !in_array(strtolower($pathinfo['extension']), $imgExtensions)) {
            throw new LocalizedException(__('Please correct the image file type.'));
        }

        $fileName = \Magento\MediaStorage\Model\File\Uploader::getCorrectFileName($pathinfo['basename']);
        $dispretionPath = \Magento\MediaStorage\Model\File\Uploader::getDispretionPath($fileName);
        $fileName = $dispretionPath . '/' . $fileName;

        $fileName = $this->getNotDuplicatedFilename($fileName, $dispretionPath);

        $destinationFile = $this->mediaConfig->getTmpMediaPath($fileName);

        try {
            /** @var $storageHelper \Magento\MediaStorage\Helper\File\Storage\Database */
            $storageHelper = $this->fileStorageDb;
            if ($move) {
                $this->mediaDirectory->renameFile($file, $destinationFile);

                //If this is used, filesystem should be configured properly
                $storageHelper->saveFile($this->mediaConfig->getTmpMediaShortUrl($fileName));
            } else {
                $this->mediaDirectory->copyFile($file, $destinationFile);

                $storageHelper->saveFile($this->mediaConfig->getTmpMediaShortUrl($fileName));
            }
        } catch (\Exception $e) {
            throw new LocalizedException(__('We couldn\'t move this file: %1.', $e->getMessage()));
        }

        $fileName = str_replace('\\', '/', $fileName);

        $attrCode = $this->getAttribute()->getAttributeCode();
        $mediaGalleryData = $product->getData($attrCode);
        $position = 0;
        if (!is_array($mediaGalleryData)) {
            $mediaGalleryData = ['images' => []];
        }

        foreach ($mediaGalleryData['images'] as &$image) {
            if (isset($image['position']) && $image['position'] > $position) {
                $position = $image['position'];
            }
        }

        $position++;

        unset($videoData['file']);
        $mediaGalleryData['images'][] = array_merge([
            'file' => $fileName,
            'label' => $videoData['video_title'],
            'position' => $position,
            'disabled' => (int)$exclude
        ], $videoData);

        $product->setData($attrCode, $mediaGalleryData);

        if ($mediaAttribute !== null) {
            $product->setMediaAttribute($product, $mediaAttribute, $fileName);
        }

        $this->createHandler->execute($product);

        return $fileName;
    }
}
1 Like

Thanks @Antti_Ranta,

It worked for me but I have few questions.

  1. What is video_id, do I have to randomly generate it
  2. Is there a way to get screenshot from video directly like magento do.
  3. why the video is disabled condition when uploading
  1. Video id is the code of the video. Code is taken from url: https://www.youtube.com/watch?v=sGF6bOi1NfA.

  2. Magento 2 fetches video data (id, title, description, thumbnail, etc.) using javascript from file vendor/magento/module-product-video/view/adminhtml/web/js/get-video-information.js. If you to do this in backend, you need to implement similar code yourself.

  3. Changing the last parameter to false should enable video:
    $this->videoGalleryProcessor->addVideo(
    $product,
    $videoData,
    [‘image’, ‘small_image’, ‘thumbnail’],
    false,
    true // Change this to false

I think it is also possible to save video by using “create” function from Magento\Catalog\Model\Product\Gallery\GalleryManagement class, but I haven’t tried it.

@Antti_Ranta : Everything is working using this code. Just one issue I got is video is saving with only one store_id. And so in backend I did not get it as “Video” instead I got that as image only !!

Any solution ?

works like a charm, thanks a lot :+1:

@Mohamed_Elwan: Thank you, I had already forgotten this thread.

@Chintangrapes: Sorry for really late answer. You should check from database tables catalog_product_entity_media_gallery_value and catalog_product_entity_media_gallery_value_video if store_id of rows is 0. If store_id is bigger, video will only display in a particular store, and if you go to default view (or another store view), you will only see an image. In sample code I set store id to zero for product so that this shouldn’t happen. Maybe you can debug \Magento\Catalog\Model\Product\Gallery\CreateHandler or \Magento\ProductVideo\Model\Plugin\Catalog\Product\Gallery\CreateHandler class to see what store id is being used before saving happens?

Btw, I also tested how video saving might work by using \Magento\Catalog\Api\ProductAttributeMediaGalleryManagementInterface create() function. Sample code became maybe bit less low level than my previous code:

<?php
namespace Yourcompany\Yourmodule\Controller\Test;

use Magento\Framework\Api\Data\VideoContentInterface;
use Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryInterface;
use Magento\Framework\App\Action\Context;
use Magento\ProductVideo\Model\Product\Attribute\Media\ExternalVideoEntryConverter;
use Magento\Store\Model\Store;
use Magento\Framework\Exception\InputException;
use Magento\Framework\Exception\StateException;
use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Catalog\Api\Data\ProductInterfaceFactory;

class Test extends \Magento\Framework\App\Action\Action
{
    private $mimeTypes = [
        'png' => 'image/png',
        'jpe' => 'image/jpeg',
        'jpeg' => 'image/jpeg',
        'jpg' => 'image/jpeg',
        'gif' => 'image/gif',
        'bmp' => 'image/bmp',
        'ico' => 'image/vnd.microsoft.icon',
        'tiff' => 'image/tiff',
        'tif' => 'image/tiff',
        'svg' => 'image/svg+xml',
        'svgz' => 'image/svg+xml',
    ];

    private $defaultMimeType = 'application/octet-stream';
    private $productFactory;
    private $externalVideoEntryConverter;
    private $productRepository;
    private $contentValidator;
    private $curl;
    private $imageContentFactory;

    /**
     * @param Context $context
     * @param ProductInterfaceFactory $productFactory
     * @param ExternalVideoEntryConverter $externalVideoEntryConverter
     * @param \Magento\Catalog\Api\ProductRepositoryInterface $productRepository
     * @param \Magento\Framework\Api\ImageContentValidatorInterface $contentValidator
     * @param \Magento\Framework\HTTP\Adapter\Curl $curl
     * @param \Magento\Framework\Api\Data\ImageContentInterfaceFactory $imageContentFactory
     */
    public function __construct(
        Context $context,
        ProductInterfaceFactory $productFactory,
        ExternalVideoEntryConverter $externalVideoEntryConverter,
        \Magento\Catalog\Api\ProductRepositoryInterface $productRepository,
        \Magento\Framework\Api\ImageContentValidatorInterface $contentValidator,
        \Magento\Framework\HTTP\Adapter\Curl $curl,
        \Magento\Framework\Api\Data\ImageContentInterfaceFactory $imageContentFactory
    ) {
        parent::__construct($context);
        $this->productFactory = $productFactory;
        $this->productRepository = $productRepository;
        $this->externalVideoEntryConverter = $externalVideoEntryConverter;
        $this->contentValidator = $contentValidator;
        $this->curl = $curl;
        $this->imageContentFactory = $imageContentFactory;
    }

    public function execute()
    {
        $sampleProductId = 1;

        /** @var ProductInterface $product */
        $sampleProduct = $this->productFactory->create()->load($sampleProductId);

        // NB! Unless you have a multi store setup and need the video only for specific store,
        // this has to be done or the video entry might get wrong store id (will only be visible on one store).
        // See \Magento\ProductVideo\Model\Plugin\Catalog\Product\Gallery\CreateHandler function afterExecute().
        // However this is a hack and better way should be implemented to make sure video will get store id before save is called.
        $sampleProduct->setStoreId(Store::DEFAULT_STORE_ID);

        // Sample youtube video
        $videoUrl = "https://www.youtube.com/watch?v=sGF6bOi1NfA";
        $videoId =  "sGF6bOi1NfA";
        $videoLabel = "Cute pandas playing on the slide";
        $videoDescription = "What's more entertaining than watching a panda playing on a slide?";
        $videoProvider = "youtube";
        $thumbnailUrl = "https://i.ytimg.com/vi/sGF6bOi1NfA/hqdefault.jpg";

        // Check if video already exists, and if not, create a new one
        if(!$this->isExistingVideo($sampleProduct, $videoUrl)){
            $videoEntry = $this->buildVideoEntry($sampleProduct, $videoUrl, $videoId, $videoLabel, $videoDescription, $videoProvider, $thumbnailUrl);
            $this->addVideoForProduct($sampleProduct, $videoEntry);
        }else{
            die('Video already exists!');
        }
    }

    /**
     * @param ProductInterface $sampleProduct
     * @param string $videoUrl
     * @param string $videoId
     * @param string $videoLabel
     * @param string $videoDescription
     * @param string $videoProvider
     * @param string $thumbnailUrl
     *
     * @return ProductAttributeMediaGalleryEntryInterface
     * @throws \Magento\Framework\Exception\LocalizedException
     */
    private function buildVideoEntry($sampleProduct, $videoUrl, $videoId, $videoLabel, $videoDescription, $videoProvider, $thumbnailUrl)
    {
        // Build thumbnail image data for sample video
        $parts = explode('/', $thumbnailUrl);
        $thumbnailImageName = end($parts);
        $thumbnailImage = $this->getRemoteImage($thumbnailUrl); // Fetch image from server

        /** @var \Magento\Framework\Api\Data\ImageContentInterface $imageContent */
        $imageContent = $this->imageContentFactory->create();
        $imageContent->setName($videoProvider . '_' . $videoId)
            ->setType($this->getMimeContentType($thumbnailImageName))
            ->setBase64EncodedData(base64_encode($thumbnailImage));

        // Build video data array for video entry converter
        $generalMediaEntryData = [
            ProductAttributeMediaGalleryEntryInterface::LABEL => $videoLabel,
            ProductAttributeMediaGalleryEntryInterface::TYPES => ['thumbnail', 'image', 'small_image'], // Optional, depends on what is wanted
            ProductAttributeMediaGalleryEntryInterface::CONTENT => $imageContent,
            ProductAttributeMediaGalleryEntryInterface::DISABLED => false
        ];

        $videoData = array_merge($generalMediaEntryData, [
            VideoContentInterface::TITLE => $videoLabel,
            VideoContentInterface::DESCRIPTION => $videoDescription,
            VideoContentInterface::PROVIDER => $videoProvider,
            VideoContentInterface::METADATA => null,
            VideoContentInterface::URL => $videoUrl,
            VideoContentInterface::TYPE => ExternalVideoEntryConverter::MEDIA_TYPE_CODE,
        ]);

        // Convert video data array to video entry
        return $this->externalVideoEntryConverter->convertTo($sampleProduct, $videoData);
    }

    /**
     * Copy of \Magento\Catalog\Api\ProductAttributeMediaGalleryManagementInterface create() function.
     * Only difference is that we don't reload product with sku, and thus we don't lose store id 0
     * that we set earlier for sample product.
     *
     * @param ProductInterface $sampleProduct
     * @param ProductAttributeMediaGalleryEntryInterface $videoEntry
     *
     * @return int|null
     * @throws InputException
     * @throws StateException
     */
    private function addVideoForProduct($sampleProduct, $videoEntry)
    {
        /** @var $entry ProductAttributeMediaGalleryEntryInterface */
        $entryContent = $videoEntry->getContent();

        if (!$this->contentValidator->isValid($entryContent)) {
            throw new InputException(__('The image content is not valid.'));
        }

        $existingMediaGalleryEntries = $sampleProduct->getMediaGalleryEntries();
        $existingEntryIds = [];
        if ($existingMediaGalleryEntries == null) {
            $existingMediaGalleryEntries = [$videoEntry];
        } else {
            foreach ($existingMediaGalleryEntries as $existingEntries) {
                $existingEntryIds[$existingEntries->getId()] = $existingEntries->getId();
            }
            $existingMediaGalleryEntries[] = $videoEntry;
        }
        $sampleProduct->setMediaGalleryEntries($existingMediaGalleryEntries);

        try {
            $product = $this->productRepository->save($sampleProduct);
        } catch (InputException $inputException) {
            throw $inputException;
        } catch (\Exception $e) {
            throw new StateException(__('Cannot save product.'));
        }

        foreach ($product->getMediaGalleryEntries() as $entry) {
            if (!isset($existingEntryIds[$entry->getId()])) {
                return $entry->getId();
            }
        }
        throw new StateException(__('Failed to save new media gallery entry.'));
    }

    /**
     * @param ProductInterface $sampleProduct
     * @param string $url
     *
     * @return bool
     */
    private function isExistingVideo($sampleProduct, $url)
    {
        if ($mediaEntries = $sampleProduct->getMediaGalleryEntries()) {
            foreach($mediaEntries as $entryKey => $mediaEntry) {
                if($mediaEntry->getMediaType() == ExternalVideoEntryConverter::MEDIA_TYPE_CODE) {
                    $videoUrl = $mediaEntry->getExtensionAttributes()->getVideoContent()->getVideoUrl();
                    if($videoUrl == $url){
                        return true;
                    }
                }
            }
        }
        return false;
    }

    /**
     * @param string $fileUrl
     *
     * @return string
     * @throws \Magento\Framework\Exception\LocalizedException
     */
    private function getRemoteImage($fileUrl)
    {
        $this->curl->setConfig(['header' => false]);
        $this->curl->write('GET', $fileUrl);
        $image = $this->curl->read();

        if (empty($image)) {
            throw new \Magento\Framework\Exception\LocalizedException(
                __('Could not get preview image information. Please check your connection and try again.')
            );
        }
        return $image;
    }

    /**
     * @param string $filename
     *
     * @return string
     */
    private function getMimeContentType($filename)
    {
        $parts = explode('.',$filename);
        $ext = strtolower(array_pop($parts));
        if (array_key_exists($ext, $this->mimeTypes)) {
            return $this->mimeTypes[$ext];
        } else {
            return $this->defaultMimeType;
        }
    }
}
1 Like

@Antti_Ranta
Great script Thanks.
worked like charm !

This worked for me but I am unable to see the video in the frontend. It shows in the admin. Can I know what i might be missing. also checked the store id is set 0.

Great Piece of work.
Works like charm