From 9fa10f178b3d88c3a1e94d6ca3c6ac988ede8337 Mon Sep 17 00:00:00 2001 From: Changjian Wang Date: Tue, 10 Mar 2026 18:14:50 +0800 Subject: [PATCH 1/6] Add tests for ContentRange functionality in Content Understanding SDK - Enhance ContentRangeTest with additional unit tests for various scenarios including: - Handling same start and end times. - Rejecting negative start times. - Validating time ranges and content range combinations. - Update Sample01_AnalyzeBinaryAsyncTest and Sample01_AnalyzeBinaryTest to include tests for analyzing binary documents with page content ranges. - Introduce new tests in Sample02_AnalyzeUrlAsyncTest and Sample02_AnalyzeUrlTest for analyzing URLs with page and time content ranges, ensuring proper functionality for both document and audio/video analyses. --- .../samples/Sample01_AnalyzeBinary.java | 76 ++++ .../samples/Sample01_AnalyzeBinaryAsync.java | 144 +++++++ .../samples/Sample02_AnalyzeUrl.java | 160 ++++++++ .../samples/Sample02_AnalyzeUrlAsync.java | 368 ++++++++++++++++++ .../tests/ContentRangeTest.java | 109 ++++++ .../Sample01_AnalyzeBinaryAsyncTest.java | 125 ++++++ .../samples/Sample01_AnalyzeBinaryTest.java | 104 +++++ .../samples/Sample02_AnalyzeUrlAsyncTest.java | 282 ++++++++++++++ .../samples/Sample02_AnalyzeUrlTest.java | 217 +++++++++++ 9 files changed, 1585 insertions(+) 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..eac7dec5a1d8 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 (4 pages) + String multiPageFilePath = "src/samples/resources/mixed_financial_docs.pdf"; + Path multiPagePath = Paths.get(multiPageFilePath); + byte[] multiPageBytes = Files.readAllBytes(multiPagePath); + BinaryData multiPageData = BinaryData.fromBytes(multiPageBytes); + + // BEGIN:ContentUnderstandingAnalyzeBinaryWithContentRange + // 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() + ")"); + + // 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() + ")"); + + // Combine with out-of-range pages (clamped by the service) + 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..abfd2cf3782a 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 (4 pages) + String multiPageFilePath = "src/samples/resources/mixed_financial_docs.pdf"; + Path multiPagePath = Paths.get(multiPageFilePath); + byte[] multiPageBytes = Files.readAllBytes(multiPagePath); + BinaryData multiPageData = BinaryData.fromBytes(multiPageBytes); + + CountDownLatch rangeLatch = new CountDownLatch(1); + + // BEGIN:ContentUnderstandingAnalyzeBinaryWithContentRangeAsync + // 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() + ")"); + }); + + // 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() + ")"); + }); + + // Combine with out-of-range pages (clamped by the service) + 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() + pagesFromMono + .then(pageMono) + .then(pagesMono) + .then(combineMono) + .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..cdb3c9c3c3cd 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,153 @@ 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/documents/mixed_financial_docs.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"); + // 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"); + // 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..69f94376487d 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,361 @@ 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/documents/mixed_financial_docs.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()); + }) + .subscribe( + result -> latch.countDown(), + error -> latch.countDown() + ); + + try { + latch.await(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException("Interrupted while waiting for document 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); + } + // END:ContentUnderstandingAnalyzeAudioUrlWithContentRangeAsync + } } 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..0a6710712fa7 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,128 @@ public void testAnalyzeBinaryAsync() throws IOException { } // END:Assertion_ContentUnderstandingAccessDocumentProperties } + + @Test + public void testAnalyzeBinaryWithPageContentRangesAsync() throws IOException { + + // Load the multi-page sample file (4 pages) + String filePath = "src/samples/resources/mixed_financial_docs.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(4, fullDoc.getPages().size(), "Full document should return all 4 pages"); + + // ---- 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(); + + assertNotNull(rangeResult); + assertNotNull(rangeResult.getContents()); + DocumentContent rangeDoc = (DocumentContent) rangeResult.getContents().get(0); + assertEquals(2, rangeDoc.getPages().size(), "With ContentRange.pagesFrom(3), should return only 2 pages"); + assertEquals(3, rangeDoc.getStartPageNumber(), "pagesFrom(3) should start at page 3"); + assertEquals(4, rangeDoc.getEndPageNumber(), "pagesFrom(3) should end at page 4"); + assertTrue(fullDoc.getPages().size() > rangeDoc.getPages().size()); + assertTrue(fullDoc.getMarkdown().length() > rangeDoc.getMarkdown().length()); + + // ---- 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); + + 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()); + + // ---- 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); + + 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()); + + // ---- 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); + + assertTrue(combineDoc.getPages().size() >= 2); + assertEquals(1, combineDoc.getStartPageNumber()); + assertTrue(combineDoc.getEndPageNumber() >= 4); + assertTrue(fullDoc.getMarkdown().length() >= combineDoc.getMarkdown().length()); + + // ---- Combine(Pages(1,3), Page(5), PagesFrom(9)) — combined page ranges with out-of-range pages ---- + // Note: The document has only 4 pages, so Page(5) and PagesFrom(9) will be clamped + 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); + + assertNotNull(bigCombineResult); + assertNotNull(bigCombineResult.getContents()); + assertTrue(bigCombineDoc.getPages().size() > 0); + assertTrue(fullDoc.getMarkdown().length() >= bigCombineDoc.getMarkdown().length()); + } } 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..5265578dc4e0 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,107 @@ public void testAnalyzeBinary() throws IOException { } // END:Assertion_ContentUnderstandingAccessDocumentProperties } + + @Test + public void testAnalyzeBinaryWithPageContentRanges() throws IOException { + + // Load the multi-page sample file (4 pages) + String filePath = "src/samples/resources/mixed_financial_docs.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(4, fullDoc.getPages().size(), "Full document should return all 4 pages"); + + // 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(2, rangeDoc.getPages().size(), "With ContentRange.pagesFrom(3), should return only 2 pages"); + assertEquals(3, rangeDoc.getStartPageNumber(), "pagesFrom(3) should start at page 3"); + assertEquals(4, rangeDoc.getEndPageNumber(), "pagesFrom(3) should end at page 4"); + 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 ---- + // Note: The document has only 4 pages, so Page(5) and PagesFrom(9) will be clamped + 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); + assertTrue(combineRangeDoc.getPages().size() > 0); + assertTrue(fullDoc.getMarkdown().length() >= combineRangeDoc.getMarkdown().length()); + // END:Assertion_ContentUnderstandingAnalyzeBinaryWithCombinedPages + + // 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:ContentUnderstandingAnalyzeBinaryWithPageRange + // ---- 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:ContentUnderstandingAnalyzeBinaryWithPageRange + + // BEGIN:Assertion_ContentUnderstandingAnalyzeBinaryWithPageRange + 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_ContentUnderstandingAnalyzeBinaryWithPageRange + + // 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 + assertTrue(combineDoc.getPages().size() >= 2); + assertEquals(1, combineDoc.getStartPageNumber()); + assertTrue(combineDoc.getEndPageNumber() >= 4); + assertTrue(fullDoc.getMarkdown().length() >= combineDoc.getMarkdown().length()); + // END:Assertion_ContentUnderstandingAnalyzeBinaryWithCombinedPageAndRange + } } 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..3d84c3a4cce0 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,6 +8,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; @@ -23,6 +24,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; @@ -439,4 +441,284 @@ public void testAnalyzeImageUrlAsync() { System.out.println("Image analysis validation completed successfully"); // END:Assertion_ContentUnderstandingAnalyzeImageUrlAsyncAsync } + + @Test + public void testAnalyzeUrlWithPageContentRangesAsync() { + + String uriSource + = "https://raw.githubusercontent.com/Azure-Samples/azure-ai-content-understanding-assets/main/documents/mixed_financial_docs.pdf"; + + // Full 4-page 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(); + + assertEquals(4, fullDoc.getPages().size(), "Full document should return all 4 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()); + } + + @Test + public void testAnalyzeVideoUrlWithTimeContentRangesAsync() { + + 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(); + + assertNotNull(fullResult); + assertNotNull(fullResult.getContents()); + assertTrue(fullResult.getContents().size() > 0); + + // ---- 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(); + + 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()); + } + + // ---- 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()); + 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()); + } + + // ---- 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()); + assertNotNull(avContent.getMarkdown()); + assertFalse(avContent.getMarkdown().isEmpty()); + } + } + + @Test + public void testAnalyzeAudioUrlWithTimeContentRangesAsync() { + + 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(); + + 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); + + // ---- 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); + 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); + long subSecondDurationMs + = audioSubSecondContent.getEndTime().toMillis() - audioSubSecondContent.getStartTime().toMillis(); + assertTrue(fullDurationMs >= subSecondDurationMs); + } } 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..f283cd958df8 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,6 +8,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; @@ -26,6 +27,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. @@ -363,6 +365,221 @@ public void testAnalyzeAudioUrl() { // END:Assertion_ContentUnderstandingAnalyzeAudioUrl } + @Test + public void testAnalyzeUrlWithPageContentRanges() { + + // BEGIN:ContentUnderstandingAnalyzeUrlWithPageContentRanges + String uriSource + = "https://raw.githubusercontent.com/Azure-Samples/azure-ai-content-understanding-assets/main/documents/mixed_financial_docs.pdf"; + + // Full 4-page 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(4, fullDoc.getPages().size(), "Full document should return all 4 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 + } + + @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()); + } + // 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()); + 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()); + } + + // ---- 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()); + assertNotNull(avContent.getMarkdown()); + assertFalse(avContent.getMarkdown().isEmpty()); + } + } + + @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); + // 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); + 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); + long subSecondDurationMs + = audioSubSecondContent.getEndTime().toMillis() - audioSubSecondContent.getStartTime().toMillis(); + assertTrue(fullDurationMs >= subSecondDurationMs); + } + @Test public void testAnalyzeImageUrl() { // BEGIN:ContentUnderstandingAnalyzeImageUrl From b70d35b6da5303d50e7d0aac4d946b8da25601a0 Mon Sep 17 00:00:00 2001 From: Changjian Wang Date: Wed, 11 Mar 2026 13:05:52 +0800 Subject: [PATCH 2/6] Fix document URL in ContentRange sample and tests --- .../ai/contentunderstanding/samples/Sample02_AnalyzeUrl.java | 2 +- .../contentunderstanding/samples/Sample02_AnalyzeUrlAsync.java | 2 +- .../tests/samples/Sample02_AnalyzeUrlAsyncTest.java | 2 +- .../tests/samples/Sample02_AnalyzeUrlTest.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) 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 cdb3c9c3c3cd..2790937a7637 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 @@ -299,7 +299,7 @@ public static void analyzeImageUrl(ContentUnderstandingClient client) { public static void analyzeDocumentUrlWithContentRange(ContentUnderstandingClient client) { // BEGIN:ContentUnderstandingAnalyzeUrlWithContentRange String uriSource - = "https://raw.githubusercontent.com/Azure-Samples/azure-ai-content-understanding-assets/main/documents/mixed_financial_docs.pdf"; + = "https://raw.githubusercontent.com/Azure-Samples/azure-ai-content-understanding-assets/main/document/mixed_financial_docs.pdf"; // Extract only page 1 using ContentRange AnalysisInput rangeInput = new AnalysisInput(); 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 69f94376487d..41787da968d8 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 @@ -434,7 +434,7 @@ public static void analyzeImageUrl(ContentUnderstandingAsyncClient client) { public static void analyzeDocumentUrlWithContentRange(ContentUnderstandingAsyncClient client) { // BEGIN:ContentUnderstandingAnalyzeDocumentUrlWithContentRangeAsync String uriSource - = "https://raw.githubusercontent.com/Azure-Samples/azure-ai-content-understanding-assets/main/documents/mixed_financial_docs.pdf"; + = "https://raw.githubusercontent.com/Azure-Samples/azure-ai-content-understanding-assets/main/document/mixed_financial_docs.pdf"; // Analyze only page 1 using ContentRange AnalysisInput input = new AnalysisInput(); 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 3d84c3a4cce0..05ab9bef190c 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 @@ -446,7 +446,7 @@ public void testAnalyzeImageUrlAsync() { public void testAnalyzeUrlWithPageContentRangesAsync() { String uriSource - = "https://raw.githubusercontent.com/Azure-Samples/azure-ai-content-understanding-assets/main/documents/mixed_financial_docs.pdf"; + = "https://raw.githubusercontent.com/Azure-Samples/azure-ai-content-understanding-assets/main/document/mixed_financial_docs.pdf"; // Full 4-page analysis for comparison AnalysisInput fullInput = new AnalysisInput(); 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 f283cd958df8..9cc382e14741 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 @@ -370,7 +370,7 @@ public void testAnalyzeUrlWithPageContentRanges() { // BEGIN:ContentUnderstandingAnalyzeUrlWithPageContentRanges String uriSource - = "https://raw.githubusercontent.com/Azure-Samples/azure-ai-content-understanding-assets/main/documents/mixed_financial_docs.pdf"; + = "https://raw.githubusercontent.com/Azure-Samples/azure-ai-content-understanding-assets/main/document/mixed_financial_docs.pdf"; // Full 4-page analysis for comparison AnalysisInput fullInput = new AnalysisInput(); From 422eeaadc04a394f743c6a456cf9010d816da2ee Mon Sep 17 00:00:00 2001 From: Changjian Wang Date: Wed, 11 Mar 2026 13:21:32 +0800 Subject: [PATCH 3/6] Update test recordings for ContentRange samples --- .../azure-ai-contentunderstanding/assets.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/assets.json b/sdk/contentunderstanding/azure-ai-contentunderstanding/assets.json index 83ce14e6ee66..3b72fa4403b4 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_a546deb443" } From 76a0ec7ad03d8b87d9828e0878638c50b6dcd2d5 Mon Sep 17 00:00:00 2001 From: Changjian Wang Date: Fri, 13 Mar 2026 16:37:26 +0800 Subject: [PATCH 4/6] Add raw ContentRange examples and tests for binary and URL analysis --- .../samples/Sample01_AnalyzeBinary.java | 39 ++++++++++ .../samples/Sample01_AnalyzeBinaryAsync.java | 20 +++++ .../samples/Sample02_AnalyzeUrl.java | 34 ++++++++ .../samples/Sample02_AnalyzeUrlAsync.java | 31 ++++++++ .../Sample01_AnalyzeBinaryAsyncTest.java | 78 +++++++++++++++++++ .../samples/Sample01_AnalyzeBinaryTest.java | 48 ++++++++++++ .../samples/Sample02_AnalyzeUrlAsyncTest.java | 55 +++++++++++++ .../samples/Sample02_AnalyzeUrlTest.java | 42 ++++++++++ 8 files changed, 347 insertions(+) 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 eac7dec5a1d8..f7a348125d7e 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 @@ -196,6 +196,45 @@ public static void analyzeBinaryWithContentRange(ContentUnderstandingClient clie = (DocumentContent) bigCombineOperation.getFinalResult().getContents().get(0); System.out.println( "Combine(Pages(1,3), Page(5), PagesFrom(9)): returned " + bigCombineDoc.getPages().size() + " pages"); + + // Raw string "2" — single page, equivalent to ContentRange.page(2) + SyncPoller rawPage2Operation + = client.beginAnalyzeBinary("prebuilt-documentSearch", multiPageData, + new ContentRange("2"), "application/octet-stream", null); + DocumentContent rawPage2Doc + = (DocumentContent) rawPage2Operation.getFinalResult().getContents().get(0); + System.out.println("Raw ContentRange('2'): " + rawPage2Doc.getPages().size() + " page, " + + rawPage2Doc.getMarkdown().length() + " chars"); + + // Raw string "1-3" — page range, equivalent to ContentRange.pages(1, 3) + SyncPoller rawPages13Operation + = client.beginAnalyzeBinary("prebuilt-documentSearch", multiPageData, + new ContentRange("1-3"), "application/octet-stream", null); + DocumentContent rawPages13Doc + = (DocumentContent) rawPages13Operation.getFinalResult().getContents().get(0); + System.out.println("Raw ContentRange('1-3'): " + rawPages13Doc.getPages().size() + " pages, " + + rawPages13Doc.getMarkdown().length() + " chars"); + + // Raw string "3-" — pages from, equivalent to ContentRange.pagesFrom(3) + SyncPoller rawPagesFrom3Operation + = client.beginAnalyzeBinary("prebuilt-documentSearch", multiPageData, + new ContentRange("3-"), "application/octet-stream", null); + DocumentContent rawPagesFrom3Doc + = (DocumentContent) rawPagesFrom3Operation.getFinalResult().getContents().get(0); + System.out.println("Raw ContentRange('3-'): " + rawPagesFrom3Doc.getPages().size() + " pages, " + + rawPagesFrom3Doc.getMarkdown().length() + " chars"); + + // BEGIN:ContentUnderstandingAnalyzeBinaryWithRawContentRange + // Analyze pages 1-3, page 5, and pages 9 onward using a raw range string. + // This is equivalent to: ContentRange.combine(ContentRange.pages(1, 3), ContentRange.page(5), ContentRange.pagesFrom(9)) + SyncPoller rawRangeOperation + = client.beginAnalyzeBinary("prebuilt-documentSearch", multiPageData, + new ContentRange("1-3,5,9-"), "application/octet-stream", null); + DocumentContent rawRangeDoc + = (DocumentContent) rawRangeOperation.getFinalResult().getContents().get(0); + System.out.println("Raw ContentRange('1-3,5,9-'): " + rawRangeDoc.getPages().size() + " pages, " + + rawRangeDoc.getMarkdown().length() + " chars"); + // END:ContentUnderstandingAnalyzeBinaryWithRawContentRange // END:ContentUnderstandingAnalyzeBinaryWithContentRange System.out.println("ContentRange binary analysis completed successfully"); 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 abfd2cf3782a..6eccfcdc05a5 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 @@ -293,6 +293,26 @@ public static void analyzeBinaryWithContentRange(ContentUnderstandingAsyncClient .then(pagesMono) .then(combineMono) .then(bigCombineMono) + .then(Mono.defer(() -> { + // Raw string "1-3,5,9-" — equivalent to Combine(Pages(1,3), Page(5), PagesFrom(9)) + return client + .beginAnalyzeBinary("prebuilt-documentSearch", multiPageData, + new ContentRange("1-3,5,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 rawRangeDoc = (DocumentContent) result.getContents().get(0); + System.out.println("Raw ContentRange('1-3,5,9-'): " + rawRangeDoc.getPages().size() + + " pages, " + rawRangeDoc.getMarkdown().length() + " chars"); + }); + })) .doOnError(error -> System.err.println("Error: " + error.getMessage())) .subscribe( result -> { 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 2790937a7637..41b021796817 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 @@ -389,6 +389,24 @@ public static void analyzeVideoUrlWithContentRange(ContentUnderstandingClient cl + " ms, End=" + videoContent.getEndTime().toMillis() + " ms"); segmentIndex++; } + + // BEGIN:ContentUnderstandingAnalyzeVideoUrlWithRawContentRange + // Analyze the first 5 seconds using a raw range string (milliseconds). + // This is equivalent to: ContentRange.timeRange(Duration.ZERO, Duration.ofSeconds(5)) + AnalysisInput rawRangeInput = new AnalysisInput(); + rawRangeInput.setUrl(uriSource); + rawRangeInput.setContentRange(new ContentRange("0-5000")); + + SyncPoller rawRangeOperation + = client.beginAnalyze("prebuilt-videoSearch", Arrays.asList(rawRangeInput)); + AnalysisResult rawRangeResult = rawRangeOperation.getFinalResult(); + + for (AnalysisContent rawMedia : rawRangeResult.getContents()) { + AudioVisualContent rawVideoContent = (AudioVisualContent) rawMedia; + System.out.println("Raw ContentRange segment: " + rawVideoContent.getStartTime().toMillis() + + " ms - " + rawVideoContent.getEndTime().toMillis() + " ms"); + } + // END:ContentUnderstandingAnalyzeVideoUrlWithRawContentRange // END:ContentUnderstandingAnalyzeVideoUrlWithContentRange } @@ -439,6 +457,22 @@ public static void analyzeAudioUrlWithContentRange(ContentUnderstandingClient cl AudioVisualContent subSecondContent = (AudioVisualContent) subSecondResult.getContents().get(0); System.out.println("TimeRange(1200ms, 3651ms): Start=" + subSecondContent.getStartTime().toMillis() + " ms, End=" + subSecondContent.getEndTime().toMillis() + " ms"); + + // BEGIN:ContentUnderstandingAnalyzeAudioUrlWithRawContentRange + // Analyze audio from 5 seconds onward using a raw range string (milliseconds). + // This is equivalent to: ContentRange.timeRangeFrom(Duration.ofSeconds(5)) + AnalysisInput rawRangeInput = new AnalysisInput(); + rawRangeInput.setUrl(uriSource); + rawRangeInput.setContentRange(new ContentRange("5000-")); + + SyncPoller rawRangeOperation + = client.beginAnalyze("prebuilt-audioSearch", Arrays.asList(rawRangeInput)); + AnalysisResult rawRangeResult = rawRangeOperation.getFinalResult(); + + AudioVisualContent rawAudioContent = (AudioVisualContent) rawRangeResult.getContents().get(0); + System.out.println("Raw ContentRange audio analysis: " + rawAudioContent.getStartTime().toMillis() + + " ms onward"); + // END:ContentUnderstandingAnalyzeAudioUrlWithRawContentRange // 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 41787da968d8..193d6abb52fb 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 @@ -782,6 +782,37 @@ public static void analyzeAudioUrlWithContentRange(ContentUnderstandingAsyncClie Thread.currentThread().interrupt(); throw new RuntimeException("Interrupted while waiting for audio analysis", e); } + // Raw string "5000-" — equivalent to TimeRangeFrom(5s) + AnalysisInput rawInput = new AnalysisInput(); + rawInput.setUrl(audioUrl); + rawInput.setContentRange(new ContentRange("5000-")); + + PollerFlux rawOperation + = client.beginAnalyze("prebuilt-audioSearch", Arrays.asList(rawInput)); + + CountDownLatch latch4 = new CountDownLatch(1); + + rawOperation.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 rawAudioContent = (AudioVisualContent) result.getContents().get(0); + System.out.println("Raw ContentRange audio analysis: " + + rawAudioContent.getStartTime().toMillis() + " ms onward"); + }) + .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/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 0a6710712fa7..f09e654657ff 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 @@ -392,5 +392,83 @@ public void testAnalyzeBinaryWithPageContentRangesAsync() throws IOException { assertNotNull(bigCombineResult.getContents()); assertTrue(bigCombineDoc.getPages().size() > 0); assertTrue(fullDoc.getMarkdown().length() >= bigCombineDoc.getMarkdown().length()); + + // ---- Raw string "2" — single page, equivalent to ContentRange.page(2) ---- + PollerFlux rawPage2Operation + = contentUnderstandingAsyncClient.beginAnalyzeBinary("prebuilt-documentSearch", binaryData, + new ContentRange("2"), "application/octet-stream", null); + AnalysisResult rawPage2Result = rawPage2Operation.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 rawPage2Doc = (DocumentContent) rawPage2Result.getContents().get(0); + assertEquals(1, rawPage2Doc.getPages().size(), "Raw ContentRange('2') should return exactly 1 page"); + assertEquals(2, rawPage2Doc.getStartPageNumber(), "Raw ContentRange('2') should start at page 2"); + assertEquals(2, rawPage2Doc.getEndPageNumber(), "Raw ContentRange('2') should end at page 2"); + assertEquals(page2Doc.getMarkdown().length(), rawPage2Doc.getMarkdown().length(), + "Raw ContentRange('2') should return same markdown length as Page(2)"); + + // ---- Raw string "1-3" — page range, equivalent to ContentRange.pages(1, 3) ---- + PollerFlux rawPages13Operation + = contentUnderstandingAsyncClient.beginAnalyzeBinary("prebuilt-documentSearch", binaryData, + new ContentRange("1-3"), "application/octet-stream", null); + AnalysisResult rawPages13Result = rawPages13Operation.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 rawPages13Doc = (DocumentContent) rawPages13Result.getContents().get(0); + assertEquals(3, rawPages13Doc.getPages().size(), "Raw ContentRange('1-3') should return exactly 3 pages"); + assertEquals(1, rawPages13Doc.getStartPageNumber(), "Raw ContentRange('1-3') should start at page 1"); + assertEquals(3, rawPages13Doc.getEndPageNumber(), "Raw ContentRange('1-3') should end at page 3"); + assertEquals(pages13Doc.getMarkdown().length(), rawPages13Doc.getMarkdown().length(), + "Raw ContentRange('1-3') should return same markdown length as Pages(1,3)"); + + // ---- Raw string "3-" — pages from, equivalent to ContentRange.pagesFrom(3) ---- + PollerFlux rawPagesFrom3Operation + = contentUnderstandingAsyncClient.beginAnalyzeBinary("prebuilt-documentSearch", binaryData, + new ContentRange("3-"), "application/octet-stream", null); + AnalysisResult rawPagesFrom3Result = rawPagesFrom3Operation.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 rawPagesFrom3Doc = (DocumentContent) rawPagesFrom3Result.getContents().get(0); + assertEquals(2, rawPagesFrom3Doc.getPages().size(), "Raw ContentRange('3-') should return 2 pages (pages 3-4)"); + assertEquals(3, rawPagesFrom3Doc.getStartPageNumber(), "Raw ContentRange('3-') should start at page 3"); + assertEquals(4, rawPagesFrom3Doc.getEndPageNumber(), "Raw ContentRange('3-') should end at page 4"); + assertEquals(rangeDoc.getMarkdown().length(), rawPagesFrom3Doc.getMarkdown().length(), + "Raw ContentRange('3-') should return same markdown length as PagesFrom(3)"); + + // ---- Raw string "1-3,5,9-" — combine, equivalent to Combine(Pages(1,3), Page(5), PagesFrom(9)) ---- + PollerFlux rawRangeOperation + = contentUnderstandingAsyncClient.beginAnalyzeBinary("prebuilt-documentSearch", binaryData, + new ContentRange("1-3,5,9-"), "application/octet-stream", null); + AnalysisResult rawRangeResult = rawRangeOperation.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 rawRangeDoc = (DocumentContent) rawRangeResult.getContents().get(0); + assertTrue(rawRangeDoc.getPages().size() > 0, "Raw ContentRange should return at least one page"); + assertEquals(bigCombineDoc.getPages().size(), rawRangeDoc.getPages().size(), + "Raw ContentRange('1-3,5,9-') should return same page count as Combine equivalent"); + assertEquals(bigCombineDoc.getMarkdown().length(), rawRangeDoc.getMarkdown().length(), + "Raw ContentRange('1-3,5,9-') should return same markdown length as Combine equivalent"); + System.out.println("Raw ContentRange('1-3,5,9-'): " + rawRangeDoc.getPages().size() + " pages, " + + rawRangeDoc.getMarkdown().length() + " chars"); } } 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 5265578dc4e0..9547a1831f0d 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 @@ -362,5 +362,53 @@ public void testAnalyzeBinaryWithPageContentRanges() throws IOException { assertTrue(combineDoc.getEndPageNumber() >= 4); assertTrue(fullDoc.getMarkdown().length() >= combineDoc.getMarkdown().length()); // END:Assertion_ContentUnderstandingAnalyzeBinaryWithCombinedPageAndRange + + // ---- Raw string "2" — single page, equivalent to ContentRange.page(2) ---- + SyncPoller rawPage2Operation + = contentUnderstandingClient.beginAnalyzeBinary("prebuilt-documentSearch", binaryData, + new ContentRange("2"), "application/octet-stream", null); + DocumentContent rawPage2Doc = (DocumentContent) rawPage2Operation.getFinalResult().getContents().get(0); + assertEquals(1, rawPage2Doc.getPages().size(), "Raw ContentRange('2') should return exactly 1 page"); + assertEquals(2, rawPage2Doc.getStartPageNumber(), "Raw ContentRange('2') should start at page 2"); + assertEquals(2, rawPage2Doc.getEndPageNumber(), "Raw ContentRange('2') should end at page 2"); + assertEquals(page2Doc.getMarkdown().length(), rawPage2Doc.getMarkdown().length(), + "Raw ContentRange('2') should return same markdown length as Page(2)"); + + // ---- Raw string "1-3" — page range, equivalent to ContentRange.pages(1, 3) ---- + SyncPoller rawPages13Operation + = contentUnderstandingClient.beginAnalyzeBinary("prebuilt-documentSearch", binaryData, + new ContentRange("1-3"), "application/octet-stream", null); + DocumentContent rawPages13Doc = (DocumentContent) rawPages13Operation.getFinalResult().getContents().get(0); + assertEquals(3, rawPages13Doc.getPages().size(), "Raw ContentRange('1-3') should return exactly 3 pages"); + assertEquals(1, rawPages13Doc.getStartPageNumber(), "Raw ContentRange('1-3') should start at page 1"); + assertEquals(3, rawPages13Doc.getEndPageNumber(), "Raw ContentRange('1-3') should end at page 3"); + assertEquals(pages13Doc.getMarkdown().length(), rawPages13Doc.getMarkdown().length(), + "Raw ContentRange('1-3') should return same markdown length as Pages(1,3)"); + + // ---- Raw string "3-" — pages from, equivalent to ContentRange.pagesFrom(3) ---- + SyncPoller rawPagesFrom3Operation + = contentUnderstandingClient.beginAnalyzeBinary("prebuilt-documentSearch", binaryData, + new ContentRange("3-"), "application/octet-stream", null); + DocumentContent rawPagesFrom3Doc + = (DocumentContent) rawPagesFrom3Operation.getFinalResult().getContents().get(0); + assertEquals(2, rawPagesFrom3Doc.getPages().size(), "Raw ContentRange('3-') should return 2 pages (pages 3-4)"); + assertEquals(3, rawPagesFrom3Doc.getStartPageNumber(), "Raw ContentRange('3-') should start at page 3"); + assertEquals(4, rawPagesFrom3Doc.getEndPageNumber(), "Raw ContentRange('3-') should end at page 4"); + DocumentContent rangeFromDoc = (DocumentContent) rangeOperation.getFinalResult().getContents().get(0); + assertEquals(rangeFromDoc.getMarkdown().length(), rawPagesFrom3Doc.getMarkdown().length(), + "Raw ContentRange('3-') should return same markdown length as PagesFrom(3)"); + + // ---- Raw string "1-3,5,9-" — combine, equivalent to ContentRange.combine(Pages(1,3), Page(5), PagesFrom(9)) ---- + SyncPoller rawRangeOperation + = contentUnderstandingClient.beginAnalyzeBinary("prebuilt-documentSearch", binaryData, + new ContentRange("1-3,5,9-"), "application/octet-stream", null); + DocumentContent rawRangeDoc = (DocumentContent) rawRangeOperation.getFinalResult().getContents().get(0); + assertTrue(rawRangeDoc.getPages().size() > 0, "Raw ContentRange should return at least one page"); + assertEquals(combineRangeDoc.getPages().size(), rawRangeDoc.getPages().size(), + "Raw ContentRange('1-3,5,9-') should return same page count as Combine equivalent"); + assertEquals(combineRangeDoc.getMarkdown().length(), rawRangeDoc.getMarkdown().length(), + "Raw ContentRange('1-3,5,9-') should return same markdown length as Combine equivalent"); + System.out.println("Raw ContentRange('1-3,5,9-'): " + rawRangeDoc.getPages().size() + " pages, " + + rawRangeDoc.getMarkdown().length() + " chars"); } } 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 05ab9bef190c..d9c70d09fad4 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 @@ -620,6 +620,34 @@ public void testAnalyzeVideoUrlWithTimeContentRangesAsync() { assertNotNull(avContent.getMarkdown()); assertFalse(avContent.getMarkdown().isEmpty()); } + + // ---- Raw string "0-5000" — equivalent to TimeRange(0, 5s) ---- + AnalysisInput rawVideoInput = new AnalysisInput(); + rawVideoInput.setUrl(uriSource); + rawVideoInput.setContentRange(new ContentRange("0-5000")); + + PollerFlux rawVideoOperation + = contentUnderstandingAsyncClient.beginAnalyze("prebuilt-videoSearch", Arrays.asList(rawVideoInput)); + AnalysisResult rawVideoResult = rawVideoOperation.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(rawVideoResult); + assertNotNull(rawVideoResult.getContents()); + assertTrue(rawVideoResult.getContents().size() > 0, "Raw range video should return segments"); + for (AnalysisContent content : rawVideoResult.getContents()) { + assertTrue(content instanceof AudioVisualContent); + AudioVisualContent avContent = (AudioVisualContent) content; + assertTrue(avContent.getEndTime().toMillis() > avContent.getStartTime().toMillis()); + } + assertEquals(rangeResult.getContents().size(), rawVideoResult.getContents().size(), + "Raw ContentRange('0-5000') should return same segment count as TimeRange equivalent"); + System.out.println("Raw ContentRange('0-5000'): " + rawVideoResult.getContents().size() + " segment(s)"); } @Test @@ -720,5 +748,32 @@ public void testAnalyzeAudioUrlWithTimeContentRangesAsync() { long subSecondDurationMs = audioSubSecondContent.getEndTime().toMillis() - audioSubSecondContent.getStartTime().toMillis(); assertTrue(fullDurationMs >= subSecondDurationMs); + + // ---- Raw string "5000-" — equivalent to TimeRangeFrom(5s) ---- + AnalysisInput rawAudioInput = new AnalysisInput(); + rawAudioInput.setUrl(uriSource); + rawAudioInput.setContentRange(new ContentRange("5000-")); + + PollerFlux rawAudioOperation + = contentUnderstandingAsyncClient.beginAnalyze("prebuilt-audioSearch", Arrays.asList(rawAudioInput)); + AnalysisResult rawAudioResult = rawAudioOperation.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(rawAudioResult); + assertNotNull(rawAudioResult.getContents()); + assertTrue(rawAudioResult.getContents().size() > 0, "Raw range audio should return content"); + AudioVisualContent rawAudioContent = (AudioVisualContent) rawAudioResult.getContents().get(0); + assertTrue(rawAudioContent.getStartTime().toMillis() >= 5000, + "Raw ContentRange('5000-') audio StartTime should be >= 5000 ms"); + assertEquals(rangeAudioContent.getMarkdown().length(), rawAudioContent.getMarkdown().length(), + "Raw ContentRange('5000-') should return same markdown length as TimeRangeFrom equivalent"); + System.out.println("Raw ContentRange('5000-'): " + rawAudioContent.getMarkdown().length() + " chars, starts at " + + rawAudioContent.getStartTime().toMillis() + " ms"); } } 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 9cc382e14741..f4425f943a99 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 @@ -503,6 +503,28 @@ public void testAnalyzeVideoUrlWithTimeContentRanges() { assertNotNull(avContent.getMarkdown()); assertFalse(avContent.getMarkdown().isEmpty()); } + + // ---- Raw string "0-5000" — equivalent to TimeRange(0, 5s) ---- + AnalysisInput rawVideoInput = new AnalysisInput(); + rawVideoInput.setUrl(uriSource); + rawVideoInput.setContentRange(new ContentRange("0-5000")); + + SyncPoller rawVideoOperation + = contentUnderstandingClient.beginAnalyze("prebuilt-videoSearch", Arrays.asList(rawVideoInput)); + AnalysisResult rawVideoResult = rawVideoOperation.getFinalResult(); + + assertNotNull(rawVideoResult); + assertNotNull(rawVideoResult.getContents()); + assertTrue(rawVideoResult.getContents().size() > 0, "Raw range video should return segments"); + for (AnalysisContent content : rawVideoResult.getContents()) { + assertTrue(content instanceof AudioVisualContent); + AudioVisualContent avContent = (AudioVisualContent) content; + assertTrue(avContent.getEndTime().toMillis() > avContent.getStartTime().toMillis()); + } + // The raw string "0-5000" should produce identical results to TimeRange(0, 5s) + assertEquals(rangeResult.getContents().size(), rawVideoResult.getContents().size(), + "Raw ContentRange('0-5000') should return same segment count as TimeRange equivalent"); + System.out.println("Raw ContentRange('0-5000'): " + rawVideoResult.getContents().size() + " segment(s)"); } @Test @@ -578,6 +600,26 @@ public void testAnalyzeAudioUrlWithTimeContentRanges() { long subSecondDurationMs = audioSubSecondContent.getEndTime().toMillis() - audioSubSecondContent.getStartTime().toMillis(); assertTrue(fullDurationMs >= subSecondDurationMs); + + // ---- Raw string "5000-" — equivalent to TimeRangeFrom(5s) ---- + AnalysisInput rawAudioInput = new AnalysisInput(); + rawAudioInput.setUrl(uriSource); + rawAudioInput.setContentRange(new ContentRange("5000-")); + + SyncPoller rawAudioOperation + = contentUnderstandingClient.beginAnalyze("prebuilt-audioSearch", Arrays.asList(rawAudioInput)); + AnalysisResult rawAudioResult = rawAudioOperation.getFinalResult(); + + assertNotNull(rawAudioResult); + assertNotNull(rawAudioResult.getContents()); + assertTrue(rawAudioResult.getContents().size() > 0, "Raw range audio should return content"); + AudioVisualContent rawAudioContent = (AudioVisualContent) rawAudioResult.getContents().get(0); + assertTrue(rawAudioContent.getStartTime().toMillis() >= 5000, + "Raw ContentRange('5000-') audio StartTime should be >= 5000 ms"); + assertEquals(rangeAudioContent.getMarkdown().length(), rawAudioContent.getMarkdown().length(), + "Raw ContentRange('5000-') should return same markdown length as TimeRangeFrom equivalent"); + System.out.println("Raw ContentRange('5000-'): " + rawAudioContent.getMarkdown().length() + " chars, starts at " + + rawAudioContent.getStartTime().toMillis() + " ms"); } @Test From 2dc0db8de622401b115ee45c8b68ee7ad93c0c8c Mon Sep 17 00:00:00 2001 From: Changjian Wang Date: Mon, 16 Mar 2026 13:54:47 +0800 Subject: [PATCH 5/6] Update assets.json tag and modify sample file paths --- .../azure-ai-contentunderstanding/assets.json | 2 +- .../samples/Sample01_AnalyzeBinary.java | 6 +-- .../samples/Sample01_AnalyzeBinaryAsync.java | 6 +-- .../resources/mixed_financial_invoices.pdf | Bin 0 -> 15324 bytes .../Sample01_AnalyzeBinaryAsyncTest.java | 38 +++++++++++++----- .../samples/Sample01_AnalyzeBinaryTest.java | 37 ++++++++++++----- 6 files changed, 60 insertions(+), 29 deletions(-) create mode 100644 sdk/contentunderstanding/azure-ai-contentunderstanding/src/samples/resources/mixed_financial_invoices.pdf diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/assets.json b/sdk/contentunderstanding/azure-ai-contentunderstanding/assets.json index 3b72fa4403b4..d770358ba5ed 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_a546deb443" + "Tag": "java/contentunderstanding/azure-ai-contentunderstanding_acf3c730ff" } 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 f7a348125d7e..92e63d08c6e8 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 @@ -140,8 +140,8 @@ public static void main(String[] args) throws IOException { */ public static void analyzeBinaryWithContentRange(ContentUnderstandingClient client) { try { - // Load a multi-page document (4 pages) - String multiPageFilePath = "src/samples/resources/mixed_financial_docs.pdf"; + // 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); @@ -184,7 +184,7 @@ public static void analyzeBinaryWithContentRange(ContentUnderstandingClient clie System.out.println("Combine(Page(1), Pages(3,4)): returned " + combineDoc.getPages().size() + " pages" + " (pages " + combineDoc.getStartPageNumber() + "-" + combineDoc.getEndPageNumber() + ")"); - // Combine with out-of-range pages (clamped by the service) + // Combine multiple page ranges: pages 1-3, page 5, and pages 9 onward SyncPoller bigCombineOperation = client.beginAnalyzeBinary("prebuilt-documentSearch", multiPageData, ContentRange.combine( 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 6eccfcdc05a5..8d831ebcc1ce 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 @@ -176,8 +176,8 @@ public static void main(String[] args) throws IOException, InterruptedException */ public static void analyzeBinaryWithContentRange(ContentUnderstandingAsyncClient client) throws IOException, InterruptedException { - // Load a multi-page document (4 pages) - String multiPageFilePath = "src/samples/resources/mixed_financial_docs.pdf"; + // 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); @@ -264,7 +264,7 @@ public static void analyzeBinaryWithContentRange(ContentUnderstandingAsyncClient + " (pages " + combineDoc.getStartPageNumber() + "-" + combineDoc.getEndPageNumber() + ")"); }); - // Combine with out-of-range pages (clamped by the service) + // Combine multiple page ranges: pages 1-3, page 5, and pages 9 onward Mono bigCombineMono = client .beginAnalyzeBinary("prebuilt-documentSearch", multiPageData, ContentRange.combine( 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 0000000000000000000000000000000000000000..793c3d4e24b224a54b3080866a997d7df1dee832 GIT binary patch literal 15324 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 LPcNw?h5r8lGu)Xy literal 0 HcmV?d00001 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 f09e654657ff..4c346a5b0dc1 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 @@ -273,8 +273,8 @@ public void testAnalyzeBinaryAsync() throws IOException { @Test public void testAnalyzeBinaryWithPageContentRangesAsync() throws IOException { - // Load the multi-page sample file (4 pages) - String filePath = "src/samples/resources/mixed_financial_docs.pdf"; + // 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); @@ -291,7 +291,7 @@ public void testAnalyzeBinaryWithPageContentRangesAsync() throws IOException { } }).block(); DocumentContent fullDoc = (DocumentContent) fullResult.getContents().get(0); - assertEquals(4, fullDoc.getPages().size(), "Full document should return all 4 pages"); + assertEquals(10, fullDoc.getPages().size(), "Full document should return all 10 pages"); // ---- PagesFrom(3) — extract pages 3 to end ---- PollerFlux rangeOperation @@ -309,9 +309,9 @@ public void testAnalyzeBinaryWithPageContentRangesAsync() throws IOException { assertNotNull(rangeResult); assertNotNull(rangeResult.getContents()); DocumentContent rangeDoc = (DocumentContent) rangeResult.getContents().get(0); - assertEquals(2, rangeDoc.getPages().size(), "With ContentRange.pagesFrom(3), should return only 2 pages"); + 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(4, rangeDoc.getEndPageNumber(), "pagesFrom(3) should end at page 4"); + 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()); @@ -368,12 +368,19 @@ public void testAnalyzeBinaryWithPageContentRangesAsync() throws IOException { DocumentContent combineDoc = (DocumentContent) combineResult.getContents().get(0); assertTrue(combineDoc.getPages().size() >= 2); + 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()); - assertTrue(combineDoc.getEndPageNumber() >= 4); + assertEquals(4, combineDoc.getEndPageNumber(), "Combine should end at page 4"); assertTrue(fullDoc.getMarkdown().length() >= combineDoc.getMarkdown().length()); - // ---- Combine(Pages(1,3), Page(5), PagesFrom(9)) — combined page ranges with out-of-range pages ---- - // Note: The document has only 4 pages, so Page(5) and PagesFrom(9) will be clamped + // ---- 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)), @@ -390,7 +397,15 @@ public void testAnalyzeBinaryWithPageContentRangesAsync() throws IOException { assertNotNull(bigCombineResult); assertNotNull(bigCombineResult.getContents()); - assertTrue(bigCombineDoc.getPages().size() > 0); + 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()); // ---- Raw string "2" — single page, equivalent to ContentRange.page(2) ---- @@ -444,9 +459,10 @@ public void testAnalyzeBinaryWithPageContentRangesAsync() throws IOException { } }).block(); DocumentContent rawPagesFrom3Doc = (DocumentContent) rawPagesFrom3Result.getContents().get(0); - assertEquals(2, rawPagesFrom3Doc.getPages().size(), "Raw ContentRange('3-') should return 2 pages (pages 3-4)"); + assertEquals(8, rawPagesFrom3Doc.getPages().size(), + "Raw ContentRange('3-') should return 8 pages (pages 3-10)"); assertEquals(3, rawPagesFrom3Doc.getStartPageNumber(), "Raw ContentRange('3-') should start at page 3"); - assertEquals(4, rawPagesFrom3Doc.getEndPageNumber(), "Raw ContentRange('3-') should end at page 4"); + assertEquals(10, rawPagesFrom3Doc.getEndPageNumber(), "Raw ContentRange('3-') should end at page 10"); assertEquals(rangeDoc.getMarkdown().length(), rawPagesFrom3Doc.getMarkdown().length(), "Raw ContentRange('3-') should return same markdown length as PagesFrom(3)"); 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 9547a1831f0d..fe18467d11c7 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 @@ -264,8 +264,8 @@ public void testAnalyzeBinary() throws IOException { @Test public void testAnalyzeBinaryWithPageContentRanges() throws IOException { - // Load the multi-page sample file (4 pages) - String filePath = "src/samples/resources/mixed_financial_docs.pdf"; + // 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); @@ -275,7 +275,7 @@ public void testAnalyzeBinaryWithPageContentRanges() throws IOException { = contentUnderstandingClient.beginAnalyzeBinary("prebuilt-documentSearch", binaryData); AnalysisResult fullResult = fullOperation.getFinalResult(); DocumentContent fullDoc = (DocumentContent) fullResult.getContents().get(0); - assertEquals(4, fullDoc.getPages().size(), "Full document should return all 4 pages"); + assertEquals(10, fullDoc.getPages().size(), "Full document should return all 10 pages"); // BEGIN:ContentUnderstandingAnalyzeBinaryWithPagesFrom // ---- PagesFrom(3) — extract pages 3 to end ---- @@ -291,16 +291,15 @@ public void testAnalyzeBinaryWithPageContentRanges() throws IOException { assertNotNull(rangeResult); assertNotNull(rangeResult.getContents()); DocumentContent rangeDoc = (DocumentContent) rangeResult.getContents().get(0); - assertEquals(2, rangeDoc.getPages().size(), "With ContentRange.pagesFrom(3), should return only 2 pages"); + 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(4, rangeDoc.getEndPageNumber(), "pagesFrom(3) should end at page 4"); + 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 ---- - // Note: The document has only 4 pages, so Page(5) and PagesFrom(9) will be clamped SyncPoller combineRangeOperation = contentUnderstandingClient.beginAnalyzeBinary("prebuilt-documentSearch", binaryData, ContentRange.combine(ContentRange.pages(1, 3), ContentRange.page(5), ContentRange.pagesFrom(9)), @@ -314,7 +313,15 @@ public void testAnalyzeBinaryWithPageContentRanges() throws IOException { assertNotNull(combineRangeResult); assertNotNull(combineRangeResult.getContents()); DocumentContent combineRangeDoc = (DocumentContent) combineRangeResult.getContents().get(0); - assertTrue(combineRangeDoc.getPages().size() > 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 @@ -357,9 +364,16 @@ public void testAnalyzeBinaryWithPageContentRanges() throws IOException { // END:ContentUnderstandingAnalyzeBinaryWithCombinedPageAndRange // BEGIN:Assertion_ContentUnderstandingAnalyzeBinaryWithCombinedPageAndRange - assertTrue(combineDoc.getPages().size() >= 2); + 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()); - assertTrue(combineDoc.getEndPageNumber() >= 4); + assertEquals(4, combineDoc.getEndPageNumber(), "Combine should end at page 4"); assertTrue(fullDoc.getMarkdown().length() >= combineDoc.getMarkdown().length()); // END:Assertion_ContentUnderstandingAnalyzeBinaryWithCombinedPageAndRange @@ -391,9 +405,10 @@ public void testAnalyzeBinaryWithPageContentRanges() throws IOException { new ContentRange("3-"), "application/octet-stream", null); DocumentContent rawPagesFrom3Doc = (DocumentContent) rawPagesFrom3Operation.getFinalResult().getContents().get(0); - assertEquals(2, rawPagesFrom3Doc.getPages().size(), "Raw ContentRange('3-') should return 2 pages (pages 3-4)"); + assertEquals(8, rawPagesFrom3Doc.getPages().size(), + "Raw ContentRange('3-') should return 8 pages (pages 3-10)"); assertEquals(3, rawPagesFrom3Doc.getStartPageNumber(), "Raw ContentRange('3-') should start at page 3"); - assertEquals(4, rawPagesFrom3Doc.getEndPageNumber(), "Raw ContentRange('3-') should end at page 4"); + assertEquals(10, rawPagesFrom3Doc.getEndPageNumber(), "Raw ContentRange('3-') should end at page 10"); DocumentContent rangeFromDoc = (DocumentContent) rangeOperation.getFinalResult().getContents().get(0); assertEquals(rangeFromDoc.getMarkdown().length(), rawPagesFrom3Doc.getMarkdown().length(), "Raw ContentRange('3-') should return same markdown length as PagesFrom(3)"); From 490216e2ea0dd147789040750fbc47f716334ae9 Mon Sep 17 00:00:00 2001 From: Changjian Wang Date: Mon, 16 Mar 2026 18:48:08 +0800 Subject: [PATCH 6/6] Add assertions for start and end time ranges in audio/video analysis tests --- .../samples/Sample02_AnalyzeUrlAsyncTest.java | 27 +++++++++++++++++++ .../samples/Sample02_AnalyzeUrlTest.java | 27 +++++++++++++++++++ 2 files changed, 54 insertions(+) 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 d9c70d09fad4..d86364d44054 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 @@ -539,6 +539,10 @@ public void testAnalyzeVideoUrlWithTimeContentRangesAsync() { 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"); } // ---- TimeRangeFrom(10s) — from 10 seconds to end ---- @@ -564,6 +568,8 @@ public void testAnalyzeVideoUrlWithTimeContentRangesAsync() { 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()); } @@ -591,6 +597,10 @@ public void testAnalyzeVideoUrlWithTimeContentRangesAsync() { 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-) ---- @@ -617,6 +627,9 @@ public void testAnalyzeVideoUrlWithTimeContentRangesAsync() { 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()); } @@ -644,6 +657,10 @@ public void testAnalyzeVideoUrlWithTimeContentRangesAsync() { assertTrue(content instanceof AudioVisualContent); AudioVisualContent avContent = (AudioVisualContent) content; assertTrue(avContent.getEndTime().toMillis() > avContent.getStartTime().toMillis()); + assertTrue(avContent.getStartTime().toMillis() >= 0, "Raw range(0-5000) segment StartTime (" + + avContent.getStartTime().toMillis() + " ms) should be >= 0 ms"); + assertTrue(avContent.getEndTime().toMillis() <= 5000, "Raw range(0-5000) segment EndTime (" + + avContent.getEndTime().toMillis() + " ms) should be <= 5000 ms"); } assertEquals(rangeResult.getContents().size(), rawVideoResult.getContents().size(), "Raw ContentRange('0-5000') should return same segment count as TimeRange equivalent"); @@ -702,6 +719,8 @@ public void testAnalyzeAudioUrlWithTimeContentRangesAsync() { 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"); // ---- TimeRange(2s, 8s) — specific time window ---- AnalysisInput windowInput = new AnalysisInput(); @@ -722,6 +741,10 @@ public void testAnalyzeAudioUrlWithTimeContentRangesAsync() { 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); @@ -745,6 +768,10 @@ public void testAnalyzeAudioUrlWithTimeContentRangesAsync() { 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); 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 f4425f943a99..673aee696e85 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 @@ -442,6 +442,10 @@ public void testAnalyzeVideoUrlWithTimeContentRanges() { 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 @@ -461,6 +465,8 @@ public void testAnalyzeVideoUrlWithTimeContentRanges() { 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()); } @@ -481,6 +487,10 @@ public void testAnalyzeVideoUrlWithTimeContentRanges() { 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-) ---- @@ -500,6 +510,9 @@ public void testAnalyzeVideoUrlWithTimeContentRanges() { 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()); } @@ -520,6 +533,10 @@ public void testAnalyzeVideoUrlWithTimeContentRanges() { assertTrue(content instanceof AudioVisualContent); AudioVisualContent avContent = (AudioVisualContent) content; assertTrue(avContent.getEndTime().toMillis() > avContent.getStartTime().toMillis()); + assertTrue(avContent.getStartTime().toMillis() >= 0, "Raw range(0-5000) segment StartTime (" + + avContent.getStartTime().toMillis() + " ms) should be >= 0 ms"); + assertTrue(avContent.getEndTime().toMillis() <= 5000, "Raw range(0-5000) segment EndTime (" + + avContent.getEndTime().toMillis() + " ms) should be <= 5000 ms"); } // The raw string "0-5000" should produce identical results to TimeRange(0, 5s) assertEquals(rangeResult.getContents().size(), rawVideoResult.getContents().size(), @@ -567,6 +584,8 @@ public void testAnalyzeAudioUrlWithTimeContentRanges() { 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 ---- @@ -581,6 +600,10 @@ public void testAnalyzeAudioUrlWithTimeContentRanges() { 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); @@ -597,6 +620,10 @@ public void testAnalyzeAudioUrlWithTimeContentRanges() { 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);