We try to make video posts with the linkedin API, in the whole process of upload initialization, video segmentation, upload, upload finalization and post I have no errors, we even managed to publish the first time but now no more posts are published.
In the same way, when I try to publish a duplicate linkedin post, it refuses because it's a duplicate post.
CODE:
Upload of the video:
async initializeUploadToLinkedinVideo(filePath) {
const params = [
{ value: this.profileUrn, name: 'profileUrn' },
{ value: this.token, name: 'token' },
{ value: filePath, name: 'filePath' }
]
const error = verifyParams(params)
if (error) {
return error
}
let fileSize
try {
fileSize = await getSize(filePath)
if (fileSize === 0) {
return { error: 'File size is 0' }
}
} catch (e) {
console.error('Error getting file size:', e)
return { error: 'Failed to get file size' }
}
try {
const uploadResponse = await axios.post(
`https://api.linkedin.com/rest/videos?action=initializeUpload`,
{
initializeUploadRequest: {
owner: this.profileUrn,
fileSizeBytes: fileSize,
uploadCaptions: false,
uploadThumbnail: false
}
},
{
headers: {
'Authorization': `Bearer ${this.token}`,
'X-Restli-Protocol-Version': '2.0.0',
'LinkedIn-Version': '202405',
'Content-Type': 'application/json'
}
}
)
return uploadResponse.data
} catch (e) {
console.error(`Error during LinkedIn initialize upload video:`, e.response ? e.response.data : e.message)
return { error: 'Failed to upload to LinkedIn' }
}
}
async uploadToLinkedinVideo(filePath, uploadInstructions) {
const params = [
{ value: this.token, name: 'token'},
{ value: uploadInstructions, name: 'uploadInstructions' },
{ value: filePath, name: 'filePath' }
]
const error = verifyParams(params)
if (error) {
return error
}
let videoSplit
try {
videoSplit = await videoSplitter(filePath, uploadInstructions)
if (videoSplit?.error) {
console.error('Error splitting video:', videoSplit.error)
return videoSplit
}
} catch (e) {
console.error('Error splitting video:', e)
return { error: 'Failed to split video' }
}
console.log('videoSplit:', videoSplit)
try {
const uploadPromises = videoSplit.map(async (video) => {
const { outputFilePath, uploadUrl } = video
const fileStream = fs.createReadStream(outputFilePath)
const formData = new FormData()
formData.append('file', fileStream)
const uploadResponse = await axios.put(uploadUrl, formData,
{
headers: {
...formData.getHeaders()
}
}
)
return {
status: uploadResponse.status,
etag: uploadResponse.headers.etag
}
})
return await Promise.all(uploadPromises)
} catch (e) {
console.error('Error during LinkedIn upload video:', e.response ? e.response.data : e.message)
return { error: 'Failed to upload to LinkedIn' }
}
}
async finalizeVideoUpload(videoUrn, eTags) {
const params = [
{ value: this.token, name: 'token' },
{ value: videoUrn, name: 'videoUrn' }
]
const error = verifyParams(params)
if (error) {
return error
}
try {
const finalizeResponse = await axios.post(
`https://api.linkedin.com/rest/videos?action=finalizeUpload`,
{
finalizeUploadRequest: {
video: videoUrn,
uploadToken: "",
uploadedPartIds: eTags
}
},
{
headers: {
"Authorization": `Bearer ${this.token}`,
"X-Restli-Protocol-Version": "2.0.0",
"LinkedIn-Version": "202405",
"Content-Type": "application/json"
}
}
)
return finalizeResponse.status
} catch (e) {
console.error('Error during LinkedIn finalize video upload:', e.response ? e.response.data : e.message)
return { error: 'Failed to finalize video upload' }
}
}
async initializeAndUploadToLinkedinVideo(filePath) {
const initializeUploadToLinkedinResponse = await this.initializeUploadToLinkedinVideo(filePath)
console.log('initializeUploadToLinkedinResponse:', initializeUploadToLinkedinResponse)
if (initializeUploadToLinkedinResponse?.error) {
return initializeUploadToLinkedinResponse
}
const videoUrn = initializeUploadToLinkedinResponse?.value?.video
if (!videoUrn) {
return { error: 'Failed to get video URN' }
}
const uploadInstructions = initializeUploadToLinkedinResponse?.value?.uploadInstructions
console.log('uploadInstructions:', uploadInstructions)
if (!uploadInstructions) {
return { error: 'Failed to get upload instructions' }
}
const uploadToLinkedinResponse = await this.uploadToLinkedinVideo(filePath, uploadInstructions)
console.log('uploadToLinkedinResponse:', uploadToLinkedinResponse)
if (uploadToLinkedinResponse?.error) {
return uploadToLinkedinResponse
}
const eTags = uploadToLinkedinResponse.map(upload => upload.etag)
const finalizeVideoUploadResponse = await this.finalizeVideoUpload(videoUrn, eTags)
console.log('finalizeVideoUploadResponse:', finalizeVideoUploadResponse)
if (finalizeVideoUploadResponse?.error) {
return finalizeVideoUploadResponse
}
if (finalizeVideoUploadResponse !== 200) {
return { error: 'Failed to finalize video upload' }
}
return videoUrn
}
NOTE: verifyParams()
, getSize()
and videoSplitter()
are defined in an other file, below the function used to split the video according to the documentation
exports.videoSplitter = async (filePath, uploadInstructions) => {
const outputDir = `./uploads/linkedin/${filePath.split('/').pop().split('.')[0]}`
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true })
}
const videoPaths = []
const splitPromise = uploadInstructions.map((range, index) => {
return new Promise((resolve, reject) => {
const { firstByte, lastByte, uploadUrl } = range
const outputFilePath = `${outputDir}/segment_${index + 1}.mp4`
// Adjust the end position to include the last byte
const readStream = fs.createReadStream(filePath, { start: firstByte, end: lastByte })
const writeStream = fs.createWriteStream(outputFilePath)
readStream.pipe(writeStream)
readStream.on('end', () => {
console.log(`Segment ${index + 1} written successfully`)
const absolutePath = fs.realpathSync(outputFilePath)
videoPaths.push({ outputFilePath: absolutePath, uploadUrl })
resolve()
})
readStream.on('error', (err) => {
console.error(`Error reading segment ${index + 1}:`, err)
reject(err)
})
writeStream.on('error', (err) => {
console.error(`Error writing segment ${index + 1}:`, err)
reject(err)
})
})
})
try {
await Promise.all(splitPromise)
} catch (e) {
console.error('Error splitting video:', e)
return { error: 'Failed to split video', details: e }
}
if (videoPaths.length !== uploadInstructions.length) {
return { error: 'Failed to split video' }
}
return videoPaths
}
Below the request to post and get a video:
Post:
POST https://api.linkedin.com/rest/posts
Authorization: Bearer <ACCESS_TOKEN>
Linkedin-Version: 202405
X-RestLi-Protocol-Version: 2.0.0
Content-Type: application/json
{
"author": "urn:li:organization:<ORGANIZATION_ID>",
"commentary": "Post video from api test longer /3!",
"visibility": "PUBLIC",
"distribution": {
"feedDistribution": "MAIN_FEED",
"targetEntities": [],
"thirdPartyDistributionChannels": []
},
"content": {
"media": {
"title": "Post video from api test longer /2!",
"id": "urn:li:video:<VIDEO_ID>"
}
},
"lifecycleState": "PUBLISHED",
"isReshareDisabledByAuthor": false
}
get:
GET https://api.linkedin.com/rest/videos/urn:li:video:<VIDEO_ID>
Authorization: Bearer <ACCESS_TOKEN>
Linkedin-Version: 202405
Content-Type: application/json
# Response:
{
"owner": "urn:li:organization:<ORGANIZATION_ID>",
"processingFailureReason": "CORRUPTED_ENTITY",
"id": "urn:li:video:<VIDEO_ID>",
"status": "PROCESSING_FAILED"
}
I also tried this but I have an issue when splitting the video.
I have the same issue when trying to upload video < 4Mb (unsplitted video)
The problem was in the way the videos were split in videoSplitter()
, here is a corrected function
const fs = require('fs')
const path = require('path')
const { exec } = require('child_process')
exports.videoSplitter = async (filePath, uploadInstructions) => {
const outputDir = path.join('./uploads/linkedin', path.basename(filePath, path.extname(filePath)))
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true })
}
const videoPaths = []
const splitCommand = `split -b 4194303 ${filePath} ${path.join(outputDir, 'part-')}`
try {
await new Promise((resolve, reject) => {
exec(splitCommand, (error, stdout, stderr) => {
if (error) {
console.error(`Error splitting video: ${error}`)
reject(error)
} else {
resolve()
}
})
})
const partFiles = fs.readdirSync(outputDir).filter(file => file.startsWith('part-'))
if (partFiles.length !== uploadInstructions.length) {
return { error: 'Failed to split video: mismatch between number of parts and upload instructions' }
}
partFiles.forEach((file, index) => {
const absolutePath = fs.realpathSync(path.join(outputDir, file))
const uploadUrl = uploadInstructions[index].uploadUrl
videoPaths.push({ outputFilePath: absolutePath, uploadUrl })
})
} catch (e) {
console.error('Error splitting video:', e)
return { error: 'Failed to split video', details: e }
}
return videoPaths
}