Uploading video to blogger from NodeJS

I am trying to upload video to blogger using following code. I am able to get the response from my code. I am getting stuck at, that I am not able to transfer the video data to blogger.

My NodeJS code:

const axios = require('axios');
const fs = require('fs');
const path = require('path');

const videoPath = '/Downloads/11H3Bepq6Hx_seEhq2iO7OSW1zC01PnKo.mp4';
const videoStream = fs.createReadStream(videoPath);

let data = '{"protocolVersion":"0.8","createSessionRequest":{"fields":[{"external":{"name":"file","filename":"11H3Bepq6Hx_seEhq2iO7OSW1zC01PnKo.mp4","put":{},"size":1018020}},{"inlined":{"name":"title","content":"11H3Bepq6Hx_seEhq2iO7OSW1zC01PnKo.mp4","contentType":"text/plain"}},{"inlined":{"name":"addtime","content":"1726417159033","contentType":"text/plain"}},{"inlined":{"name":"onepick_version","content":"v2","contentType":"text/plain"}},{"inlined":{"name":"onepick_host_id","content":"10","contentType":"text/plain"}},{"inlined":{"name":"onepick_host_usecase","content":"RichEditor","contentType":"text/plain"}},{"inlined":{"name":"tos","content":"true","contentType":"text/plain"}},{"inlined":{"name":"blogID","content":"add-your-own-id-here","contentType":"text/plain"}},{"inlined":{"name":"postID","content":"add-your-own-id-here","contentType":"text/plain"}}]}}';

let config = {
    method: 'post',
    url: 'https://docs.google.com/upload/blogger/resumable?authuser=0&opi=98421741',
    headers: {
        'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8',
        'Accept': '*/*',
        'Sec-Fetch-Site': 'same-origin',
        'Accept-Language': 'en-IN,en-GB;q=0.9,en;q=0.8',
        'Accept-Encoding': 'gzip, deflate, br',
        'Sec-Fetch-Mode': 'cors',
        'Host': 'docs.google.com',
        'Origin': 'https://docs.google.com',
        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.6 Safari/605.1.15',
        'Referer': 'https://docs.google.com/',
        'Content-Length': '860',
        'Connection': 'keep-alive',
        'Sec-Fetch-Dest': 'empty',
        'Cookie': '',
        'X-Goog-Upload-Header-Content-Type': 'video/mp4',
        'X-Goog-Upload-Header-Content-Length': '1018020',
        'X-Client-Pctx': 'CgcSBWjtl_cu',
        'X-Goog-Upload-Protocol': 'resumable',
        'X-Goog-Upload-Command': 'start'
    },
    data: data
};

axios.request(config)
    .then((response) => {
        console.log(JSON.stringify(response.data));
        
        const boundary = '----WebKitFormBoundary7MA4YWxkTrZu0gW';
        const body = `--${boundary}\r\n` +
            `Content-Disposition: form-data; name="metadata"\r\n\r\n` +
            `${data}\r\n` +
            `--${boundary}\r\n` +
            `Content-Disposition: form-data; name="Filedata"; filename="${path.basename(videoPath)}"\r\n` +
            `Content-Type: video/mp4\r\n` +
            `Content-Transfer-Encoding: binary\r\n\r\n` +
            `${fs.readFileSync(videoPath, { encoding: 'binary' })}\r\n` +
            `--${boundary}--`;

        let config = {
            method: 'post',
            url: `https://docs.google.com/upload/blogger/resumable?authuser=0&opi=98421741&upload_id=${response.data['sessionStatus']['upload_id']}&upload_protocol=resumable`,
            headers: {
                'Content-Type': `multipart/related; boundary=${boundary}`,
                'Accept': '*/*',
                'Sec-Fetch-Site': 'same-origin',
                'Accept-Language': 'en-IN,en-GB;q=0.9,en;q=0.8',
                'Accept-Encoding': 'gzip, deflate, br',
                'Sec-Fetch-Mode': 'cors',
                'Host': 'docs.google.com',
                'Origin': 'https://docs.google.com',
                'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.6 Safari/605.1.15',
                'Referer': 'https://docs.google.com/',
                'Content-Length': '1018020',
                'Connection': 'keep-alive',
                'Sec-Fetch-Dest': 'empty',
                'Cookie': '',
                "X-Goog-Upload-Protocol": "multipart",
            },
            "cache": "default",
            "credentials": "include",
            "mode": "cors",
            "redirect": "follow",
            "referrer": "https://docs.google.com/",
            "referrerPolicy": "origin",
            data: body
        };

        axios.request(config)
            .then((response) => {
                console.log(JSON.stringify(response.data));
            })
            .catch((error) => {
                console.log(error);
            });
    })
    .catch((error) => {
        console.log(error);
    });

