diff --git a/samples/createJobWithAnimatedOverlay.js b/samples/createJobWithAnimatedOverlay.js new file mode 100644 index 0000000..05411d2 --- /dev/null +++ b/samples/createJobWithAnimatedOverlay.js @@ -0,0 +1,131 @@ +/** + * Copyright 2021, Google, Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +function main(projectId, location, inputUri, overlayImageUri, outputUri) { + // [START transcoder_create_job_with_animated_overlay] + /** + * TODO(developer): Uncomment these variables before running the sample. + */ + // projectId = 'my-project-id'; + // location = 'us-central1'; + // inputUri = 'gs://my-bucket/my-video-file'; + // overlayImageUri = 'gs://my-bucket/my-overlay-image-file'; // Must be a JPEG + // outputUri = 'gs://my-bucket/my-output-folder/'; + + // Imports the Transcoder library + const {TranscoderServiceClient} = require('@google-cloud/video-transcoder'); + + // Instantiates a client + const transcoderServiceClient = new TranscoderServiceClient(); + + async function createJobFromAnimatedOverlay() { + // Construct request + const request = { + parent: transcoderServiceClient.locationPath(projectId, location), + job: { + inputUri: inputUri, + outputUri: outputUri, + config: { + elementaryStreams: [ + { + key: 'video-stream0', + videoStream: { + codec: 'h264', + heightPixels: 360, + widthPixels: 640, + bitrateBps: 550000, + frameRate: 60, + }, + }, + { + key: 'audio-stream0', + audioStream: { + codec: 'aac', + bitrateBps: 64000, + }, + }, + ], + muxStreams: [ + { + key: 'sd', + container: 'mp4', + elementaryStreams: ['video-stream0', 'audio-stream0'], + }, + ], + overlays: [ + { + image: { + uri: overlayImageUri, + resolution: { + x: 0, + y: 0, + }, + alpha: 1.0, + }, + animations: [ + { + animationFade: { + fadeType: 'FADE_IN', + xy: { + x: 0.5, + y: 0.5, + }, + startTimeOffset: { + seconds: 5, + }, + endTimeOffset: { + seconds: 10, + }, + }, + }, + { + animationFade: { + fadeType: 'FADE_OUT', + xy: { + x: 0.5, + y: 0.5, + }, + startTimeOffset: { + seconds: 12, + }, + endTimeOffset: { + seconds: 15, + }, + }, + }, + ], + }, + ], + }, + }, + }; + + // Run request + const [response] = await transcoderServiceClient.createJob(request); + console.log(`Job: ${response.name}`); + } + + createJobFromAnimatedOverlay(); + // [END transcoder_create_job_with_animated_overlay] +} + +// node createJobFromAnimatedOverlay.js +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/samples/createJobWithStaticOverlay.js b/samples/createJobWithStaticOverlay.js new file mode 100644 index 0000000..9a80cc9 --- /dev/null +++ b/samples/createJobWithStaticOverlay.js @@ -0,0 +1,119 @@ +/** + * Copyright 2021, Google, Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +function main(projectId, location, inputUri, overlayImageUri, outputUri) { + // [START transcoder_create_job_with_static_overlay] + /** + * TODO(developer): Uncomment these variables before running the sample. + */ + // projectId = 'my-project-id'; + // location = 'us-central1'; + // inputUri = 'gs://my-bucket/my-video-file'; + // overlayImageUri = 'gs://my-bucket/my-overlay-image-file'; // Must be a JPEG + // outputUri = 'gs://my-bucket/my-output-folder/'; + + // Imports the Transcoder library + const {TranscoderServiceClient} = require('@google-cloud/video-transcoder'); + + // Instantiates a client + const transcoderServiceClient = new TranscoderServiceClient(); + + async function createJobFromStaticOverlay() { + // Construct request + const request = { + parent: transcoderServiceClient.locationPath(projectId, location), + job: { + inputUri: inputUri, + outputUri: outputUri, + config: { + elementaryStreams: [ + { + key: 'video-stream0', + videoStream: { + codec: 'h264', + heightPixels: 360, + widthPixels: 640, + bitrateBps: 550000, + frameRate: 60, + }, + }, + { + key: 'audio-stream0', + audioStream: { + codec: 'aac', + bitrateBps: 64000, + }, + }, + ], + muxStreams: [ + { + key: 'sd', + container: 'mp4', + elementaryStreams: ['video-stream0', 'audio-stream0'], + }, + ], + overlays: [ + { + image: { + uri: overlayImageUri, + resolution: { + x: 1, + y: 0.5, + }, + alpha: 1.0, + }, + animations: [ + { + animationStatic: { + xy: { + x: 0, + y: 0, + }, + startTimeOffset: { + seconds: 0, + }, + }, + }, + { + animationEnd: { + startTimeOffset: { + seconds: 10, + }, + }, + }, + ], + }, + ], + }, + }, + }; + + // Run request + const [response] = await transcoderServiceClient.createJob(request); + console.log(`Job: ${response.name}`); + } + + createJobFromStaticOverlay(); + // [END transcoder_create_job_with_static_overlay] +} + +// node createJobFromStaticOverlay.js +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/samples/test/transcoder.test.js b/samples/test/transcoder.test.js index 69e739a..9d3098d 100644 --- a/samples/test/transcoder.test.js +++ b/samples/test/transcoder.test.js @@ -34,13 +34,19 @@ const preset = 'preset/web-hd'; const templateName = `projects/${projectNumber}/locations/${location}/jobTemplates/${templateId}`; const testFileName = 'ChromeCast.mp4'; +const testOverlayFileName = 'overlay.jpg'; + const inputUri = `gs://${bucketName}/${testFileName}`; +const overlayUri = `gs://${bucketName}/${testOverlayFileName}`; const outputUriForPreset = `gs://${bucketName}/test-output-preset/`; const outputUriForTemplate = `gs://${bucketName}/test-output-template/`; const outputUriForAdHoc = `gs://${bucketName}/test-output-adhoc/`; +const outputUriForStaticOverlay = `gs://${bucketName}/test-output-static-overlay/`; +const outputUriForAnimatedOverlay = `gs://${bucketName}/test-output-animated-overlay/`; const cwd = path.join(__dirname, '..'); -const resourceFile = `testdata/${testFileName}`; +const videoFile = `testdata/${testFileName}`; +const overlayFile = `testdata/${testOverlayFileName}`; function wait(ms) { return new Promise(resolve => { @@ -57,7 +63,8 @@ before(async () => { ); // Create a Cloud Storage bucket to be used for testing. await storage.createBucket(bucketName); - await storage.bucket(bucketName).upload(resourceFile); + await storage.bucket(bucketName).upload(videoFile); + await storage.bucket(bucketName).upload(overlayFile); }); after(async () => { @@ -162,8 +169,6 @@ describe('Job functions preset', () => { `node getJobState.js ${projectId} ${location} ${this.presetJobId}`, {cwd} ); - // TODO(bcoe): remove this debug information once passing: - console.info(output.toString('utf8')); assert.ok(output.includes('Job state: SUCCEEDED')); }); }); @@ -222,8 +227,6 @@ describe('Job functions template', () => { `node getJobState.js ${projectId} ${location} ${this.templateJobId}`, {cwd} ); - // TODO(bcoe): remove this debug information once passing: - console.info(output.toString('utf8')); assert.ok(output.includes('Job state: SUCCEEDED')); }); }); @@ -272,8 +275,102 @@ describe('Job functions adhoc', () => { `node getJobState.js ${projectId} ${location} ${this.adhocJobId}`, {cwd} ); - // TODO(bcoe): remove this debug information once passing: - console.info(output.toString('utf8')); + assert.ok(output.includes('Job state: SUCCEEDED')); + }); +}); + +describe('Job with static overlay functions', () => { + before(function () { + const output = execSync( + `node createJobWithStaticOverlay.js ${projectId} ${location} ${inputUri} ${overlayUri} ${outputUriForStaticOverlay}`, + {cwd} + ); + assert.ok( + output.includes(`projects/${projectNumber}/locations/${location}/jobs/`) + ); + this.staticOverlayJobId = output.toString().split('/').pop(); + }); + + after(function () { + const output = execSync( + `node deleteJob.js ${projectId} ${location} ${this.staticOverlayJobId}`, + {cwd} + ); + assert.ok(output.includes('Deleted job')); + }); + + it('should get a job', function () { + const output = execSync( + `node getJob.js ${projectId} ${location} ${this.staticOverlayJobId}`, + {cwd} + ); + const jobName = `projects/${projectNumber}/locations/${location}/jobs/${this.staticOverlayJobId}`; + assert.ok(output.includes(jobName)); + }); + + it('should show a list of jobs', function () { + const output = execSync(`node listJobs.js ${projectId} ${location}`, { + cwd, + }); + const jobName = `projects/${projectNumber}/locations/${location}/jobs/${this.staticOverlayJobId}`; + assert.ok(output.includes(jobName)); + }); + + it('should check that the job succeeded', async function () { + this.retries(5); + await wait(90000); + const output = execSync( + `node getJobState.js ${projectId} ${location} ${this.staticOverlayJobId}`, + {cwd} + ); + assert.ok(output.includes('Job state: SUCCEEDED')); + }); +}); + +describe('Job with animated overlay functions', () => { + before(function () { + const output = execSync( + `node createJobWithAnimatedOverlay.js ${projectId} ${location} ${inputUri} ${overlayUri} ${outputUriForAnimatedOverlay}`, + {cwd} + ); + assert.ok( + output.includes(`projects/${projectNumber}/locations/${location}/jobs/`) + ); + this.animatedOverlayJobId = output.toString().split('/').pop(); + }); + + after(function () { + const output = execSync( + `node deleteJob.js ${projectId} ${location} ${this.animatedOverlayJobId}`, + {cwd} + ); + assert.ok(output.includes('Deleted job')); + }); + + it('should get a job', function () { + const output = execSync( + `node getJob.js ${projectId} ${location} ${this.animatedOverlayJobId}`, + {cwd} + ); + const jobName = `projects/${projectNumber}/locations/${location}/jobs/${this.animatedOverlayJobId}`; + assert.ok(output.includes(jobName)); + }); + + it('should show a list of jobs', function () { + const output = execSync(`node listJobs.js ${projectId} ${location}`, { + cwd, + }); + const jobName = `projects/${projectNumber}/locations/${location}/jobs/${this.animatedOverlayJobId}`; + assert.ok(output.includes(jobName)); + }); + + it('should check that the job succeeded', async function () { + this.retries(5); + await wait(90000); + const output = execSync( + `node getJobState.js ${projectId} ${location} ${this.animatedOverlayJobId}`, + {cwd} + ); assert.ok(output.includes('Job state: SUCCEEDED')); }); }); diff --git a/samples/testdata/ChromeCast.mp4 b/samples/testdata/ChromeCast.mp4 index f1c799f..8a06ad7 100644 Binary files a/samples/testdata/ChromeCast.mp4 and b/samples/testdata/ChromeCast.mp4 differ diff --git a/samples/testdata/overlay.jpg b/samples/testdata/overlay.jpg new file mode 100644 index 0000000..ded44b4 Binary files /dev/null and b/samples/testdata/overlay.jpg differ