Getting error when uploading video using Tweetinvi - twitter

I am trying to upload files larger than 5MB but less than 15MB. In this case its 10MB... sample video from sample-videos.com
I am using Tweetinvi, and it works great with files less than 5MB, but fails on chunked uploads. I've tried the easy and the hard way.
Easy way:
var video = File.ReadAllBytes(#"D:\Projects\SampleVideo_1280x720_10mb.mp4");
var media = Upload.UploadVideo(video); // Error here... Invalid Content
var tweet = user.PublishTweet(message, new PublishTweetOptionalParameters
{
Medias = { media }
});
I've pulled in the Tweetinvi solution from Git (currently 0.9.13.0 repository here) and see that the above is getting an error "Invalid Content" when calling Upload.UploadVideo(...). It appears to fail on the command FINALIZE.
Tried the hard way:
using (var fileStream = File.OpenRead(#"D:\Projects\SampleVideo_1280x720_10mb.mp4"))
{
var initSucceeded = uploader.Init("video/mp4", (int)fileStream.Length);
byte[] buffer = new byte[4900000]; //Your chunk MUST be 5MB or less or else the Append function will fail silently.
int bytesRead = 0;
while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) > 0)
{
byte[] copy = new byte[bytesRead];
Buffer.BlockCopy(buffer, 0, copy, 0, bytesRead);
var appendResult = uploader.Append(new ChunkUploadAppendParameters(copy, "video/mp4", null) { SegmentIndex = uploader.NextSegmentIndex });
}
var video = uploader.Complete(); // Fails here... Returned error: Segments do not add up to provided total file size
var tweet = user.PublishTweet(message, new PublishTweetOptionalParameters()
{
//Medias = { video }
MediaIds = { video.MediaId.Value }
});
}
The above fails on upload.Complete() with the Twitter API returning "Segments do not add up to provided total file size"
What am I missing?
TIA

I think the problem you have is with the video file. The video seems to be using a 6 channels audio and Twitter Public Upload API only allow developers to upload video with mono or stereo audio.
Source : https://dev.twitter.com/rest/media/uploading-media
I am no expert in video properties so feel free to prove me wrong if I am.
The above fails on upload.Complete() with the Twitter API returning "Segments do not add up to provided total file size"
This error means that you are not actually sending all the byte that you promised Twitter. During the INIT, you tell Twitter the size of your media, if what it receives in the combined APPEND is not equals to the value you specified in the INIT, the error you described is thrown.
PS : I have tried using a 2 channels 14.8 MB mp4 and it works properly. var media = Upload.UploadVideo(binary);

Related

Preview image attachments in ChatMessage

We are using the ms graph api to post messages to a teams channel from a internal desktop application. The main purpose is to attach images to the message. We upload the image files into the one-drive folder of the channel as shown below.
var uploadProps = new DriveItemUploadableProperties
{
ODataType = null,
AdditionalData = new Dictionary<string, object>
{
{ "#microsoft.graph.conflictBehavior", "replace" }
}
};
var session = await graphClient.Drives[driveId]
.Items[parentId].ItemWithPath(fileName).CreateUploadSession(uploadProps).Request().PostAsync(token);
int maxSliceSize = 320 * 1024;
var fileUploadTask =
new LargeFileUploadTask<DriveItem>(session, fileStream, maxSliceSize);
// Create a callback that is invoked after each slice is uploaded
IProgress<long> progress = new Progress<long>(reportAsync);
// Upload the file
var uploadResult = await fileUploadTask.UploadAsync(progress);
if (uploadResult.UploadSucceeded)
{
return uploadResult.ItemResponse;
}
We then send a message to the channel and attach the images uploaded previously as reference attachments.
var chatMsg = new ChatMessage();
chatMsg.Body = new ItemBody();
chatMsg.Body.ContentType = BodyType.Html;
chatMsg.Body.Content = msg + " " + string.Join(" ", attachments.Select(d => $"<attachment id=\"{parseEtag(d.ETag)}\"></attachment>"));
chatMsg.Attachments = attachments.Select(d => new ChatMessageAttachment()
{
Id = parseEtag(d.ETag),
ContentType = "reference",
ContentUrl = d.WebUrl,
Name = d.Name
});
return await this.graphClient.Teams[teamId].Channels[channelId].Messages
.Request()
.AddAsync(chatMsg, token);
The problem is that the message only shows the names of the attachments with no preview as seen in the message at the bottom. We want to have a preview as seen (top message) when attaching a file within the teams application.
We've tried to set the thumbnailurl property of the attachment to the thumbnail url fetched from the ms-graph api with no success.
We've uploaded a file using the teams application (with preview) and then created an identical message with the same file (same driveitem id) in our application (show's no preview). Then we fetched both messages using the graph api and could not discern any differences between the two besides the message id's ofc.
We've scoured these forums, the ms documentations and even suggestion pages and found nothing.
We have been able to show previews separately in the body of the message referencing the thumbnail urls and in messagecards but ideally we want the preview directly in the attachments.
EDIT
The thumbnail urls seem to expire after 24 hours and are therefor not a great solution.
We managed to solve exactly this problem using the Simple Upload Api, with the added ?$expand=thumbnails query parameter. I haven't tried but the query param ought to work for the endpoint you're using as well.
Pick a size from the ThumbnailSet in the upload response and add it to the body of your message as an image tag. See below:
// channel, file, extractIdFromEtag, message omitted for brevity.
// PUT /groups/{group-id}/drive/items/{parent-id}:/{filename}:/content
const uploadUrl = `https://graph.microsoft.com/beta/groups/${channel.teamId}/drive/items/root:/${channel.displayName}/${file.name}:/content?$expand=thumbnails`;
const res = await this.http.put(uploadUrl, file).toPromise(); // FYI Using Angular http service
const attachment = {
id: extractIdFromEtag(res.eTag),
contentType: 'reference',
contentUrl: res.webUrl,
name: res.name,
thumbnailUrl: res.webUrl
};
const postBody = {
subject: null,
body: {
contentType: 'html',
content: message
},
};
// This is what makes the image show in the message as if posted from teams
postBody.body.content += `<br><br><img src="${res.thumbnails[0].large.url}" alt="${res.name}"/>`;
const messageUrl = `https://graph.microsoft.com/beta/teams/${channel.teamId}/channels/${channel.id}/messages`;
const result = await this.http.post(messageUrl, postBody).toPromise();
// Done
You can also keep adding the attachment as you already do, if you want the original image attached as a file, as well as showing the image preview in the message.

