Input
inputBlob
- A PDF or ZIP file containing all the pages of many students' test papers.extension
- The file type ("pdf" or "zip").numPages
- The number of pages in a single test. For example, ifnumPages
is 2 and the input file contains 10 pages, then we must have 5 students' tests.
Routine
- Extract all the pages as images.
- Upload the images to blob storage.
- For each test (i.e. set of images), queue another routine to process it.
Code
I've created PdfPack
and ZipPack
classes (implementations not included here) which implement a common interface:
public interface IPack : IDisposable
{
void Open(Stream stream);
Stream GetPage(int index);
int PageCount { get; }
}
Here's the function itself:
public static async Task Run(
CloudBlockBlob inputBlob,
string extension,
int numPages,
CloudBlobContainer container,
IAsyncCollector<StitchRequest> queue)
{
IPack pack = null;
try {
using (var inputStream = new MemoryStream()) {
await inputBlob.DownloadToStreamAsync(inputStream);
switch (extension.ToLowerInvariant()) {
case "zip":
pack = new ZipPack();
break;
case "pdf":
pack = new PdfPack();
break;
default:
throw new InvalidOperationException("Invalid file type.");
}
pack.Open(inputStream);
if (pack.PageCount == 0) {
throw new InvalidOperationException("No pages found.");
}
if (pack.PageCount % numPages != 0) {
throw new InvalidOperationException($"Pages not a multiple of {numPages}.");
}
Guid groupId = default;
for (var i = 0; i < pack.PageCount; i++) {
if (i % numPages == 0) {
groupId = Guid.NewGuid();
}
using (Stream imageStream = pack.GetPage(i)) {
var imageBlob = container.GetBlockBlobReference($"{groupId}/{i % numPages}");
await imageBlob.UploadFromStreamAsync(imageStream);
}
if (i % numPages == numPages - 1) {
await queue.AddAsync(new StitchRequest {
GroupId = groupId.ToString("D"),
Count = numPages
});
}
}
}
await inputBlob.DeleteAsync();
} finally {
pack?.Dispose();
}
}