I wrote a program that gets youtube video URL and downloads it
Up today I did this:
1. get video "token" from "/get_video_info?video_id=ID" like:
http://www.youtube.com/get_video_info?video_id=jN0nWjvzeNc
2. Download Video by requesting it from "/get_video?video_id=ID&t=TOKEN&fmt=FORMAT_ID" like:
http://www.youtube.com/get_video?video_id=jN0nWjvzeNc&t=vjVQa1PpcFMgAK0HB1VRbinpVOwm29eGugPh3fBi6Dg%3D&fmt=18
But this doesn't work anymore!
What is the new download URL?
Thanks
Actually I'm working on the similar project that downloading the video file from youtube. I find that the get_video might be blocked by Youtube. so instead of using get_video., I use the video info retrieved from get_video_info and extract it to get the video file url.
Within the get_video_info, there are url_encoded_fmt_stream_map. After encoding it, you can find url and signature value of every video with different format. So the file url is like [url value]+'&signature='+[sig value].
Additionally I find the following topic that using same method with mine. Hope it can help you.
Can't Download from youtube
If you are interested about how to downloading youtube video file, there is a small program written by me to demonstrate the process. You are free to use it.
https://github.com/johnny0614/YoutubeVideoDownload
Add &asv=2 to the end of the URL.
You can get the stream directly by using only
http://www.youtube.com/get_video_info?video_id=jN0nWjvzeNc
I made a little script to stream youtube videos in PHP. See how the script get the video file.
<?php
#set_time_limit(0);
$id = $_GET['id']; //The youtube video ID
$type = $_GET['type']; //the MIME type of the video
parse_str(file_get_contents('http://www.youtube.com/get_video_info?video_id='.$id),$info);
$streams = explode(',',$info['url_encoded_fmt_stream_map']);
foreach($streams as $stream){
parse_str($stream,$real_stream);
$stype = $real_stream['type'];
if(strpos($real_stream['type'],';') !== false){
$tmp = explode(';',$real_stream['type']);
$stype = $tmp[0];
unset($tmp);
}
if($stype == $type && ($real_stream['quality'] == 'large' || $real_stream['quality'] == 'medium' || $real_stream['quality'] == 'small')){
header('Content-type: '.$stype);
header('Transfer-encoding: chunked');
#readfile($real_stream['url'].'&signature='.$real_stream['sig']); //Change here to do other things such as save the file to the filesystem etc.
ob_flush();
flush();
break;
}
}
?>
See the working demo here. I hope this can help you.
After a lot of failed tries, this github repositories help me:
https://github.com/rg3/youtube-dl
Get the url only like:
youtube-dl 'https://www.youtube.com/watch?v=bo_efYhYU2A' --get-url
download an mp4 and save as a.mp4 like:
youtube-dl 'https://www.youtube.com/watch?v=bo_efYhYU2A' -f mp4 -o a.mp4
Good luck.
Last time I was working on fixing one of the broken Chrome extensions to download YouTube video. I fixed it by altering the script part.
(Javascript)
var links = new String();
var downlink = new String();
var has22 = new Boolean();
has22 = false;
var Marked = false;
var FMT_DATA = fmt_url_map;//This is html text that you have to grab. In case of extension it was readily available through:document.getElementsByTagName('script');
var StrSplitter1 = '%2C', StrSplitter2 = '%26', StrSplitter3 = '%3D';
if (FMT_DATA.indexOf(',') > -1) { //Found ,
StrSplitter1 = ',';
StrSplitter2 = (FMT_DATA.indexOf('&') > -1) ? '&' : '\\u0026';
StrSplitter3 = '=';
}
var videoURL = new Array();
var FMT_DATA_PACKET = new Array();
var FMT_DATA_PACKET = FMT_DATA.split(StrSplitter1);
for (var i = 0; i < FMT_DATA_PACKET.length; i++) {
var FMT_DATA_FRAME = FMT_DATA_PACKET[i].split(StrSplitter2);
var FMT_DATA_DUEO = new Array();
for (var j = 0; j < FMT_DATA_FRAME.length; j++) {
var pair = FMT_DATA_FRAME[j].split(StrSplitter3);
if (pair.length == 2) {
FMT_DATA_DUEO[pair[0]] = pair[1];
}
}
var url = (FMT_DATA_DUEO['url']) ? FMT_DATA_DUEO['url'] : null;
if (url == null) continue;
url = unescape(unescape(url)).replace(/\\\//g, '/').replace(/\\u0026/g, '&');
var itag = (FMT_DATA_DUEO['itag']) ? FMT_DATA_DUEO['itag'] : null;
var itag = (FMT_DATA_DUEO['itag']) ? FMT_DATA_DUEO['itag'] : null;
if (itag == null) continue;
var signature = (FMT_DATA_DUEO['sig']) ? FMT_DATA_DUEO['sig'] : null;
if (signature != null) {
url = url + "&signature=" + signature;
}
if (url.toLowerCase().indexOf('http') == 0) { // validate URL
if (itag == '5') {
links += '<span class="yt-uix-button-menu-item" id="v240p">FLV (240p)</span>';
}
if (itag == '18') {
links += '<span class="yt-uix-button-menu-item" id="v360p">MP4 (360p)</span>';
}
if (itag == '35') {
links += '<span class="yt-uix-button-menu-item" id="v480p">FLV (480p)</span>';
}
if (itag == '22') {
links += '<span class="yt-uix-button-menu-item" id="v720p">MP4 HD (720p)</span>';
}
if (itag == '37') {
links += ' <span class="yt-uix-button-menu-item" id="v1080p">MP4 HD (1080p)</span>';
}
if (itag == '38') {
links += '<span class="yt-uix-button-menu-item" id="v4k">MP4 HD (4K)</span>';
}
FavVideo();
videoURL[itag] = url;
console.log(itag);
}
}
You can get separate video link from videoURL[itag] array.
The extension can be downloaded from here.
I hope this would help someone. This is working solution (as of 06-Apr-2013)
Related
I have my songs stored in database as bytes[]. How do I use these in the <audio> tag.
So something like this. Do I need to convert the bytes to something else first? I am not sure.
foreach (var item in Model)
{
<audio controls>
<source src=#item.SongBytes type="audio/mp3"/>
</audio>
}
One way would be to add a new action in your controller that returns the data:
public ActionResult Audio(int someId)
{
byte[] songBytes;
// Add code to get data
return new FileStreamResult(songBytes, "audio/mp3");
}
Then put the URL to that into the src attribute:
foreach (var item in Model)
{
<audio controls>
<source src="/Controller/Audio/#item.someId" type="audio/mp3"/>
</audio>
}
Google chrome/Ipad require support for content-range requests, so to add to given answer here, do something like this:
public FileStreamResult StreamUploadedSongs(int id)
{
byte[] song = db.UploadedSongs.Where(x => x.Id == id).FirstOrDefault().SongBytes;
long fSize = song.Length;
long startbyte = 0;
long endbyte = fSize - 1;
int statusCode = 200;
if ((Request.Headers["Range"] != null))
{
//Get the actual byte range from the range header string, and set the starting byte.
string[] range = Request.Headers["Range"].Split(new char[] { '=', '-' });
startbyte = Convert.ToInt64(range[1]);
if (range.Length > 2 && range[2] != "") endbyte = Convert.ToInt64(range[2]);
//If the start byte is not equal to zero, that means the user is requesting partial content.
if (startbyte != 0 || endbyte != fSize - 1 || range.Length > 2 && range[2] == "")
{ statusCode = 206; }//Set the status code of the response to 206 (Partial Content) and add a content range header.
}
long desSize = endbyte - startbyte + 1;
//Headers
Response.StatusCode = statusCode;
Response.ContentType = "audio/mp3";
Response.AddHeader("Content-Accept", Response.ContentType);
Response.AddHeader("Content-Length", desSize.ToString());
Response.AddHeader("Content-Range", string.Format("bytes {0}-{1}/{2}", startbyte, endbyte, fSize));
//Data
var stream = new MemoryStream(song, (int)startbyte, (int)desSize);
return new FileStreamResult(stream, Response.ContentType);
}
It works for me, my issue is when I tried to play the .wav file via wavesurfer.js in chrome by calling controller method which return the ActionResult then file is playing but I was not able to seek, forward and backward. When I do seek, player returns to start position. Although this functionality is working fine in Firfox.
I want to upload multiple files to rails server and want to show separate progress bar for each files. I am using ng-file-upload to upload files. files are getting uploaded but it showing progress for only last file and not for others..I am attaching my code. please help.
Angular controller code:
$scope.upload_file = function() {
var files = $scope.files;
var uploadUrl = base_url+"/upload_image";
var job_id = $scope.directive.id;
if(current_user.role == "third_party_vendor" || current_user.role == "employee")
{
var source = current_user.user_login+"-"+current_user.role;
}
else
{
var source = $scope.dataObj.source["user_login"]+"-"+$scope.dataObj.source["role"];
}
if(job_id === "" || job_id === undefined || files === undefined || files === ""){
error_notification("Please select a job and a file.");
return;
}
hideLoader();
$("#upload_resume_queue").modal('show');
var formData = new Array();
formData['job_id'] = job_id;
formData['context'] = "inside_page";
formData['source'] = source;
for (var i = 0; i < files.length; i++) {
var file = files[i];
console.log(file.name);
$scope.upload = $upload.upload({
url: uploadUrl,
data:{myObj: formData},
file: file,
}).progress(function(evt) {
//console.log('percent: ' +parseInt(100.0 * evt.loaded / evt.total));
file.progress = Math.round(evt.loaded * 100 / evt.total)
}).success(function(responseText) {
hideLoader();
try{
var response = responseText;
}catch(e){
error_notification("Invalid Excel file Imported.");
return;
}
if(response.status==='wrong_content_type')
error_notification("Please upload a valid file format.",0);
if(response.status==='job_application_present'){
$scope.duplicate = true;
$scope.jobID = job_id;
$scope.user_id = response.user_id;
$scope.application_id = response.application_id;
//showModal('#duplicate_application_modal');
error_notification("Job Application already present for this user and job.",0);
}
if(response.status==='invalid_email')
error_notification("The email in the resume is an invalid one.",0);
if(response.status==='success')
success_notification("The uploaded resume has been parsed.",0);
});
}
};
Html code:
<input type="file" class="required file_browse" ng-file-select="" ng-model="files" multiple />
I am not able test the following but, I think I found what is wrong.
In JavaScript variables are scoped to functions. So, in your for loop you change value of file in var file = files[i]; line. At the end, after for loop finished the value of file is the last file.
At some point .progress event is fired by ng-file-upload to notify you about progress (for one of the files). And you update the status, but, since file has the value of the last file, not the one you expected to be, the last file's status is being updated.
That's why only the last file updated. To solve, you need to access the correct file for each progress event. To do this, you can keep file variable using an anonymous function.
for (var i = 0; i < files.length; i++) {
(function(file) {
console.log(file.name);
$scope.upload = $upload.upload({
url: uploadUrl,
data:{myObj: formData},
file: file,
}).progress(function(evt) {
//console.log('percent: ' +parseInt(100.0 * evt.loaded / evt.total));
file.progress = Math.round(evt.loaded * 100 / evt.total)
}).success(function(responseText) {
// the other stuff
});
})(files[i])
}
In your code there may be other problems related to variable scopes and javascript event loop. For more information please take a look at this.
I am trying to play a YouTube video in my application. Everything works fine. However, when I try to watch a video that contains content from Vevo, it fails.
I had also tried to pass el=vevo in get_video_info:
http://www.youtube.com/get_video_info?video_id=uuZE_IRwLNI&el=vevo&ps=default&eurl=&gl=US&hl=en
stream
{
"fallback_host" = "tc.v12.cache7.googlevideo.com";
itag = 22;
quality = hd720;
s = "8E6E5D13EB65FB653B173B94CB0BCC3A20853F5EDE8.5E2E87DF33EEDE165FEA90109D3C7D5DADA06B6BB60";
type = "video/mp4; codecs=\"avc1.64001F, mp4a.40.2\"";
url = "http://r7---sn-cvh7zn7r.googlevideo.com/videoplayback?pcm2fr=yes&sver=3&expire=1393773646&itag=22&id=bae644fc84702cd2&upn=SjZd81MudQs&sparams=gcr%2Cid%2Cip%2Cipbits%2Citag%2Cpcm2fr%2Cratebypass%2Csource%2Cupn%2Cexpire&ms=au&gcr=in&mt=1393747698&source=youtube&ratebypass=yes&ipbits=0&fexp=935620%2C919120%2C912523%2C932288%2C914084%2C916626%2C937417%2C937416%2C913434%2C932289%2C936910%2C936913%2C902907&mv=m&key=yt5&ip=103.250.162.79";
}
When I use url its not playing. Is there any solution?
get_video_info works only for the videos which are allowed to be viewed as embedded videos in other websites. I struggled a lot with get_video_info but could find any solution for vevo. however I was able to make it work by retrieving the actual video page, in actual video page you have to grab player version and hit url (specified in code) to grab the streams links and actual signatures.
youtube might change this in future but today following solutions is working great for me.
Its c# you should know how to convert it into object-C, entry point of following code is ExtractUrls function and remember to pass it html of video page.
e.g. html content of http://www.youtube.com/watch?v=J5iS3tULXMQ&nomobile=1
private static List<string> ExtractUrls(string html)
{
string Player_Version = Regex.Match(html, #"""\\/\\/s.ytimg.com\\/yts\\/jsbin\\/html5player-(.+?)\.js""").Groups[1].ToString();
string Player_Code = new WebClient().DownloadString("http://s.ytimg.com/yts/jsbin/" + "html5player-" + Player_Version + ".js");
html = Uri.UnescapeDataString( Regex.Match(html, #"""url_encoded_fmt_stream_map"":\s+""(.+?)""", RegexOptions.Singleline).Groups[1].ToString());
var Streams = Regex.Matches(html, #"(^url=|(\\u0026url=|,url=))(.+?)(\\u0026|,|$)");
var Signatures = Regex.Matches(html, #"(^s=|(\\u0026s=|,s=))(.+?)(\\u0026|,|$)");
List<string> urls = new List<string>();
for (int i = 0; i < Streams.Count - 1; i++)
{
string URL = Streams[i].Groups[3].ToString();
if (Signatures.Count > 0)
{
string Sign = Sign_Decipher(Signatures[i].Groups[3].ToString(), Player_Code);
URL += "&signature=" + Sign;
}
urls.Add(URL.Trim());
}
return urls;
}
public static string Sign_Decipher(string s, string Code)
{
string Function_Name = Regex.Match(Code, #"signature=(\w+)\(\w+\)").Groups[1].ToString();
var Function_Match = Regex.Match(Code, "function " + Function_Name + #"\((\w+)\)\{(.+?)\}",RegexOptions.Singleline);
string Var = Function_Match.Groups[1].ToString();
string Decipher = Function_Match.Groups[2].ToString();
var Lines = Decipher.Split(';');
for (int i = 0; i < Lines.Length; i++)
{
string Line = Lines[i].Trim();
if (Regex.IsMatch(Line, Var + "=" + Var + #"\.reverse\(\)"))
{
char[] charArray = s.ToCharArray();
Array.Reverse(charArray);
s = new string(charArray);
}
else if (Regex.IsMatch(Line, Var + "=" + Var + #"\.slice\(\d+\)"))
{
s = Slice(s, Convert.ToInt32(Regex.Match(Line, Var + "=" + Var + #"\.slice\((\d+)\)").Groups[1].ToString()));
}
else if (Regex.IsMatch(Line, Var + #"=\w+\(" + Var + #",\d+\)"))
{
s = Swap(s, Convert.ToInt32(Regex.Match(Line, Var + #"=\w+\(" + Var + #",(\d+)\)").Groups[1].ToString()));
}
else if (Regex.IsMatch(Line, Var + #"\[0\]=" + Var + #"\[\d+%" + Var + #"\.length\]"))
{
s = Swap(s, Convert.ToInt32(Regex.Match(Line, Var + #"\[0\]=" + Var + #"\[(\d+)%" + Var + #"\.length\]").Groups[1].ToString()));
}
}
return s;
}
private static string Slice(string Input, int Length)
{
return Input.Substring(Length, Input.Length - 1);
}
private static string Swap(string Input, int Position)
{
var Str = new StringBuilder(Input);
var SwapChar = Str[Position];
Str[Position] = Str[0];
Str[0] = SwapChar;
return Str.ToString();
}
credit goes to comments under this code project artical
Certain videos have a domain-level whitelist or blacklist applied to them. This is done at the discretion of the content owner.
If there is a whitelist or a blacklist, and the domain of the embedding site can't be determined (perhaps because of there not being a real referring domain in the case of your native application), then the default behavior is to block playback.
This blog post has a bit more detail as well: http://apiblog.youtube.com/2011/12/understanding-playback-restrictions.html
That specific video can only be played when it's embedded on a real website with a real referring URL, due to the way domain white/blacklisting works. And, we don't expose those lists via the API. It's a longstanding feature request
YouTube video URL should contain a signature (which is included in the 's' field), to use this url, you need to decrypt the signature first and add it to the URL.
The signature decryptor can be found on the web page of the video (i.e. youtube.com/watch?v=VIDEO_ID).
I can't provide more info as it would be against YouTube terms of service :).
Our ASP.NET MVC 3 application is running on Azure and using Blob as file storage. I have the upload part figured out.
The View is going to have the File Name, which, when clicked will prompt the file download screen to appear.
Can anyone tell me how to go about doing this?
Two options really... the first is to just redirect the user to the blob directly (if the blobs are in a public container). That would look a bit like:
return Redirect(container.GetBlobReference(name).Uri.AbsoluteUri);
If the blob is in a private container, you could either use a Shared Access Signature and do redirection like the previous example, or you could read the blob in your controller action and push it down to the client as a download:
Response.AddHeader("Content-Disposition", "attachment; filename=" + name); // force download
container.GetBlobReference(name).DownloadToStream(Response.OutputStream);
return new EmptyResult();
Here's a resumable version (useful for large files or allowing seek in video or audio playback) of private blob access:
public class AzureBlobStream : ActionResult
{
private string filename, containerName;
public AzureBlobStream(string containerName, string filename)
{
this.containerName = containerName;
this.filename = filename;
this.contentType = contentType;
}
public override void ExecuteResult(ControllerContext context)
{
var response = context.HttpContext.Response;
var request = context.HttpContext.Request;
var connectionString = ConfigurationManager.ConnectionStrings["Storage"].ConnectionString;
var account = CloudStorageAccount.Parse(connectionString);
var client = account.CreateCloudBlobClient();
var container = client.GetContainerReference(containerName);
var blob = container.GetBlockBlobReference(filename);
blob.FetchAttributes();
var fileLength = blob.Properties.Length;
var fileExists = fileLength > 0;
var etag = blob.Properties.ETag;
var responseLength = fileLength;
var buffer = new byte[4096];
var startIndex = 0;
//if the "If-Match" exists and is different to etag (or is equal to any "*" with no resource) then return 412 precondition failed
if (request.Headers["If-Match"] == "*" && !fileExists ||
request.Headers["If-Match"] != null && request.Headers["If-Match"] != "*" && request.Headers["If-Match"] != etag)
{
response.StatusCode = (int)HttpStatusCode.PreconditionFailed;
return;
}
if (!fileExists)
{
response.StatusCode = (int)HttpStatusCode.NotFound;
return;
}
if (request.Headers["If-None-Match"] == etag)
{
response.StatusCode = (int)HttpStatusCode.NotModified;
return;
}
if (request.Headers["Range"] != null && (request.Headers["If-Range"] == null || request.Headers["IF-Range"] == etag))
{
var match = Regex.Match(request.Headers["Range"], #"bytes=(\d*)-(\d*)");
startIndex = Util.Parse<int>(match.Groups[1].Value);
responseLength = (Util.Parse<int?>(match.Groups[2].Value) + 1 ?? fileLength) - startIndex;
response.StatusCode = (int)HttpStatusCode.PartialContent;
response.Headers["Content-Range"] = "bytes " + startIndex + "-" + (startIndex + responseLength - 1) + "/" + fileLength;
}
response.Headers["Accept-Ranges"] = "bytes";
response.Headers["Content-Length"] = responseLength.ToString();
response.Cache.SetCacheability(HttpCacheability.Public); //required for etag output
response.Cache.SetETag(etag); //required for IE9 resumable downloads
response.ContentType = blob.Properties.ContentType;
blob.DownloadRangeToStream(response.OutputStream, startIndex, responseLength);
}
}
Example:
Response.AddHeader("Content-Disposition", "attachment; filename=" + filename); // force download
return new AzureBlobStream(blobContainerName, filename);
I noticed that writing to the response stream from the action method messes up the HTTP headers. Some expected headers are missing and others are not set correctly.
So instead of writing to the response stream, I get the blob content as a stream and pass it to the Controller.File() method.
CloudBlockBlob blob = container.GetBlockBlobReference(blobName);
Stream blobStream = blob.OpenRead();
return File(blobStream, blob.Properties.ContentType, "FileName.txt");
I have a webpage with prettyPhoto and a youtube video inside the web page.
With jquery I do:
$("#youtubevideo embed").attr("wmode", "opaque");
also tried $("#youtubevideo embed").attr("wmode", "transparent");
In firefox image is over the youtube video, but the corners of pretty photo are missing. Not really missing because if I scroll up ad down they are shown. But still they don't appear correctly.
In Chrome video is still on top of the images :( Is there a way to fix this? Thanks
after 2 days of searching the web for the answer i've found a pure JS function that fix it in all browsers!
there you go:
function fix_flash() {
// loop through every embed tag on the site
var embeds = document.getElementsByTagName('embed');
for (i = 0; i < embeds.length; i++) {
embed = embeds[i];
var new_embed;
// everything but Firefox & Konqueror
if (embed.outerHTML) {
var html = embed.outerHTML;
// replace an existing wmode parameter
if (html.match(/wmode\s*=\s*('|")[a-zA-Z]+('|")/i))
new_embed = html.replace(/wmode\s*=\s*('|")window('|")/i, "wmode='transparent'");
// add a new wmode parameter
else
new_embed = html.replace(/<embed\s/i, "<embed wmode='transparent' ");
// replace the old embed object with the fixed version
embed.insertAdjacentHTML('beforeBegin', new_embed);
embed.parentNode.removeChild(embed);
} else {
// cloneNode is buggy in some versions of Safari & Opera, but works fine in FF
new_embed = embed.cloneNode(true);
if (!new_embed.getAttribute('wmode') || new_embed.getAttribute('wmode').toLowerCase() == 'window')
new_embed.setAttribute('wmode', 'transparent');
embed.parentNode.replaceChild(new_embed, embed);
}
}
// loop through every object tag on the site
var objects = document.getElementsByTagName('object');
for (i = 0; i < objects.length; i++) {
object = objects[i];
var new_object;
// object is an IE specific tag so we can use outerHTML here
if (object.outerHTML) {
var html = object.outerHTML;
// replace an existing wmode parameter
if (html.match(/<param\s+name\s*=\s*('|")wmode('|")\s+value\s*=\s*('|")[a-zA-Z]+('|")\s*\/?\>/i))
new_object = html.replace(/<param\s+name\s*=\s*('|")wmode('|")\s+value\s*=\s*('|")window('|")\s*\/?\>/i, "<param name='wmode' value='transparent' />");
// add a new wmode parameter
else
new_object = html.replace(/<\/object\>/i, "<param name='wmode' value='transparent' />\n</object>");
// loop through each of the param tags
var children = object.childNodes;
for (j = 0; j < children.length; j++) {
try {
if (children[j] != null) {
var theName = children[j].getAttribute('name');
if (theName != null && theName.match(/flashvars/i)) {
new_object = new_object.replace(/<param\s+name\s*=\s*('|")flashvars('|")\s+value\s*=\s*('|")[^'"]*('|")\s*\/?\>/i, "<param name='flashvars' value='" + children[j].getAttribute('value') + "' />");
}
}
}
catch (err) {
}
}
// replace the old embed object with the fixed versiony
object.insertAdjacentHTML('beforeBegin', new_object);
object.parentNode.removeChild(object);
}
}
}
now you can just run in when the page loads with jQuery:
$(document).ready(function () {
fix_flash();
}
Also you can add ?wmode=transparent to each youtube link
So if you have code like:
<iframe src="http://www.youtube.com/embed/aoZbiS20HGI">
</iframe>
You need to change it to:
<iframe src="http://www.youtube.com/embed/aoZbiS20HGI?wmode=transparent">
</iframe>