"The audience claim value is invalid for current resource" when using ChunkedUploadProvider for Attachment in Microsoft Graph Client API

I am trying to use the following code, but am getting "Message: The audience claim value is invalid for current resource. Audience claim is 'https://graph.microsoft.com', request url is 'https://outlook.office.com/api/beta/Users..."
I get it on the provider.GetUploadChunkRequests(); call below:
AttachmentItem attachmentItem= new AttachmentItem
{
Name = [Name],
AttachmentType = AttachmentType.File,
Size = [Size]
};
var session = graphClient.Users[USEREMAIL].Messages[MESSAGEID].Attachments.CreateUploadSession(attachmentItem).Request().PostAsync().Result;
var stream = new MemoryStream(BYTEARRAY);
var maxSizeChunk = DEFAULT_CHUNK_SIZE;
var provider = new ChunkedUploadProvider(session, graphClient, stream, maxSizeChunk);
var chunkRequests = provider.GetUploadChunkRequests();
(I am using the graphClient to send emails successfully, and have also used it to upload large files using the uploadSession method)
From Andrue Eastman on GitHub:
You are most likely getting the error because of using the ChunkedUploadPorvider instead of using the FileUploadTask to upload the attachment which is setting the Auth header to cause the error you are receiving.
To use the file upload task, follow the following steps
First create an upload session and handing it over to the task as illustrated.
// Create task
var maxSliceSize = 320 * 1024; // 320 KB - Change this to your chunk size. 4MB is the default.
LargeFileUploadTask<FileAttachment> largeFileUploadTask = new LargeFileUploadTask<FileAttachment>(uploadSession, stream, maxSliceSize);
Create an upload monitor (optional)
// Setup the progress monitoring
IProgress<long> progress = new Progress<long>(progress =>
{
Console.WriteLine($"Uploaded {progress} bytes of {stream.Length} bytes");
});
The service only returns location URI which can be read off from the result object as follows.
UploadResult<FileAttachment> uploadResult = null;
try
{
uploadResult = await largeFileUploadTask.UploadAsync(progress);
if (uploadResult.UploadSucceeded)
{
Console.WriteLine(uploadResult.Location);//the location of the object
}
}
catch (ServiceException e)
{
Console.WriteLine(e.Message);
}

