diff --git a/src/OpenTok/Archive.php b/src/OpenTok/Archive.php
index c40423e..7e07a47 100644
--- a/src/OpenTok/Archive.php
+++ b/src/OpenTok/Archive.php
@@ -92,6 +92,13 @@
* "available"; for other archives, (including archives with the status "uploaded") this property is
* set to null. The download URL is obfuscated, and the file is only available from the URL for
* 10 minutes. To generate a new URL, call the Archive.listArchives() or OpenTok.getArchive() method.
+*
+* @property bool $hasTranscription
+* Whether the archive has a transcription of the audio of the session (true) or not (false).
+*
+* @property array $transcription
+* Properties of the transcription attached to this archive, including status, url, reason,
+* primaryLanguageCode, and hasSummary.
*/
class Archive
{
@@ -184,6 +191,8 @@ public function __get($name)
case 'streamMode':
case 'maxBitrate':
case 'quantizationParameter':
+ case 'hasTranscription':
+ case 'transcription':
return $this->data[$name];
case 'multiArchiveTag':
return $this->multiArchiveTag;
diff --git a/src/OpenTok/OpenTok.php b/src/OpenTok/OpenTok.php
index 8363d38..7db1b78 100644
--- a/src/OpenTok/OpenTok.php
+++ b/src/OpenTok/OpenTok.php
@@ -546,6 +546,19 @@ public function getRender($renderId): Render
* (HD portrait), or "1080x1920" (FHD portrait). This property only applies to composed archives. If you set
* this property and set the outputMode property to "individual", a call to the method
* results in an error.
+ *
+ *
'hasTranscription' (Boolean) — Whether the archive will have a transcription of the audio
+ * of the session (true) or not (false, the default).
+ *
+ * 'transcriptionProperties' (Array) — An array defining transcription properties. This array
+ * includes the following keys:
+ *
+ * 'primaryLanguageCode' (String) — The primary language spoken in the archive to be
+ * transcribed, in BCP-47 format (e.g., "en-US", "es-ES", "pt-BR").
+ * 'hasSummary' (Boolean) — Whether the transcription should include a summary of the
+ * session (true) or not (false, the default).
+ *
+ *
*
*
* @return Archive The Archive object, which includes properties defining the archive, including
@@ -570,6 +583,8 @@ public function startArchive(string $sessionId, $options = []): Archive
'outputMode' => OutputMode::COMPOSED,
'resolution' => null,
'streamMode' => StreamMode::AUTO,
+ 'hasTranscription' => false,
+ 'transcriptionProperties' => null,
);
// Horrible hack to workaround the defaults behaviour
@@ -577,9 +592,28 @@ public function startArchive(string $sessionId, $options = []): Archive
$maxBitrate = $options['maxBitrate'];
}
+ // Preserve transcription fields from user input
+ $hasTranscription = isset($options['hasTranscription']) ? $options['hasTranscription'] : false;
+ $transcriptionProperties = isset($options['transcriptionProperties']) ? $options['transcriptionProperties'] : null;
+
$options = array_merge($defaults, array_intersect_key($options, $defaults));
list($name, $hasVideo, $hasAudio, $outputMode, $resolution, $streamMode) = array_values($options);
+ // Re-add transcription options to options array for API call
+ $options['hasTranscription'] = $hasTranscription;
+ if ($hasTranscription && $transcriptionProperties !== null) {
+ $options['transcriptionProperties'] = $transcriptionProperties;
+ }
+
+ if (isset($maxBitrate)) {
+ $options['maxBitrate'] = $maxBitrate;
+ }
+
+ // Remove null values before sending to API
+ $options = array_filter($options, function($value) {
+ return $value !== null;
+ });
+
if (isset($maxBitrate)) {
$options['maxBitrate'] = $maxBitrate;
}
@@ -595,6 +629,8 @@ public function startArchive(string $sessionId, $options = []): Archive
Validators::validateArchiveHasAudio($hasAudio);
Validators::validateArchiveOutputMode($outputMode);
Validators::validateHasStreamMode($streamMode);
+ Validators::validateArchiveHasTranscription($hasTranscription);
+ Validators::validateArchiveTranscriptionProperties($transcriptionProperties);
if ((is_null($resolution) || empty($resolution)) && $outputMode === OutputMode::COMPOSED) {
$options['resolution'] = "640x480";
diff --git a/src/OpenTok/Util/Validators.php b/src/OpenTok/Util/Validators.php
index e8b0bde..bb77f57 100755
--- a/src/OpenTok/Util/Validators.php
+++ b/src/OpenTok/Util/Validators.php
@@ -525,4 +525,43 @@ public static function validateBroadcastBitrate($maxBitRate): void
throw new \OutOfBoundsException('Max Bitrate must be between 400000 and 2000000');
}
}
+
+ public static function validateArchiveHasTranscription($hasTranscription)
+ {
+ if (!is_bool($hasTranscription)) {
+ throw new InvalidArgumentException(
+ 'hasTranscription must be either true or false.'
+ );
+ }
+ }
+
+ public static function validateArchiveTranscriptionProperties($transcriptionProperties)
+ {
+ if ($transcriptionProperties === null) {
+ return;
+ }
+
+ if (!is_array($transcriptionProperties)) {
+ throw new InvalidArgumentException(
+ 'transcriptionProperties must be an array.'
+ );
+ }
+
+ if (isset($transcriptionProperties['primaryLanguageCode'])) {
+ if (!is_string($transcriptionProperties['primaryLanguageCode']) ||
+ empty($transcriptionProperties['primaryLanguageCode'])) {
+ throw new InvalidArgumentException(
+ 'primaryLanguageCode must be a non-empty string in BCP-47 format.'
+ );
+ }
+ }
+
+ if (isset($transcriptionProperties['hasSummary'])) {
+ if (!is_bool($transcriptionProperties['hasSummary'])) {
+ throw new InvalidArgumentException(
+ 'hasSummary must be either true or false.'
+ );
+ }
+ }
+ }
}
diff --git a/tests/OpenTokTest/OpenTokTest.php b/tests/OpenTokTest/OpenTokTest.php
index f167505..b74a4ef 100644
--- a/tests/OpenTokTest/OpenTokTest.php
+++ b/tests/OpenTokTest/OpenTokTest.php
@@ -1617,6 +1617,207 @@ public function testGetsExpiredArchive(): void
$this->assertEquals("expired", $archive->status);
}
+ public function testStartsArchiveWithTranscription(): void
+ {
+ // Arrange
+ $this->setupOTWithMocks([[
+ 'code' => 200,
+ 'headers' => [
+ 'Content-Type' => 'application/json'
+ ],
+ 'path' => 'v2/project/APIKEY/archive/session_hasTranscription-true'
+ ]]);
+
+ // This sessionId was generated using a different apiKey, but this method doesn't do any
+ // decoding to check, so it's fine.
+ $sessionId = '2_MX44NTQ1MTF-flR1ZSBOb3YgMTIgMDk6NDA6NTkgUFNUIDIwMTN-MC43NjU0Nzh-';
+
+ // Act
+ $archive = $this->opentok->startArchive($sessionId, [
+ 'hasTranscription' => true,
+ 'transcriptionProperties' => [
+ 'primaryLanguageCode' => 'en-US',
+ 'hasSummary' => false
+ ]
+ ]);
+
+ // Assert
+ $this->assertCount(1, $this->historyContainer);
+
+ $request = $this->historyContainer[0]['request'];
+ $this->assertEquals('POST', strtoupper($request->getMethod()));
+ $this->assertEquals('/v2/project/' . $this->API_KEY . '/archive', $request->getUri()->getPath());
+ $this->assertEquals('api.opentok.com', $request->getUri()->getHost());
+ $this->assertEquals('https', $request->getUri()->getScheme());
+
+ $contentType = $request->getHeaderLine('Content-Type');
+ $this->assertNotEmpty($contentType);
+ $this->assertEquals('application/json', $contentType);
+
+ $authString = $request->getHeaderLine('X-OPENTOK-AUTH');
+ $this->assertEquals(true, TestHelpers::validateOpenTokAuthHeader($this->API_KEY, $this->API_SECRET, $authString));
+
+ // Test request body contains transcription fields
+ $body = json_decode($request->getBody());
+ $this->assertEquals(true, $body->hasTranscription);
+ $this->assertEquals('en-US', $body->transcriptionProperties->primaryLanguageCode);
+ $this->assertEquals(false, $body->transcriptionProperties->hasSummary);
+
+ // Test response properties
+ $this->assertInstanceOf('OpenTok\Archive', $archive);
+ $this->assertEquals(true, $archive->hasTranscription);
+ $this->assertIsArray($archive->transcription);
+ $this->assertEquals('requested', $archive->transcription['status']);
+ $this->assertEquals('en-US', $archive->transcription['primaryLanguageCode']);
+ $this->assertEquals(false, $archive->transcription['hasSummary']);
+ }
+
+ public function testStartsArchiveWithTranscriptionAndSummary(): void
+ {
+ // Arrange
+ $this->setupOTWithMocks([[
+ 'code' => 200,
+ 'headers' => [
+ 'Content-Type' => 'application/json'
+ ],
+ 'path' => 'v2/project/APIKEY/archive/session_transcription-with-summary'
+ ]]);
+
+ $sessionId = '2_MX44NTQ1MTF-flR1ZSBOb3YgMTIgMDk6NDA6NTkgUFNUIDIwMTN-MC43NjU0Nzh-';
+
+ // Act
+ $archive = $this->opentok->startArchive($sessionId, [
+ 'hasTranscription' => true,
+ 'transcriptionProperties' => [
+ 'primaryLanguageCode' => 'es-ES',
+ 'hasSummary' => true
+ ]
+ ]);
+
+ // Assert
+ $this->assertCount(1, $this->historyContainer);
+
+ $request = $this->historyContainer[0]['request'];
+
+ // Test request body contains transcription fields
+ $body = json_decode($request->getBody());
+ $this->assertEquals(true, $body->hasTranscription);
+ $this->assertEquals('es-ES', $body->transcriptionProperties->primaryLanguageCode);
+ $this->assertEquals(true, $body->transcriptionProperties->hasSummary);
+
+ // Test response properties
+ $this->assertInstanceOf('OpenTok\Archive', $archive);
+ $this->assertEquals(true, $archive->hasTranscription);
+ $this->assertIsArray($archive->transcription);
+ $this->assertEquals('requested', $archive->transcription['status']);
+ $this->assertEquals('es-ES', $archive->transcription['primaryLanguageCode']);
+ $this->assertEquals(true, $archive->transcription['hasSummary']);
+ }
+
+ public function testStartsArchiveWithoutTranscription(): void
+ {
+ // Arrange
+ $this->setupOTWithMocks([[
+ 'code' => 200,
+ 'headers' => [
+ 'Content-Type' => 'application/json'
+ ],
+ 'path' => 'v2/project/APIKEY/archive/session'
+ ]]);
+
+ $sessionId = '2_MX44NTQ1MTF-flR1ZSBOb3YgMTIgMDk6NDA6NTkgUFNUIDIwMTN-MC43NjU0Nzh-';
+
+ // Act - explicitly set hasTranscription to false
+ $archive = $this->opentok->startArchive($sessionId, [
+ 'hasTranscription' => false
+ ]);
+
+ // Assert
+ $this->assertCount(1, $this->historyContainer);
+
+ $request = $this->historyContainer[0]['request'];
+
+ // Test request body
+ $body = json_decode($request->getBody());
+ $this->assertEquals(false, $body->hasTranscription);
+ $this->assertObjectNotHasAttribute('transcriptionProperties', $body);
+
+ // Test response properties (archive without transcription)
+ $this->assertInstanceOf('OpenTok\Archive', $archive);
+ // The hasTranscription property should not be set in response if transcription is disabled
+ }
+
+ public function testCannotStartArchiveWithInvalidTranscriptionProperties(): void
+ {
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage('transcriptionProperties must be an array.');
+
+ // Set up the OpenTok instance first
+ $this->setupOTWithMocks([]);
+
+ $sessionId = '2_MX44NTQ1MTF-flR1ZSBOb3YgMTIgMDk6NDA6NTkgUFNUIDIwMTN-MC43NjU0Nzh-';
+
+ // Act - pass invalid transcriptionProperties (not an array)
+ $this->opentok->startArchive($sessionId, [
+ 'hasTranscription' => true,
+ 'transcriptionProperties' => 'invalid'
+ ]);
+ }
+
+ public function testCannotStartArchiveWithInvalidHasTranscription(): void
+ {
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage('hasTranscription must be either true or false.');
+
+ // Set up the OpenTok instance first
+ $this->setupOTWithMocks([]);
+
+ $sessionId = '2_MX44NTQ1MTF-flR1ZSBOb3YgMTIgMDk6NDA6NTkgUFNUIDIwMTN-MC43NjU0Nzh-';
+
+ // Act - pass invalid hasTranscription (not a boolean)
+ $this->opentok->startArchive($sessionId, [
+ 'hasTranscription' => 'invalid'
+ ]);
+ }
+
+ public function testCannotStartArchiveWithInvalidPrimaryLanguageCode(): void
+ {
+ $this->setupOT();
+
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage('primaryLanguageCode must be a non-empty string in BCP-47 format.');
+
+ $sessionId = '2_MX44NTQ1MTF-flR1ZSBOb3YgMTIgMDk6NDA6NTkgUFNUIDIwMTN-MC43NjU0Nzh-';
+
+ // Act - pass empty primaryLanguageCode
+ $this->opentok->startArchive($sessionId, [
+ 'hasTranscription' => true,
+ 'transcriptionProperties' => [
+ 'primaryLanguageCode' => '',
+ 'hasSummary' => false
+ ]
+ ]);
+ }
+
+ public function testCannotStartArchiveWithInvalidHasSummary(): void
+ {
+ $this->setupOT();
+
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage('hasSummary must be either true or false.');
+
+ $sessionId = '2_MX44NTQ1MTF-flR1ZSBOb3YgMTIgMDk6NDA6NTkgUFNUIDIwMTN-MC43NjU0Nzh-';
+
+ // Act - pass invalid hasSummary (not a boolean)
+ $this->opentok->startArchive($sessionId, [
+ 'hasTranscription' => true,
+ 'transcriptionProperties' => [
+ 'primaryLanguageCode' => 'en-US',
+ 'hasSummary' => 'invalid'
+ ]
+ ]);
+ }
+
public function testForceDisconnect(): void
{
// Arrange
diff --git a/tests/mock/v2/project/APIKEY/archive/session_hasTranscription-true b/tests/mock/v2/project/APIKEY/archive/session_hasTranscription-true
new file mode 100644
index 0000000..16a1c4e
--- /dev/null
+++ b/tests/mock/v2/project/APIKEY/archive/session_hasTranscription-true
@@ -0,0 +1,24 @@
+{
+ "createdAt" : 1394321113584,
+ "duration" : 0,
+ "id" : "832641bf-5dbf-41a1-ad94-fea213e59a92",
+ "name" : null,
+ "partnerId" : 12345678,
+ "reason" : "",
+ "sessionId" : "2_MX44NTQ1MTF-flR1ZSBOb3YgMTIgMDk6NDA6NTkgUFNUIDIwMTN-MC43NjU0Nzh-",
+ "size" : 0,
+ "status" : "started",
+ "url" : null,
+ "hasVideo" : true,
+ "hasAudio" : true,
+ "outputMode" : "composed",
+ "streamMode" : "auto",
+ "hasTranscription" : true,
+ "transcription" : {
+ "status" : "requested",
+ "url" : null,
+ "reason" : null,
+ "primaryLanguageCode" : "en-US",
+ "hasSummary" : false
+ }
+}
\ No newline at end of file
diff --git a/tests/mock/v2/project/APIKEY/archive/session_transcription-with-summary b/tests/mock/v2/project/APIKEY/archive/session_transcription-with-summary
new file mode 100644
index 0000000..de61b90
--- /dev/null
+++ b/tests/mock/v2/project/APIKEY/archive/session_transcription-with-summary
@@ -0,0 +1,24 @@
+{
+ "createdAt" : 1394321113584,
+ "duration" : 0,
+ "id" : "832641bf-5dbf-41a1-ad94-fea213e59a93",
+ "name" : null,
+ "partnerId" : 12345678,
+ "reason" : "",
+ "sessionId" : "2_MX44NTQ1MTF-flR1ZSBOb3YgMTIgMDk6NDA6NTkgUFNUIDIwMTN-MC43NjU0Nzh-",
+ "size" : 0,
+ "status" : "started",
+ "url" : null,
+ "hasVideo" : true,
+ "hasAudio" : true,
+ "outputMode" : "composed",
+ "streamMode" : "auto",
+ "hasTranscription" : true,
+ "transcription" : {
+ "status" : "requested",
+ "url" : null,
+ "reason" : null,
+ "primaryLanguageCode" : "es-ES",
+ "hasSummary" : true
+ }
+}
\ No newline at end of file