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: + * + *
  • * * * @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