tweetinvi Chunked Uploads for large uploads

I am trying to file upload with tweetinvi.File upload work for image but same code does not work for video(large video more than 20 mb)
I asked here but now answear
TweetInvi Large Video Upload Failing With Null Reference
so I look for another solution.There is tweetinvi chunked uploads
I coded this but it does not work,it does not give error but it does not work
if (file.ContentType.Contains("video"))//video
{
var video1 = System.IO.File.ReadAllBytes(path);
var chunk = Upload.CreateChunkedUploader(); //Create an instance of the ChunkedUploader class (I believe this is the only way to get this object)
using (FileStream fs = System.IO.File.OpenRead(path))
{
chunk.Init("video/mp4", (int)fs.Length); //Important! When initialized correctly, your "chunk" object will now have a type long "MediaId"
byte[] buffer = new byte[video1.Length]; //Your chunk MUST be 5MB or less or else the Append function will fail silently.
int bytesRead = 0;
while ((bytesRead = fs.Read(buffer, 0, buffer.Length)) > 0)
{
byte[] copy = new byte[bytesRead];
Buffer.BlockCopy(buffer, 0, copy, 0, bytesRead);
TimeSpan s = new TimeSpan();
chunk.Append(copy, chunk.NextSegmentIndex.ToString()); //The library says the NextSegment Parameter is optional, however I wasn't able to get it to work if I left it out.
}
}
var video = chunk.Complete(); //This tells the API that we are done uploading.
listMedia.Add(video);
}
I wanted to say that after working on your bug I was able to identify the problem you were encountering.
The issue is that you were not specifying the media_category of the upload.
In addition to this you need to wait for the media to be processed by Twitter.
In Tweetinvi 2.1, the process should be made easier. Please use the following code :
var binary = File.ReadAllBytes(#"video_path");
var media = Upload.UploadVideo(binary);
// The upload is completed but it does not mean it succeeded!
if (!media.HasBeenUploaded)
{
// Something went wrong during the upload.
// Please retry or check the video type/settings.
return;
}
// Just wait for Twitter to have processed the upload (RECOMMENDED)
Upload.WaitForMediaProcessingToGetAllMetadata(media);
// Now the media is ready to be used in a Tweet
var tweet = Tweet.PublishTweet("hello", new PublishTweetOptionalParameters
{
Medias = { media }
});
You can read more about the upload in the updated documentation : https://github.com/linvi/tweetinvi/wiki/Upload#upload-status-video
Finally please note that further improvements of upload are planned for version 2.2 and 2.3.
Have a great day and thank you for reporting this problem,
Cheers
Linvi

audio Blob not working in IOS / Safari

I am recording audio, sending it as a blob to a nodejs server. The nodejs server then sends it to all connected users that are not currently recording.
Sending the blob:
mediaRecorder.onstop = function(e) {
var blob = new Blob(this.chunks, { 'type' : 'audio/ogg; codecs=opus' });
socket.emit('radio', blob);
};
Server receiving the blob:
socket.on('radio', function(blob) {
socket.broadcast.emit('voice', blob);
});
Listener receiving the blob:
socket.on('voice', function(arrayBuffer) {
var blob = new Blob([arrayBuffer], { 'type' : 'audio/ogg; codecs=opus' });
var audio = document.getElementById('audio');
audio.src = window.URL.createObjectURL(blob);
audio.play();
});
This works in all browsers/devices except safari and any ios device. Taking it further with safari's inspector I found this:
Does safari require something else in its headers for blob objects to be interpreted properly? I've researched accepted audio types, tried aac/mp3/ogg without any success. Upon reading further I've heard references to the fact that there is a bug with streaming blob audio/video data in safari and IOS though I'm not too clear any the details.
Guidance in the rite direction would be very helpful!
EDIT: It looks like this line audio.src = window.URL.createObjectURL(blob); in the receiving blob is what is causing the blob errors (image i linked)
EDIT 2: I tried to see if using another format other than blob would work, opting for base64 encoded string. Looks like this works on all devices and browsers except for IOS and Safari. I'm getting the impression it has something to do with how IOS interprets/loads the data...
For me the solution was to insert a source element into the audio element, and use the sourceElement.src attribute to refer to the blob. I didn't even need to attach the audio-element to the DOM. Example below, hope it helps someone.
var audioElement = document.createElement('audio')
var sourceElement = document.createElement('source')
audioElement.appendChild(sourceElement)
sourceElement.src = '<your blob url>'
sourceElement.type = 'audio/mp3' // or whatever
audioElement.load()
audioElement.play()
I haven't been able to find a solution using an audio element, however the Web Audio Api seems to do the trick: https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API
var audioContext = new (window.AudioContext || window.webkitAudioContext);
socket.on('voice', function(arrayBuffer) {
audioContext.decodeAudioData(arrayBuffer, audioData => {
var source = audioContext.createBufferSource();
source.buffer = audioData;
source.connect(audioContext.destination);
source.start()
});
You may still have an issue on iOS as any audio/video must be triggered by a user action.
I had a similar problem this week. I was recording the audio on Safari and the audio blob was being generated just fine. But when I tried to send the blob to server, no data was getting there. The solution bellow reads the blob with File Reader to convert it to base64, and afterwards send it to server. Here is what worked for me:
const reader = new FileReader();
reader.readAsDataURL(audioBlob);
reader.onloadend = () => {
const base64data = reader.result;
const audioName = uuid.v4() + '.mp3';
this.http.post(this.app.cloudStorageEndpoint + '/audios/local?audioName=' + audioName, base64data , header).subscribe(
res => { resolve(audioName); },
err => { reject(err); }
);
};
I've tested with Safari 12 on both iPad and iPhone and it worked fine.

NAudio in MVC, how do i can give file path through file uploader? other option?

I am using NAudio to convert my mp3 file to waveform image, for this i am using file uploader
<input type="file" name="file" />
so user can select their mp3 file and can upload the wave image in particular location in server. If i hard code "c:\media\cat\001\music.mp3" it is working properly, how do i can change according the user uploads.
//here filePath should be "c:\media\cat\001\music.mp3"
using(Mp3FileReader reader = new Mp3FileReader(filePath)) {
using(WaveStream pcmStream = WaveFormatConversionStream.CreatePcmStream(reader)) {
WaveFileWriter.CreateWaveFile(outputFile, pcmStream);
}
}
Please give me some solution for this to get the full path of the file? i know through file upload we never get client file full path, so how to read that file, above code will fit to WPF , how to do in MVC?
I found the solution to read from url, that way i can pass multiple files to read/stream, here is the code
string filePath = "your mp3 url";
using(Stream ms = new MemoryStream()) {
using(Stream stream = WebRequest.Create(filePath)
.GetResponse().GetResponseStream()) {
byte[] buffer = new byte[32768];
int read;
while((read = stream.Read(buffer, 0, buffer.Length)) > 0) {
ms.Write(buffer, 0, read);
}
}
this code will stream mp3 file from url and read the mp3 and keep in memory.

Resources