Waleed Daud
Waleed Daud

Reputation: 1

Transcoding job fails with code 13 Internal Error

At first I thought maybe the issue is that videos are without audio so I changed the config settings but when ever I upload the video I get internal error

Here's the code for reference, is there error on my part or random failure of transcoding job

const express = require('express');
const multer = require('multer');
const { Storage } = require('@google-cloud/storage');
const { TranscoderServiceClient } = require('@google-cloud/video-transcoder');
const path = require('path');
const fs = require('fs');
require('dotenv').config();

// Validate Environment Variables
if (!process.env.GOOGLE_CLOUD_PROJECT_ID || 
    !process.env.GOOGLE_CLOUD_PRIVATE_KEY || 
    !process.env.GOOGLE_CLOUD_EMAIL || 
    !process.env.BUCKET_NAME) {
  console.error('Missing required environment variables.');
  process.exit(1);
}

// Initialize App
const app = express();
app.use(express.json());

// Google Cloud Storage Setup
const storage = new Storage({
  projectId: process.env.GOOGLE_CLOUD_PROJECT_ID,
  credentials: {
    type: "service_account",
    project_id: process.env.GOOGLE_CLOUD_PROJECT_ID,
    private_key: process.env.GOOGLE_CLOUD_PRIVATE_KEY.replace(/\\n/g, '\n'),
    client_email: process.env.GOOGLE_CLOUD_EMAIL,
  },
});

const bucketName = process.env.BUCKET_NAME; // Replace with your bucket name
const bucket = storage.bucket(bucketName);

// Transcoder API Client
const transcoderClient = new TranscoderServiceClient({
  credentials: {
    type: "service_account",
    project_id: process.env.GOOGLE_CLOUD_PROJECT_ID,
    private_key: process.env.GOOGLE_CLOUD_PRIVATE_KEY.replace(/\\n/g, '\n'),
    client_email: process.env.GOOGLE_CLOUD_EMAIL,
  },
});

// Multer Setup for File Uploads
const upload = multer({
  dest: 'uploads/', // Temporary folder for storing files
});

// Function to check if the file has audio (e.g., using ffprobe or similar)
const hasAudio = (filePath) => {
  return new Promise((resolve, reject) => {
    const { exec } = require('child_process');
    exec(`ffprobe -v error -select_streams a:0 -show_entries stream=codec_type -of default=noprint_wrappers=1:nokey=1 ${filePath}`, (error, stdout, stderr) => {
      if (error) {
        reject(error);
      }
      resolve(stdout.trim() === 'audio');
    });
  });
};

const getVideoProperties = (filePath) => {
  return new Promise((resolve, reject) => {
    const { exec } = require('child_process');
    exec(
      `ffprobe -v error -select_streams v:0 -show_entries stream=width,height,bit_rate,r_frame_rate -of json ${filePath}`,
      (error, stdout, stderr) => {
        if (error) {
          return reject(error);
        }
        try {
          const metadata = JSON.parse(stdout);
          const videoStream = metadata.streams[0];
          const frameRate = videoStream.r_frame_rate.split('/').map(Number);
          resolve({
            width: videoStream.width,
            height: videoStream.height,
            bitrate: parseInt(videoStream.bit_rate, 10),
            frameRate: frameRate[0] / frameRate[1],
          });
        } catch (err) {
          reject(err);
        }
      }
    );
  });
};

const transcodingConfig = (outputUri, hasAudio, videoProperties) => {
  const { width, height, bitrate, frameRate } = videoProperties;

  const config = {
    elementaryStreams: [
      {
        key: 'video-stream0',
        videoStream: {
          h264: {
            heightPixels: height,
            widthPixels: width,
            bitrateBps: bitrate,
            frameRate: Math.round(frameRate),
          },
        },
      },
    ],
    muxStreams: [
      {
        key: 'output-stream',
        container: 'mp4',
        elementaryStreams: ['video-stream0'],
      },
    ],
  };

  if (hasAudio) {
    config.elementaryStreams.push({
      key: 'audio-stream',
      audioStream: {
        codec: 'aac',
        bitrateBps: 64000,
      },
    });
    config.muxStreams[0].elementaryStreams.push('audio-stream');
  }

  return config;
};

// Upload and Transcode Endpoint
app.post('/upload', upload.single('file'), async (req, res) => {
  try {
    const file = req.file;
    if (!file) {
      return res.status(400).json({ error: 'No file uploaded' });
    }

    const inputUri = `gs://${bucketName}/`;
    const outputUri = `gs://${bucketName}/`;

    // Check if the uploaded video file has audio
    const fileHasAudio = await hasAudio(file.path);

    // Extract video properties
    const videoProperties = await getVideoProperties(file.path);

    // Generate transcoding config based on dynamic properties
    const jobConfig = transcodingConfig(outputUri, fileHasAudio, videoProperties);

    // Upload raw file to GCS
    await bucket.upload(file.path, { destination: file.filename });

    // Transcoding Job
    const location = 'asia-south1';
    const [response] = await transcoderClient.createJob({
      parent: transcoderClient.locationPath(process.env.GOOGLE_CLOUD_PROJECT_ID, location),
      job: {
        inputUri: inputUri,
        outputUri: outputUri,
        config: jobConfig,
      },
    });

    // Cleanup Local File
    fs.unlinkSync(file.path);

    res.status(200).json({
      message: 'File uploaded and transcoding job created',
      jobId: response.name,
      outputUri,
    });
  } catch (error) {
    console.error('Error during transcoding:', error);
    res.status(500).json({ error: 'Internal server error', details: error.message });
  }
});

// Method to get the state of the transcoding job
async function getJobState(jobId) {
  try {
    const projectId = process.env.GOOGLE_CLOUD_PROJECT_ID;
    const location = 'asia-south1'; // Ensure this matches your location

    // Use the transcoder client to get the job state
    const [job] = await transcoderClient.getJob({
      name: transcoderClient.jobPath(projectId, location, jobId),
    });

    // If the job failed, include error details
    if (job.state === 'FAILED') {
      console.error('Transcoding job failed:', job.error);
    }

    return job;
  } catch (error) {
    console.error('Error fetching job state:', error);
    throw new Error('Failed to retrieve job state');
  }
}

// API endpoint to fetch the state of a transcoding job
app.get('/job-state/:jobId', async (req, res) => {
  const { jobId } = req.params;
  
  try {
    // Get the state of the transcoding job
    const job = await getJobState(jobId);

    // Prepare the response
    const response = {
      jobName: job.name,
      state: job.state,
      outputUri: job.outputUri,
      createTime: job.createTime,
      updateTime: job.updateTime,
    };

    // Include error details if the job failed
    if (job.state === 'FAILED') {
      response.errorDetails = job.error;
    }

    // Send the job state in the response
    res.status(200).json(response);
  } catch (error) {
    res.status(500).json({
      error: 'Error retrieving job state',
      details: error.message,
    });
  }
});

// Start the Server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

I was trying to make transcoding job and I sometimes get failure with code 13 and sometimes successful job.

Upvotes: 0

Views: 27

Answers (0)

Related Questions