janwebdev / symfony-social-video-bundle
Symfony bundle for posting short videos (Reels/Shorts) to YouTube, Instagram, Facebook, X/Twitter, Threads, and TikTok
Package info
github.com/janwebdev/symfony-social-video-bundle
Type:symfony-bundle
pkg:composer/janwebdev/symfony-social-video-bundle
1.0.0
2026-03-21 15:21 UTC
Requires
- php: >=8.4
- psr/log: ^3.0
- symfony/framework-bundle: >=7.4
- symfony/http-client: >=7.4
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.0
- phpstan/phpstan: ^2.0
- phpstan/phpstan-phpunit: ^2.0
- phpunit/phpunit: ^11.0
- symfony/event-dispatcher: >=7.4
- symfony/messenger: >=7.4
Suggests
- symfony/event-dispatcher: For BeforePublish/AfterPublish events
- symfony/messenger: For async video publishing via publishAsync()
This package is auto-updated.
Last update: 2026-03-21 15:22:53 UTC
README
Symfony bundle for posting short videos (Reels/Shorts) to social networks.
โจ Features
- ๐ฌ 6 Platforms: YouTube Shorts, Instagram Reels, Facebook Reels, X/Twitter, Threads, TikTok
- ๐ Flexible Sources: Local files or public URLs (platform-dependent)
- โก Async Support: Symfony Messenger integration
- ๐ฏ Type Safe: PHP 8.4 readonly properties throughout
- ๐ No External SDK: All API clients built-in
- ๐ Fluent API:
VideoMessageBuilderfor easy video post creation - ๐ช Event System: Before/After/Failed publish events
๐ฆ Installation
composer require janwebdev/symfony-social-video-bundle
Register the bundle in config/bundles.php:
return [ // ... Janwebdev\SocialVideoBundle\SocialVideoBundle::class => ['all' => true], ];
โ๏ธ Configuration
Create config/packages/social_video.yaml:
social_video: providers: youtube: enabled: true access_token: "%env(YOUTUBE_ACCESS_TOKEN)%" privacy_status: "public" # public | private | unlisted instagram: enabled: true account_id: "%env(INSTAGRAM_ACCOUNT_ID)%" access_token: "%env(INSTAGRAM_ACCESS_TOKEN)%" graph_version: "v22.0" facebook: enabled: true page_id: "%env(FACEBOOK_PAGE_ID)%" access_token: "%env(FACEBOOK_ACCESS_TOKEN)%" graph_version: "v22.0" twitter: enabled: true api_key: "%env(TWITTER_API_KEY)%" api_secret: "%env(TWITTER_API_SECRET)%" access_token: "%env(TWITTER_ACCESS_TOKEN)%" access_token_secret: "%env(TWITTER_ACCESS_TOKEN_SECRET)%" threads: enabled: true user_id: "%env(THREADS_USER_ID)%" access_token: "%env(THREADS_ACCESS_TOKEN)%" # NOTE: Threads API requires a public video URL โ local files are not supported. tiktok: enabled: true client_key: "%env(TIKTOK_CLIENT_KEY)%" access_token: "%env(TIKTOK_ACCESS_TOKEN)%" # NOTE: Requires video.publish scope approval from TikTok. Until approved, # all videos are posted as private (SELF_ONLY).
Environment Variables
# YouTube Data API v3 YOUTUBE_ACCESS_TOKEN=ya29.your_oauth_token # Instagram Graph API (Business/Creator account required) INSTAGRAM_ACCOUNT_ID=your_ig_business_account_id INSTAGRAM_ACCESS_TOKEN=your_page_access_token # Facebook Graph API (Page token with publish_video permission) FACEBOOK_PAGE_ID=your_page_id FACEBOOK_ACCESS_TOKEN=your_page_access_token # X/Twitter API v2 (OAuth 1.0a) TWITTER_API_KEY=your_api_key TWITTER_API_SECRET=your_api_secret TWITTER_ACCESS_TOKEN=your_access_token TWITTER_ACCESS_TOKEN_SECRET=your_access_token_secret # Threads API THREADS_USER_ID=your_threads_user_id THREADS_ACCESS_TOKEN=your_threads_access_token # TikTok Content Posting API v2 TIKTOK_CLIENT_KEY=your_client_key TIKTOK_ACCESS_TOKEN=your_access_token
๐ Usage
Basic Usage
use Janwebdev\SocialVideoBundle\Message\VideoMessageBuilder; use Janwebdev\SocialVideoBundle\Publisher\VideoPublisher; class YourService { public function __construct(private VideoPublisher $publisher) {} public function postVideo(): void { $message = VideoMessageBuilder::create() ->setVideoPath('/path/to/reel.mp4') // local file // OR: ->setVideoUrl('https://cdn.example.com/reel.mp4') ->setTitle('My Amazing Short #Shorts') ->setDescription('Check this out!') ->addHashtag('reels') ->addHashtag('fyp') ->setPrivacy('public') ->forNetworks(['youtube', 'instagram', 'tiktok']) ->build(); $results = $this->publisher->publish($message); foreach ($results as $result) { if ($result->isSuccess()) { echo "{$result->getProviderName()}: {$result->getPostUrl()}\n"; } else { echo "Failed: {$result->getErrorMessage()}\n"; } } } }
Async Publishing (requires symfony/messenger)
// Dispatch to message queue $this->publisher->publishAsync($message);
Check Results
$results = $this->publisher->publish($message); if ($results->isAllSuccessful()) { echo "Posted to all networks!\n"; } $ytResult = $results->getResult('youtube'); if ($ytResult?->isSuccess()) { echo "YouTube URL: " . $ytResult->getPostUrl() . "\n"; } foreach ($results->getFailed() as $result) { echo "Failed on {$result->getProviderName()}: {$result->getErrorMessage()}\n"; }
Event Listeners
use Janwebdev\SocialVideoBundle\Publisher\Event\AfterPublishEvent; use Symfony\Component\EventDispatcher\EventSubscriberInterface; class VideoPostSubscriber implements EventSubscriberInterface { public static function getSubscribedEvents(): array { return [AfterPublishEvent::class => 'onAfterPublish']; } public function onAfterPublish(AfterPublishEvent $event): void { foreach ($event->results->getSuccessful() as $result) { // Log to database, send notifications, etc. } } }
๐ Platform Reference
YouTube Shorts
- Auth: OAuth 2.0 user token (
YOUTUBE_ACCESS_TOKEN) - Upload: Resumable PUT upload (Google's resumable upload protocol)
- Shorts: Automatic classification โ vertical (9:16) + โค3 min +
#Shortsin title/description - Credential setup: Google Cloud Console โ Create Project โ Enable YouTube Data API v3 โ OAuth 2.0 credentials
Instagram Reels
- Auth: Page Access Token (long-lived)
- Account: Instagram Business or Creator account required
- Upload: Local files via rupload.facebook.com OR public video URL
- Max duration: 90 seconds
- Required permissions:
instagram_basic,instagram_content_publish - Credential setup: Facebook Developers โ App โ Instagram product
Facebook Reels
- Auth: Page Access Token with
publish_videopermission - Account: Facebook Pages only (not personal profiles)
- Upload: Local binary or public URL
- Max duration: 60 seconds
- Credential setup: Same Facebook app, add
publish_videopermission
X/Twitter
- Auth: OAuth 1.0a (
api_key,api_secret,access_token,access_token_secret) - Upload: Chunked INIT/APPEND/FINALIZE (5 MB chunks)
- Max duration: 140 seconds. Max file: 512 MB
- Rate limit (Free tier): 17 upload sessions per 24 hours
- Credential setup: X Developer Portal โ App โ Keys & Tokens
Threads
- Auth: Threads OAuth 2.0 user token
- โ ๏ธ URL only: Threads API does not support binary upload. Use
setVideoUrl()with a publicly accessible video URL. - Max duration: 5 minutes
- Required permissions:
threads_basic,threads_content_publish - Credential setup: Facebook Developers โ App โ Threads product
TikTok
- Auth: OAuth 2.0 Bearer token
- โ ๏ธ Scope approval required:
video.publishscope requires business developer account and TikTok audit (5โ10 business days). Until approved, all videos are private (SELF_ONLY). - Upload: Local files (10 MB chunked) or public URL
- Max duration: 5 minutes. Rate limit: 6 init requests/min
- Credential setup: TikTok for Developers โ App โ Content Posting API โ Apply for
video.publish
๐งช Testing
# Run tests composer run-tests # Static analysis (PHPStan level 9) composer run-static-analysis # Code style check composer check-code-style # Fix code style composer fix-code-style
๐ Upload Matrix
| Platform | Local File | Public URL |
|---|---|---|
| YouTube Shorts | โ | โ (download first) |
| Instagram Reels | โ (rupload) | โ (video_url) |
| Facebook Reels | โ (rupload) | โ (file_url) |
| X/Twitter | โ | โ (download first) |
| Threads | โ | โ required |
| TikTok | โ (chunked) | โ (PULL_FROM_URL) |
๐ License
MIT License. See LICENSE file.
๐ Credits
Built by Yan Rogozinsky as part of the Symfony Social Post Bundle ecosystem.
๐ Related
- symfony-social-post-bundle โ text/image posting to the same platforms