The MediaSpaceManager handles media file uploads (images and videos) to Shopee's media space for use in product listings and other content.
The MediaSpaceManager provides methods for:
- Uploading product images with different scenes and aspect ratios
- Multi-part video upload with session management
- Video transcoding status tracking
- Video upload cancellation
// Upload an image
const imageResult = await sdk.mediaSpace.uploadImage({
scene: 'normal',
ratio: '1:1',
});
// Initialize video upload
const initResult = await sdk.mediaSpace.initVideoUpload({
file_md5: '2abf0b6e5ff90ff24437a0808f171a93',
file_size: 1261876,
});
// Check video transcoding status
const statusResult = await sdk.mediaSpace.getVideoUploadResult({
video_upload_id: initResult.response.video_upload_id,
});API Documentation: v2.media_space.upload_image
Upload multiple image files to MediaSpace (up to 9 images).
const response = await sdk.mediaSpace.uploadImage({
scene: 'normal', // 'normal' or 'desc'
ratio: '1:1', // '1:1' or '3:4' (whitelisted sellers only)
});
// Access uploaded images
response.response.image_info_list?.forEach((imageInfo) => {
console.log('Image ID:', imageInfo.image_info?.image_id);
console.log('URLs:', imageInfo.image_info?.image_url_list);
});Parameters:
scene(optional):'normal'- Process as square image (recommended for item images)'desc'- Don't process image (recommended for descriptions)
ratio(optional):'1:1'(default) or'3:4'(whitelisted sellers only)image(optional): Image file(s) - Max 10MB each, formats: JPG, JPEG, PNG
Use Cases:
- Upload product main images
- Upload product description images
- Upload product variant images
API Documentation: v2.media_space.init_video_upload
Initiate a video upload session. Video duration should be between 10s and 60s.
const response = await sdk.mediaSpace.initVideoUpload({
file_md5: '2abf0b6e5ff90ff24437a0808f171a93',
file_size: 1261876, // Size in bytes, max 30MB
});
const uploadId = response.response.video_upload_id;
console.log('Upload session ID:', uploadId);Parameters:
file_md5: MD5 hash of the video filefile_size: Size of video file in bytes (maximum 30MB)
Use Cases:
- Start video upload for product listing
- Initialize video session before uploading parts
API Documentation: v2.media_space.upload_video_part
Upload video file by parts. Part size should be exactly 4MB except for the last part.
await sdk.mediaSpace.uploadVideoPart({
video_upload_id: 'sg_90ce045e-fd92-4f0b-97a4-eda40546cd9f_000000',
part_seq: 0, // Sequence number starting from 0
content_md5: '3bb08579fffbfc13ed9d23cda8bbb46d',
// part_content would be the actual file data
});Parameters:
video_upload_id: The upload ID from initVideoUploadpart_seq: Sequence number of the part, starting from 0content_md5: MD5 hash of this partpart_content: The file content (handled as multipart/form-data)
Use Cases:
- Upload large videos in chunks
- Resume failed uploads by uploading missing parts
API Documentation: v2.media_space.complete_video_upload
Complete the video upload and start transcoding when all parts are uploaded.
await sdk.mediaSpace.completeVideoUpload({
video_upload_id: 'sg_90ce045e-fd92-4f0b-97a4-eda40546cd9f_000000',
part_seq_list: [0, 1, 2, 3],
report_data: {
upload_cost: 11832, // Time in milliseconds
},
});Parameters:
video_upload_id: The upload ID from initVideoUploadpart_seq_list: Array of all uploaded part sequence numbersreport_data.upload_cost: Upload time in milliseconds for tracking
Use Cases:
- Finalize video upload after all parts are uploaded
- Trigger video transcoding process
API Documentation: v2.media_space.get_video_upload_result
Query the upload status and result of video transcoding.
const response = await sdk.mediaSpace.getVideoUploadResult({
video_upload_id: 'sg_90ce045e-fd92-4f0b-97a4-eda40546cd9f_000000',
});
console.log('Status:', response.response.status);
if (response.response.status === 'SUCCEEDED') {
console.log('Video URLs:', response.response.video_info?.video_url_list);
console.log('Thumbnails:', response.response.video_info?.thumbnail_url_list);
console.log('Duration:', response.response.video_info?.duration);
}Video Status Values:
INITIATED- Waiting for parts or complete callTRANSCODING- Transcoding in progressSUCCEEDED- Ready for useFAILED- Upload failed (check message field)CANCELLED- Upload was cancelled
Use Cases:
- Poll for video transcoding completion
- Get video URLs after successful transcoding
- Check for upload failures
API Documentation: v2.media_space.cancel_video_upload
Cancel an ongoing video upload session.
await sdk.mediaSpace.cancelVideoUpload({
video_upload_id: 'sg_90ce045e-fd92-4f0b-97a4-eda40546cd9f_000000',
});Parameters:
video_upload_id: The upload ID to cancel
Use Cases:
- Cancel failed or unwanted uploads
- Clean up incomplete upload sessions
async function uploadProductImages(imageFiles: File[]) {
try {
// Upload images as product images (normal scene)
const result = await sdk.mediaSpace.uploadImage({
scene: 'normal',
ratio: '1:1',
});
// Extract image IDs for product creation
const imageIds = result.response.image_info_list?.map(
(info) => info.image_info?.image_id
).filter(Boolean) || [];
console.log('β
Uploaded images:', imageIds);
return imageIds;
} catch (error) {
console.error('β Image upload failed:', error);
throw error;
}
}async function uploadProductVideo(videoFile: Buffer) {
const crypto = require('crypto');
// Calculate MD5 hash
const fileMd5 = crypto.createHash('md5').update(videoFile).digest('hex');
const fileSize = videoFile.length;
try {
// Step 1: Initialize video upload
console.log('π Initializing video upload...');
const initResult = await sdk.mediaSpace.initVideoUpload({
file_md5: fileMd5,
file_size: fileSize,
});
const uploadId = initResult.response.video_upload_id;
console.log('β
Upload session created:', uploadId);
// Step 2: Split file into 4MB chunks and upload
const CHUNK_SIZE = 4 * 1024 * 1024; // 4MB
const chunks = Math.ceil(fileSize / CHUNK_SIZE);
const startTime = Date.now();
console.log(`π Uploading ${chunks} parts...`);
for (let i = 0; i < chunks; i++) {
const start = i * CHUNK_SIZE;
const end = Math.min(start + CHUNK_SIZE, fileSize);
const chunk = videoFile.slice(start, end);
const chunkMd5 = crypto.createHash('md5').update(chunk).digest('hex');
await sdk.mediaSpace.uploadVideoPart({
video_upload_id: uploadId,
part_seq: i,
content_md5: chunkMd5,
// part_content: chunk would be passed here
});
console.log(` β
Part ${i + 1}/${chunks} uploaded`);
}
// Step 3: Complete upload
const uploadCost = Date.now() - startTime;
console.log('π Completing upload...');
await sdk.mediaSpace.completeVideoUpload({
video_upload_id: uploadId,
part_seq_list: Array.from({ length: chunks }, (_, i) => i),
report_data: { upload_cost: uploadCost },
});
console.log('β
Upload complete, transcoding started');
// Step 4: Poll for transcoding completion
console.log('π Waiting for transcoding...');
let status: string = 'INITIATED';
let attempts = 0;
const maxAttempts = 60; // 5 minutes with 5-second intervals
while (status !== 'SUCCEEDED' && attempts < maxAttempts) {
await new Promise((resolve) => setTimeout(resolve, 5000));
const result = await sdk.mediaSpace.getVideoUploadResult({
video_upload_id: uploadId,
});
status = result.response.status;
console.log(` Status: ${status}`);
if (status === 'FAILED') {
throw new Error(`Transcoding failed: ${result.response.message}`);
}
if (status === 'CANCELLED') {
throw new Error('Upload was cancelled');
}
attempts++;
}
if (status !== 'SUCCEEDED') {
throw new Error('Transcoding timeout');
}
// Step 5: Get video information
const finalResult = await sdk.mediaSpace.getVideoUploadResult({
video_upload_id: uploadId,
});
console.log('β
Video ready!');
console.log('Video ID:', uploadId);
console.log('Duration:', finalResult.response.video_info?.duration, 'seconds');
return {
video_upload_id: uploadId,
video_info: finalResult.response.video_info,
};
} catch (error) {
console.error('β Video upload failed:', error);
// Attempt to cancel on error
try {
await sdk.mediaSpace.cancelVideoUpload({
video_upload_id: uploadId!,
});
console.log('ποΈ Upload cancelled');
} catch (cancelError) {
// Ignore cancel errors
}
throw error;
}
}async function uploadVideoPartsWithRetry(
uploadId: string,
chunks: Buffer[],
maxRetries: number = 3
) {
const crypto = require('crypto');
for (let i = 0; i < chunks.length; i++) {
const chunk = chunks[i];
const chunkMd5 = crypto.createHash('md5').update(chunk).digest('hex');
let attempt = 0;
let uploaded = false;
while (attempt < maxRetries && !uploaded) {
try {
await sdk.mediaSpace.uploadVideoPart({
video_upload_id: uploadId,
part_seq: i,
content_md5: chunkMd5,
// part_content: chunk
});
uploaded = true;
console.log(`β
Part ${i + 1}/${chunks.length} uploaded`);
} catch (error) {
attempt++;
console.warn(`β οΈ Part ${i + 1} failed (attempt ${attempt}/${maxRetries})`);
if (attempt >= maxRetries) {
throw new Error(`Failed to upload part ${i} after ${maxRetries} attempts`);
}
// Wait before retry with exponential backoff
await new Promise((resolve) => setTimeout(resolve, 1000 * Math.pow(2, attempt)));
}
}
}
}import crypto from 'crypto';
function calculateFileMd5(fileBuffer: Buffer): string {
return crypto.createHash('md5').update(fileBuffer).digest('hex');
}
function calculatePartMd5(partBuffer: Buffer): string {
return crypto.createHash('md5').update(partBuffer).digest('hex');
}async function safeUploadVideo(videoFile: Buffer) {
try {
return await uploadProductVideo(videoFile);
} catch (error) {
if (error.error === 'error_file_size') {
console.error('Video file too large (max 30MB)');
} else if (error.error === 'error_invalid_upload_id') {
console.error('Invalid or expired upload session');
} else if (error.error === 'error_invalid_part_seq') {
console.error('Invalid part sequence number');
} else {
console.error('Upload failed:', error);
}
return null;
}
}class VideoUploadProgress {
private totalParts: number;
private uploadedParts: number = 0;
constructor(fileSize: number) {
const CHUNK_SIZE = 4 * 1024 * 1024;
this.totalParts = Math.ceil(fileSize / CHUNK_SIZE);
}
onPartUploaded() {
this.uploadedParts++;
const progress = (this.uploadedParts / this.totalParts) * 100;
console.log(`Progress: ${progress.toFixed(1)}%`);
}
getProgress(): number {
return (this.uploadedParts / this.totalParts) * 100;
}
}async function waitForTranscoding(
uploadId: string,
onProgress?: (status: string) => void
): Promise<void> {
const delays = [2000, 3000, 5000, 5000, 10000]; // Progressive delay
let delayIndex = 0;
while (true) {
const result = await sdk.mediaSpace.getVideoUploadResult({
video_upload_id: uploadId,
});
const status = result.response.status;
onProgress?.(status);
if (status === 'SUCCEEDED') {
return;
}
if (status === 'FAILED') {
throw new Error(`Transcoding failed: ${result.response.message}`);
}
if (status === 'CANCELLED') {
throw new Error('Upload was cancelled');
}
// Use progressive delays
const delay = delays[Math.min(delayIndex, delays.length - 1)];
await new Promise((resolve) => setTimeout(resolve, delay));
delayIndex++;
}
}async function uploadWithCleanup(videoFile: Buffer) {
let uploadId: string | undefined;
try {
const result = await uploadProductVideo(videoFile);
return result;
} catch (error) {
// Clean up on error
if (uploadId) {
try {
await sdk.mediaSpace.cancelVideoUpload({
video_upload_id: uploadId,
});
} catch (cleanupError) {
console.warn('Failed to cancel upload:', cleanupError);
}
}
throw error;
}
}| Error Code | Description | Solution |
|---|---|---|
error_file_size |
File too large | Video must be < 30MB |
error_param |
Invalid parameters | Check file_md5 format and file_size value |
error_invalid_upload_id |
Invalid or expired upload ID | Start a new upload session |
error_invalid_part_seq |
Invalid part sequence | Ensure part_seq starts from 0 and is sequential |
error_invalid_part_size |
Invalid part size | Each part must be 4MB except the last part |
error_already_completed |
Upload already completed | Cannot modify completed upload |
error_tier_img_partial |
Image upload error | Contact support |
- ProductManager - Use uploaded media in products
- Authentication Guide - API authentication
- Shopee Media Requirements - Official media specifications