The response I am getting from blogger:

{"sessionStatus":{"state":"FINALIZED","externalFieldTransfers":[{"name":"file","status":"COMPLETED","bytesTransferred":0,"bytesTotal":1018020,"putInfo":{"url":"https://docs.google.com/upload/blogger/resumable?authuser=0&opi=98421741&upload_protocol=resumable&upload_id=AD-8ljsco6WMZdutraflzfW1AtmJ1k5OYO2PudwjgaYof8PaCSqRn5kqZofxDnlNtVlaig95-jM&file_id=000"},"content_type":"video/mp4"}],"additionalInfo":{"uploader_service.GoogleRupioAdditionalInfo":{"completionInfo":{"status":"SUCCESS"}}},"upload_id":"AD-8ljsco6WMZdutraflzfW1AtmJ1k5OYO2PudwjgaYof8PaCSqRn5kqZofxDnlNtVlaig95-jM"}}

You see the bytesTransferred shows 0. It means video data is not transferred.

When tranferring through browser, response generated is as follows:

{"sessionStatus":{"state":"FINALIZED","externalFieldTransfers":[{"name":"file","status":"COMPLETED","bytesTransferred":1018020,"bytesTotal":1018020,"putInfo":{"url":"https://docs.google.com/upload/blogger/resumable?authuser=0\u0026opi=98421741\u0026upload_id=AD-8ljvGMD4Mg6k6oSxW74UCa0gsZKJt3grXpwNS6UFtbCnu0D-HyPXF5_B_Krr1aJ-mZQjJ3jcicPLGUSAARAl4TJdDosvQiwltu-Xd139T0aI0\u0026file_id=000"},"content_type":"video/mp4"}],"additionalInfo":{"uploader_service.GoogleRupioAdditionalInfo":{"completionInfo":{"status":"SUCCESS","customerSpecificInfo":{"contentId": "ed34314396d57090"}}}},"upload_id":"AD-8ljvGMD4Mg6k6oSxW74UCa0gsZKJt3grXpwNS6UFtbCnu0D-HyPXF5_B_Krr1aJ-mZQjJ3jcicPLGUSAARAl4TJdDosvQiwltu-Xd139T0aI0"}}

Here bytesTransferred is 1018020 same as bytesTotal.

Upon more research, I came across this code:

h.Xc = function() {
        var a = yd(this.Ca);
        this.V.length != null && (a["Content-Length"] = this.V.length);
        a = fac(a);
        a["X-Goog-Upload-Protocol"] = "multipart";
        a["Content-Type"] = "multipart/related; boundary=" + this.U;
        this.H = "Transferring";
        this.N = new DZ;
        this.O.te();
        this.O.listen(this.N, "progress", this.pda);
        this.O.listen(this.N, "complete", this.oda);
        var b = this.N,
            c = b.send,
            d = this.Ia,
            e = this.Ha,
            f = "--" + this.U + '\r\nContent-Disposition: form-data; name="metadata"\r\n\r\n' + (this.Ga + "\r\n--") + this.U + '\r\nContent-Disposition: form-data; name="Filedata"\r\nContent-Transfer-Encoding: ' +
            (this.La + "\r\n\r\n") + this.V + "\r\n--" + this.U + "--\r\n";
        this.wa = f.length - this.V.length;
        c.call(b, d, e, f, a)
    };

But still the error is there. In my code, to simulate please login in blogger. Grab cookie value. Also replace add-your-own-id-here in data variable with appropriate values.

Thank You

