Skip to content

Commit 4c3725a

Browse files
samples: add code sample and test for concatenating two input videos (#133)
* feat: add code sample and test for concatenating two input videos * 🦉 Updates from OwlBot See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md Co-authored-by: Owl Bot <gcf-owl-bot[bot]@users.noreply.github.com>
1 parent 929c973 commit 4c3725a

File tree

4 files changed

+218
-0
lines changed

4 files changed

+218
-0
lines changed
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
/**
2+
* Copyright 2021, Google, Inc.
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
'use strict';
17+
18+
function main(
19+
projectId,
20+
location,
21+
inputUri1,
22+
startTimeOffset1,
23+
endTimeOffset1,
24+
inputUri2,
25+
startTimeOffset2,
26+
endTimeOffset2,
27+
outputUri
28+
) {
29+
// [START transcoder_create_job_with_concatenated_inputs]
30+
/**
31+
* TODO(developer): Uncomment these variables before running the sample.
32+
*/
33+
// projectId = 'my-project-id';
34+
// location = 'us-central1';
35+
// inputUri1 = 'gs://my-bucket/my-video-file1';
36+
// startTimeOffset1 = 0;
37+
// endTimeOffset1 = 8.1;
38+
// inputUri2 = 'gs://my-bucket/my-video-file2';
39+
// startTimeOffset2 = 3.5;
40+
// endTimeOffset2 = 15;
41+
// outputUri = 'gs://my-bucket/my-output-folder/';
42+
43+
function calcOffsetNanoSec(offsetValueFractionalSecs) {
44+
if (offsetValueFractionalSecs.toString().indexOf('.') !== -1) {
45+
return (
46+
1000000000 *
47+
Number('.' + offsetValueFractionalSecs.toString().split('.')[1])
48+
);
49+
}
50+
return 0;
51+
}
52+
const startTimeOffset1Sec = Math.trunc(startTimeOffset1);
53+
const startTimeOffset1NanoSec = calcOffsetNanoSec(startTimeOffset1);
54+
const endTimeOffset1Sec = Math.trunc(endTimeOffset1);
55+
const endTimeOffset1NanoSec = calcOffsetNanoSec(endTimeOffset1);
56+
57+
const startTimeOffset2Sec = Math.trunc(startTimeOffset2);
58+
const startTimeOffset2NanoSec = calcOffsetNanoSec(startTimeOffset2);
59+
const endTimeOffset2Sec = Math.trunc(endTimeOffset2);
60+
const endTimeOffset2NanoSec = calcOffsetNanoSec(endTimeOffset2);
61+
62+
// Imports the Transcoder library
63+
const {TranscoderServiceClient} =
64+
require('@google-cloud/video-transcoder').v1;
65+
66+
// Instantiates a client
67+
const transcoderServiceClient = new TranscoderServiceClient();
68+
69+
async function createJobWithConcatenatedInputs() {
70+
// Construct request
71+
const request = {
72+
parent: transcoderServiceClient.locationPath(projectId, location),
73+
job: {
74+
outputUri: outputUri,
75+
config: {
76+
inputs: [
77+
{
78+
key: 'input1',
79+
uri: inputUri1,
80+
},
81+
{
82+
key: 'input2',
83+
uri: inputUri2,
84+
},
85+
],
86+
editList: [
87+
{
88+
key: 'atom1',
89+
inputs: ['input1'],
90+
startTimeOffset: {
91+
seconds: startTimeOffset1Sec,
92+
nanos: startTimeOffset1NanoSec,
93+
},
94+
endTimeOffset: {
95+
seconds: endTimeOffset1Sec,
96+
nanos: endTimeOffset1NanoSec,
97+
},
98+
},
99+
{
100+
key: 'atom2',
101+
inputs: ['input2'],
102+
startTimeOffset: {
103+
seconds: startTimeOffset2Sec,
104+
nanos: startTimeOffset2NanoSec,
105+
},
106+
endTimeOffset: {
107+
seconds: endTimeOffset2Sec,
108+
nanos: endTimeOffset2NanoSec,
109+
},
110+
},
111+
],
112+
113+
elementaryStreams: [
114+
{
115+
key: 'video-stream0',
116+
videoStream: {
117+
h264: {
118+
heightPixels: 360,
119+
widthPixels: 640,
120+
bitrateBps: 550000,
121+
frameRate: 60,
122+
},
123+
},
124+
},
125+
{
126+
key: 'audio-stream0',
127+
audioStream: {
128+
codec: 'aac',
129+
bitrateBps: 64000,
130+
},
131+
},
132+
],
133+
muxStreams: [
134+
{
135+
key: 'sd',
136+
container: 'mp4',
137+
elementaryStreams: ['video-stream0', 'audio-stream0'],
138+
},
139+
],
140+
},
141+
},
142+
};
143+
144+
// Run request
145+
const [response] = await transcoderServiceClient.createJob(request);
146+
console.log(`Job: ${response.name}`);
147+
}
148+
149+
createJobWithConcatenatedInputs();
150+
// [END transcoder_create_job_with_concatenated_inputs]
151+
}
152+
153+
// node createJobFromStaticOverlay.js <projectId> <location> <inputUri1> <startTimeOffset1> <endTimeOffset1> <inputUri2> <startTimeOffset2> <endTimeOffset2> <outputUri>
154+
process.on('unhandledRejection', err => {
155+
console.error(err.message);
156+
process.exitCode = 1;
157+
});
158+
main(...process.argv.slice(2));

