@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 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 Downloads your video from a URL](#option-1-pbv-downloads-your-video-from-a-url)
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pbvision/partner-sdk",
3
3
  "description": "SDK for interacting with the PB Vision pickleball AI engine.",
4
- "version": "0.1.4",
4
+ "version": "0.1.5",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "engines": {
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
- assert(apiKey && apiKey.indexOf('_') !== -1,
30
- `invalid API key: ${apiKey}`)
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 {string} the full Google Cloud Storage filename which this was
101
- * uploaded to
113
+ * @returns {VideoUrlToDownloadResponse}
102
114
  */
103
115
  async uploadVideo (mp4Filename, { userEmails = [], name, desc, gameStartEpoch } = {}) {
104
- // get the upload signing key if we didn't get it earlier
105
- if (!this.signingKey) {
106
- this.signingKey = await this.__callAPI('get_signing_key')
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 = makeSignedGoogleCloudStorageVideoObjectName({
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
- return objName
126
+ const ret = { vid }
127
+ if (hasCredits !== undefined) {
128
+ ret.hasCredits = hasCredits
129
+ }
130
+ return ret
126
131
  }
127
132
  }
128
133
 
@@ -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
- }