diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/assets.json b/sdk/contentunderstanding/azure-ai-contentunderstanding/assets.json index 83ce14e6ee66..58186b3555c8 100644 --- a/sdk/contentunderstanding/azure-ai-contentunderstanding/assets.json +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/assets.json @@ -2,5 +2,5 @@ "AssetsRepo": "Azure/azure-sdk-assets", "AssetsRepoPrefixPath": "java", "TagPrefix": "java/contentunderstanding/azure-ai-contentunderstanding", - "Tag": "java/contentunderstanding/azure-ai-contentunderstanding_6d6c9cbfc1" + "Tag": "java/contentunderstanding/azure-ai-contentunderstanding_670ad2966f" } diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/samples/java/com/azure/ai/contentunderstanding/samples/Sample01_AnalyzeBinary.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/samples/java/com/azure/ai/contentunderstanding/samples/Sample01_AnalyzeBinary.java index efcdebca52f0..6eb859da1856 100644 --- a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/samples/java/com/azure/ai/contentunderstanding/samples/Sample01_AnalyzeBinary.java +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/samples/java/com/azure/ai/contentunderstanding/samples/Sample01_AnalyzeBinary.java @@ -8,6 +8,7 @@ import com.azure.ai.contentunderstanding.ContentUnderstandingClientBuilder; import com.azure.ai.contentunderstanding.models.AnalysisResult; import com.azure.ai.contentunderstanding.models.ContentAnalyzerAnalyzeOperationStatus; +import com.azure.ai.contentunderstanding.models.ContentRange; import com.azure.ai.contentunderstanding.models.DocumentContent; import com.azure.ai.contentunderstanding.models.DocumentPage; import com.azure.ai.contentunderstanding.models.DocumentTable; @@ -29,6 +30,7 @@ * 2. Analyzing the document * 3. Extracting markdown content * 4. Accessing document properties (pages, tables, etc.) + * 5. Using ContentRange to analyze specific pages */ public class Sample01_AnalyzeBinary { @@ -126,5 +128,79 @@ public static void main(String[] args) throws IOException { // END:ContentUnderstandingAccessDocumentProperties System.out.println("\nBinary document analysis completed successfully"); + + // Demonstrate ContentRange usage with a multi-page document + System.out.println("\n--- ContentRange Examples ---"); + analyzeBinaryWithContentRange(client); + } + + /** + * Sample demonstrating how to use ContentRange to analyze specific pages of a binary document. + * ContentRange allows you to specify which pages to analyze instead of the entire document. + */ + public static void analyzeBinaryWithContentRange(ContentUnderstandingClient client) { + try { + // Load a multi-page document (10 pages) + String multiPageFilePath = "src/samples/resources/mixed_financial_invoices.pdf"; + Path multiPagePath = Paths.get(multiPageFilePath); + byte[] multiPageBytes = Files.readAllBytes(multiPagePath); + BinaryData multiPageData = BinaryData.fromBytes(multiPageBytes); + + // BEGIN:ContentUnderstandingAnalyzeBinaryWithContentRange + // Analyze a single page using ContentRange.page() + SyncPoller pageOperation + = client.beginAnalyzeBinary("prebuilt-documentSearch", multiPageData, + ContentRange.page(2), "application/octet-stream", null); + DocumentContent pageDoc = (DocumentContent) pageOperation.getFinalResult().getContents().get(0); + System.out.println("Page(2): returned " + pageDoc.getPages().size() + " page" + + " (page " + pageDoc.getStartPageNumber() + ")"); + + // Analyze a page range using ContentRange.pages() + SyncPoller pagesOperation + = client.beginAnalyzeBinary("prebuilt-documentSearch", multiPageData, + ContentRange.pages(1, 3), "application/octet-stream", null); + DocumentContent pagesDoc = (DocumentContent) pagesOperation.getFinalResult().getContents().get(0); + System.out.println("Pages(1,3): returned " + pagesDoc.getPages().size() + " pages" + + " (pages " + pagesDoc.getStartPageNumber() + "-" + pagesDoc.getEndPageNumber() + ")"); + + // Combine multiple ranges using ContentRange.combine() + SyncPoller combineOperation + = client.beginAnalyzeBinary("prebuilt-documentSearch", multiPageData, + ContentRange.combine( + ContentRange.page(1), + ContentRange.pages(3, 4)), + "application/octet-stream", null); + DocumentContent combineDoc = (DocumentContent) combineOperation.getFinalResult().getContents().get(0); + System.out.println("Combine(Page(1), Pages(3,4)): returned " + combineDoc.getPages().size() + " pages" + + " (pages " + combineDoc.getStartPageNumber() + "-" + combineDoc.getEndPageNumber() + ")"); + + // Analyze only pages 3 to end using ContentRange.pagesFrom() + SyncPoller rangeOperation + = client.beginAnalyzeBinary("prebuilt-documentSearch", multiPageData, + ContentRange.pagesFrom(3), "application/octet-stream", null); + AnalysisResult rangeResult = rangeOperation.getFinalResult(); + + DocumentContent rangeDoc = (DocumentContent) rangeResult.getContents().get(0); + System.out.println("PagesFrom(3): returned " + rangeDoc.getPages().size() + " pages" + + " (pages " + rangeDoc.getStartPageNumber() + "-" + rangeDoc.getEndPageNumber() + ")"); + + // Combine multiple page ranges: pages 1-3, page 5, and pages 9 onward + SyncPoller bigCombineOperation + = client.beginAnalyzeBinary("prebuilt-documentSearch", multiPageData, + ContentRange.combine( + ContentRange.pages(1, 3), + ContentRange.page(5), + ContentRange.pagesFrom(9)), + "application/octet-stream", null); + DocumentContent bigCombineDoc + = (DocumentContent) bigCombineOperation.getFinalResult().getContents().get(0); + System.out.println( + "Combine(Pages(1,3), Page(5), PagesFrom(9)): returned " + bigCombineDoc.getPages().size() + " pages"); + // END:ContentUnderstandingAnalyzeBinaryWithContentRange + + System.out.println("ContentRange binary analysis completed successfully"); + } catch (IOException e) { + System.err.println("Error reading multi-page file: " + e.getMessage()); + } } } diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/samples/java/com/azure/ai/contentunderstanding/samples/Sample01_AnalyzeBinaryAsync.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/samples/java/com/azure/ai/contentunderstanding/samples/Sample01_AnalyzeBinaryAsync.java index e9ca0edb8e55..71b3d47122bd 100644 --- a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/samples/java/com/azure/ai/contentunderstanding/samples/Sample01_AnalyzeBinaryAsync.java +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/samples/java/com/azure/ai/contentunderstanding/samples/Sample01_AnalyzeBinaryAsync.java @@ -8,6 +8,7 @@ import com.azure.ai.contentunderstanding.ContentUnderstandingClientBuilder; import com.azure.ai.contentunderstanding.models.AnalysisResult; import com.azure.ai.contentunderstanding.models.ContentAnalyzerAnalyzeOperationStatus; +import com.azure.ai.contentunderstanding.models.ContentRange; import com.azure.ai.contentunderstanding.models.DocumentContent; import com.azure.ai.contentunderstanding.models.DocumentPage; import com.azure.ai.contentunderstanding.models.DocumentTable; @@ -32,6 +33,7 @@ * 2. Analyzing the document * 3. Extracting markdown content * 4. Accessing document properties (pages, tables, etc.) + * 5. Using ContentRange to analyze specific pages */ public class Sample01_AnalyzeBinaryAsync { @@ -162,5 +164,147 @@ public static void main(String[] args) throws IOException, InterruptedException if (!latch.await(2, TimeUnit.MINUTES)) { System.err.println("Timed out waiting for async operations to complete."); } + + // Demonstrate ContentRange usage with a multi-page document + System.out.println("\n--- ContentRange Examples ---"); + analyzeBinaryWithContentRange(client); + } + + /** + * Sample demonstrating how to use ContentRange to analyze specific pages of a binary document asynchronously. + * ContentRange allows you to specify which pages to analyze instead of the entire document. + */ + public static void analyzeBinaryWithContentRange(ContentUnderstandingAsyncClient client) + throws IOException, InterruptedException { + // Load a multi-page document (10 pages) + String multiPageFilePath = "src/samples/resources/mixed_financial_invoices.pdf"; + Path multiPagePath = Paths.get(multiPageFilePath); + byte[] multiPageBytes = Files.readAllBytes(multiPagePath); + BinaryData multiPageData = BinaryData.fromBytes(multiPageBytes); + + CountDownLatch rangeLatch = new CountDownLatch(1); + + // BEGIN:ContentUnderstandingAnalyzeBinaryWithContentRangeAsync + // Analyze a single page using ContentRange.page() + Mono pageMono = client + .beginAnalyzeBinary("prebuilt-documentSearch", multiPageData, + ContentRange.page(2), "application/octet-stream", null) + .last() + .flatMap(pollResponse -> { + if (pollResponse.getStatus().isComplete()) { + return pollResponse.getFinalResult(); + } else { + return Mono.error(new RuntimeException( + "Polling completed unsuccessfully with status: " + pollResponse.getStatus())); + } + }) + .doOnNext(result -> { + DocumentContent pageDoc = (DocumentContent) result.getContents().get(0); + System.out.println("Page(2): returned " + pageDoc.getPages().size() + " page" + + " (page " + pageDoc.getStartPageNumber() + ")"); + }); + + // Analyze a page range using ContentRange.pages() + Mono pagesMono = client + .beginAnalyzeBinary("prebuilt-documentSearch", multiPageData, + ContentRange.pages(1, 3), "application/octet-stream", null) + .last() + .flatMap(pollResponse -> { + if (pollResponse.getStatus().isComplete()) { + return pollResponse.getFinalResult(); + } else { + return Mono.error(new RuntimeException( + "Polling completed unsuccessfully with status: " + pollResponse.getStatus())); + } + }) + .doOnNext(result -> { + DocumentContent pagesDoc = (DocumentContent) result.getContents().get(0); + System.out.println("Pages(1,3): returned " + pagesDoc.getPages().size() + " pages" + + " (pages " + pagesDoc.getStartPageNumber() + "-" + pagesDoc.getEndPageNumber() + ")"); + }); + + // Combine multiple ranges using ContentRange.combine() + Mono combineMono = client + .beginAnalyzeBinary("prebuilt-documentSearch", multiPageData, + ContentRange.combine( + ContentRange.page(1), + ContentRange.pages(3, 4)), + "application/octet-stream", null) + .last() + .flatMap(pollResponse -> { + if (pollResponse.getStatus().isComplete()) { + return pollResponse.getFinalResult(); + } else { + return Mono.error(new RuntimeException( + "Polling completed unsuccessfully with status: " + pollResponse.getStatus())); + } + }) + .doOnNext(result -> { + DocumentContent combineDoc = (DocumentContent) result.getContents().get(0); + System.out.println("Combine(Page(1), Pages(3,4)): returned " + combineDoc.getPages().size() + " pages" + + " (pages " + combineDoc.getStartPageNumber() + "-" + combineDoc.getEndPageNumber() + ")"); + }); + + // Analyze only pages 3 to end using ContentRange.pagesFrom() + Mono pagesFromMono = client + .beginAnalyzeBinary("prebuilt-documentSearch", multiPageData, + ContentRange.pagesFrom(3), "application/octet-stream", null) + .last() + .flatMap(pollResponse -> { + if (pollResponse.getStatus().isComplete()) { + return pollResponse.getFinalResult(); + } else { + return Mono.error(new RuntimeException( + "Polling completed unsuccessfully with status: " + pollResponse.getStatus())); + } + }) + .doOnNext(result -> { + DocumentContent rangeDoc = (DocumentContent) result.getContents().get(0); + System.out.println("PagesFrom(3): returned " + rangeDoc.getPages().size() + " pages" + + " (pages " + rangeDoc.getStartPageNumber() + "-" + rangeDoc.getEndPageNumber() + ")"); + }); + + // Combine multiple page ranges: pages 1-3, page 5, and pages 9 onward + Mono bigCombineMono = client + .beginAnalyzeBinary("prebuilt-documentSearch", multiPageData, + ContentRange.combine( + ContentRange.pages(1, 3), + ContentRange.page(5), + ContentRange.pagesFrom(9)), + "application/octet-stream", null) + .last() + .flatMap(pollResponse -> { + if (pollResponse.getStatus().isComplete()) { + return pollResponse.getFinalResult(); + } else { + return Mono.error(new RuntimeException( + "Polling completed unsuccessfully with status: " + pollResponse.getStatus())); + } + }) + .doOnNext(result -> { + DocumentContent bigCombineDoc = (DocumentContent) result.getContents().get(0); + System.out.println("Combine(Pages(1,3), Page(5), PagesFrom(9)): returned " + + bigCombineDoc.getPages().size() + " pages"); + }); + + // Chain all operations sequentially using then() + pageMono + .then(pagesMono) + .then(combineMono) + .then(pagesFromMono) + .then(bigCombineMono) + .doOnError(error -> System.err.println("Error: " + error.getMessage())) + .subscribe( + result -> { + System.out.println("ContentRange async analysis completed successfully"); + rangeLatch.countDown(); + }, + error -> rangeLatch.countDown() + ); + // END:ContentUnderstandingAnalyzeBinaryWithContentRangeAsync + + if (!rangeLatch.await(5, TimeUnit.MINUTES)) { + System.err.println("Timed out waiting for ContentRange async operations."); + } } } diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/samples/java/com/azure/ai/contentunderstanding/samples/Sample02_AnalyzeUrl.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/samples/java/com/azure/ai/contentunderstanding/samples/Sample02_AnalyzeUrl.java index 3006c2abd851..a6e165ccc27d 100644 --- a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/samples/java/com/azure/ai/contentunderstanding/samples/Sample02_AnalyzeUrl.java +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/samples/java/com/azure/ai/contentunderstanding/samples/Sample02_AnalyzeUrl.java @@ -10,6 +10,7 @@ import com.azure.ai.contentunderstanding.models.AnalysisResult; import com.azure.ai.contentunderstanding.models.AudioVisualContent; import com.azure.ai.contentunderstanding.models.ContentAnalyzerAnalyzeOperationStatus; +import com.azure.ai.contentunderstanding.models.ContentRange; import com.azure.ai.contentunderstanding.models.DocumentContent; import com.azure.ai.contentunderstanding.models.DocumentPage; import com.azure.ai.contentunderstanding.models.DocumentTable; @@ -19,6 +20,7 @@ import com.azure.core.util.polling.SyncPoller; import com.azure.identity.DefaultAzureCredentialBuilder; +import java.time.Duration; import java.util.Arrays; import java.util.List; @@ -67,6 +69,15 @@ public static void main(String[] args) { System.out.println("\n--- Image Analysis Example ---"); analyzeImageUrl(client); + + System.out.println("\n--- Document ContentRange Example ---"); + analyzeDocumentUrlWithContentRange(client); + + System.out.println("\n--- Video ContentRange Example ---"); + analyzeVideoUrlWithContentRange(client); + + System.out.println("\n--- Audio ContentRange Example ---"); + analyzeAudioUrlWithContentRange(client); } /** @@ -281,4 +292,185 @@ public static void analyzeImageUrl(ContentUnderstandingClient client) { System.out.println("Summary: " + summary); // END:ContentUnderstandingAnalyzeImageUrl } + + /** + * Sample demonstrating how to analyze a document URL with ContentRange to extract specific pages. + */ + public static void analyzeDocumentUrlWithContentRange(ContentUnderstandingClient client) { + // BEGIN:ContentUnderstandingAnalyzeUrlWithContentRange + String uriSource + = "https://raw.githubusercontent.com/Azure-Samples/azure-ai-content-understanding-assets/main/document/mixed_financial_invoices.pdf"; + + // Extract only page 1 using ContentRange + AnalysisInput rangeInput = new AnalysisInput(); + rangeInput.setUrl(uriSource); + rangeInput.setContentRange(ContentRange.page(1)); + + SyncPoller rangeOperation + = client.beginAnalyze("prebuilt-documentSearch", Arrays.asList(rangeInput)); + AnalysisResult rangeResult = rangeOperation.getFinalResult(); + + DocumentContent rangeDoc = (DocumentContent) rangeResult.getContents().get(0); + System.out.println("Page(1): returned " + rangeDoc.getPages().size() + " page" + + " (page " + rangeDoc.getStartPageNumber() + ")"); + System.out.println("Markdown length: " + rangeDoc.getMarkdown().length() + " characters"); + + // Combine multiple page ranges: pages 1-3, page 5, and pages 9 onward + AnalysisInput combineInput = new AnalysisInput(); + combineInput.setUrl(uriSource); + combineInput.setContentRange(ContentRange.combine( + ContentRange.pages(1, 3), + ContentRange.page(5), + ContentRange.pagesFrom(9))); + + SyncPoller combineOperation + = client.beginAnalyze("prebuilt-documentSearch", Arrays.asList(combineInput)); + AnalysisResult combineResult = combineOperation.getFinalResult(); + + DocumentContent combineDoc = (DocumentContent) combineResult.getContents().get(0); + System.out.println("Combine(1-3, 5, 9-): returned " + combineDoc.getPages().size() + " pages" + + " (pages " + combineDoc.getStartPageNumber() + "-" + combineDoc.getEndPageNumber() + ")"); + System.out.println("Markdown length: " + combineDoc.getMarkdown().length() + " characters"); + // END:ContentUnderstandingAnalyzeUrlWithContentRange + } + + /** + * Sample demonstrating how to analyze a video URL with ContentRange to extract specific time ranges. + */ + public static void analyzeVideoUrlWithContentRange(ContentUnderstandingClient client) { + // BEGIN:ContentUnderstandingAnalyzeVideoUrlWithContentRange + String uriSource + = "https://raw.githubusercontent.com/Azure-Samples/azure-ai-content-understanding-assets/main/videos/sdk_samples/FlightSimulator.mp4"; + + // Analyze only the first 5 seconds using ContentRange.timeRange() + AnalysisInput rangeInput = new AnalysisInput(); + rangeInput.setUrl(uriSource); + rangeInput.setContentRange(ContentRange.timeRange(Duration.ZERO, Duration.ofSeconds(5))); + + SyncPoller rangeOperation + = client.beginAnalyze("prebuilt-videoSearch", Arrays.asList(rangeInput)); + AnalysisResult rangeResult = rangeOperation.getFinalResult(); + + for (AnalysisContent media : rangeResult.getContents()) { + AudioVisualContent videoContent = (AudioVisualContent) media; + System.out.println("TimeRange(0, 5s): Start=" + videoContent.getStartTime().toMillis() + + " ms, End=" + videoContent.getEndTime().toMillis() + " ms"); + } + + // Analyze from 10 seconds onward using ContentRange.timeRangeFrom() + AnalysisInput rangeFromInput = new AnalysisInput(); + rangeFromInput.setUrl(uriSource); + rangeFromInput.setContentRange(ContentRange.timeRangeFrom(Duration.ofSeconds(10))); + + SyncPoller rangeFromOperation + = client.beginAnalyze("prebuilt-videoSearch", Arrays.asList(rangeFromInput)); + AnalysisResult rangeFromResult = rangeFromOperation.getFinalResult(); + + for (AnalysisContent media : rangeFromResult.getContents()) { + AudioVisualContent videoContent = (AudioVisualContent) media; + System.out.println("TimeRangeFrom(10s): Start=" + videoContent.getStartTime().toMillis() + + " ms, End=" + videoContent.getEndTime().toMillis() + " ms"); + } + + // Analyze with sub-second precision using milliseconds + AnalysisInput subSecondInput = new AnalysisInput(); + subSecondInput.setUrl(uriSource); + subSecondInput.setContentRange( + ContentRange.timeRange(Duration.ofMillis(1200), Duration.ofMillis(3651))); + + SyncPoller subSecondOperation + = client.beginAnalyze("prebuilt-videoSearch", Arrays.asList(subSecondInput)); + AnalysisResult subSecondResult = subSecondOperation.getFinalResult(); + + for (AnalysisContent media : subSecondResult.getContents()) { + AudioVisualContent videoContent = (AudioVisualContent) media; + System.out.println("TimeRange(1200ms, 3651ms): Start=" + videoContent.getStartTime().toMillis() + + " ms, End=" + videoContent.getEndTime().toMillis() + " ms"); + } + + // Combine multiple time ranges + AnalysisInput combineInput = new AnalysisInput(); + combineInput.setUrl(uriSource); + combineInput.setContentRange(ContentRange.combine( + ContentRange.timeRange(Duration.ZERO, Duration.ofSeconds(3)), + ContentRange.timeRangeFrom(Duration.ofSeconds(30)))); + + SyncPoller combineOperation + = client.beginAnalyze("prebuilt-videoSearch", Arrays.asList(combineInput)); + AnalysisResult combineResult = combineOperation.getFinalResult(); + + int segmentIndex = 1; + for (AnalysisContent media : combineResult.getContents()) { + AudioVisualContent videoContent = (AudioVisualContent) media; + System.out.println("Combined segment " + segmentIndex + ": Start=" + videoContent.getStartTime().toMillis() + + " ms, End=" + videoContent.getEndTime().toMillis() + " ms"); + segmentIndex++; + } + // END:ContentUnderstandingAnalyzeVideoUrlWithContentRange + } + + /** + * Sample demonstrating how to analyze an audio URL with ContentRange to extract specific time ranges. + */ + public static void analyzeAudioUrlWithContentRange(ContentUnderstandingClient client) { + // BEGIN:ContentUnderstandingAnalyzeAudioUrlWithContentRange + String uriSource + = "https://raw.githubusercontent.com/Azure-Samples/azure-ai-content-understanding-assets/main/audio/callCenterRecording.mp3"; + + // Analyze from 5 seconds to end using ContentRange.timeRangeFrom() + AnalysisInput rangeInput = new AnalysisInput(); + rangeInput.setUrl(uriSource); + rangeInput.setContentRange(ContentRange.timeRangeFrom(Duration.ofSeconds(5))); + + SyncPoller rangeOperation + = client.beginAnalyze("prebuilt-audioSearch", Arrays.asList(rangeInput)); + AnalysisResult rangeResult = rangeOperation.getFinalResult(); + + AudioVisualContent audioContent = (AudioVisualContent) rangeResult.getContents().get(0); + System.out.println("TimeRangeFrom(5s): Start=" + audioContent.getStartTime().toMillis() + + " ms, End=" + audioContent.getEndTime().toMillis() + " ms"); + + // Analyze a specific time window + AnalysisInput windowInput = new AnalysisInput(); + windowInput.setUrl(uriSource); + windowInput.setContentRange(ContentRange.timeRange(Duration.ofSeconds(2), Duration.ofSeconds(8))); + + SyncPoller windowOperation + = client.beginAnalyze("prebuilt-audioSearch", Arrays.asList(windowInput)); + AnalysisResult windowResult = windowOperation.getFinalResult(); + + AudioVisualContent windowContent = (AudioVisualContent) windowResult.getContents().get(0); + System.out.println("TimeRange(2s, 8s): Start=" + windowContent.getStartTime().toMillis() + + " ms, End=" + windowContent.getEndTime().toMillis() + " ms"); + + // Analyze with sub-second precision using milliseconds + AnalysisInput subSecondInput = new AnalysisInput(); + subSecondInput.setUrl(uriSource); + subSecondInput.setContentRange( + ContentRange.timeRange(Duration.ofMillis(1200), Duration.ofMillis(3651))); + + SyncPoller subSecondOperation + = client.beginAnalyze("prebuilt-audioSearch", Arrays.asList(subSecondInput)); + AnalysisResult subSecondResult = subSecondOperation.getFinalResult(); + + AudioVisualContent subSecondContent = (AudioVisualContent) subSecondResult.getContents().get(0); + System.out.println("TimeRange(1200ms, 3651ms): Start=" + subSecondContent.getStartTime().toMillis() + + " ms, End=" + subSecondContent.getEndTime().toMillis() + " ms"); + + // Combine multiple time ranges + AnalysisInput combineInput = new AnalysisInput(); + combineInput.setUrl(uriSource); + combineInput.setContentRange(ContentRange.combine( + ContentRange.timeRange(Duration.ZERO, Duration.ofSeconds(3)), + ContentRange.timeRangeFrom(Duration.ofSeconds(30)))); + + SyncPoller combineOperation + = client.beginAnalyze("prebuilt-audioSearch", Arrays.asList(combineInput)); + AnalysisResult combineResult = combineOperation.getFinalResult(); + + AudioVisualContent combineContent = (AudioVisualContent) combineResult.getContents().get(0); + System.out.println("Combined time ranges: Start=" + combineContent.getStartTime().toMillis() + + " ms, End=" + combineContent.getEndTime().toMillis() + " ms"); + // END:ContentUnderstandingAnalyzeAudioUrlWithContentRange + } } diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/samples/java/com/azure/ai/contentunderstanding/samples/Sample02_AnalyzeUrlAsync.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/samples/java/com/azure/ai/contentunderstanding/samples/Sample02_AnalyzeUrlAsync.java index 758ecfb223ba..2c4812505d4b 100644 --- a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/samples/java/com/azure/ai/contentunderstanding/samples/Sample02_AnalyzeUrlAsync.java +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/samples/java/com/azure/ai/contentunderstanding/samples/Sample02_AnalyzeUrlAsync.java @@ -10,6 +10,7 @@ import com.azure.ai.contentunderstanding.models.AnalysisResult; import com.azure.ai.contentunderstanding.models.AudioVisualContent; import com.azure.ai.contentunderstanding.models.ContentAnalyzerAnalyzeOperationStatus; +import com.azure.ai.contentunderstanding.models.ContentRange; import com.azure.ai.contentunderstanding.models.DocumentContent; import com.azure.ai.contentunderstanding.models.DocumentPage; import com.azure.ai.contentunderstanding.models.DocumentTable; @@ -20,6 +21,7 @@ import com.azure.identity.DefaultAzureCredentialBuilder; import reactor.core.publisher.Mono; +import java.time.Duration; import java.util.Arrays; import java.util.List; import java.util.concurrent.CountDownLatch; @@ -63,6 +65,15 @@ public static void main(String[] args) { System.out.println("\n--- Image Analysis Example ---"); analyzeImageUrl(client); + + System.out.println("\n--- Document URL with ContentRange Example ---"); + analyzeDocumentUrlWithContentRange(client); + + System.out.println("\n--- Video URL with ContentRange Example ---"); + analyzeVideoUrlWithContentRange(client); + + System.out.println("\n--- Audio URL with ContentRange Example ---"); + analyzeAudioUrlWithContentRange(client); } /** @@ -416,4 +427,445 @@ public static void analyzeImageUrl(ContentUnderstandingAsyncClient client) { } // END:ContentUnderstandingAnalyzeImageUrlAsyncAsync } + + /** + * Sample demonstrating how to analyze a document URL with page-based ContentRange. + */ + public static void analyzeDocumentUrlWithContentRange(ContentUnderstandingAsyncClient client) { + // BEGIN:ContentUnderstandingAnalyzeDocumentUrlWithContentRangeAsync + String uriSource + = "https://raw.githubusercontent.com/Azure-Samples/azure-ai-content-understanding-assets/main/document/mixed_financial_invoices.pdf"; + + // Analyze only page 1 using ContentRange + AnalysisInput input = new AnalysisInput(); + input.setUrl(uriSource); + input.setContentRange(ContentRange.page(1)); + + PollerFlux operation + = client.beginAnalyze("prebuilt-documentSearch", Arrays.asList(input)); + + CountDownLatch latch = new CountDownLatch(1); + + operation.last() + .flatMap(pollResponse -> { + if (pollResponse.getStatus().isComplete()) { + return pollResponse.getFinalResult(); + } else { + return Mono.error(new RuntimeException( + "Polling completed unsuccessfully with status: " + pollResponse.getStatus())); + } + }) + .doOnNext(result -> { + DocumentContent document = (DocumentContent) result.getContents().get(0); + System.out.println("Page(1): returned " + document.getPages().size() + " page" + + " (page " + document.getStartPageNumber() + ")"); + System.out.println("Markdown length: " + document.getMarkdown().length() + " characters"); + }) + .doOnError(error -> { + System.err.println("Error occurred: " + error.getMessage()); + }) + .doFinally(signalType -> latch.countDown()) + .subscribe( + result -> { }, + error -> { } + ); + + try { + latch.await(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException("Interrupted while waiting for document analysis", e); + } + + // Combine multiple page ranges: pages 1-3, page 5, and pages 9 onward + AnalysisInput combineInput = new AnalysisInput(); + combineInput.setUrl(uriSource); + combineInput.setContentRange(ContentRange.combine( + ContentRange.pages(1, 3), + ContentRange.page(5), + ContentRange.pagesFrom(9))); + + CountDownLatch combineLatch = new CountDownLatch(1); + + PollerFlux combineOperation + = client.beginAnalyze("prebuilt-documentSearch", Arrays.asList(combineInput)); + + combineOperation.last() + .flatMap(pollResponse -> { + if (pollResponse.getStatus().isComplete()) { + return pollResponse.getFinalResult(); + } else { + return Mono.error(new RuntimeException( + "Polling completed unsuccessfully with status: " + pollResponse.getStatus())); + } + }) + .doOnNext(result -> { + DocumentContent combineDoc = (DocumentContent) result.getContents().get(0); + System.out.println("Combine(1-3, 5, 9-): returned " + combineDoc.getPages().size() + " pages" + + " (pages " + combineDoc.getStartPageNumber() + "-" + combineDoc.getEndPageNumber() + ")"); + System.out.println("Markdown length: " + combineDoc.getMarkdown().length() + " characters"); + }) + .doOnError(error -> { + System.err.println("Error occurred: " + error.getMessage()); + }) + .subscribe( + result -> combineLatch.countDown(), + error -> combineLatch.countDown() + ); + + try { + combineLatch.await(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException("Interrupted while waiting for combined range analysis", e); + } + // END:ContentUnderstandingAnalyzeDocumentUrlWithContentRangeAsync + } + + /** + * Sample demonstrating how to analyze a video URL with time-based ContentRange. + */ + public static void analyzeVideoUrlWithContentRange(ContentUnderstandingAsyncClient client) { + // BEGIN:ContentUnderstandingAnalyzeVideoUrlWithContentRangeAsync + String videoUrl + = "https://raw.githubusercontent.com/Azure-Samples/azure-ai-content-understanding-assets/main/videos/sdk_samples/FlightSimulator.mp4"; + + // Analyze the first 5 seconds of the video + AnalysisInput input1 = new AnalysisInput(); + input1.setUrl(videoUrl); + input1.setContentRange(ContentRange.timeRange(Duration.ZERO, Duration.ofSeconds(5))); + + PollerFlux operation1 + = client.beginAnalyze("prebuilt-videoSearch", Arrays.asList(input1)); + + CountDownLatch latch1 = new CountDownLatch(1); + + operation1.last() + .flatMap(pollResponse -> { + if (pollResponse.getStatus().isComplete()) { + return pollResponse.getFinalResult(); + } else { + return Mono.error(new RuntimeException( + "Polling completed unsuccessfully with status: " + pollResponse.getStatus())); + } + }) + .doOnNext(result -> { + for (AnalysisContent media : result.getContents()) { + AudioVisualContent videoContent = (AudioVisualContent) media; + System.out.println("TimeRange(0, 5s): Start=" + videoContent.getStartTime().toMillis() + + " ms, End=" + videoContent.getEndTime().toMillis() + " ms"); + } + }) + .doOnError(error -> { + System.err.println("Error occurred: " + error.getMessage()); + }) + .subscribe( + result -> latch1.countDown(), + error -> latch1.countDown() + ); + + try { + latch1.await(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException("Interrupted while waiting for video analysis", e); + } + + // Analyze from 10 seconds onward using ContentRange.timeRangeFrom() + AnalysisInput input2 = new AnalysisInput(); + input2.setUrl(videoUrl); + input2.setContentRange(ContentRange.timeRangeFrom(Duration.ofSeconds(10))); + + PollerFlux operation2 + = client.beginAnalyze("prebuilt-videoSearch", Arrays.asList(input2)); + + CountDownLatch latch2 = new CountDownLatch(1); + + operation2.last() + .flatMap(pollResponse -> { + if (pollResponse.getStatus().isComplete()) { + return pollResponse.getFinalResult(); + } else { + return Mono.error(new RuntimeException( + "Polling completed unsuccessfully with status: " + pollResponse.getStatus())); + } + }) + .doOnNext(result -> { + for (AnalysisContent media : result.getContents()) { + AudioVisualContent videoContent = (AudioVisualContent) media; + System.out.println("TimeRangeFrom(10s): Start=" + videoContent.getStartTime().toMillis() + + " ms, End=" + videoContent.getEndTime().toMillis() + " ms"); + } + }) + .doOnError(error -> { + System.err.println("Error occurred: " + error.getMessage()); + }) + .subscribe( + result -> latch2.countDown(), + error -> latch2.countDown() + ); + + try { + latch2.await(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException("Interrupted while waiting for video analysis", e); + } + + // Analyze with sub-second precision using milliseconds + AnalysisInput input3 = new AnalysisInput(); + input3.setUrl(videoUrl); + input3.setContentRange( + ContentRange.timeRange(Duration.ofMillis(1200), Duration.ofMillis(3651))); + + PollerFlux operation3 + = client.beginAnalyze("prebuilt-videoSearch", Arrays.asList(input3)); + + CountDownLatch latch3 = new CountDownLatch(1); + + operation3.last() + .flatMap(pollResponse -> { + if (pollResponse.getStatus().isComplete()) { + return pollResponse.getFinalResult(); + } else { + return Mono.error(new RuntimeException( + "Polling completed unsuccessfully with status: " + pollResponse.getStatus())); + } + }) + .doOnNext(result -> { + for (AnalysisContent media : result.getContents()) { + AudioVisualContent videoContent = (AudioVisualContent) media; + System.out.println("TimeRange(1200ms, 3651ms): Start=" + videoContent.getStartTime().toMillis() + + " ms, End=" + videoContent.getEndTime().toMillis() + " ms"); + } + }) + .doOnError(error -> { + System.err.println("Error occurred: " + error.getMessage()); + }) + .subscribe( + result -> latch3.countDown(), + error -> latch3.countDown() + ); + + try { + latch3.await(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException("Interrupted while waiting for video analysis", e); + } + + // Analyze with combined time ranges + AnalysisInput input4 = new AnalysisInput(); + input4.setUrl(videoUrl); + input4.setContentRange(ContentRange.combine( + ContentRange.timeRange(Duration.ZERO, Duration.ofSeconds(3)), + ContentRange.timeRangeFrom(Duration.ofSeconds(30)))); + + PollerFlux operation4 + = client.beginAnalyze("prebuilt-videoSearch", Arrays.asList(input4)); + + CountDownLatch latch4 = new CountDownLatch(1); + + operation4.last() + .flatMap(pollResponse -> { + if (pollResponse.getStatus().isComplete()) { + return pollResponse.getFinalResult(); + } else { + return Mono.error(new RuntimeException( + "Polling completed unsuccessfully with status: " + pollResponse.getStatus())); + } + }) + .doOnNext(result -> { + int segmentIndex = 1; + for (AnalysisContent media : result.getContents()) { + AudioVisualContent videoContent = (AudioVisualContent) media; + System.out.println( + "Combined segment " + segmentIndex + ": Start=" + + videoContent.getStartTime().toMillis() + + " ms, End=" + + videoContent.getEndTime().toMillis() + + " ms"); + segmentIndex++; + } + }) + .doOnError(error -> { + System.err.println("Error occurred: " + error.getMessage()); + }) + .subscribe( + result -> latch4.countDown(), + error -> latch4.countDown() + ); + + try { + latch4.await(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException("Interrupted while waiting for video analysis", e); + } + // END:ContentUnderstandingAnalyzeVideoUrlWithContentRangeAsync + } + + /** + * Sample demonstrating how to analyze an audio URL with time-based ContentRange. + */ + public static void analyzeAudioUrlWithContentRange(ContentUnderstandingAsyncClient client) { + // BEGIN:ContentUnderstandingAnalyzeAudioUrlWithContentRangeAsync + String audioUrl + = "https://raw.githubusercontent.com/Azure-Samples/azure-ai-content-understanding-assets/main/audio/callCenterRecording.mp3"; + + // Analyze from 5 seconds onward + AnalysisInput input1 = new AnalysisInput(); + input1.setUrl(audioUrl); + input1.setContentRange(ContentRange.timeRangeFrom(Duration.ofSeconds(5))); + + PollerFlux operation1 + = client.beginAnalyze("prebuilt-audioSearch", Arrays.asList(input1)); + + CountDownLatch latch1 = new CountDownLatch(1); + + operation1.last() + .flatMap(pollResponse -> { + if (pollResponse.getStatus().isComplete()) { + return pollResponse.getFinalResult(); + } else { + return Mono.error(new RuntimeException( + "Polling completed unsuccessfully with status: " + pollResponse.getStatus())); + } + }) + .doOnNext(result -> { + AudioVisualContent audioContent = (AudioVisualContent) result.getContents().get(0); + System.out.println("TimeRangeFrom(5s): Start=" + audioContent.getStartTime().toMillis() + + " ms, End=" + audioContent.getEndTime().toMillis() + " ms"); + }) + .doOnError(error -> { + System.err.println("Error occurred: " + error.getMessage()); + }) + .subscribe( + result -> latch1.countDown(), + error -> latch1.countDown() + ); + + try { + latch1.await(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException("Interrupted while waiting for audio analysis", e); + } + + // Analyze a specific time range (2s to 8s) + AnalysisInput input2 = new AnalysisInput(); + input2.setUrl(audioUrl); + input2.setContentRange(ContentRange.timeRange(Duration.ofSeconds(2), Duration.ofSeconds(8))); + + PollerFlux operation2 + = client.beginAnalyze("prebuilt-audioSearch", Arrays.asList(input2)); + + CountDownLatch latch2 = new CountDownLatch(1); + + operation2.last() + .flatMap(pollResponse -> { + if (pollResponse.getStatus().isComplete()) { + return pollResponse.getFinalResult(); + } else { + return Mono.error(new RuntimeException( + "Polling completed unsuccessfully with status: " + pollResponse.getStatus())); + } + }) + .doOnNext(result -> { + AudioVisualContent windowContent = (AudioVisualContent) result.getContents().get(0); + System.out.println("TimeRange(2s, 8s): Start=" + windowContent.getStartTime().toMillis() + + " ms, End=" + windowContent.getEndTime().toMillis() + " ms"); + }) + .doOnError(error -> { + System.err.println("Error occurred: " + error.getMessage()); + }) + .subscribe( + result -> latch2.countDown(), + error -> latch2.countDown() + ); + + try { + latch2.await(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException("Interrupted while waiting for audio analysis", e); + } + + // Analyze with sub-second precision using milliseconds + AnalysisInput input3 = new AnalysisInput(); + input3.setUrl(audioUrl); + input3.setContentRange( + ContentRange.timeRange(Duration.ofMillis(1200), Duration.ofMillis(3651))); + + PollerFlux operation3 + = client.beginAnalyze("prebuilt-audioSearch", Arrays.asList(input3)); + + CountDownLatch latch3 = new CountDownLatch(1); + + operation3.last() + .flatMap(pollResponse -> { + if (pollResponse.getStatus().isComplete()) { + return pollResponse.getFinalResult(); + } else { + return Mono.error(new RuntimeException( + "Polling completed unsuccessfully with status: " + pollResponse.getStatus())); + } + }) + .doOnNext(result -> { + AudioVisualContent subSecondContent = (AudioVisualContent) result.getContents().get(0); + System.out.println("TimeRange(1200ms, 3651ms): Start=" + subSecondContent.getStartTime().toMillis() + + " ms, End=" + subSecondContent.getEndTime().toMillis() + " ms"); + }) + .doOnError(error -> { + System.err.println("Error occurred: " + error.getMessage()); + }) + .subscribe( + result -> latch3.countDown(), + error -> latch3.countDown() + ); + + try { + latch3.await(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException("Interrupted while waiting for audio analysis", e); + } + + // Combine multiple time ranges + AnalysisInput combineInput = new AnalysisInput(); + combineInput.setUrl(audioUrl); + combineInput.setContentRange(ContentRange.combine( + ContentRange.timeRange(Duration.ZERO, Duration.ofSeconds(3)), + ContentRange.timeRangeFrom(Duration.ofSeconds(30)))); + + PollerFlux combineOperation + = client.beginAnalyze("prebuilt-audioSearch", Arrays.asList(combineInput)); + + CountDownLatch latch4 = new CountDownLatch(1); + + combineOperation.last() + .flatMap(pollResponse -> { + if (pollResponse.getStatus().isComplete()) { + return pollResponse.getFinalResult(); + } else { + return Mono.error(new RuntimeException( + "Polling completed unsuccessfully with status: " + pollResponse.getStatus())); + } + }) + .doOnNext(result -> { + AudioVisualContent combineContent = (AudioVisualContent) result.getContents().get(0); + System.out.println("Combined time ranges: Start=" + combineContent.getStartTime().toMillis() + + " ms, End=" + combineContent.getEndTime().toMillis() + " ms"); + }) + .doOnError(error -> System.err.println("Error occurred: " + error.getMessage())) + .subscribe(result -> latch4.countDown(), error -> latch4.countDown()); + + try { + latch4.await(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException("Interrupted while waiting for audio analysis", e); + } + // END:ContentUnderstandingAnalyzeAudioUrlWithContentRangeAsync + } } diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/samples/resources/mixed_financial_invoices.pdf b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/samples/resources/mixed_financial_invoices.pdf new file mode 100644 index 000000000000..793c3d4e24b2 Binary files /dev/null and b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/samples/resources/mixed_financial_invoices.pdf differ diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/ContentRangeTest.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/ContentRangeTest.java index bbaa94b264fa..29572f98a155 100644 --- a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/ContentRangeTest.java +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/ContentRangeTest.java @@ -3,6 +3,7 @@ package com.azure.ai.contentunderstanding.tests; +import com.azure.ai.contentunderstanding.models.AnalysisInput; import com.azure.ai.contentunderstanding.models.ContentRange; import org.junit.jupiter.api.Test; @@ -10,6 +11,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; /** @@ -77,6 +79,11 @@ public void timeRangeMilliseconds() { assertEquals("1000-2000", ContentRange.timeRange(Duration.ofMillis(1000), Duration.ofMillis(2000)).toString()); } + @Test + public void timeRangeSameStartAndEnd() { + assertEquals("1000-1000", ContentRange.timeRange(Duration.ofMillis(1000), Duration.ofMillis(1000)).toString()); + } + @Test public void timeRangeRejectsNegativeStart() { assertThrows(IllegalArgumentException.class, @@ -95,6 +102,12 @@ public void timeRangeFromOpenEnded() { assertEquals("0-", ContentRange.timeRangeFrom(Duration.ZERO).toString()); } + @Test + public void timeRangeFromZeroStartTime() { + ContentRange range = ContentRange.timeRangeFrom(Duration.ZERO); + assertEquals("0-", range.toString()); + } + @Test public void timeRangeFromRejectsNegative() { assertThrows(IllegalArgumentException.class, () -> ContentRange.timeRangeFrom(Duration.ofMillis(-1))); @@ -184,4 +197,100 @@ public void timeRangeDurationRejectsNullEnd() { public void timeRangeFromDurationRejectsNull() { assertThrows(NullPointerException.class, () -> ContentRange.timeRangeFrom((Duration) null)); } + + @Test + public void timeRangeDurationRejectsNegativeStart() { + assertThrows(IllegalArgumentException.class, + () -> ContentRange.timeRange(Duration.ofMillis(-1), Duration.ofSeconds(5))); + } + + @Test + public void timeRangeDurationEndBeforeStart() { + assertThrows(IllegalArgumentException.class, + () -> ContentRange.timeRange(Duration.ofSeconds(5), Duration.ofSeconds(1))); + } + + @Test + public void timeRangeFromDurationRejectsNegative() { + assertThrows(IllegalArgumentException.class, () -> ContentRange.timeRangeFrom(Duration.ofMillis(-1))); + } + + @Test + public void constructorRawStringCustomFormats() { + ContentRange range = new ContentRange("custom-format"); + assertEquals("custom-format", range.toString()); + } + + // =================== AnalysisInput.setContentRange integration =================== + + @Test + public void analysisInputSetContentRangeAcceptsContentRange() { + AnalysisInput input = new AnalysisInput(); + input.setContentRange(ContentRange.pages(1, 3)); + // Verify via JSON round-trip that the range was stored + assertEquals("1-3", extractContentRangeFromJson(input)); + } + + @Test + public void analysisInputContentRangeNullByDefault() { + AnalysisInput input = new AnalysisInput(); + assertNull(extractContentRangeFromJson(input)); + } + + @Test + public void analysisInputContentRangeRoundTrips() { + AnalysisInput input = new AnalysisInput(); + ContentRange range = ContentRange.combine(ContentRange.pages(1, 3), ContentRange.page(5)); + input.setContentRange(range); + assertEquals("1-3,5", extractContentRangeFromJson(input)); + } + + @Test + public void analysisInputSetContentRangeNull() { + AnalysisInput input = new AnalysisInput(); + input.setContentRange(ContentRange.page(1)); + input.setContentRange((ContentRange) null); + assertNull(extractContentRangeFromJson(input)); + } + + /** + * Helper: serialize AnalysisInput to JSON and extract the "range" field value. + * This avoids depending on package-private getContentRange(). + */ + private static String extractContentRangeFromJson(AnalysisInput input) { + try { + java.io.StringWriter sw = new java.io.StringWriter(); + com.azure.json.JsonWriter writer = com.azure.json.JsonProviders.createWriter(sw); + input.toJson(writer); + writer.flush(); + com.azure.json.JsonReader reader = com.azure.json.JsonProviders.createReader(sw.toString()); + AnalysisInput deserialized = AnalysisInput.fromJson(reader); + // Use toJson again to inspect; or parse the JSON directly + java.io.StringWriter sw2 = new java.io.StringWriter(); + com.azure.json.JsonWriter writer2 = com.azure.json.JsonProviders.createWriter(sw2); + deserialized.toJson(writer2); + writer2.flush(); + String json = sw2.toString(); + // Parse "range" field from JSON + int idx = json.indexOf("\"range\""); + if (idx < 0) { + return null; + } + int colon = json.indexOf(':', idx); + int valueStart = colon + 1; + // skip whitespace + while (valueStart < json.length() && json.charAt(valueStart) == ' ') { + valueStart++; + } + if (valueStart >= json.length() || json.charAt(valueStart) == 'n') { + return null; // null value + } + // It's a quoted string + int quoteStart = json.indexOf('"', valueStart); + int quoteEnd = json.indexOf('"', quoteStart + 1); + return json.substring(quoteStart + 1, quoteEnd); + } catch (java.io.IOException e) { + throw new RuntimeException(e); + } + } } diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample01_AnalyzeBinaryAsyncTest.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample01_AnalyzeBinaryAsyncTest.java index 957b84325594..ba472cc293cb 100644 --- a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample01_AnalyzeBinaryAsyncTest.java +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample01_AnalyzeBinaryAsyncTest.java @@ -6,6 +6,7 @@ import com.azure.ai.contentunderstanding.models.AnalysisResult; import com.azure.ai.contentunderstanding.models.ContentAnalyzerAnalyzeOperationStatus; +import com.azure.ai.contentunderstanding.models.ContentRange; import com.azure.ai.contentunderstanding.models.DocumentContent; import com.azure.ai.contentunderstanding.models.DocumentPage; import com.azure.ai.contentunderstanding.models.DocumentTable; @@ -268,4 +269,162 @@ public void testAnalyzeBinaryAsync() throws IOException { } // END:Assertion_ContentUnderstandingAccessDocumentProperties } + + @Test + public void testAnalyzeBinaryWithPageContentRangesAsync() throws IOException { + + // Load the multi-page sample file (10 pages) + String filePath = "src/samples/resources/mixed_financial_invoices.pdf"; + Path path = Paths.get(filePath); + byte[] fileBytes = Files.readAllBytes(path); + BinaryData binaryData = BinaryData.fromBytes(fileBytes); + + // Full analysis for comparison + PollerFlux fullOperation + = contentUnderstandingAsyncClient.beginAnalyzeBinary("prebuilt-documentSearch", binaryData); + AnalysisResult fullResult = fullOperation.last().flatMap(pollResponse -> { + if (pollResponse.getStatus().isComplete()) { + return pollResponse.getFinalResult(); + } else { + return Mono.error( + new RuntimeException("Polling completed unsuccessfully with status: " + pollResponse.getStatus())); + } + }).block(); + DocumentContent fullDoc = (DocumentContent) fullResult.getContents().get(0); + assertEquals(10, fullDoc.getPages().size(), "Full document should return all 10 pages"); + + // BEGIN:ContentUnderstandingAnalyzeBinaryWithSinglePageAsync + // ---- Page(2) — single page ---- + PollerFlux page2Operation + = contentUnderstandingAsyncClient.beginAnalyzeBinary("prebuilt-documentSearch", binaryData, + ContentRange.page(2), "application/octet-stream", null); + AnalysisResult page2Result = page2Operation.last().flatMap(pollResponse -> { + if (pollResponse.getStatus().isComplete()) { + return pollResponse.getFinalResult(); + } else { + return Mono.error( + new RuntimeException("Polling completed unsuccessfully with status: " + pollResponse.getStatus())); + } + }).block(); + DocumentContent page2Doc = (DocumentContent) page2Result.getContents().get(0); + // END:ContentUnderstandingAnalyzeBinaryWithSinglePageAsync + + // BEGIN:Assertion_ContentUnderstandingAnalyzeBinaryWithSinglePageAsync + assertEquals(1, page2Doc.getPages().size(), "Page(2) should return exactly 1 page"); + assertEquals(2, page2Doc.getStartPageNumber(), "Page(2) should start at page 2"); + assertEquals(2, page2Doc.getEndPageNumber(), "Page(2) should end at page 2"); + assertTrue(fullDoc.getMarkdown().length() > page2Doc.getMarkdown().length()); + // END:Assertion_ContentUnderstandingAnalyzeBinaryWithSinglePageAsync + + // BEGIN:ContentUnderstandingAnalyzeBinaryWithPagesAsync + // ---- Pages(1, 3) — page range ---- + PollerFlux pages13Operation + = contentUnderstandingAsyncClient.beginAnalyzeBinary("prebuilt-documentSearch", binaryData, + ContentRange.pages(1, 3), "application/octet-stream", null); + AnalysisResult pages13Result = pages13Operation.last().flatMap(pollResponse -> { + if (pollResponse.getStatus().isComplete()) { + return pollResponse.getFinalResult(); + } else { + return Mono.error( + new RuntimeException("Polling completed unsuccessfully with status: " + pollResponse.getStatus())); + } + }).block(); + DocumentContent pages13Doc = (DocumentContent) pages13Result.getContents().get(0); + // END:ContentUnderstandingAnalyzeBinaryWithPagesAsync + + // BEGIN:Assertion_ContentUnderstandingAnalyzeBinaryWithPagesAsync + assertEquals(3, pages13Doc.getPages().size(), "Pages(1,3) should return exactly 3 pages"); + assertEquals(1, pages13Doc.getStartPageNumber()); + assertEquals(3, pages13Doc.getEndPageNumber()); + assertTrue(fullDoc.getMarkdown().length() > pages13Doc.getMarkdown().length()); + // END:Assertion_ContentUnderstandingAnalyzeBinaryWithPagesAsync + + // BEGIN:ContentUnderstandingAnalyzeBinaryWithCombinedPageAndRangeAsync + // ---- Combine(Page(1), Pages(3, 4)) — combined single page and page range ---- + PollerFlux combineOperation + = contentUnderstandingAsyncClient.beginAnalyzeBinary("prebuilt-documentSearch", binaryData, + ContentRange.combine(ContentRange.page(1), ContentRange.pages(3, 4)), "application/octet-stream", null); + AnalysisResult combineResult = combineOperation.last().flatMap(pollResponse -> { + if (pollResponse.getStatus().isComplete()) { + return pollResponse.getFinalResult(); + } else { + return Mono.error( + new RuntimeException("Polling completed unsuccessfully with status: " + pollResponse.getStatus())); + } + }).block(); + DocumentContent combineDoc = (DocumentContent) combineResult.getContents().get(0); + // END:ContentUnderstandingAnalyzeBinaryWithCombinedPageAndRangeAsync + + // BEGIN:Assertion_ContentUnderstandingAnalyzeBinaryWithCombinedPageAndRangeAsync + assertEquals(3, combineDoc.getPages().size(), "Combine(Page(1), Pages(3,4)) should return exactly 3 pages"); + java.util.List combinePageNumbers = combineDoc.getPages() + .stream() + .map(p -> p.getPageNumber()) + .sorted() + .collect(java.util.stream.Collectors.toList()); + assertEquals(java.util.Arrays.asList(1, 3, 4), combinePageNumbers, + "Combine(Page(1), Pages(3,4)) should extract pages 1, 3, 4"); + assertEquals(1, combineDoc.getStartPageNumber()); + assertEquals(4, combineDoc.getEndPageNumber(), "Combine should end at page 4"); + assertTrue(fullDoc.getMarkdown().length() >= combineDoc.getMarkdown().length()); + // END:Assertion_ContentUnderstandingAnalyzeBinaryWithCombinedPageAndRangeAsync + + // BEGIN:ContentUnderstandingAnalyzeBinaryWithPagesFromAsync + // ---- PagesFrom(3) — extract pages 3 to end ---- + PollerFlux rangeOperation + = contentUnderstandingAsyncClient.beginAnalyzeBinary("prebuilt-documentSearch", binaryData, + ContentRange.pagesFrom(3), "application/octet-stream", null); + AnalysisResult rangeResult = rangeOperation.last().flatMap(pollResponse -> { + if (pollResponse.getStatus().isComplete()) { + return pollResponse.getFinalResult(); + } else { + return Mono.error( + new RuntimeException("Polling completed unsuccessfully with status: " + pollResponse.getStatus())); + } + }).block(); + // END:ContentUnderstandingAnalyzeBinaryWithPagesFromAsync + + // BEGIN:Assertion_ContentUnderstandingAnalyzeBinaryWithPagesFromAsync + assertNotNull(rangeResult); + assertNotNull(rangeResult.getContents()); + DocumentContent rangeDoc = (DocumentContent) rangeResult.getContents().get(0); + assertEquals(8, rangeDoc.getPages().size(), "With ContentRange.pagesFrom(3), should return only 8 pages"); + assertEquals(3, rangeDoc.getStartPageNumber(), "pagesFrom(3) should start at page 3"); + assertEquals(10, rangeDoc.getEndPageNumber(), "pagesFrom(3) should end at page 10"); + assertTrue(fullDoc.getPages().size() > rangeDoc.getPages().size()); + assertTrue(fullDoc.getMarkdown().length() > rangeDoc.getMarkdown().length()); + // END:Assertion_ContentUnderstandingAnalyzeBinaryWithPagesFromAsync + + // BEGIN:ContentUnderstandingAnalyzeBinaryWithCombinedPagesAsync + // ---- Combine(Pages(1,3), Page(5), PagesFrom(9)) — combined page ranges ---- + PollerFlux bigCombineOperation + = contentUnderstandingAsyncClient.beginAnalyzeBinary("prebuilt-documentSearch", binaryData, + ContentRange.combine(ContentRange.pages(1, 3), ContentRange.page(5), ContentRange.pagesFrom(9)), + "application/octet-stream", null); + AnalysisResult bigCombineResult = bigCombineOperation.last().flatMap(pollResponse -> { + if (pollResponse.getStatus().isComplete()) { + return pollResponse.getFinalResult(); + } else { + return Mono.error( + new RuntimeException("Polling completed unsuccessfully with status: " + pollResponse.getStatus())); + } + }).block(); + DocumentContent bigCombineDoc = (DocumentContent) bigCombineResult.getContents().get(0); + // END:ContentUnderstandingAnalyzeBinaryWithCombinedPagesAsync + + // BEGIN:Assertion_ContentUnderstandingAnalyzeBinaryWithCombinedPagesAsync + assertNotNull(bigCombineResult); + assertNotNull(bigCombineResult.getContents()); + assertEquals(6, bigCombineDoc.getPages().size(), + "Combine(Pages(1,3), Page(5), PagesFrom(9)) should return exactly 6 pages"); + java.util.List combineRangePageNumbers = bigCombineDoc.getPages() + .stream() + .map(p -> p.getPageNumber()) + .sorted() + .collect(java.util.stream.Collectors.toList()); + assertEquals(java.util.Arrays.asList(1, 2, 3, 5, 9, 10), combineRangePageNumbers, + "Combine(Pages(1,3), Page(5), PagesFrom(9)) should extract pages 1, 2, 3, 5, 9, 10"); + assertTrue(fullDoc.getMarkdown().length() >= bigCombineDoc.getMarkdown().length()); + // END:Assertion_ContentUnderstandingAnalyzeBinaryWithCombinedPagesAsync + } } diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample01_AnalyzeBinaryTest.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample01_AnalyzeBinaryTest.java index cfcff1e2ccc1..9eecf856f757 100644 --- a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample01_AnalyzeBinaryTest.java +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample01_AnalyzeBinaryTest.java @@ -6,6 +6,7 @@ import com.azure.ai.contentunderstanding.models.AnalysisResult; import com.azure.ai.contentunderstanding.models.ContentAnalyzerAnalyzeOperationStatus; +import com.azure.ai.contentunderstanding.models.ContentRange; import com.azure.ai.contentunderstanding.models.DocumentContent; import com.azure.ai.contentunderstanding.models.DocumentPage; import com.azure.ai.contentunderstanding.models.DocumentTable; @@ -259,4 +260,121 @@ public void testAnalyzeBinary() throws IOException { } // END:Assertion_ContentUnderstandingAccessDocumentProperties } + + @Test + public void testAnalyzeBinaryWithPageContentRanges() throws IOException { + + // Load the multi-page sample file (10 pages) + String filePath = "src/samples/resources/mixed_financial_invoices.pdf"; + Path path = Paths.get(filePath); + byte[] fileBytes = Files.readAllBytes(path); + BinaryData binaryData = BinaryData.fromBytes(fileBytes); + + // Full analysis for comparison + SyncPoller fullOperation + = contentUnderstandingClient.beginAnalyzeBinary("prebuilt-documentSearch", binaryData); + AnalysisResult fullResult = fullOperation.getFinalResult(); + DocumentContent fullDoc = (DocumentContent) fullResult.getContents().get(0); + assertEquals(10, fullDoc.getPages().size(), "Full document should return all 10 pages"); + + // BEGIN:ContentUnderstandingAnalyzeBinaryWithSinglePage + // ---- Page(2) — single page ---- + SyncPoller page2Operation + = contentUnderstandingClient.beginAnalyzeBinary("prebuilt-documentSearch", binaryData, ContentRange.page(2), + "application/octet-stream", null); + DocumentContent page2Doc = (DocumentContent) page2Operation.getFinalResult().getContents().get(0); + // END:ContentUnderstandingAnalyzeBinaryWithSinglePage + + // BEGIN:Assertion_ContentUnderstandingAnalyzeBinaryWithSinglePage + assertEquals(1, page2Doc.getPages().size(), "Page(2) should return exactly 1 page"); + assertEquals(2, page2Doc.getStartPageNumber(), "Page(2) should start at page 2"); + assertEquals(2, page2Doc.getEndPageNumber(), "Page(2) should end at page 2"); + assertTrue(fullDoc.getMarkdown().length() > page2Doc.getMarkdown().length()); + // END:Assertion_ContentUnderstandingAnalyzeBinaryWithSinglePage + + // BEGIN:ContentUnderstandingAnalyzeBinaryWithPages + // ---- Pages(1, 3) — page range ---- + SyncPoller pages13Operation + = contentUnderstandingClient.beginAnalyzeBinary("prebuilt-documentSearch", binaryData, + ContentRange.pages(1, 3), "application/octet-stream", null); + DocumentContent pages13Doc = (DocumentContent) pages13Operation.getFinalResult().getContents().get(0); + // END:ContentUnderstandingAnalyzeBinaryWithPages + + // BEGIN:Assertion_ContentUnderstandingAnalyzeBinaryWithPages + assertEquals(3, pages13Doc.getPages().size(), "Pages(1,3) should return exactly 3 pages"); + assertEquals(1, pages13Doc.getStartPageNumber()); + assertEquals(3, pages13Doc.getEndPageNumber()); + assertTrue(fullDoc.getMarkdown().length() > pages13Doc.getMarkdown().length()); + // END:Assertion_ContentUnderstandingAnalyzeBinaryWithPages + + // BEGIN:ContentUnderstandingAnalyzeBinaryWithCombinedPageAndRange + // ---- Combine(Page(1), Pages(3, 4)) — combined single page and page range ---- + SyncPoller combineOperation + = contentUnderstandingClient.beginAnalyzeBinary("prebuilt-documentSearch", binaryData, + ContentRange.combine(ContentRange.page(1), ContentRange.pages(3, 4)), "application/octet-stream", null); + DocumentContent combineDoc = (DocumentContent) combineOperation.getFinalResult().getContents().get(0); + // END:ContentUnderstandingAnalyzeBinaryWithCombinedPageAndRange + + // BEGIN:Assertion_ContentUnderstandingAnalyzeBinaryWithCombinedPageAndRange + assertEquals(3, combineDoc.getPages().size(), "Combine(Page(1), Pages(3,4)) should return exactly 3 pages"); + java.util.List combinePageNumbers = combineDoc.getPages() + .stream() + .map(p -> p.getPageNumber()) + .sorted() + .collect(java.util.stream.Collectors.toList()); + assertEquals(java.util.Arrays.asList(1, 3, 4), combinePageNumbers, + "Combine(Page(1), Pages(3,4)) should extract pages 1, 3, 4"); + assertEquals(1, combineDoc.getStartPageNumber()); + assertEquals(4, combineDoc.getEndPageNumber(), "Combine should end at page 4"); + assertTrue(fullDoc.getMarkdown().length() >= combineDoc.getMarkdown().length()); + // END:Assertion_ContentUnderstandingAnalyzeBinaryWithCombinedPageAndRange + + // BEGIN:ContentUnderstandingAnalyzeBinaryWithPagesFrom + // ---- PagesFrom(3) — extract pages 3 to end ---- + SyncPoller rangeOperation + = contentUnderstandingClient.beginAnalyzeBinary("prebuilt-documentSearch", binaryData, + ContentRange.pagesFrom(3), "application/octet-stream", null); + AnalysisResult rangeResult = rangeOperation.getFinalResult(); + // END:ContentUnderstandingAnalyzeBinaryWithPagesFrom + + // BEGIN:Assertion_ContentUnderstandingAnalyzeBinaryWithPagesFrom + assertNotNull(rangeOperation); + assertTrue(rangeOperation.waitForCompletion().getStatus().isComplete()); + assertNotNull(rangeResult); + assertNotNull(rangeResult.getContents()); + DocumentContent rangeDoc = (DocumentContent) rangeResult.getContents().get(0); + assertEquals(8, rangeDoc.getPages().size(), "With ContentRange.pagesFrom(3), should return only 8 pages"); + assertEquals(3, rangeDoc.getStartPageNumber(), "pagesFrom(3) should start at page 3"); + assertEquals(10, rangeDoc.getEndPageNumber(), "pagesFrom(3) should end at page 10"); + assertTrue(fullDoc.getPages().size() > rangeDoc.getPages().size()); + assertTrue(fullDoc.getMarkdown().length() > rangeDoc.getMarkdown().length()); + // END:Assertion_ContentUnderstandingAnalyzeBinaryWithPagesFrom + + // BEGIN:ContentUnderstandingAnalyzeBinaryWithCombinedPages + // ---- Combine(Pages(1,3), Page(5), PagesFrom(9)) — combined page ranges ---- + SyncPoller combineRangeOperation + = contentUnderstandingClient.beginAnalyzeBinary("prebuilt-documentSearch", binaryData, + ContentRange.combine(ContentRange.pages(1, 3), ContentRange.page(5), ContentRange.pagesFrom(9)), + "application/octet-stream", null); + AnalysisResult combineRangeResult = combineRangeOperation.getFinalResult(); + // END:ContentUnderstandingAnalyzeBinaryWithCombinedPages + + // BEGIN:Assertion_ContentUnderstandingAnalyzeBinaryWithCombinedPages + assertNotNull(combineRangeOperation); + assertTrue(combineRangeOperation.waitForCompletion().getStatus().isComplete()); + assertNotNull(combineRangeResult); + assertNotNull(combineRangeResult.getContents()); + DocumentContent combineRangeDoc = (DocumentContent) combineRangeResult.getContents().get(0); + assertEquals(6, combineRangeDoc.getPages().size(), + "Combine(Pages(1,3), Page(5), PagesFrom(9)) should return exactly 6 pages"); + java.util.List combineRangePageNumbers = combineRangeDoc.getPages() + .stream() + .map(p -> p.getPageNumber()) + .sorted() + .collect(java.util.stream.Collectors.toList()); + assertEquals(java.util.Arrays.asList(1, 2, 3, 5, 9, 10), combineRangePageNumbers, + "Combine(Pages(1,3), Page(5), PagesFrom(9)) should extract pages 1, 2, 3, 5, 9, 10"); + assertTrue(fullDoc.getMarkdown().length() >= combineRangeDoc.getMarkdown().length()); + // END:Assertion_ContentUnderstandingAnalyzeBinaryWithCombinedPages + } } diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample02_AnalyzeUrlAsyncTest.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample02_AnalyzeUrlAsyncTest.java index 5b146e03d78d..9a632d4e1d5c 100644 --- a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample02_AnalyzeUrlAsyncTest.java +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample02_AnalyzeUrlAsyncTest.java @@ -8,12 +8,14 @@ import com.azure.ai.contentunderstanding.models.AnalysisResult; import com.azure.ai.contentunderstanding.models.AudioVisualContent; import com.azure.ai.contentunderstanding.models.ContentAnalyzerAnalyzeOperationStatus; +import com.azure.ai.contentunderstanding.models.ContentRange; import com.azure.ai.contentunderstanding.models.DocumentContent; import com.azure.ai.contentunderstanding.models.DocumentPage; import com.azure.ai.contentunderstanding.models.DocumentTable; import com.azure.ai.contentunderstanding.models.DocumentTableCell; import com.azure.ai.contentunderstanding.models.AnalysisContent; import com.azure.ai.contentunderstanding.models.TranscriptPhrase; +import com.azure.core.test.annotation.LiveOnly; import com.azure.core.util.polling.PollerFlux; import org.junit.jupiter.api.Test; import reactor.core.publisher.Mono; @@ -23,6 +25,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.time.Duration; import java.util.Arrays; import java.util.HashSet; import java.util.List; @@ -41,7 +44,7 @@ public class Sample02_AnalyzeUrlAsyncTest extends ContentUnderstandingClientTest @Test public void testAnalyzeUrlAsync() { - // BEGIN:ContentUnderstandingAnalyzeUrlAsyncAsync + // BEGIN:ContentUnderstandingAnalyzeUrlAsync // Using a publicly accessible sample file from Azure-Samples GitHub repository String uriSource = "https://raw.githubusercontent.com/Azure-Samples/azure-ai-content-understanding-dotnet/main/ContentUnderstanding.Common/data/invoice.pdf"; @@ -62,9 +65,9 @@ public void testAnalyzeUrlAsync() { new RuntimeException("Polling completed unsuccessfully with status: " + pollResponse.getStatus())); } }).block(); // block() is used here for testing; in production, use subscribe() - // END:ContentUnderstandingAnalyzeUrlAsyncAsync + // END:ContentUnderstandingAnalyzeUrlAsync - // BEGIN:Assertion_ContentUnderstandingAnalyzeUrlAsyncAsync + // BEGIN:Assertion_ContentUnderstandingAnalyzeUrlAsync assertNotNull(uriSource, "URI source should not be null"); assertNotNull(operation, "Analysis operation should not be null"); assertNotNull(result, "Analysis result should not be null"); @@ -72,7 +75,7 @@ public void testAnalyzeUrlAsync() { System.out.println("Analysis operation properties verified"); System.out.println("Analysis result contains " + (result.getContents() != null ? result.getContents().size() : 0) + " content(s)"); - // END:Assertion_ContentUnderstandingAnalyzeUrlAsyncAsync + // END:Assertion_ContentUnderstandingAnalyzeUrlAsync // A PDF file has only one content element even if it contains multiple pages AnalysisContent content = null; @@ -243,7 +246,7 @@ public void testAnalyzeUrlAsync() { @Test public void testAnalyzeVideoUrlAsync() { - // BEGIN:ContentUnderstandingAnalyzeVideoUrlAsyncAsync + // BEGIN:ContentUnderstandingAnalyzeVideoUrlAsync String uriSource = "https://raw.githubusercontent.com/Azure-Samples/azure-ai-content-understanding-assets/main/videos/sdk_samples/FlightSimulator.mp4"; @@ -289,9 +292,9 @@ public void testAnalyzeVideoUrlAsync() { System.out.println("---------------------"); segmentIndex++; } - // END:ContentUnderstandingAnalyzeVideoUrlAsyncAsync + // END:ContentUnderstandingAnalyzeVideoUrlAsync - // BEGIN:Assertion_ContentUnderstandingAnalyzeVideoUrlAsyncAsync + // BEGIN:Assertion_ContentUnderstandingAnalyzeVideoUrlAsync assertNotNull(operation, "Analysis operation should not be null"); assertNotNull(result, "Analysis result should not be null"); assertNotNull(result.getContents(), "Result contents should not be null"); @@ -308,12 +311,12 @@ public void testAnalyzeVideoUrlAsync() { assertFalse(summaryStr.trim().isEmpty(), "Summary should not be empty"); } System.out.println("Video analysis validation completed successfully"); - // END:Assertion_ContentUnderstandingAnalyzeVideoUrlAsyncAsync + // END:Assertion_ContentUnderstandingAnalyzeVideoUrlAsync } @Test public void testAnalyzeAudioUrlAsync() { - // BEGIN:ContentUnderstandingAnalyzeAudioUrlAsyncAsync + // BEGIN:ContentUnderstandingAnalyzeAudioUrlAsync String uriSource = "https://raw.githubusercontent.com/Azure-Samples/azure-ai-content-understanding-assets/main/audio/callCenterRecording.mp3"; @@ -362,9 +365,9 @@ public void testAnalyzeAudioUrlAsync() { count++; } } - // END:ContentUnderstandingAnalyzeAudioUrlAsyncAsync + // END:ContentUnderstandingAnalyzeAudioUrlAsync - // BEGIN:Assertion_ContentUnderstandingAnalyzeAudioUrlAsyncAsync + // BEGIN:Assertion_ContentUnderstandingAnalyzeAudioUrlAsync assertNotNull(operation, "Analysis operation should not be null"); assertNotNull(result, "Analysis result should not be null"); assertNotNull(result.getContents(), "Result contents should not be null"); @@ -384,12 +387,12 @@ public void testAnalyzeAudioUrlAsync() { assertFalse(summaryStr.trim().isEmpty(), "Summary should not be empty"); } System.out.println("Audio analysis validation completed successfully"); - // END:Assertion_ContentUnderstandingAnalyzeAudioUrlAsyncAsync + // END:Assertion_ContentUnderstandingAnalyzeAudioUrlAsync } @Test public void testAnalyzeImageUrlAsync() { - // BEGIN:ContentUnderstandingAnalyzeImageUrlAsyncAsync + // BEGIN:ContentUnderstandingAnalyzeImageUrlAsync String uriSource = "https://raw.githubusercontent.com/Azure-Samples/azure-ai-content-understanding-assets/main/image/pieChart.jpg"; @@ -420,9 +423,9 @@ public void testAnalyzeImageUrlAsync() { : "") : ""; System.out.println("Summary: " + summary); - // END:ContentUnderstandingAnalyzeImageUrlAsyncAsync + // END:ContentUnderstandingAnalyzeImageUrlAsync - // BEGIN:Assertion_ContentUnderstandingAnalyzeImageUrlAsyncAsync + // BEGIN:Assertion_ContentUnderstandingAnalyzeImageUrlAsync assertNotNull(operation, "Analysis operation should not be null"); assertNotNull(result, "Analysis result should not be null"); assertNotNull(result.getContents(), "Result contents should not be null"); @@ -437,6 +440,382 @@ public void testAnalyzeImageUrlAsync() { assertFalse(summaryStr.trim().isEmpty(), "Summary should not be empty"); } System.out.println("Image analysis validation completed successfully"); - // END:Assertion_ContentUnderstandingAnalyzeImageUrlAsyncAsync + // END:Assertion_ContentUnderstandingAnalyzeImageUrlAsync + } + + @Test + public void testAnalyzeUrlWithPageContentRangesAsync() { + + // BEGIN:ContentUnderstandingAnalyzeUrlWithPageContentRangesAsync + String uriSource + = "https://raw.githubusercontent.com/Azure-Samples/azure-ai-content-understanding-assets/main/document/mixed_financial_invoices.pdf"; + + // Full document analysis for comparison + AnalysisInput fullInput = new AnalysisInput(); + fullInput.setUrl(uriSource); + + PollerFlux fullOperation + = contentUnderstandingAsyncClient.beginAnalyze("prebuilt-documentSearch", Arrays.asList(fullInput)); + AnalysisResult fullResult = fullOperation.last().flatMap(pollResponse -> { + if (pollResponse.getStatus().isComplete()) { + return pollResponse.getFinalResult(); + } else { + return Mono.error( + new RuntimeException("Polling completed unsuccessfully with status: " + pollResponse.getStatus())); + } + }).block(); + DocumentContent fullDoc = (DocumentContent) fullResult.getContents().get(0); + + // Extract only page 1 via AnalysisInput.setContentRange() + AnalysisInput rangeInput = new AnalysisInput(); + rangeInput.setUrl(uriSource); + rangeInput.setContentRange(ContentRange.page(1)); + + PollerFlux rangeOperation + = contentUnderstandingAsyncClient.beginAnalyze("prebuilt-documentSearch", Arrays.asList(rangeInput)); + AnalysisResult rangeResult = rangeOperation.last().flatMap(pollResponse -> { + if (pollResponse.getStatus().isComplete()) { + return pollResponse.getFinalResult(); + } else { + return Mono.error( + new RuntimeException("Polling completed unsuccessfully with status: " + pollResponse.getStatus())); + } + }).block(); + // END:ContentUnderstandingAnalyzeUrlWithPageContentRangesAsync + + // BEGIN:Assertion_ContentUnderstandingAnalyzeUrlWithPageContentRangesAsync + assertEquals(10, fullDoc.getPages().size(), "Full document should return all 10 pages"); + assertNotNull(rangeResult); + assertNotNull(rangeResult.getContents()); + DocumentContent rangeDocContent = (DocumentContent) rangeResult.getContents().get(0); + assertEquals(1, rangeDocContent.getPages().size(), "With ContentRange.page(1), should return only 1 page"); + assertEquals(1, rangeDocContent.getStartPageNumber()); + assertEquals(1, rangeDocContent.getEndPageNumber()); + assertTrue(fullDoc.getPages().size() > rangeDocContent.getPages().size()); + assertTrue(fullDoc.getMarkdown().length() > rangeDocContent.getMarkdown().length()); + // END:Assertion_ContentUnderstandingAnalyzeUrlWithPageContentRangesAsync + + // Combined disjoint page ranges: pages 1-3, page 5, and pages 9 onward + // Document has 10 pages, so pages 1-3, 5, 9-10 match (6 pages total) + AnalysisInput combineInput = new AnalysisInput(); + combineInput.setUrl(uriSource); + combineInput.setContentRange( + ContentRange.combine(ContentRange.pages(1, 3), ContentRange.page(5), ContentRange.pagesFrom(9))); + + PollerFlux combineOperation + = contentUnderstandingAsyncClient.beginAnalyze("prebuilt-documentSearch", Arrays.asList(combineInput)); + AnalysisResult combineResult = combineOperation.last().flatMap(pollResponse -> { + if (pollResponse.getStatus().isComplete()) { + return pollResponse.getFinalResult(); + } else { + return Mono.error( + new RuntimeException("Polling completed unsuccessfully with status: " + pollResponse.getStatus())); + } + }).block(); + + assertNotNull(combineResult); + assertNotNull(combineResult.getContents()); + DocumentContent combineDoc = (DocumentContent) combineResult.getContents().get(0); + assertEquals(6, combineDoc.getPages().size(), + "Combine(1-3, 5, 9-) should return 6 pages (1-3, 5, 9-10), got " + combineDoc.getPages().size()); + assertEquals(1, combineDoc.getStartPageNumber()); + assertEquals(10, combineDoc.getEndPageNumber()); + List combinePageNumbers = new java.util.ArrayList<>(); + for (DocumentPage page : combineDoc.getPages()) { + combinePageNumbers.add(page.getPageNumber()); + } + java.util.Collections.sort(combinePageNumbers); + assertEquals(Arrays.asList(1, 2, 3, 5, 9, 10), combinePageNumbers, + "Combine(1-3, 5, 9-) page numbers should be [1, 2, 3, 5, 9, 10]"); + } + + @LiveOnly + @Test + public void testAnalyzeVideoUrlWithTimeContentRangesAsync() { + + // BEGIN:ContentUnderstandingAnalyzeVideoUrlWithTimeContentRangesAsync + String uriSource + = "https://raw.githubusercontent.com/Azure-Samples/azure-ai-content-understanding-assets/main/videos/sdk_samples/FlightSimulator.mp4"; + + // Full analysis for comparison + AnalysisInput fullInput = new AnalysisInput(); + fullInput.setUrl(uriSource); + + PollerFlux fullOperation + = contentUnderstandingAsyncClient.beginAnalyze("prebuilt-videoSearch", Arrays.asList(fullInput)); + AnalysisResult fullResult = fullOperation.last().flatMap(pollResponse -> { + if (pollResponse.getStatus().isComplete()) { + return pollResponse.getFinalResult(); + } else { + return Mono.error( + new RuntimeException("Polling completed unsuccessfully with status: " + pollResponse.getStatus())); + } + }).block(); + + // ---- TimeRange(0, 5s) — first 5 seconds ---- + AnalysisInput rangeInput = new AnalysisInput(); + rangeInput.setUrl(uriSource); + rangeInput.setContentRange(ContentRange.timeRange(Duration.ZERO, Duration.ofSeconds(5))); + + PollerFlux rangeOperation + = contentUnderstandingAsyncClient.beginAnalyze("prebuilt-videoSearch", Arrays.asList(rangeInput)); + AnalysisResult rangeResult = rangeOperation.last().flatMap(pollResponse -> { + if (pollResponse.getStatus().isComplete()) { + return pollResponse.getFinalResult(); + } else { + return Mono.error( + new RuntimeException("Polling completed unsuccessfully with status: " + pollResponse.getStatus())); + } + }).block(); + // END:ContentUnderstandingAnalyzeVideoUrlWithTimeContentRangesAsync + + // BEGIN:Assertion_ContentUnderstandingAnalyzeVideoUrlWithTimeContentRangesAsync + assertNotNull(fullResult); + assertNotNull(fullResult.getContents()); + assertTrue(fullResult.getContents().size() > 0); + + assertNotNull(rangeResult); + assertNotNull(rangeResult.getContents()); + assertTrue(rangeResult.getContents().size() > 0); + for (AnalysisContent content : rangeResult.getContents()) { + assertTrue(content instanceof AudioVisualContent, "Video analysis should return AudioVisualContent"); + AudioVisualContent avContent = (AudioVisualContent) content; + assertTrue(avContent.getEndTime().toMillis() > avContent.getStartTime().toMillis()); + assertTrue(avContent.getStartTime().toMillis() >= 0, + "Range(0-5s) segment StartTime (" + avContent.getStartTime().toMillis() + " ms) should be >= 0 ms"); + assertTrue(avContent.getEndTime().toMillis() <= 5000, + "Range(0-5s) segment EndTime (" + avContent.getEndTime().toMillis() + " ms) should be <= 5000 ms"); + } + // END:Assertion_ContentUnderstandingAnalyzeVideoUrlWithTimeContentRangesAsync + + // ---- TimeRangeFrom(10s) — from 10 seconds to end ---- + AnalysisInput rangeFromInput = new AnalysisInput(); + rangeFromInput.setUrl(uriSource); + rangeFromInput.setContentRange(ContentRange.timeRangeFrom(Duration.ofSeconds(10))); + + PollerFlux rangeFromOperation + = contentUnderstandingAsyncClient.beginAnalyze("prebuilt-videoSearch", Arrays.asList(rangeFromInput)); + AnalysisResult rangeFromResult = rangeFromOperation.last().flatMap(pollResponse -> { + if (pollResponse.getStatus().isComplete()) { + return pollResponse.getFinalResult(); + } else { + return Mono.error( + new RuntimeException("Polling completed unsuccessfully with status: " + pollResponse.getStatus())); + } + }).block(); + + assertNotNull(rangeFromResult); + assertNotNull(rangeFromResult.getContents()); + assertTrue(rangeFromResult.getContents().size() > 0); + for (AnalysisContent content : rangeFromResult.getContents()) { + assertTrue(content instanceof AudioVisualContent); + AudioVisualContent avContent = (AudioVisualContent) content; + assertTrue(avContent.getEndTime().toMillis() > avContent.getStartTime().toMillis()); + assertTrue(avContent.getStartTime().toMillis() >= 10000, "TimeRangeFrom(10s) segment StartTime (" + + avContent.getStartTime().toMillis() + " ms) should be >= 10000 ms"); + assertNotNull(avContent.getMarkdown()); + assertFalse(avContent.getMarkdown().isEmpty()); + } + + // ---- TimeRange sub-second precision (1200ms-3651ms) ---- + AnalysisInput subSecondInput = new AnalysisInput(); + subSecondInput.setUrl(uriSource); + subSecondInput.setContentRange(ContentRange.timeRange(Duration.ofMillis(1200), Duration.ofMillis(3651))); + + PollerFlux subSecondOperation + = contentUnderstandingAsyncClient.beginAnalyze("prebuilt-videoSearch", Arrays.asList(subSecondInput)); + AnalysisResult subSecondResult = subSecondOperation.last().flatMap(pollResponse -> { + if (pollResponse.getStatus().isComplete()) { + return pollResponse.getFinalResult(); + } else { + return Mono.error( + new RuntimeException("Polling completed unsuccessfully with status: " + pollResponse.getStatus())); + } + }).block(); + + assertNotNull(subSecondResult); + assertNotNull(subSecondResult.getContents()); + assertTrue(subSecondResult.getContents().size() > 0); + for (AnalysisContent content : subSecondResult.getContents()) { + assertTrue(content instanceof AudioVisualContent); + AudioVisualContent avContent = (AudioVisualContent) content; + assertTrue(avContent.getEndTime().toMillis() > avContent.getStartTime().toMillis()); + assertTrue(avContent.getStartTime().toMillis() >= 1200, "Range(1200ms-3651ms) segment StartTime (" + + avContent.getStartTime().toMillis() + " ms) should be >= 1200 ms"); + assertTrue(avContent.getEndTime().toMillis() <= 3651, "Range(1200ms-3651ms) segment EndTime (" + + avContent.getEndTime().toMillis() + " ms) should be <= 3651 ms"); + } + + // ---- Combine multiple time ranges (0-3s, 30s-) ---- + AnalysisInput combineInput = new AnalysisInput(); + combineInput.setUrl(uriSource); + combineInput.setContentRange(ContentRange.combine(ContentRange.timeRange(Duration.ZERO, Duration.ofSeconds(3)), + ContentRange.timeRangeFrom(Duration.ofSeconds(30)))); + + PollerFlux combineOperation + = contentUnderstandingAsyncClient.beginAnalyze("prebuilt-videoSearch", Arrays.asList(combineInput)); + AnalysisResult combineResult = combineOperation.last().flatMap(pollResponse -> { + if (pollResponse.getStatus().isComplete()) { + return pollResponse.getFinalResult(); + } else { + return Mono.error( + new RuntimeException("Polling completed unsuccessfully with status: " + pollResponse.getStatus())); + } + }).block(); + + assertNotNull(combineResult); + assertNotNull(combineResult.getContents()); + assertTrue(combineResult.getContents().size() > 0); + for (AnalysisContent content : combineResult.getContents()) { + assertTrue(content instanceof AudioVisualContent); + AudioVisualContent avContent = (AudioVisualContent) content; + assertTrue(avContent.getEndTime().toMillis() > avContent.getStartTime().toMillis()); + assertTrue(avContent.getEndTime().toMillis() <= 3000 || avContent.getStartTime().toMillis() >= 30000, + "Combine(0-3s, 30s-) segment should be in [0,3s] or [30s,∞), but was " + + avContent.getStartTime().toMillis() + "-" + avContent.getEndTime().toMillis() + " ms"); + assertNotNull(avContent.getMarkdown()); + assertFalse(avContent.getMarkdown().isEmpty()); + } + + } + + @LiveOnly + @Test + public void testAnalyzeAudioUrlWithTimeContentRangesAsync() { + + // BEGIN:ContentUnderstandingAnalyzeAudioUrlWithTimeContentRangesAsync + String uriSource + = "https://raw.githubusercontent.com/Azure-Samples/azure-ai-content-understanding-assets/main/audio/callCenterRecording.mp3"; + + // Full analysis for comparison + AnalysisInput fullInput = new AnalysisInput(); + fullInput.setUrl(uriSource); + + PollerFlux fullOperation + = contentUnderstandingAsyncClient.beginAnalyze("prebuilt-audioSearch", Arrays.asList(fullInput)); + AnalysisResult fullResult = fullOperation.last().flatMap(pollResponse -> { + if (pollResponse.getStatus().isComplete()) { + return pollResponse.getFinalResult(); + } else { + return Mono.error( + new RuntimeException("Polling completed unsuccessfully with status: " + pollResponse.getStatus())); + } + }).block(); + AudioVisualContent fullAudioContent = (AudioVisualContent) fullResult.getContents().get(0); + long fullDurationMs = fullAudioContent.getEndTime().toMillis() - fullAudioContent.getStartTime().toMillis(); + + // ---- TimeRangeFrom(5s) — from 5 seconds to end ---- + AnalysisInput rangeInput = new AnalysisInput(); + rangeInput.setUrl(uriSource); + rangeInput.setContentRange(ContentRange.timeRangeFrom(Duration.ofSeconds(5))); + + PollerFlux rangeOperation + = contentUnderstandingAsyncClient.beginAnalyze("prebuilt-audioSearch", Arrays.asList(rangeInput)); + AnalysisResult rangeResult = rangeOperation.last().flatMap(pollResponse -> { + if (pollResponse.getStatus().isComplete()) { + return pollResponse.getFinalResult(); + } else { + return Mono.error( + new RuntimeException("Polling completed unsuccessfully with status: " + pollResponse.getStatus())); + } + }).block(); + // END:ContentUnderstandingAnalyzeAudioUrlWithTimeContentRangesAsync + + // BEGIN:Assertion_ContentUnderstandingAnalyzeAudioUrlWithTimeContentRangesAsync + assertNotNull(rangeResult); + assertNotNull(rangeResult.getContents()); + assertTrue(rangeResult.getContents().size() > 0); + AudioVisualContent rangeAudioContent = (AudioVisualContent) rangeResult.getContents().get(0); + assertTrue(rangeAudioContent instanceof AudioVisualContent); + assertTrue(fullAudioContent.getMarkdown().length() >= rangeAudioContent.getMarkdown().length()); + int fullPhraseCount + = fullAudioContent.getTranscriptPhrases() != null ? fullAudioContent.getTranscriptPhrases().size() : 0; + int rangePhraseCount + = rangeAudioContent.getTranscriptPhrases() != null ? rangeAudioContent.getTranscriptPhrases().size() : 0; + assertTrue(fullPhraseCount >= rangePhraseCount); + long rangeDurationMs = rangeAudioContent.getEndTime().toMillis() - rangeAudioContent.getStartTime().toMillis(); + assertTrue(fullDurationMs >= rangeDurationMs); + assertTrue(rangeAudioContent.getStartTime().toMillis() >= 5000, "TimeRangeFrom(5s) audio StartTime (" + + rangeAudioContent.getStartTime().toMillis() + " ms) should be >= 5000 ms"); + // END:Assertion_ContentUnderstandingAnalyzeAudioUrlWithTimeContentRangesAsync + + // ---- TimeRange(2s, 8s) — specific time window ---- + AnalysisInput windowInput = new AnalysisInput(); + windowInput.setUrl(uriSource); + windowInput.setContentRange(ContentRange.timeRange(Duration.ofSeconds(2), Duration.ofSeconds(8))); + + PollerFlux windowOperation + = contentUnderstandingAsyncClient.beginAnalyze("prebuilt-audioSearch", Arrays.asList(windowInput)); + AnalysisResult windowResult = windowOperation.last().flatMap(pollResponse -> { + if (pollResponse.getStatus().isComplete()) { + return pollResponse.getFinalResult(); + } else { + return Mono.error( + new RuntimeException("Polling completed unsuccessfully with status: " + pollResponse.getStatus())); + } + }).block(); + AudioVisualContent audioWindowContent = (AudioVisualContent) windowResult.getContents().get(0); + + assertTrue(audioWindowContent.getEndTime().toMillis() > audioWindowContent.getStartTime().toMillis()); + assertTrue(audioWindowContent.getMarkdown().length() > 0); + assertTrue(audioWindowContent.getStartTime().toMillis() >= 2000, "Range(2s-8s) audio StartTime (" + + audioWindowContent.getStartTime().toMillis() + " ms) should be >= 2000 ms"); + assertTrue(audioWindowContent.getEndTime().toMillis() <= 8000, + "Range(2s-8s) audio EndTime (" + audioWindowContent.getEndTime().toMillis() + " ms) should be <= 8000 ms"); + long windowDurationMs + = audioWindowContent.getEndTime().toMillis() - audioWindowContent.getStartTime().toMillis(); + assertTrue(fullDurationMs >= windowDurationMs); + + // ---- TimeRange sub-second precision (1200ms-3651ms) ---- + AnalysisInput subSecondInput = new AnalysisInput(); + subSecondInput.setUrl(uriSource); + subSecondInput.setContentRange(ContentRange.timeRange(Duration.ofMillis(1200), Duration.ofMillis(3651))); + + PollerFlux subSecondOperation + = contentUnderstandingAsyncClient.beginAnalyze("prebuilt-audioSearch", Arrays.asList(subSecondInput)); + AnalysisResult subSecondResult = subSecondOperation.last().flatMap(pollResponse -> { + if (pollResponse.getStatus().isComplete()) { + return pollResponse.getFinalResult(); + } else { + return Mono.error( + new RuntimeException("Polling completed unsuccessfully with status: " + pollResponse.getStatus())); + } + }).block(); + AudioVisualContent audioSubSecondContent = (AudioVisualContent) subSecondResult.getContents().get(0); + + assertTrue(audioSubSecondContent.getEndTime().toMillis() > audioSubSecondContent.getStartTime().toMillis()); + assertTrue(audioSubSecondContent.getMarkdown().length() > 0); + assertTrue(audioSubSecondContent.getStartTime().toMillis() >= 1200, "Range(1200ms-3651ms) audio StartTime (" + + audioSubSecondContent.getStartTime().toMillis() + " ms) should be >= 1200 ms"); + assertTrue(audioSubSecondContent.getEndTime().toMillis() <= 3651, "Range(1200ms-3651ms) audio EndTime (" + + audioSubSecondContent.getEndTime().toMillis() + " ms) should be <= 3651 ms"); + long subSecondDurationMs + = audioSubSecondContent.getEndTime().toMillis() - audioSubSecondContent.getStartTime().toMillis(); + assertTrue(fullDurationMs >= subSecondDurationMs); + + // ---- Combine multiple time ranges (0-3s, 30s-) ---- + AnalysisInput combineInput = new AnalysisInput(); + combineInput.setUrl(uriSource); + combineInput.setContentRange(ContentRange.combine(ContentRange.timeRange(Duration.ZERO, Duration.ofSeconds(3)), + ContentRange.timeRangeFrom(Duration.ofSeconds(30)))); + + PollerFlux combineOperation + = contentUnderstandingAsyncClient.beginAnalyze("prebuilt-audioSearch", Arrays.asList(combineInput)); + AnalysisResult combineResult = combineOperation.last().flatMap(pollResponse -> { + if (pollResponse.getStatus().isComplete()) { + return pollResponse.getFinalResult(); + } else { + return Mono.error( + new RuntimeException("Polling completed unsuccessfully with status: " + pollResponse.getStatus())); + } + }).block(); + + assertNotNull(combineResult); + assertNotNull(combineResult.getContents()); + assertTrue(combineResult.getContents().size() > 0); + AudioVisualContent combineContent = (AudioVisualContent) combineResult.getContents().get(0); + assertTrue(combineContent.getEndTime().toMillis() > combineContent.getStartTime().toMillis()); + assertNotNull(combineContent.getMarkdown()); + assertFalse(combineContent.getMarkdown().isEmpty()); } } diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample02_AnalyzeUrlTest.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample02_AnalyzeUrlTest.java index f0d0df4f9426..7c538bb4a2b5 100644 --- a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample02_AnalyzeUrlTest.java +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample02_AnalyzeUrlTest.java @@ -8,12 +8,14 @@ import com.azure.ai.contentunderstanding.models.AnalysisResult; import com.azure.ai.contentunderstanding.models.AudioVisualContent; import com.azure.ai.contentunderstanding.models.ContentAnalyzerAnalyzeOperationStatus; +import com.azure.ai.contentunderstanding.models.ContentRange; import com.azure.ai.contentunderstanding.models.DocumentContent; import com.azure.ai.contentunderstanding.models.DocumentPage; import com.azure.ai.contentunderstanding.models.DocumentTable; import com.azure.ai.contentunderstanding.models.DocumentTableCell; import com.azure.ai.contentunderstanding.models.AnalysisContent; import com.azure.ai.contentunderstanding.models.TranscriptPhrase; +import com.azure.core.test.annotation.LiveOnly; import com.azure.core.util.polling.SyncPoller; import org.junit.jupiter.api.Test; @@ -26,6 +28,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import java.time.Duration; /** * Sample demonstrating how to analyze documents from URL using Content Understanding service. @@ -407,4 +410,289 @@ public void testAnalyzeImageUrl() { System.out.println("Image analysis validation completed successfully"); // END:Assertion_ContentUnderstandingAnalyzeImageUrl } + + @Test + public void testAnalyzeUrlWithPageContentRanges() { + + // BEGIN:ContentUnderstandingAnalyzeUrlWithPageContentRanges + String uriSource + = "https://raw.githubusercontent.com/Azure-Samples/azure-ai-content-understanding-assets/main/document/mixed_financial_invoices.pdf"; + + // Full document analysis for comparison + AnalysisInput fullInput = new AnalysisInput(); + fullInput.setUrl(uriSource); + + SyncPoller fullOperation + = contentUnderstandingClient.beginAnalyze("prebuilt-documentSearch", Arrays.asList(fullInput)); + AnalysisResult fullResult = fullOperation.getFinalResult(); + DocumentContent fullDoc = (DocumentContent) fullResult.getContents().get(0); + + // Extract only page 1 via AnalysisInput.setContentRange() + AnalysisInput rangeInput = new AnalysisInput(); + rangeInput.setUrl(uriSource); + rangeInput.setContentRange(ContentRange.page(1)); + + SyncPoller rangeOperation + = contentUnderstandingClient.beginAnalyze("prebuilt-documentSearch", Arrays.asList(rangeInput)); + AnalysisResult rangeResult = rangeOperation.getFinalResult(); + // END:ContentUnderstandingAnalyzeUrlWithPageContentRanges + + // BEGIN:Assertion_ContentUnderstandingAnalyzeUrlWithPageContentRanges + assertEquals(10, fullDoc.getPages().size(), "Full document should return all 10 pages"); + assertNotNull(rangeOperation); + assertTrue(rangeOperation.waitForCompletion().getStatus().isComplete()); + assertNotNull(rangeResult); + assertNotNull(rangeResult.getContents()); + DocumentContent rangeDocContent = (DocumentContent) rangeResult.getContents().get(0); + assertEquals(1, rangeDocContent.getPages().size(), "With ContentRange.page(1), should return only 1 page"); + assertEquals(1, rangeDocContent.getStartPageNumber()); + assertEquals(1, rangeDocContent.getEndPageNumber()); + assertTrue(fullDoc.getPages().size() > rangeDocContent.getPages().size()); + assertTrue(fullDoc.getMarkdown().length() > rangeDocContent.getMarkdown().length()); + // END:Assertion_ContentUnderstandingAnalyzeUrlWithPageContentRanges + + // Combined disjoint page ranges: pages 1-3, page 5, and pages 9 onward + // Document has 10 pages, so pages 1-3, 5, 9-10 match (6 pages total) + AnalysisInput combineInput = new AnalysisInput(); + combineInput.setUrl(uriSource); + combineInput.setContentRange( + ContentRange.combine(ContentRange.pages(1, 3), ContentRange.page(5), ContentRange.pagesFrom(9))); + + SyncPoller combineOperation + = contentUnderstandingClient.beginAnalyze("prebuilt-documentSearch", Arrays.asList(combineInput)); + AnalysisResult combineResult = combineOperation.getFinalResult(); + + assertNotNull(combineResult); + assertNotNull(combineResult.getContents()); + DocumentContent combineDoc = (DocumentContent) combineResult.getContents().get(0); + assertEquals(6, combineDoc.getPages().size(), + "Combine(1-3, 5, 9-) should return 6 pages (1-3, 5, 9-10), got " + combineDoc.getPages().size()); + assertEquals(1, combineDoc.getStartPageNumber()); + assertEquals(10, combineDoc.getEndPageNumber()); + List combinePageNumbers = new java.util.ArrayList<>(); + for (DocumentPage page : combineDoc.getPages()) { + combinePageNumbers.add(page.getPageNumber()); + } + java.util.Collections.sort(combinePageNumbers); + assertEquals(Arrays.asList(1, 2, 3, 5, 9, 10), combinePageNumbers, + "Combine(1-3, 5, 9-) page numbers should be [1, 2, 3, 5, 9, 10]"); + } + + @LiveOnly + @Test + public void testAnalyzeVideoUrlWithTimeContentRanges() { + // BEGIN:ContentUnderstandingAnalyzeVideoUrlWithTimeContentRanges + String uriSource + = "https://raw.githubusercontent.com/Azure-Samples/azure-ai-content-understanding-assets/main/videos/sdk_samples/FlightSimulator.mp4"; + + // Full analysis for comparison + AnalysisInput fullInput = new AnalysisInput(); + fullInput.setUrl(uriSource); + + SyncPoller fullOperation + = contentUnderstandingClient.beginAnalyze("prebuilt-videoSearch", Arrays.asList(fullInput)); + AnalysisResult fullResult = fullOperation.getFinalResult(); + + // ---- TimeRange(0, 5s) — first 5 seconds ---- + AnalysisInput rangeInput = new AnalysisInput(); + rangeInput.setUrl(uriSource); + rangeInput.setContentRange(ContentRange.timeRange(Duration.ZERO, Duration.ofSeconds(5))); + + SyncPoller rangeOperation + = contentUnderstandingClient.beginAnalyze("prebuilt-videoSearch", Arrays.asList(rangeInput)); + AnalysisResult rangeResult = rangeOperation.getFinalResult(); + // END:ContentUnderstandingAnalyzeVideoUrlWithTimeContentRanges + + // BEGIN:Assertion_ContentUnderstandingAnalyzeVideoUrlWithTimeContentRanges + assertNotNull(fullResult); + assertNotNull(fullResult.getContents()); + assertTrue(fullResult.getContents().size() > 0); + + assertNotNull(rangeResult); + assertNotNull(rangeResult.getContents()); + assertTrue(rangeResult.getContents().size() > 0); + for (AnalysisContent content : rangeResult.getContents()) { + assertTrue(content instanceof AudioVisualContent, "Video analysis should return AudioVisualContent"); + AudioVisualContent avContent = (AudioVisualContent) content; + assertTrue(avContent.getEndTime().toMillis() > avContent.getStartTime().toMillis()); + assertTrue(avContent.getStartTime().toMillis() >= 0, + "Range(0-5s) segment StartTime (" + avContent.getStartTime().toMillis() + " ms) should be >= 0 ms"); + assertTrue(avContent.getEndTime().toMillis() <= 5000, + "Range(0-5s) segment EndTime (" + avContent.getEndTime().toMillis() + " ms) should be <= 5000 ms"); + } + // END:Assertion_ContentUnderstandingAnalyzeVideoUrlWithTimeContentRanges + + // ---- TimeRangeFrom(10s) — from 10 seconds to end ---- + AnalysisInput rangeFromInput = new AnalysisInput(); + rangeFromInput.setUrl(uriSource); + rangeFromInput.setContentRange(ContentRange.timeRangeFrom(Duration.ofSeconds(10))); + + SyncPoller rangeFromOperation + = contentUnderstandingClient.beginAnalyze("prebuilt-videoSearch", Arrays.asList(rangeFromInput)); + AnalysisResult rangeFromResult = rangeFromOperation.getFinalResult(); + + assertNotNull(rangeFromResult); + assertNotNull(rangeFromResult.getContents()); + assertTrue(rangeFromResult.getContents().size() > 0); + for (AnalysisContent content : rangeFromResult.getContents()) { + assertTrue(content instanceof AudioVisualContent); + AudioVisualContent avContent = (AudioVisualContent) content; + assertTrue(avContent.getEndTime().toMillis() > avContent.getStartTime().toMillis()); + assertTrue(avContent.getStartTime().toMillis() >= 10000, "TimeRangeFrom(10s) segment StartTime (" + + avContent.getStartTime().toMillis() + " ms) should be >= 10000 ms"); + assertNotNull(avContent.getMarkdown()); + assertFalse(avContent.getMarkdown().isEmpty()); + } + + // ---- TimeRange sub-second precision (1200ms-3651ms) ---- + AnalysisInput subSecondInput = new AnalysisInput(); + subSecondInput.setUrl(uriSource); + subSecondInput.setContentRange(ContentRange.timeRange(Duration.ofMillis(1200), Duration.ofMillis(3651))); + + SyncPoller subSecondOperation + = contentUnderstandingClient.beginAnalyze("prebuilt-videoSearch", Arrays.asList(subSecondInput)); + AnalysisResult subSecondResult = subSecondOperation.getFinalResult(); + + assertNotNull(subSecondResult); + assertNotNull(subSecondResult.getContents()); + assertTrue(subSecondResult.getContents().size() > 0); + for (AnalysisContent content : subSecondResult.getContents()) { + assertTrue(content instanceof AudioVisualContent); + AudioVisualContent avContent = (AudioVisualContent) content; + assertTrue(avContent.getEndTime().toMillis() > avContent.getStartTime().toMillis()); + assertTrue(avContent.getStartTime().toMillis() >= 1200, "Range(1200ms-3651ms) segment StartTime (" + + avContent.getStartTime().toMillis() + " ms) should be >= 1200 ms"); + assertTrue(avContent.getEndTime().toMillis() <= 3651, "Range(1200ms-3651ms) segment EndTime (" + + avContent.getEndTime().toMillis() + " ms) should be <= 3651 ms"); + } + + // ---- Combine multiple time ranges (0-3s, 30s-) ---- + AnalysisInput combineInput = new AnalysisInput(); + combineInput.setUrl(uriSource); + combineInput.setContentRange(ContentRange.combine(ContentRange.timeRange(Duration.ZERO, Duration.ofSeconds(3)), + ContentRange.timeRangeFrom(Duration.ofSeconds(30)))); + + SyncPoller combineOperation + = contentUnderstandingClient.beginAnalyze("prebuilt-videoSearch", Arrays.asList(combineInput)); + AnalysisResult combineResult = combineOperation.getFinalResult(); + + assertNotNull(combineResult); + assertNotNull(combineResult.getContents()); + assertTrue(combineResult.getContents().size() > 0); + for (AnalysisContent content : combineResult.getContents()) { + assertTrue(content instanceof AudioVisualContent); + AudioVisualContent avContent = (AudioVisualContent) content; + assertTrue(avContent.getEndTime().toMillis() > avContent.getStartTime().toMillis()); + assertTrue(avContent.getEndTime().toMillis() <= 3000 || avContent.getStartTime().toMillis() >= 30000, + "Combine(0-3s, 30s-) segment should be in [0,3s] or [30s,∞), but was " + + avContent.getStartTime().toMillis() + "-" + avContent.getEndTime().toMillis() + " ms"); + assertNotNull(avContent.getMarkdown()); + assertFalse(avContent.getMarkdown().isEmpty()); + } + + } + + @LiveOnly + @Test + public void testAnalyzeAudioUrlWithTimeContentRanges() { + // BEGIN:ContentUnderstandingAnalyzeAudioUrlWithTimeContentRanges + String uriSource + = "https://raw.githubusercontent.com/Azure-Samples/azure-ai-content-understanding-assets/main/audio/callCenterRecording.mp3"; + + // Full analysis for comparison + AnalysisInput fullInput = new AnalysisInput(); + fullInput.setUrl(uriSource); + + SyncPoller fullOperation + = contentUnderstandingClient.beginAnalyze("prebuilt-audioSearch", Arrays.asList(fullInput)); + AnalysisResult fullResult = fullOperation.getFinalResult(); + AudioVisualContent fullAudioContent = (AudioVisualContent) fullResult.getContents().get(0); + long fullDurationMs = fullAudioContent.getEndTime().toMillis() - fullAudioContent.getStartTime().toMillis(); + + // ---- TimeRangeFrom(5s) — from 5 seconds to end ---- + AnalysisInput rangeInput = new AnalysisInput(); + rangeInput.setUrl(uriSource); + rangeInput.setContentRange(ContentRange.timeRangeFrom(Duration.ofSeconds(5))); + + SyncPoller rangeOperation + = contentUnderstandingClient.beginAnalyze("prebuilt-audioSearch", Arrays.asList(rangeInput)); + AnalysisResult rangeResult = rangeOperation.getFinalResult(); + // END:ContentUnderstandingAnalyzeAudioUrlWithTimeContentRanges + + // BEGIN:Assertion_ContentUnderstandingAnalyzeAudioUrlWithTimeContentRanges + assertNotNull(rangeResult); + assertNotNull(rangeResult.getContents()); + assertTrue(rangeResult.getContents().size() > 0); + AudioVisualContent rangeAudioContent = (AudioVisualContent) rangeResult.getContents().get(0); + assertTrue(rangeAudioContent instanceof AudioVisualContent); + assertTrue(fullAudioContent.getMarkdown().length() >= rangeAudioContent.getMarkdown().length()); + int fullPhraseCount + = fullAudioContent.getTranscriptPhrases() != null ? fullAudioContent.getTranscriptPhrases().size() : 0; + int rangePhraseCount + = rangeAudioContent.getTranscriptPhrases() != null ? rangeAudioContent.getTranscriptPhrases().size() : 0; + assertTrue(fullPhraseCount >= rangePhraseCount); + long rangeDurationMs = rangeAudioContent.getEndTime().toMillis() - rangeAudioContent.getStartTime().toMillis(); + assertTrue(fullDurationMs >= rangeDurationMs); + assertTrue(rangeAudioContent.getStartTime().toMillis() >= 5000, "TimeRangeFrom(5s) audio StartTime (" + + rangeAudioContent.getStartTime().toMillis() + " ms) should be >= 5000 ms"); + // END:Assertion_ContentUnderstandingAnalyzeAudioUrlWithTimeContentRanges + + // ---- TimeRange(2s, 8s) — specific time window ---- + AnalysisInput windowInput = new AnalysisInput(); + windowInput.setUrl(uriSource); + windowInput.setContentRange(ContentRange.timeRange(Duration.ofSeconds(2), Duration.ofSeconds(8))); + + SyncPoller windowOperation + = contentUnderstandingClient.beginAnalyze("prebuilt-audioSearch", Arrays.asList(windowInput)); + AnalysisResult windowResult = windowOperation.getFinalResult(); + AudioVisualContent audioWindowContent = (AudioVisualContent) windowResult.getContents().get(0); + + assertTrue(audioWindowContent.getEndTime().toMillis() > audioWindowContent.getStartTime().toMillis()); + assertTrue(audioWindowContent.getMarkdown().length() > 0); + assertTrue(audioWindowContent.getStartTime().toMillis() >= 2000, "Range(2s-8s) audio StartTime (" + + audioWindowContent.getStartTime().toMillis() + " ms) should be >= 2000 ms"); + assertTrue(audioWindowContent.getEndTime().toMillis() <= 8000, + "Range(2s-8s) audio EndTime (" + audioWindowContent.getEndTime().toMillis() + " ms) should be <= 8000 ms"); + long windowDurationMs + = audioWindowContent.getEndTime().toMillis() - audioWindowContent.getStartTime().toMillis(); + assertTrue(fullDurationMs >= windowDurationMs); + + // ---- TimeRange sub-second precision (1200ms-3651ms) ---- + AnalysisInput subSecondInput = new AnalysisInput(); + subSecondInput.setUrl(uriSource); + subSecondInput.setContentRange(ContentRange.timeRange(Duration.ofMillis(1200), Duration.ofMillis(3651))); + + SyncPoller subSecondOperation + = contentUnderstandingClient.beginAnalyze("prebuilt-audioSearch", Arrays.asList(subSecondInput)); + AnalysisResult subSecondResult = subSecondOperation.getFinalResult(); + AudioVisualContent audioSubSecondContent = (AudioVisualContent) subSecondResult.getContents().get(0); + + assertTrue(audioSubSecondContent.getEndTime().toMillis() > audioSubSecondContent.getStartTime().toMillis()); + assertTrue(audioSubSecondContent.getMarkdown().length() > 0); + assertTrue(audioSubSecondContent.getStartTime().toMillis() >= 1200, "Range(1200ms-3651ms) audio StartTime (" + + audioSubSecondContent.getStartTime().toMillis() + " ms) should be >= 1200 ms"); + assertTrue(audioSubSecondContent.getEndTime().toMillis() <= 3651, "Range(1200ms-3651ms) audio EndTime (" + + audioSubSecondContent.getEndTime().toMillis() + " ms) should be <= 3651 ms"); + long subSecondDurationMs + = audioSubSecondContent.getEndTime().toMillis() - audioSubSecondContent.getStartTime().toMillis(); + assertTrue(fullDurationMs >= subSecondDurationMs); + + // ---- Combine multiple time ranges (0-3s, 30s-) ---- + AnalysisInput combineInput = new AnalysisInput(); + combineInput.setUrl(uriSource); + combineInput.setContentRange(ContentRange.combine(ContentRange.timeRange(Duration.ZERO, Duration.ofSeconds(3)), + ContentRange.timeRangeFrom(Duration.ofSeconds(30)))); + + SyncPoller combineOperation + = contentUnderstandingClient.beginAnalyze("prebuilt-audioSearch", Arrays.asList(combineInput)); + AnalysisResult combineResult = combineOperation.getFinalResult(); + + assertNotNull(combineResult); + assertNotNull(combineResult.getContents()); + assertTrue(combineResult.getContents().size() > 0); + AudioVisualContent combineContent = (AudioVisualContent) combineResult.getContents().get(0); + assertTrue(combineContent.getEndTime().toMillis() > combineContent.getStartTime().toMillis()); + assertNotNull(combineContent.getMarkdown()); + assertFalse(combineContent.getMarkdown().isEmpty()); + } }