Just like with images, Cloudinary offers powerful tools for optimizing and automating video workflows. From handling client-side video uploads and integrating AI moderation for content safety asynchronously to delivering seamless video streaming, Cloudinary’s flexibility shines.
In this blog post, we’ll walk through a video workflow example using Cloudinary’s PHP SDK, demonstrated with a product catalog app. While the demo focuses on a product catalog, the techniques and workflows apply to any application that manages videos. Here’s the video workflow we’ll explore:
As you read, think about which aspects of this video workflow might best suit your own PHP app as we explore how it integrates seamlessly into the PHP Product Catalog demo app.
If you’re just getting started with video workflows, be sure to check out the previous post in this series, where we explored how Cloudinary handles image uploads, AI-driven transformations, and delivery.
The PHP Product Catalog app showcases a simulated product catalog with products stored in a database. Each product includes a name, metadata (SKU, price, and category), an auto-generated description, an image, and a video. For details on the video upload-to-delivery workflow, check out the previous blog in this series.
With this app, you can:
- Add new products with images.
- View all products or individual product details.
- Edit product metadata and media.
See the app’s image features in action:
To follow along with this blog, make sure you:
- Sign up for a Cloudinary account and try it for free!
- Find the full code for this app on GitHub and follow the README for setup and installation instructions.
The first step in managing video workflows is uploading files to Cloudinary. The Upload widget provides an intuitive interface for direct uploads, removing the need for backend dependencies.
Here’s how the Upload widget is configured in the demo app:
// Configure the upload widget for videos
const videoWidget = cloudinary.createUploadWidget({
cloudName: '<?php echo $_ENV['CLOUDINARY_CLOUD_NAME']; ?>', // Replace with your Cloudinary cloud name
uploadPreset: 'php_demo_preset', // Replace with your upload preset
sources: ['local', 'url'], // Allow uploads from local files and URLs
resourceType: 'video', // Specify resource type as video
maxFileSize: 50000000, // Set a max file size (optional)
folder: 'products/videos', // Optional folder path
}, (error, result) => {
if (!error && result && result.event === "success") {
console.log('Video uploaded successfully:', result.info.secure_url);
document.getElementById('video_url').value = result.info.secure_url;
document.getElementById('video_id').value = result.info.public_id;
// Display the new video alongside the current one
const videoSection = document.getElementById("video_section");
videoSection.innerHTML = `
<label>Video:</label>
<div>
<video controls style="max-width: 200px; height: auto; margin-bottom: 15px;">
<source src="${result.info.secure_url}" type="video/mp4">
Your browser does not support the video tag.
</video>
</div>
`;
}
});
// Open the video upload widget
document.getElementById('upload_video_button').addEventListener('click', () => {
videoWidget.open();
});
</script>
Code language: JavaScript (javascript)
You need to specify your cloudName
and an unsigned uploadPreset
to instantiate an Upload Widget.
- Find your
cloudName
on your Cloudinary dashboard. - In the Product Catalog demo app, you run the script to create the
php_demo_preset
when clicking the first time setup button on the main page of the app. For more information about this upload preset and how to create it, see AI Image Workflows with PHP and Cloudinary
AI moderation ensures content safety, especially for user-generated videos. With Cloudinary add-ons like Amazon Rekognition Video Moderation, you can have videos automatically reviewed asynchronously.
In our demo app, when you add or edit a product, the app sends the uploaded video for automatic moderation using the explicit method of the Upload API. The review process occurs in the background, allowing users to continue using the app without interruptions. A placeholder message informs users that the video is under review. The app marks the video’s moderation status as “pending” and updates the database with the results once moderation is complete.
Here’s the code:
<?php
require_once __DIR__ . '/../vendor/autoload.php';
require_once __DIR__ . '/../includes/database.php';
require_once __DIR__ . '/../includes/functions.php';
require_once __DIR__ . '/../config/cloudinary_config.php';
use Dotenv\Dotenv;
use Cloudinary\Configuration\Configuration;
use Cloudinary\Cloudinary;
use Cloudinary\Api\Admin\AdminApi;
$dotenv = Dotenv::createImmutable(dirname(__DIR__));
$dotenv->load();
// Initialize Configuration
$config = new Configuration($_ENV['CLOUDINARY_URL']);
$api = new AdminAPI($config);
// Handle Video Moderation and Metadata
if (!empty($_POST['video_url'])) {
$product_video_url = $_POST['video_url'];
// Hold the video public ID temporarily until moderation status is confirmed.
$video_public_id_temp = $_POST['video_id'];
// Set metadata and mark the video for moderation.
$cloudinary_result = $cld->uploadApi()->explicit($video_public_id_temp, ['type' => 'upload', 'resource_type' => 'video', 'moderation' => 'aws_rek_video', "metadata" => $metadata]);
// Set initial values, pending moderation
$product_video_url = null;
$video_public_id = "pending";
$video_moderation_status="pending";
} else {
// Set values in case there's no video.
$product_video_url="invalid";
$video_public_id = "invalid";
$video_moderation_status=null;
$video_public_id_temp=null;
}
// Save the values in the database.
$product_id = saveProduct($pdo, $name, $product_image_url, $product_video_url, $image_public_id, $video_public_id, $video_moderation_status, $image_caption, $video_public_id_temp);
header("Location: products.php");
exit;
Code language: PHP (php)
Explanation:
- In this example, we use the explicit method of the Upload API to moderate videos that are already uploaded.
use Cloudinary\Api\Upload\UploadApi;
imports the library.$api = new AdminAPI($config);
creates an instance for using those methods.
- Set the moderation parameter to
aws_rek_video
for video content moderation. (To useaws_rek_video
, you need to sign up for the Rekognition AI Video Moderation add-on as described in the app’s Readme file.) - The Cloudinary PHP SDK automatically chunks large videos for easier uploading.
Webhooks enable the page to automatically render the video or display the rejected message once it has been processed. If you choose not to implement the webhook, you’ll need to refresh the page to render the video when the moderation process is complete.
Find the code for handling moderation on page refresh in the products.php
and product.php
files of the GitHub repo.
To receive a webhook notification whenever Cloudinary finishes moderating a video asset:
- Go to the Webhook Notifications page of the Cloudinary Console Settings.
- Add the URL of your webhook endpoint with the suffix
webhooks/video_upload_webhook.php
. - Select Moderation in the Notification Type drop-down to listen for moderation notifications.
To test your app locally, you can use a tool like Ngrok to create up a secure tunnel connecting the internet to your locally running app. Alternatively, deploy the app using a service like Vercel.
Once set up, Cloudinary sends a POST
request to the specified URL with the moderation results.
Let’s walk through handling webhook notifications for video moderation using PHP.
<?php
// Set headers to prevent caching
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('Connection: keep-alive');
// Start output buffering
ob_start();
// Include necessary files
require_once __DIR__ . '/../vendor/autoload.php';
require_once __DIR__ . '/../includes/database.php';
require_once __DIR__ . '/../config/cloudinary_config.php';
// Ensure you're receiving a POST request
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Get the raw POST data from Cloudinary
$payload = json_decode(file_get_contents('php://input'), true);
// Ensure you received necessary Cloudinary data
if (isset($payload['public_id'], $payload['secure_url'])) {
// Get Cloudinary data from the webhook notification
$video_moderation_status = $payload['moderation_status'];
if ($video_moderation_status === 'approved'){
$video_public_id = $payload['public_id'];
$product_video_url = $payload['secure_url'];
$rejection_reason = null;
} else {
$video_public_id = null;
$product_video_url = null;
$rejection_reason = $payload['moderation_response']['moderation_labels'][0]['moderation_label']['name'];
}
try {
// Find the record to update based on video_public_id_temp
$sql = "SELECT id FROM products WHERE video_public_id_temp = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([$payload['public_id']]);
$product = $stmt->fetch(PDO::FETCH_ASSOC);
if ($product) {
// Get the product ID of the matching record
$product_id = $product['id'];
// Update the database with video information
$sql = "UPDATE products
SET product_video_url = ?, video_public_id = ?, video_moderation_status = ?, rejection_reason = ?
WHERE id = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([$product_video_url, $video_public_id, $video_moderation_status, $rejection_reason, $product_id]);
echo "Product with ID $product_id successfully updated.";
file_put_contents('upload_status.json', json_encode(['status' => 'completed']));
http_response_code(200);
} else {
echo "No matching record found for video_public_id_temp = $video_public_id.";
}
} catch (PDOException $e) {
// Handle database errors
error_log("Database error: " . $e->getMessage());
echo "An error occurred while updating the product.";
}
}
}
// Clean output buffer and end the script
ob_end_flush();
?>
Code language: PHP (php)
Explanation:
- The code parses and checks the POST request payload to confirm that it contains the necessary video information (like
public_id
andsecure_url
). - The code checks
moderation_status
to determine if the video is approved or rejected. - In case of approval, the code updates the database with the video’s public URL and ID.
- In case of rejection, the code records the rejection reason and updates the database accordingly.
The Cloudinary Video Player ensures fast and reliable video streaming, leveraging adaptive bitrate streaming to seamlessly adjust to varying network speeds. In addition, it’s highly customizable, allowing you to align the player’s design with your app’s style while delivering a smooth playback experience.
Finally, with the Video Player, you can optimize delivery for any screen size or internet connection, providing viewers with the best experience wherever they are.
Here’s the code to embed the Video Player:
<!-- Display product video if available -->
<div class="product-image">
<div>
<p><b>Video</b></p>
</div>
<?php if ($product['video_public_id'] && $product['video_public_id']!='pending' && $product['video_public_id']!='invalid' && $product['video_moderation_status']!='rejected'): ?>
<div>
<video id="doc-player-<?php echo $product['id']; ?>" controls muted class="cld-video-player cld-fluid"></video>
</div>
<script></script>
<?php elseif (isset($message)): ?>
<div>
<p style="color:<?php echo $color; ?>;"><?php echo htmlspecialchars($message); ?></p>
</div>
<?php endif; ?>
</div>
<p>
<a href="edit_product.php?id=<?php echo $product['id']; ?>">Edit</a>
<a href="product.php?id=<?php echo $product['id']; ?>">Preview Listing</a>
</p>
</div>
</div>
Code language: HTML, XML (xml)
Explanation:
- Rejected videos displays a message explaining the reason for rejection.
- Pending videos prompts the user to wait until the moderation process is complete.
- Approved videos renders the video using the Cloudinary Video Player, initialized using the public ID of the video.
From uploading videos on the client side and applying AI moderation asynchronously to delivering optimized video streams, Cloudinary’s workflow capabilities ensure that video content is handled efficiently and in compliance with safety standards. This post demonstrates how easy it is to integrate Cloudinary into your PHP app for managing video content, keeping your application responsive while maintaining high-quality user experiences and implementing flexible AI video workflows with PHP.
For a deeper dive into managing images with Cloudinary, don’t forget to revisit the previous post in this series. With Cloudinary’s powerful tools at your disposal, the possibilities for enhancing media workflows in your app are endless. Sign up for a free Cloudinary account today and start optimizing your media management workflows.