Recreating thumbnail from existing asset in Azure Media Services - c#

I am using Azure Media Services and Azure Functions to build a VOD element for a website. Basically, when the source video is uploaded a blob trigger starts off a DurableOrchestration to create an asset and then encode the video. It also generates 3 different size thumbnails using the default {Best} frame. So far so good.
What I want to do now is allow the user to select a frame from the encoded video and choose that to be the poster thumbnail.
I have an HttpTrigger which takes the asset id and the frame timestamp and kicks off another durable function which should recreate the thumbnails at the specified frame.
But it isn't working.
I originally got 3 blank images in a new asset and when I tried to force it to put the images back into the original asset, I got nothing.
This is the code I'm using to try to achieve this. It's pretty much the same as the code to create the original asset. The only real difference is that the json preset only has instruction for generating thumbnails, the asset already has 6 encoded videos, 3 thumbnails and associated meta files in it, and I'm not passing the original source video file to it (because I delete that as part of the clean-up once the original encoding is complete).
PostData data = inputs.GetInput<PostData>();
IJob job = null;
ITask taskEncoding = null;
IAsset outputEncoding = null;
int OutputMES = -1;
int taskindex = 0;
bool useEncoderOutputForAnalytics = false;
MediaServicesCredentials amsCredentials = new MediaServicesCredentials();
try
{
AzureAdTokenCredentials tokenCredentials = new AzureAdTokenCredentials(amsCredentials.AmsAadTenantDomain,
new AzureAdClientSymmetricKey(amsCredentials.AmsClientId, amsCredentials.AmsClientSecret),
AzureEnvironments.AzureCloudEnvironment);
AzureAdTokenProvider tokenProvider = new AzureAdTokenProvider(tokenCredentials);
_context = new CloudMediaContext(amsCredentials.AmsRestApiEndpoint, tokenProvider);
IAsset asset = _context.Assets.Where(a => a.Id == data.assetId).FirstOrDefault();
// Declare a new encoding job with the Standard encoder
int priority = 10;
job = _context.Jobs.Create("CMS encoding job", priority);
foreach (var af in asset.AssetFiles)
{
if (af.Name.Contains(".mp4)"))
af.IsPrimary = true;
else
af.IsPrimary = false;
}
// Get a media processor reference, and pass to it the name of the
// processor to use for the specific task.
IMediaProcessor processorMES = MediaServicesHelper.GetLatestMediaProcessorByName(_context, "Media Encoder Standard");
string preset = null;
preset = "MesThumbnails.json"; // the default preset
string start = data.frame;
if (preset.ToUpper().EndsWith(".JSON"))
{
// Build the folder path to the preset
string presetPath = Path.Combine(System.IO.Directory.GetParent(data.execContext.FunctionDirectory).FullName, "presets", preset);
log.Info("presetPath= " + presetPath);
preset = File.ReadAllText(presetPath).Replace("{Best}", start);
}
taskEncoding = job.Tasks.AddNew("rebuild thumbnails task",
processorMES,
preset,
TaskOptions.None);
// Specify the input asset to be encoded.
taskEncoding.InputAssets.Add(asset);
OutputMES = taskindex++;
string _storageAccountName = amsCredentials.StorageAccountName;
outputEncoding = taskEncoding.OutputAssets.AddNew(asset.Name + " MES encoded", _storageAccountName, AssetCreationOptions.None);
asset = useEncoderOutputForAnalytics ? outputEncoding : asset;
job.Submit();
await job.GetExecutionProgressTask(CancellationToken.None);
My question is whether what I am trying to do is actually possible, and if so what is wrong with the approach I'm taking.
I've searched quite a bit on this topic but can always only find reference to generating thumbnails whilst encoding a video, never generating thumbnails from encoded videos after the event.

I'm not passing the original source video file to it
That's likely why you are running into the problem. The output of your Adaptive Streaming Job, as you have seen, contains multiple files. There are some additional flags needed to tell the thumbnail generation Job to focus on just one file (typically the highest bitrate file). The preset below should do the trick.
Note how the preset starts with a Streams section which tells the encoder to pick the highest/top bitrate for video and audio
Note that the Step is set to 2, but range is 1, ensuring only one image is generated in the output
{
"Version": 1.0,
"Sources": [
{
"Streams": [
{
"Type": "AudioStream",
"Value": "TopBitrate"
},
{
"Type": "VideoStream",
"Value": "TopBitrate"
}
]
}
],
"Codecs": [
{
"Start": "00:00:03:00",
"Step": "2",
"Range": "1",
"Type": "JpgImage",
"JpgLayers": [
{
"Quality": 90,
"Type": "JpgLayer",
"Width": "100%",
"Height": "100%"
}
]
}
],
"Outputs": [
{
"FileName": "{Basename}_{Index}{Extension}",
"Format": {
"Type": "JpgFormat"
}
}
]
}

Related

C# GoogleAPI - How to set a time duration when variable type is "object"?

I'm stuck with my problem using " Google.Apis.Testing.v1.Data " and their documentation doesn't help me.
I have to set a "timeout" value (= a duration), but the variable type is "object" instead of "float" for example. I tried to put an int, a float, and a string but that doesn't work.
The object API doc is here. My variable is "TestTimeout" which is definitely a duration.
When I searched for a solution, I saw in java the variable type is string but that doesn't help (here)
Just for your information, I'm using this lib to execute my android application on their test devices. It's a service called TestLab in Firebase. The timeout value needs to be higher because I don't have enough time to execute my test. Here is my code, everything is working well besides this TimeOut.
TestMatrix testMatrix = new TestMatrix();
testMatrix.TestSpecification = new TestSpecification();
testMatrix.TestSpecification.TestTimeout = 600.0f; // I tested 600, 600.0f, "600", "30m", "500s"
testMatrix.EnvironmentMatrix = new EnvironmentMatrix();
testMatrix.EnvironmentMatrix.AndroidDeviceList = new AndroidDeviceList();
testMatrix.EnvironmentMatrix.AndroidDeviceList.AndroidDevices = new List<AndroidDevice>();
foreach (TestMatrixModel.TestData testData in _model.ListTests)
{
if (testData.IsSelected)
{
//Here I'm using my own data class to set GoogleAPI objects, it's simple
//as it asks me strings even for integer numbers, and it's working
foreach (int indice in testData.ChosenAndroidVersionsIndices)
{
AndroidDevice device = new AndroidDevice();
device.AndroidModelId = testData.ModelID;
device.AndroidVersionId = testData.AvailableAndroidVersions[indice];
device.Locale = testData.AvailableLocales[testData.ChosenLocale];
device.Orientation = testData.Orientation;
testMatrix.EnvironmentMatrix.AndroidDeviceList.AndroidDevices.Add(device);
}
}
}
Ok and here is the result of the request :
{
"testMatrixId": "matrix-2dntrwio3kco7",
"testSpecification": {
"testTimeout": "300s",
"testSetup": {},
"androidTestLoop": {
"appApk": {
"gcsPath": "gs://myLinkIntoGoogleCloudStorage.apk"
}
}
},
"environmentMatrix": {
"androidDeviceList": {
"androidDevices": [
{
"androidModelId": "grandpplte",
"androidVersionId": "23",
"locale": "en_001",
"orientation": "landscape"
},
{
"androidModelId": "hero2lte",
"androidVersionId": "23",
"locale": "en_001",
"orientation": "landscape"
},
etc.....
As you can see, it seems to be a string set to "300s"... so why "500s" cannot enter in ?
Thanks a lot.
Ok I got my answer :
testMatrix.TestSpecification.TestTimeout = "600s";
So it was a string and needed to finish with "s". Why that didn't work when I tried ? Just because my code was overrided with another TestSpecification after... my bad.

Transcoding mxf video file with CopyAudio in Azure Media Services v3

We are using Azure Media Services V3 API to transcode video files of various input formats into mp4 output. In case we have a mxf input file we receive the following exception when trying to transcode video with audio codec 'CopyAudio'): Azure Media ReEncode error message: An error has occurred. Stage: ApplyEncodeCommand. Code: 0x00000001.
This is the same issue as mentioned here (Copy audio codec throws exception when transcoding mxf video file), but for the v2 API of Azure Media Services
The answer given there is indeed the solution for Azure Media Services v2.
I'm having trouble to port it to the v3 API though. In code we are creating an instance of StandardEncoderPreset (Microsoft.Azure.Management.Media.Models.StandardEncoderPreset) and try to use the CopyAudio codec. Currently I am unable to figure out how to specify the MOVFormat there.
StandardEncoderPreset preset = new StandardEncoderPreset(
codecs: new List<Codec>()
{
new H264Video
{
KeyFrameInterval = TimeSpan.FromSeconds(2),
SceneChangeDetection = true,
//PreserveResolutionAfterRotation = true,
Layers = new[]
{
new H264Layer
{
Profile = H264VideoProfile.Auto,
Level = "Auto",
Bitrate = bitrate,
MaxBitrate = bitrate,
BufferWindow = TimeSpan.FromSeconds(5),
Width = width.ToString(),
Height = height.ToString(),
BFrames = 3,
ReferenceFrames = 3,
FrameRate = "0/1",
AdaptiveBFrame = true
}
}
}, new CopyAudio()
},
// Specify the format for the output files - one for video+audio, and another for the thumbnails
formats: new List<Format>()
{
new Mp4Format()
{
FilenamePattern = "{Basename}_" + width + "x" + height +"_{Bitrate}.mp4"
}
}
With the preset configured like that I get the same error as mentioned in the original post. CopyAudio only has a property 'Label'.
Also been thinking we need to specify an extra format in the list of 'Formats' but I can't find a MOVFormat (or PCMFormat) class.
Our v3 APIs do not yet support writing to MOV output file format. You would need to go with v2 APIs for such Jobs.

Deserialize Avro Spark

I'm pushing a stream of data to Azure EventHub with the following code leveraging Microsoft.Hadoop.Avro.. this code runs every 5 seconds, and simply plops the same two Avro serialised items 👍🏼:
var strSchema = File.ReadAllText("schema.json");
var avroSerializer = AvroSerializer.CreateGeneric(strSchema);
var rootSchema = avroSerializer.WriterSchema as RecordSchema;
var itemList = new List<AvroRecord>();
dynamic record_one = new AvroRecord(rootSchema);
record_one.FirstName = "Some";
record_one.LastName = "Guy";
itemList.Add(record_one);
dynamic record_two = new AvroRecord(rootSchema);
record_two.FirstName = "A.";
record_two.LastName = "Person";
itemList.Add(record_two);
using (var buffer = new MemoryStream())
{
using (var writer = AvroContainer.CreateGenericWriter(strSchema, buffer, Codec.Null))
{
using (var streamWriter = new SequentialWriter<object>(writer, itemList.Count))
{
foreach (var item in itemList)
{
streamWriter.Write(item);
}
}
}
eventHubClient.SendAsync(new EventData(buffer.ToArray()));
}
The schema used here is, again, v. simple:
{
"type": "record",
"name": "User",
"namespace": "SerDes",
"fields": [
{
"name": "FirstName",
"type": "string"
},
{
"name": "LastName",
"type": "string"
}
]
}
I have validated this is all good, with a simple view in Azure Stream Analytics on the portal:
So far so good, but i cannot, for the life of me correctly deserialize this in Databricks leverage the from_avro() command under Scala..
Load (the exact same) schema as a string:
val sampleJsonSchema = dbutils.fs.head("/mnt/schemas/schema.json")
Configure EventHub
val connectionString = ConnectionStringBuilder("<CONNECTION_STRING>")
.setEventHubName("<NAME_OF_EVENT_HUB>")
.build
val eventHubsConf = EventHubsConf(connectionString).setStartingPosition(EventPosition.fromEndOfStream)
val eventhubs = spark.readStream.format("eventhubs").options(eventHubsConf.toMap).load()
Read the data..
// this works, and i can see the serialised data
display(eventhubs.select($"body"))
// this fails, and with an exception: org.apache.spark.SparkException: Malformed records are detected in record parsing. Current parse Mode: FAILFAST. To process malformed records as null result, try setting the option 'mode' as 'PERMISSIVE'.
display(eventhubs.select(from_avro($"body", sampleJsonSchema)))
So essentially, what is going on here.. i am serialising the data with the same schema as deserializing, but something is malformed.. the documentation is incredibly sparse on this front (very very minimal on the Microsoft website).
The issue
After additional investigation, (and mainly with the help of this article) I found what my problem was: from_avro(data: Column, jsonFormatSchema: String) expects spark schema format and not avro schema format. The documentation is not very clear on this.
Solution 1
Databricks provides a handy method from_avro(column: Column, subject: String, schemaRegistryUrl: String)) that fetches needed avro schema from kafka schema registry and automatically converts to correct format.
Unfortunately, it is not available for pure spark, nor is it possible to use it without a kafka schema registry.
Solution 2
Use schema conversion provided by spark:
// define avro deserializer
class AvroDeserializer() extends AbstractKafkaAvroDeserializer {
override def deserialize(payload: Array[Byte]): String = {
val genericRecord = this.deserialize(payload).asInstanceOf[GenericRecord]
genericRecord.toString
}
}
// create deserializer instance
val deserializer = new AvroDeserializer()
// register deserializer
spark.udf.register("deserialize_avro", (bytes: Array[Byte]) =>
deserializer.deserialize(bytes)
)
// get avro schema from registry (but I presume that it should also work with schema read from a local file)
val registryClient = new CachedSchemaRegistryClient(kafkaSchemaRegistryUrl, 128)
val avroSchema = registryClient.getLatestSchemaMetadata(topic + "-value").getSchema
val sparkSchema = SchemaConverters.toSqlType(new Schema.Parser().parse(avroSchema))
// consume data
df.selectExpr("deserialize_avro(value) as data")
.select(from_json(col("data"), sparkSchema.dataType).as("data"))
.select("data.*")

Downloading configuration data

I have an application attached to the configuration file:
{
"ProjectModules": [
{
"Version": "1",
"LoginModule": {
"LoginLogic": "Project1.ModulesV1.LoginModule.Logic.LoginLogic"
}
},
{
"Version": "2",
"LoginModule": {
"LoginLogic": "Project1.ModulesV2.LoginModule.Logic.LoginLogic"
}
}
]
}
How to get the value for the "LoginLogic" key and for a specific version?
Here I started to do but it does not take into account that it is a data table
if (_configuration.GetSection("ProjectModules:" + moduleName).Exists())
{
var configSection = _configuration.GetSection("ProjectModules:" + moduleName);
if (configSection[sectionName] != null)
{
part = configSection[sectionName];
}
}
EDIT:
moduleName -> LoginModule
sectionName -> LoginLogic
I need to get the value for the "LoginLogic" key knowing the version "Version"
It's going to be extremely difficult, if not impossible, to do what you want with JSON formatted this way. You need to understand how the configuration system works. No matter what the config source (JSON, environment variables, console arguments, etc.) everything, and I mean everything ends up dumped into a dictionary. Pretty much the entire responsibility of a config provider is to take the source and convert it into a dictionary, which is then returned and merged into the main configuration dictionary.
As such, what you're actually creating here is:
["ProjectModules[0]:Version"] = 1
["ProjectModules[0]:LoginModule:LoginLogic"] = "Project1.ModulesV1.LoginModule.Logic.LoginLogic"
["ProjectModules[1]:Version"] = 2
["ProjectModules[1]:LoginModule:LoginLogic"] = "Project1.ModulesV2.LoginModule.Logic.LoginLogic"
As you can see, there's no real way here to tell exactly which version belongs to which LoginLogic, except for the index of ProjectModules being the same. However, since that's just a string serving as a key in the dictionary, it's not something you can easily filter or search on.
One option would be to change the format a bit if you can. For example, if you instead had JSON like:
{
"ProjectModules": {
"Version1": {
"LoginModule": {
"LoginLogic": "Project1.ModulesV1.LoginModule.Logic.LoginLogic"
}
},
"Version2": {
"LoginModule": {
"LoginLogic": "Project1.ModulesV1.LoginModule.Logic.LoginLogic"
}
}
}
}
Then, you'd end up with:
["ProjectModules:Version1:LoginModule:LoginLogic"] = "Project1.ModulesV1.LoginModule.Logic.LoginLogic"
["ProjectModules:Version2:LoginModule:LoginLogic"] = "Project1.ModulesV2.LoginModule.Logic.LoginLogic"
And, it's easy enough to distinguish then by version.

json foreach loop (without json.net)

I'm currently trying to create a small launcher to solve some problems using the existing launcher from minecraft.
I'm trying to read a .json file to get all the informations that i need.
If you need to take a look at the .json file here.
I got it working if i just need a single information like
string clienturl = readJson("//downloads/client/url");
with this:
private string readJson(string element)
{
string json = File.ReadAllText(Path.Combine(appPath + "1.10.2.json"));
var jsonReader = JsonReaderWriterFactory.CreateJsonReader(Encoding.UTF8.GetBytes(json), new System.Xml.XmlDictionaryReaderQuotas());
var root = XElement.Load(jsonReader);
return root.XPathSelectElement(element).Value;
}
The problem now is that i need to get informations for all the other files.
The "element" would be:
libraries/downloads/artifact/path
libraries/downloads/artifact/url
but obviously there is more then one entry for "path" and "url" so i need a foreach loop.
What do i need to change in my code above to make it working with a foreach loop?
Sorry for my bad english, i hope its not to hard to understand.
Small preview of the .json in case you dont want to download the file:
"libraries": [
{
"name": "com.mojang:netty:1.6",
"downloads": {
"artifact": {
"size": 7877,
"sha1": "4b75825a06139752bd800d9e29c5fd55b8b1b1e4",
"path": "com/mojang/netty/1.6/netty-1.6.jar",
"url": "https://libraries.minecraft.net/com/mojang/netty/1.6/netty-1.6.jar"
}
}
},
{
"name": "oshi-project:oshi-core:1.1",
"downloads": {
"artifact": {
"size": 30973,
"sha1": "9ddf7b048a8d701be231c0f4f95fd986198fd2d8",
"path": "oshi-project/oshi-core/1.1/oshi-core-1.1.jar",
"url": "https://libraries.minecraft.net/oshi-project/oshi-core/1.1/oshi-core-1.1.jar"
}
}
},
{
"name": "net.java.dev.jna:jna:3.4.0",
"downloads": {
"artifact": {
"size": 1008730,
"sha1": "803ff252fedbd395baffd43b37341dc4a150a554",
"path": "net/java/dev/jna/jna/3.4.0/jna-3.4.0.jar",
"url": "https://libraries.minecraft.net/net/java/dev/jna/jna/3.4.0/jna-3.4.0.jar"
}
}
}
]
The issue is that your JSON contains an array, and it's not immediately obvious how JsonReaderWriterFactory maps an array to XML elements, given that XML doesn't have the concept of an array.
One way to determine this is to read through the documentation Mapping Between JSON and XML which describes this mapping. But another is simply to determine for ourselves, by using one of the answers from Get the XPath to an XElement? to find out the actual paths for each element. Using this answer, the following code:
var paths = root.DescendantsAndSelf().Select(e => e.GetAbsoluteXPath()).ToList();
Debug.WriteLine(String.Join("\n", paths));
Produces output like:
/root/libraries
/root/libraries/item[1]
/root/libraries/item[1]/name
/root/libraries/item[1]/downloads
/root/libraries/item[1]/downloads/artifact
/root/libraries/item[1]/downloads/artifact/size
/root/libraries/item[1]/downloads/artifact/sha1
/root/libraries/item[1]/downloads/artifact/path
/root/libraries/item[1]/downloads/artifact/url
/root/libraries/item[2]
/root/libraries/item[2]/name
/root/libraries/item[2]/downloads
/root/libraries/item[2]/downloads/artifact
/root/libraries/item[2]/downloads/artifact/size
/root/libraries/item[2]/downloads/artifact/sha1
/root/libraries/item[2]/downloads/artifact/path
/root/libraries/item[2]/downloads/artifact/url
So, as you can see, each array item is placed in a synthetic <item> node.
Thus you can query your paths and urls as follows:
var files = root
.XPathSelectElements("libraries/item/downloads/artifact")
.Select(e => new PathAndUrl { Path = (string)e.Element("path"), Url = (string)e.Element("url") })
.ToList();
Placing the result into a list of the following class:
public class PathAndUrl
{
public string Path { get; set; }
public string Url { get; set; }
}

Categories