It seems that the issue might be with how the video data is being transferred in your code. To upload video data successfully, you need to set up the multipart request correctly. Here’s a step-by-step explanation and revised code to help resolve this:

1. Issues in Your Code

  • The Content-Length header you set manually may not reflect the correct size of the entire request.
  • Using the fs.readFileSync method in the body for binary data might not handle the video stream correctly.
  • The multipart request boundary needs to be set properly in the request body.

2. Solution

  1. Use FormData: The easiest way to handle multipart form uploads in Node.js is by using the form-data package.
  2. Stream the Video File: Use fs.createReadStream to stream the video file directly into the form data.
  3. Remove Manual Headers for Content-Length: Let axios and form-data handle setting the Content-Length header automatically.

3. Code Implementation

Here is a revised version of your code using form-data:

javascript

Copy code

const axios = require('axios');
const fs = require('fs');
const FormData = require('form-data');
const path = require('path');

const videoPath = '/Downloads/11H3Bepq6Hx_seEhq2iO7OSW1zC01PnKo.mp4';
const videoStream = fs.createReadStream(videoPath);

// Prepare metadata as JSON
const metadata = JSON.stringify({
    protocolVersion: "0.8",
    createSessionRequest: {
        fields: [
            {
                external: {
                    name: "file",
                    filename: "11H3Bepq6Hx_seEhq2iO7OSW1zC01PnKo.mp4",
                    put: {},
                    size: 1018020
                }
            },
            {
                inlined: {
                    name: "title",
                    content: "11H3Bepq6Hx_seEhq2iO7OSW1zC01PnKo.mp4",
                    contentType: "text/plain"
                }
            },
            {
                inlined: {
                    name: "blogID",
                    content: "add-your-own-id-here",
                    contentType: "text/plain"
                }
            },
            {
                inlined: {
                    name: "postID",
                    content: "add-your-own-id-here",
                    contentType: "text/plain"
                }
            }
            // Add other fields as needed
        ]
    }
});

// Start the upload session
let config = {
    method: 'post',
    url: 'https://docs.google.com/upload/blogger/resumable?authuser=0&opi=98421741',
    headers: {
        'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8',
        'X-Goog-Upload-Header-Content-Type': 'video/mp4',
        'X-Goog-Upload-Protocol': 'resumable',
        'X-Goog-Upload-Command': 'start',
    },
    data: metadata
};

axios.request(config)
    .then((response) => {
        console.log('Session started:', response.data);
        const uploadId = response.data['sessionStatus']['upload_id'];

        // Create a new form with metadata and file stream
        const form = new FormData();
        form.append('metadata', metadata, { contentType: 'application/json' });
        form.append('file', videoStream, { filename: path.basename(videoPath), contentType: 'video/mp4' });

        // Upload the video file
        let uploadConfig = {
            method: 'post',
            url: `https://docs.google.com/upload/blogger/resumable?authuser=0&opi=98421741&upload_id=${uploadId}&upload_protocol=resumable`,
            headers: {
                ...form.getHeaders(), // Get the appropriate headers from form-data
                'X-Goog-Upload-Protocol': 'multipart',
            },
            data: form
        };

        axios.request(uploadConfig)
            .then((uploadResponse) => {
                console.log('Upload successful:', uploadResponse.data);
            })
            .catch((error) => {
                console.error('Upload failed:', error.response ? error.response.data : error.message);
            });
    })
    .catch((error) => {
        console.error('Session initiation failed:', error.response ? error.response.data : error.message);
    });

Explanation

  1. FormData: This uses the form-data package to create a multipart form. It appends both metadata and the video file stream.
  2. Stream File: The fs.createReadStream method streams the video file to avoid loading the entire file into memory.
  3. Headers: We use form.getHeaders() to set the appropriate headers for the multipart form upload automatically.
  4. Error Handling: Enhanced error handling to output the specific response error data.

4. Additional Notes

  • Make sure to replace add-your-own-id-here with your actual blogID and postID.
  • Ensure that you have the form-data package installed:

bash

Copy code

npm install form-data
  • This example uses a multipart upload with the resumable protocol.

By using form-data and streaming the file directly, this approach handles the upload more effectively, avoiding potential issues with file size and headers.