@pbvision/partner-sdk 0.1.4 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -2
- package/package.json +1 -1
- package/src/index.js +30 -25
- package/src/video-object-name.js +0 -68
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ standard length game, so we'll notify your servers when the results are ready.
|
|
|
10
10
|
- [Using the API](#using-the-api)
|
|
11
11
|
- [Webhook Setup](#webhook-setup)
|
|
12
12
|
- [Send Videos](#send-videos)
|
|
13
|
-
- [Option 1: PBV
|
|
13
|
+
- [Option 1: PBV downloads your video from a URL](#option-1-pbv-downloads-your-video-from-a-url)
|
|
14
14
|
- [Option 2: Upload your video](#option-2-upload-your-video)
|
|
15
15
|
- [After Video Processing is Done](#after-video-processing-is-done)
|
|
16
16
|
- [Video Guidelines](#video-guidelines)
|
|
@@ -108,7 +108,7 @@ import { PBVision } from '@pbvision/partner-sdk';
|
|
|
108
108
|
const pbv = new PBVision(YOUR_API_KEY, { useProdServer: false });
|
|
109
109
|
// you can omit this metadata, or provide some or all of this object—whatever you'd like!
|
|
110
110
|
const optionalMetadata = {
|
|
111
|
-
userEmails: [],
|
|
111
|
+
userEmails: [], // passthrough partners must provide at least one email here
|
|
112
112
|
name: 'Dink Championship 2024',
|
|
113
113
|
desc: 'A longer description, if you want',
|
|
114
114
|
gameStartEpoch: 1711393200, // when the game was played
|
|
@@ -136,6 +136,8 @@ data is enabled for your API key, only some of these fields may be present):
|
|
|
136
136
|
"cutVideo": "https://storage.googleapis.com/pbv-pro/xyz/f50272db-69a8-49ed-9d92-3a4d067af87c/rallies.mp4",
|
|
137
137
|
"insights": INSIGHTS_DATA,
|
|
138
138
|
"stats": STATS_DATA,
|
|
139
|
+
"vid": STRING,
|
|
140
|
+
"aiEngineVersion": INT,
|
|
139
141
|
"error": {
|
|
140
142
|
"reason": "some explanation here..."
|
|
141
143
|
}
|
|
@@ -152,6 +154,10 @@ data is enabled for your API key, only some of these fields may be present):
|
|
|
152
154
|
- Explore the _stats_ schema at <https://pbv-public.github.io/stats?s=~stats~game>
|
|
153
155
|
- Schema changes and diffs are in our [`pbv-public/stats` repo](https://github.com/pbv-public/stats/blob/dev/CHANGELOG.md)
|
|
154
156
|
- `cutVideo` contains a link to the rally-sliced (dead time removed) .mp4.
|
|
157
|
+
- only included if `insights` is included:
|
|
158
|
+
- `vid` - the unique ID of the video in our system
|
|
159
|
+
- `aiEngineVersion` - the version number of our AI used to process the video
|
|
160
|
+
- Using these two values, you can retrieve the player thumbnail images extracted from the video like: `https://storage.googleapis.com/pbv-pro/${vid}/${aiEngineVersion}/player${playerIndex}-${imageIndex}.jpg` where `playerIndex` is in the range [0, 3] (for doubles games) and `imageIndex` is in the range [0, 7].
|
|
155
161
|
|
|
156
162
|
## Video Guidelines
|
|
157
163
|
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import assert from 'node:assert'
|
|
2
2
|
import fs from 'node:fs'
|
|
3
3
|
|
|
4
|
-
import { makeSignedGoogleCloudStorageVideoObjectName } from './video-object-name.js'
|
|
5
4
|
import fetch from 'node-fetch'
|
|
6
5
|
|
|
7
6
|
const ENVIRONMENTS = {
|
|
@@ -26,10 +25,11 @@ const ENVIRONMENTS = {
|
|
|
26
25
|
/** @public */
|
|
27
26
|
export class PBVision {
|
|
28
27
|
constructor (apiKey, { useProdServer = false } = {}) {
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
const underscoreIndex = apiKey.lastIndexOf('_')
|
|
29
|
+
assert(apiKey && underscoreIndex !== -1, `invalid API key: ${apiKey}`)
|
|
31
30
|
|
|
32
31
|
this.apiKey = apiKey
|
|
32
|
+
this.uid = apiKey.substring(0, underscoreIndex)
|
|
33
33
|
const config = useProdServer ? ENVIRONMENTS.prod : ENVIRONMENTS.test
|
|
34
34
|
this.server = config.apiServer
|
|
35
35
|
this.isDev = config === ENVIRONMENTS.test
|
|
@@ -47,20 +47,29 @@ export class PBVision {
|
|
|
47
47
|
return this.__callAPI('webhook/set', { url: webhookUrl })
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
/**
|
|
51
|
+
* @typedef {Object} VideoUrlToDownloadResponse
|
|
52
|
+
* @property {string} vid the ID of the new video
|
|
53
|
+
* @property {boolean} [hasCredits] for passthrough partners, this field will
|
|
54
|
+
* be present and indicate whether the first user has any credits available
|
|
55
|
+
*/
|
|
56
|
+
|
|
50
57
|
/**
|
|
51
58
|
* Tells PB Vision to download the specified video and process it. When
|
|
52
59
|
* processing is complete, your webhook URL will receive a callback.
|
|
53
60
|
*
|
|
54
61
|
* @param {string} videoUrl the publicly available URL of the video
|
|
55
62
|
* @param {VideoMetadata} [metadata]
|
|
63
|
+
* @returns {VideoUrlToDownloadResponse}
|
|
56
64
|
*/
|
|
57
65
|
async sendVideoUrlToDownload (videoUrl, { userEmails = [], name, desc, gameStartEpoch } = {}) {
|
|
58
66
|
assert(typeof videoUrl === 'string' && videoUrl.startsWith('http'),
|
|
59
67
|
'URL must be a string beginning with http')
|
|
60
68
|
assert(videoUrl.split('?')[0].endsWith('.mp4'), 'video URL must have the .mp4 extension')
|
|
61
|
-
this.__callAPI(
|
|
69
|
+
const resp = await this.__callAPI(
|
|
62
70
|
'add_video_by_url',
|
|
63
71
|
{ url: videoUrl, userEmails, name, desc, gameStartEpoch })
|
|
72
|
+
return JSON.parse(resp)
|
|
64
73
|
}
|
|
65
74
|
|
|
66
75
|
async __callAPI (path, body) {
|
|
@@ -95,34 +104,30 @@ export class PBVision {
|
|
|
95
104
|
|
|
96
105
|
/**
|
|
97
106
|
* Upload a video for processing by the AI.
|
|
107
|
+
*
|
|
108
|
+
* For passthrough partners, the video is only uploaded if the paying user
|
|
109
|
+
* has credit(s) available with which the video can be analyzed.
|
|
110
|
+
*
|
|
98
111
|
* @param {string} mp4Filename
|
|
99
112
|
* @param {VideoMetadata} [metadata]
|
|
100
|
-
* @returns {
|
|
101
|
-
* uploaded to
|
|
113
|
+
* @returns {VideoUrlToDownloadResponse}
|
|
102
114
|
*/
|
|
103
115
|
async uploadVideo (mp4Filename, { userEmails = [], name, desc, gameStartEpoch } = {}) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
116
|
+
const pieces = mp4Filename.split('.')
|
|
117
|
+
const ext = pieces[pieces.length - 1]
|
|
118
|
+
const makeVIDResp = await this.__callAPI('make_video_id', { userEmails, name, desc, gameStartEpoch, fileExt: ext })
|
|
119
|
+
const { hasCredits, vid } = JSON.parse(makeVIDResp)
|
|
120
|
+
if (hasCredits === false) {
|
|
121
|
+
return { hasCredits }
|
|
107
122
|
}
|
|
108
|
-
|
|
109
|
-
// create a unique, signed upload name
|
|
110
|
-
const uid = this.apiKey.split('_')[0]
|
|
111
123
|
const bucket = `pbv-uploads${this.isDev ? '-dev' : ''}`
|
|
112
|
-
const objName =
|
|
113
|
-
uploaderUID: uid,
|
|
114
|
-
signingKey: this.signingKey,
|
|
115
|
-
bucket
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
// if given extra information about this video, upload that first
|
|
119
|
-
if (userEmails.length || name || desc || gameStartEpoch) {
|
|
120
|
-
const body = { objName, userEmails, name, desc, gameStartEpoch }
|
|
121
|
-
await this.__callAPI('set_pre_upload_metadata', body)
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
+
const objName = `${this.uid}/${vid}.${ext}`
|
|
124
125
|
await uploadToGCS(bucket, objName, mp4Filename)
|
|
125
|
-
|
|
126
|
+
const ret = { vid }
|
|
127
|
+
if (hasCredits !== undefined) {
|
|
128
|
+
ret.hasCredits = hasCredits
|
|
129
|
+
}
|
|
130
|
+
return ret
|
|
126
131
|
}
|
|
127
132
|
}
|
|
128
133
|
|
package/src/video-object-name.js
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import assert from 'node:assert'
|
|
2
|
-
import crypto from 'node:crypto'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Creates the name for a video file to upload to our uploads bucket.
|
|
6
|
-
*
|
|
7
|
-
* The name is signed with the uploader's API Key to prove that the upload is
|
|
8
|
-
* indeed from them.
|
|
9
|
-
*
|
|
10
|
-
* The object name is formatted like:
|
|
11
|
-
* <User ID>/<Video ID>-<Security Hash>.<File Extension>
|
|
12
|
-
*
|
|
13
|
-
* @param {Object} opts information about the video upload
|
|
14
|
-
* @param {string} opts.uploaderUID the video uploader's user ID
|
|
15
|
-
* @param {string} opts.signingKey the video uploader's API Key
|
|
16
|
-
* @param {string} [opts.fileExt] the video's file extension
|
|
17
|
-
* @param {string} [opts.bucket] the GCS bucket to upload to
|
|
18
|
-
* @returns {string} the signed GCS object filename
|
|
19
|
-
* @protected
|
|
20
|
-
*/
|
|
21
|
-
export function makeSignedGoogleCloudStorageVideoObjectName ({
|
|
22
|
-
uploaderUID,
|
|
23
|
-
signingKey,
|
|
24
|
-
fileExt = 'mp4',
|
|
25
|
-
bucket = 'pbv-uploads',
|
|
26
|
-
uploadId = undefined
|
|
27
|
-
}) {
|
|
28
|
-
assert(typeof uploaderUID === 'string' && uploaderUID.length)
|
|
29
|
-
assert(typeof signingKey === 'string' && signingKey.length)
|
|
30
|
-
uploadId = uploadId ?? crypto.randomUUID().replace(/-/g, '')
|
|
31
|
-
assert(typeof uploadId === 'string' && uploadId.length === 32)
|
|
32
|
-
return makeObjectName(uploaderUID, signingKey, uploadId, fileExt, bucket)
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Returns the hash from the parameters used to construct a GCS object name.
|
|
37
|
-
*
|
|
38
|
-
* The hash includes all elements of the file's unique path (including the
|
|
39
|
-
* bucket) except for the hash itself.
|
|
40
|
-
* @private
|
|
41
|
-
*/
|
|
42
|
-
function getHash (uploaderUID, signingKey, uploadId, fileExt, bucket) {
|
|
43
|
-
const nameWithEmptyHash = makeObjectNameGivenHash(
|
|
44
|
-
uploaderUID, uploadId, fileExt, '')
|
|
45
|
-
return crypto.createHash('sha256')
|
|
46
|
-
.update(signingKey + bucket + nameWithEmptyHash)
|
|
47
|
-
.digest('hex')
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Returns the full GCS object name (including hash) for the given parameters.
|
|
52
|
-
* @private
|
|
53
|
-
*/
|
|
54
|
-
function makeObjectName (uploaderUID, signingKey, uploadId, fileExt, bucket) {
|
|
55
|
-
const hash = getHash(uploaderUID, signingKey, uploadId, fileExt, bucket)
|
|
56
|
-
const nameWithHash = makeObjectNameGivenHash(
|
|
57
|
-
uploaderUID, uploadId, fileExt, hash)
|
|
58
|
-
return nameWithHash
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Returns the full GCS object name using the specified hash string.
|
|
63
|
-
* @private
|
|
64
|
-
*/
|
|
65
|
-
function makeObjectNameGivenHash (uploaderUID, uploadId, fileExt, hash) {
|
|
66
|
-
assert(uploaderUID.indexOf('/') === -1)
|
|
67
|
-
return `${uploaderUID}/${uploadId}-${hash}.${fileExt}`
|
|
68
|
-
}
|