media/transcoder/test/transcoder.test.js

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,13 @@ const templateName = `projects/${projectNumber}/locations/${location}/jobTemplat
3535

3636
const testFileName = 'ChromeCast.mp4';
3737
const testOverlayFileName = 'overlay.jpg';
38+
const testConcat1FileName = 'ForBiggerEscapes.mp4';
39+
const testConcat2FileName = 'ForBiggerJoyrides.mp4';
3840

3941
const inputUri = `gs://${bucketName}/${testFileName}`;
4042
const overlayUri = `gs://${bucketName}/${testOverlayFileName}`;
43+
const concat1Uri = `gs://${bucketName}/${testConcat1FileName}`;
44+
const concat2Uri = `gs://${bucketName}/${testConcat2FileName}`;
4145
const outputUriForPreset = `gs://${bucketName}/test-output-preset/`;
4246
const outputUriForTemplate = `gs://${bucketName}/test-output-template/`;
4347
const outputUriForAdHoc = `gs://${bucketName}/test-output-adhoc/`;
@@ -53,10 +57,13 @@ const outputUriForPeriodicImagesSpritesheet = `gs://${bucketName}/${outputDirFor
5357
// Spritesheets use the following file naming conventions:
5458
const smallSpriteSheetFileName = 'small-sprite-sheet0000000000.jpeg';
5559
const largeSpriteSheetFileName = 'large-sprite-sheet0000000000.jpeg';
60+
const outputUriForConcatenated = `gs://${bucketName}/test-output-concat/`;
5661

5762
const cwd = path.join(__dirname, '..');
5863
const videoFile = `testdata/${testFileName}`;
5964
const overlayFile = `testdata/${testOverlayFileName}`;
65+
const concat1File = `testdata/${testConcat1FileName}`;
66+
const concat2File = `testdata/${testConcat2FileName}`;
6067

6168
const delay = async (test, addMs) => {
6269
const retries = test.currentRetry();
@@ -98,6 +105,8 @@ before(async () => {
98105
await storage.createBucket(bucketName);
99106
await storage.bucket(bucketName).upload(videoFile);
100107
await storage.bucket(bucketName).upload(overlayFile);
108+
await storage.bucket(bucketName).upload(concat1File);
109+
await storage.bucket(bucketName).upload(concat2File);
101110
});
102111

103112
after(async () => {
@@ -591,3 +600,54 @@ describe('Job with periodic images spritesheet', () => {
591600
);
592601
});
593602
});
603+
604+
describe('Job with concatenated inputs functions', () => {
605+
before(function () {
606+
const output = execSync(
607+
`node createJobWithConcatenatedInputs.js ${projectId} ${location} ${concat1Uri} 0 8.1 ${concat2Uri} 3.5 15 ${outputUriForConcatenated}`,
608+
{cwd}
609+
);
610+
assert.ok(
611+
output.includes(`projects/${projectNumber}/locations/${location}/jobs/`)
612+
);
613+
this.concatenatedJobId = output.toString().split('/').pop();
614+
});
615+
616+
after(function () {
617+
const output = execSync(
618+
`node deleteJob.js ${projectId} ${location} ${this.concatenatedJobId}`,
619+
{cwd}
620+
);
621+
assert.ok(output.includes('Deleted job'));
622+
});
623+
624+
it('should get a job', function () {
625+
const output = execSync(
626+
`node getJob.js ${projectId} ${location} ${this.concatenatedJobId}`,
627+
{cwd}
628+
);
629+
const jobName = `projects/${projectNumber}/locations/${location}/jobs/${this.concatenatedJobId}`;
630+
assert.ok(output.includes(jobName));
631+
});
632+
633+
it('should check that the job succeeded', async function () {
634+
this.retries(5);
635+
await delay(this.test, 30000);
636+
637+
let getAttempts = 0;
638+
while (getAttempts < 5) {
639+
const ms = Math.pow(2, getAttempts + 1) * 10000 + Math.random() * 1000;
640+
await wait(ms);
641+
const output = execSync(
642+
`node getJobState.js ${projectId} ${location} ${this.concatenatedJobId}`,
643+
{cwd}
644+
);
645+
if (output.includes('Job state: SUCCEEDED')) {
646+
assert.ok(true);
647+
return;
648+
}
649+
getAttempts++;
650+
}
651+
assert.ok(false);
652+
});
653+
});
2.19 MB
Binary file not shown.
2.26 MB
Binary file not shown.

0 commit comments

Comments
 (0)