Follow me on Twitter for my latest adventures!
We have been downloading and converting YouTube videos but have not looked at how to upload them.
I will teach you how to upload YouTube videos via a command line. To find out how a video is being uploaded we will need just the Firefox browser!
Here is a typical scenario when you would want to automatically upload your videos to YouTube. Suppose you were using some other video sharing site and had already uploaded like 100 of your videos there. To get more popular on the net you'd also want to get your videos on YouTube, right? Doing it manually is a boring and tedious job, you want a program to do it for you while you sit back and relax.
Finding the YouTube Upload Form's Elements
Log into your account and go to "My Account" menu in the upper right. Then press the "Upload New Video" button.

Now let's use Firefox's "Page Info" tool which is located under "Tools" menu.

When the "Page Info" tool pops up, select the "Forms" tab. The tool will list all the HTML forms on the page. The one named "theForm" with action's url "http://www.youtube.com/my_videos_upload" is the upload form! When a user uploads the video, it gets submitted. Our program will submit this form for us.

Now that we have found all the fields we need to submit, we can write the tool itself. I'll use my favorite programming language - Perl, again. It is perfect for this job because it has extremely good packages for working with HTTP protocol.
I will probably write another post just about the Perl programming language - how to get it working on Windows Operating System. For now, if you are running Windows, then you can download ActiveState's ActivePerl which is Perl's port to win.
Writing the Program
As I said, we will be creating the tool in Perl programming language. In the previous post about YouTube I used the WWW::Mechanize package. I can tell you in advance that it will not work this time because there will be some unexpected surprises, such as hidden form elements being created by Javascript when the form is submitted. Since WWW::Mechanize stores all the forms internally, it will not be able to submit this form with these dynamic form elements. That's why we will use the base class of WWW::Mechanize - the LWP::UserAgent.
LWP::UserAgent package can be installed with the following command:
perl -MCPAN -e 'install LWP::UserAgent'
Once you have it installed, we can start writing the program.
Step 1: Logging in to YouTube
Before we can upload a video, we need to login to YouTube. Let's use the same procedure as before to find out what form do we need to submit to login. Let's go to this url:
http://www.youtube.com/login
and look at all the forms on this page. One of them is named "loginForm" with four fields. We will need to submit this form and save the cookies. Luckily LWP::UserAgent can do it for us and we will not have to parse HTTP headers or anything like that.

We see that there is no action URL provided for the loginForm, that means that the form should be POSTed to the same URL it was located on, in our case:
http://www.youtube.com/login
We also see that the form has 4 elements which should be submitted:
- 'current_form' with value 'loginForm'
- 'username' with our youtube login/username
- 'password' with our password, and
- 'action_login' with value 'Log In'
How would we submit this form with LWP::UserAgent? Easy - it has a member function $ua->post( $url, \%form, $header_field_name => $value, ... ), where %form is a hash of key, value pairs of form elements and $header_field_name => $value is a pair of HTTP headers we would like to add to the request.
Given an object of LWP::UserAgent named $ua, code which would login us to YouTube is the following:
$ua->post('http://www.youtube.com/login',
{
current_form => 'loginForm',
username => 'youtube_login',
password => 'youtube_password',
action_login => 'Log In'
},
);
See how easy it was? I love it.
The only problem is how to detect if we have really logged in. I noticed that when we log in, the upper right menu has a 'Log Out' button. So if we logged in succesfully, we should find this button.
Step 2: Uploading the Video
Now, once we have logged in, let's upload the video.
Here is the image with all the fields we have to submit to upload a video, once more:

There are 17 fields totally:
- "field_myvideo_title" - title of the video
- "field_myvideo_descr" - description of the video
- "field_myvideo_keywords" - comma separated tags
- "field_myvideo_categories" - video category
- "language" - language of the video, i'll leave it "EN" for English
- "action_upload" - a string "Upload a video..."
- "session_token" - we will have to extract the session token for this upload
- "allow_embeddings" - should the video be allowed to be embedded on other sites, i'll leave the default "Yes"
- "allow_responses" - should we allow video responses, i'll leave the default "Yes"
- "allow_comments" - should we allow comments on the video, i'll leave the default "Yes"
- "allow_ratings" - should we allow users to rate the vide, i'll leave the default "Yes"
- "location" - latitude and longitude of the location video was shot, i'll leave it empty
- "field_date_mon" - month the video was shot, i'll use default 0 (unknown)
- "field_date_day" - day the video was shot, i'll use 0 (unknown)
- "field_date_yr" - year the video was shot, i'll use 0 (unknown)
- "field_privacy" - privacy of video (public or private), i'll use the default - "public"
- "ignore_broadcast_settings" - no idea what this is, i'll use the default value 0
The trickiest part here is to extract the "session_token" which is some kind of unique id.
Going through the HTML source of upload page:
http://www.youtube.com/my_videos_upload
We find a javascript function called "dynamic_append_session_token" which dynamically creates a hidden form element containing the session ID:

We have to extract this session ID from the line in red. It can be done with the following regular expression:
token_elem\.setAttribute\('value', '(.+?)'\);
Update 2008.03.12: This is no longer true! A session cookie is represented by a variable now:
var gXSRF_token = 'OTxXiSg8O-oQFB-PWTSaDv7oX2V8MTIwNTQ1NzYxNQ==';
When we have done this, we can submit the form. For this form we will need to add a "Content-Type: form-data" header to the request because the upload form has explicitly stated that its "enctype" is "multipart/form-data".
As it turns out the actual video upload process is a two step process. This was the first step which set the video information. After we submit this form, we get redirected to a page which contains the upload file selection form. It might seem that that is the only element in this form but not so. Actually this 2nd step form has a few new fields and it also has all the fields from 1st step as hidden elements. Here are the modified and added fields in the 2nd step:
- "contact" - no idea what this field is for
- "field_command" - always "myvideo_submit"
- "field_uploadfile" - our video file we want to upload
- "field_private_share_entities" - no idea
- "action_upload" - submit button's caption "Upload Video"
- "addresser" - another mysterious unique id
Here is what page info tool shows us about 2nd step's form elements:

Notice that the form's action URL changes, we have to extract it from the html source. Someone might suggest not to use regexes for parsing HTML but it is perfectly ok for this tiny project.
The trickiest thing here is telling LWP::UserAgent that 'field_uploadfile' is a form element of 'file' type. If we look in the LWP::UserAgent's documentation we find that the three argument post() subroutine uses "POST() function from HTTP::Request::Common to build the request." Further looking in the HTTP::Request::Common documentation we find that to specify a file to be uploaded the field has to be an array ref looking like this: [ $file, $filename, Header => Value... ].
That's it. When we submit this form the video should have been uploaded. Now we just need to detect if the upload was successful. I noticed that when the video has uploaded successfully, YouTube thanks me for uploading. It looks like this:

So checking if a video uploaded successfully is reduced to checking for the thank you message.
The program can be used from command line and takes a few mandatory arguments:
Usage: perl ytup.pl -l [login] -p [password] -f <video file> -c <category> -t <title> -d <description> -x <comma, separated, tags> The login (-l) and password (-p) arguments are not mandatory because not to expose your password on a publically shared machine (by looking at process list) they can be specified in the source file by changing YT_LOGIN and YT_PASS constants. Possible categories (for -c switch): 2 - Autos & Vehicles 23 - Comedy 24 - Entertainment 1 - Film & Animation 20 - Gadgets & Games 26 - Howto & DIY 10 - Music 25 - News & Politics 22 - People & Blogs 15 - Pets & Animals 17 - Sports 19 - Travel & Places
Here is the final code:
#!/usr/bin/perl
#
# Version 1.0: 2007.07.30
# Version 1.1: 2007.10.12: youtube changed html
# Version 1.2: 2008.03.13: youtube changed html
# Version 1.3: 2009.12.02: youtube changed html and now logins with google login
#
# Peteris Krumins (peter@catonmat.net)
# http://www.catonmat.net -- good coders code, great reuse
#
use constant VERSION => "1.3";
use strict;
use warnings;
use LWP::UserAgent;
use HTML::Entities 'decode_entities';
use Getopt::Std;
$Getopt::Std::STANDARD_HELP_VERSION = 1;
# debug?
use constant DEBUG => 0;
# set these values for default -l (login) and -p (pass) values
#
use constant YT_LOGIN => "";
use constant YT_PASS => "";
# video categories
#
my %cats = (
2 => 'Autos & Vehicles',
23 => 'Comedy',
27 => 'Education',
24 => 'Entertainment',
1 => 'Film & Animation',
20 => 'Gaming',
26 => 'Howto & Style',
10 => 'Music',
25 => 'News & Politics',
29 => 'Nonprofits & Activism',
22 => 'People & Blogs',
15 => 'Pets & Animals',
28 => 'Science & Technology',
17 => 'Sports',
19 => 'Travel & Places'
);
# various urls
my $login_url = 'http://www.youtube.com/login';
my $login_post_url = 'https://www.google.com/accounts/ServiceLoginAuth?service=youtube';
my $login_cont_url = 'http://www.youtube.com/';
my $upload_url = 'http://upload.youtube.com/my_videos_upload?restrict=html_form';
my $upload_video_url1 = 'http://www.youtube.com/gen_204?a=multi_up_queue&si=%SI%&uk=%UK%&ac=1&gbe=1&fl=0&b=0&fn=0&ti=1&d=0&ta=0&pv=0&c=0&m=0&t=0&ft=0&dn=upload.youtube.com&fe=scotty&ut=html_form';
my $upload_video_url2 = 'http://upload.youtube.com/upload/rupio';
my $upload_video_url3 = 'http://www.youtube.com/gen_204?a=multi_up_start&si=%SI%&uk=%UK%&ac=1&gbe=1&fl=0&b=0&fn=0&ti=1&d=0&ta=0&pv=0&c=0&m=0&t=0&ft=0&dn=upload.youtube.com&fe=scotty&ut=html_form';
my $upload_video_url4 = 'http://www.youtube.com/gen_204?a=multi_up_finish&si=%SI%&uk=%UK%&ac=1&gbe=1&fl=0&b=0&fn=0&ti=1&d=0&ta=0&pv=0&c=0&m=0&t=23001&ft=0&dn=upload.youtube.com&fe=scotty&ut=html_form';
my $upload_video_set_info = 'http://upload.youtube.com/my_videos_upload_json';
unless (@ARGV) {
HELP_MESSAGE();
exit 1;
}
my %opts;
getopts('l:p:f:c:t:d:x:', \%opts);
# if -l or -p are not given, try to use YT_LOGIN and YT_PASS constants
unless (defined $opts{l}) {
unless (length YT_LOGIN) {
preamble();
print "Youtube username/login as neither defined nor passed as an argument\n";
print "Use -l switch to specify the username\n";
print "Example: -l joe_random\n";
exit 1;
}
else {
$opts{l} = YT_LOGIN;
}
}
unless (defined $opts{p}) {
unless (length YT_PASS) {
preamble();
print "Password was neither defined nor passed as an argument\n";
print "Use -p switch to specify the password\n";
print "Example: -p secretPass\n";
exit 1;
}
else {
$opts{p} = YT_PASS;
}
}
unless (defined $opts{f} && length $opts{f}) {
preamble();
print "No video file was specified\n";
print "Use -f switch to specify the video file\n";
print 'Example: -f "C:\Program Files\movie.avi"', "\n";
print 'Example: -f "/home/pkrumins/super.cool.video.wmv"', "\n";
exit 1;
}
unless (-r $opts{f}) {
preamble();
print "Video file is not readable or does not exist\n";
print "Check the permissions and the path to the file\n";
exit 1;
}
unless (defined $opts{c} && length $opts{c}) {
preamble();
print "No video category was specified\n";
print "Use -c switch to set the category of the video\n";
print "Example: -c 20, would set category to Gadgets & Games\n\n";
print_cats();
exit 1;
}
unless (defined $cats{$opts{c}}) {
preamble();
print "Category '$opts{c}' does not exist\n\n";
print_cats();
exit 1;
}
unless (defined $opts{t} && length $opts{t}) {
preamble();
print "No video title was specified\n";
print "Use -t switch to set the title of the video\n";
print 'Example: -t "Super Cool Video Title"', "\n";
exit 1;
}
unless (defined $opts{d} && length $opts{d}) {
preamble();
print "No video description was specified\n";
print "Use -d switch to set the description of the video\n";
print 'Example: -d "The coolest video description"', "\n";
exit 1;
}
unless (defined $opts{x} && length $opts{x}) {
preamble();
print "No tags were specified\n";
print "Use -x switch to set the tags\n";
print 'Example: -x "foo, bar, baz, hacker, purl"', "\n";
exit 1;
}
# tags should be at least two chars, can't be just numbers
my @tags = split /,\s+/, $opts{x};
my @filtered_tags = grep { length > 2 && !/^\d+$/ } @tags;
unless (@filtered_tags) {
preamble();
print "Tags must at least two chars in length and must not be numeric!\n";
print "For example, 'foo', 'bar', 'yo29' are valid tags, but ";
print "'22222', 'hi', 'b9' are invalid\n";
exit 1;
}
$opts{x} = join ', ', @filtered_tags;
# create the user agent, have it store cookies and
# pretend to be a cool windows firefox browser
my $ua = LWP::UserAgent->new(
cookie_jar => {},
agent => 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.4) '.
'Gecko/20070515 Firefox/2.0.0.4'
);
if (DEBUG) {
$ua->add_handler("request_send", sub { print "Request: \n"; shift->dump; print "\n"; return });
$ua->add_handler("response_done", sub { print "Response: \n"; shift->dump; print "---\n"; return });
}
# let the user agent follow redirects after a POST request
push @{$ua->requests_redirectable}, 'POST';
print "Logging in to YouTube...\n";
login();
print "Uploading the video ($opts{t})...\n";
upload();
print "Done!\n";
sub login {
# go to login page to get redirected to google sign in page
my $res = $ua->get($login_url);
unless ($res->is_success) {
die "Failed going to YouTube's login URL: ", $res->status_line;
}
# extract GALX id
my $GALX = extract_field($res->content, qr/name="GALX".+?value="([^"]+)"/s);
unless ($GALX) {
die "Failed logging in. Unable to extract GALX identifier from Google's login page.";
}
# submit the login form
$res = $ua->post($login_post_url,
{
ltmpl => 'sso',
continue => 'http://www.youtube.com/signin?action_handle_signin=true&nomobiletemp=1&hl=en_US&next=%2F',
service => 'youtube',
uilel => 3,
hl => 'en_US',
GALX => $GALX,
Email => $opts{l},
Passwd => $opts{p},
rmShown => 1,
signIn => 'Sign in',
asts => ''
}
);
unless ($res->is_success) {
die "Failed logging in: failed submitting login form: ",
$res->status_line;
}
# Get the meta http-equiv="refresh" url
my $next_url = extract_field($res->content, qr/http-equiv="refresh" content="0; url='(.+?)'/);
unless ($next_url) {
die "Failed logging in. Getting next url from http-equiv failed.";
}
$next_url = decode_entities($next_url);
$res = $ua->get($next_url);
unless ($res->is_success) {
die "Failed logging in. Extraction of next_url failed: ",
$res->status_line;
}
$res = $ua->get($login_cont_url);
unless ($res->is_success) {
die "Failed logging in. Navigation to YouTube.com failed: ",
$res->status_line;
}
# We have no good way to check if we really logged in.
# I found that when you have logged in the upper right menu changes
# and you have access to 'Sign Out' option.
# We check for this string to see if we have logged in.
unless ($res->content =~ />Sign Out</) {
die "Failed logging in: username/password incorrect";
}
}
sub upload {
# upload is actually a multistep process
#
# get upload page to extract some gibberish info
my $resp = $ua->get($upload_url);
unless ($resp->is_success) {
die "Failed getting $upload_url: ", $resp->status_line;
}
my $SI = extract_field($resp->content, qr/"sessionKey": "([^"]+)"/);
unless ($SI) {
die "Failed extracting sessionKey. YouTube might have redesigned!";
}
my $UK = extract_field($resp->content, qr/"uploadKey": "([^"]+)"/);
unless ($UK) {
die "Failed extracting uploadKey. YouTube might have redesigned!";
}
my $session_token = extract_field($resp->content, qr/"session_token" value="([^"]+)"/);
unless ($session_token) {
die "Failed extracting session_token. YouTube might have redesigned!";
}
prepare_upload_urls($SI, $UK);
# tell the server that we are up for uploads
$resp = $ua->get($upload_video_url1);
unless ($resp->is_success) {
die "Failed getting upload_video_url1: ", $resp->status_line;
}
# now lets post some more gibberish data
my $post_data_gibberish =<<EOL;
{"protocolVersion":"0.7","createSessionRequest":{"fields":[{"external":{"name":"file","filename":"$opts{f}","formPost":{}}},{"inlined":{"name":"return_address","content":"upload.youtube.com","contentType":"text/plain"}},{"inlined":{"name":"upload_key","content":"$UK","contentType":"text/plain"}},{"inlined":{"name":"action_postvideo","content":"1","contentType":"text/plain"}},{"inlined":{"name":"live_thumbnail_id","content":"$SI.0","contentType":"text/plain"}},{"inlined":{"name":"parent_video_id","content":"","contentType":"text/plain"}},{"inlined":{"name":"allow_offweb","content":"True","contentType":"text/plain"}},{"inlined":{"name":"uploader_type","content":"Web_HTML","contentType":"text/plain"}}]},"clientId":"scotty html form"}
EOL
$resp = $ua->post($upload_video_url2, Content => $post_data_gibberish);
unless ($resp->is_success) {
die "Failed posting gibberish to upload_video_url2: ", $resp->status_line
}
# extract the file upload url
my $file_upload_url = extract_field($resp->content, qr/"url":"([^"]+)"/);
unless ($file_upload_url) {
die "Failed extracting file upload url. YouTube might have redesigned!";
}
# now lets tell the server that we are starting to upload
$resp = $ua->get($upload_video_url3);
unless ($resp->is_success) {
die "Failed getting upload_video_url3: ", $resp->status_line;
}
# now lets post the video
$resp = $ua->post($file_upload_url,
{
Filedata => [ $opts{f} ]
},
"Content_Type" => "form-data"
);
unless ($resp->is_success) {
die "Failed uploading the file: ", $resp->status_line;
}
my $video_id = extract_field($resp->content, qr/"video_id":"([^"]+)"/);
# tell the server that we are done
$resp = $ua->get($upload_video_url4);
unless ($resp->is_success) {
die "Failed getting upload_video_url4: ", $resp->status_line;
}
# finally set the video info
$resp = $ua->post($upload_video_set_info,
{
session_token => $session_token,
action_edit_video => 1,
updated_flag => 0,
video_id => $video_id,
title => $opts{t},
description => $opts{d},
keywords => $opts{x},
category => $opts{c},
privacy => 'public'
}
);
unless ($resp->is_success) {
die "Failed setting video info (but it uploaded ok!): ", $resp->status_line;
}
if ($resp->content =~ /"errors":\s*\[(.+?)\]/) {
die "The video uploaded OK, but there were errors setting the video info:\n", $1;
}
}
sub prepare_upload_urls {
my ($SI, $UK) = @_;
for ($upload_video_url1, $upload_video_url3, $upload_video_url4)
{
s/%SI%/$SI/;
s/%UK%/$UK/;
}
}
sub extract_field {
my ($content, $rx) = @_;
if ($content =~ /$rx/) {
return $1
}
}
sub HELP_MESSAGE {
preamble();
print "Usage: $0 ",
"-l [login] ",
"-p [password] ",
"-f <video file> ",
"-c <category> ",
"-t <title> ",
"-d <description> ",
"-x <comma, separated, tags>\n\n";
print_cats();
}
sub print_cats {
print "Possible categories (for -c switch):\n";
printf "%-4s - %s\n", $_, $cats{$_} foreach (sort {
$cats{$a} cmp $cats{$b}
} keys %cats);
}
sub VERSION_MESSAGE {
preamble();
print "Version: v", VERSION, "\n";
}
sub preamble {
print "YouTube video uploader by Peteris Krumins (peter\@catonmat.net)\n";
print "http://www.catonmat.net -- good coders code, great reuse\n";
print "\n"
}
Download
Download link: youtube video uploader (perl script)
Total downloads: 7155 times
After you download the script ytup.perl, rename it back to ytup.pl. I could not get the webserver to stop interpreting .pl extension as a CGI script, so I renamed it to ytup.perl.
Thanks for reading the tutorial! If you notice any inaccuracies, bugs or just have something to say, comment on this article!


Facebook
Plurk
more
GitHub
LinkedIn
FriendFeed
Google Plus
Amazon wish list
Comments
This will start getting abused, and then they'll just add captcha.
hi
Can you please post the same script for video.google.com ?
Gregory, I currently do not have plans to write an article about uploading videos to video.google.com.
But it can be also done very easily. The script would be very similar to this one.
I have tried to you this script for video uploading to youtube. It gives me "Failed extracting session token, YouTube might have redesigned!". Please help how can I modify it to uplaod videos
Narendra, YouTube had redesigned a little and changed the way they assign session identifier in JavaScript.
I modified the script to extract the identifier from the new code.
I tested and it works now! Have fun!
Hi,
I am trying to upload a movie file to |U Tube but keep getting message incorrect format.
I have Gogo transfer programme that I have used to convert a DVD to mpg then I have used Movie Maker to edit. Its then when I get the problems.
HELP
barrie, can you tell me what the parameters you specified for ytup.pl script looked like?
Also can you copy/paste the exact error message. I am not sure which incorrect format you are referring to.
Thanks,
Peter
Hi all,
I got inspired enough to try getting my feet wet with modifying YouTube version to go with Google Video.
Below is the outcome, it uploads the video but then dumps and ends with 404 error.
If someone is brave enough to finalize this; for me this is good enough tho.
--
#!/usr/bin/perl # # 2007-09-17 # # Mika Helander # # Original YouTube version: # 2007.07.30 # # Peteris Krumins (peter@catonmat.net) # http://www.catonmat.net - good coders code, great reuse # use constant VERSION => "1.0"; use strict; use warnings; use LWP::UserAgent; use Getopt::Std; $Getopt::Std::STANDARD_HELP_VERSION = 1; # set these values for default -l (login) and -p (pass) values # use constant YT_LOGIN => ""; use constant YT_PASS => ""; # video categories # my %cats = ( 'Action & Adventure' => "ACTION_ADV", 'Ads & Promotional' => "AD_PROMO", 'Anime & Animation' => "ANIMATION", 'Art & Experimental' => "ART_EXPR", 'Business' => "BUSINESS", 'Children & Family' => "CHILDRENS", 'Comedy' => "COMEDY", 'Dance' => "DANCE", 'Documentary' => "DOCUMENTARY", 'Drama' => "DRAMA", 'Educational' => "EDUCATIONAL", 'Entertainment' => "ENTERTAINMENT", 'Faith & Spirituality' => "FAITH_SPIRIT", 'Foreign' => "FOREIGN", 'Gaming' => "GAMING", 'Gay & Lesbian' => "GAY_LESBIAN", 'Health & Fitness' => "FITNESS", 'Home Video' => "HOME_VIDEO", 'Horror' => "HORROR", 'Independent' => "INDY", 'Mature & Adult' => "MATURE_ADULT", 'Movie (feature)' => "MOVIE_FEATURE", 'Movie (short)' => "MOVIE_SHORT", 'Movie Trailer' => "MOVIE_TRAILER", 'Music & Musical' => "MUSIC", 'Nature' => "NATURE", 'News' => "NEWS", 'Political' => "POLITCAL", 'Religious' => "RELIGIOUS", 'Romance' => "ROMANCE", 'Science & Technology' => "SCI_TECH", 'Sci-Fi & Fantasy' => "SCIENCE_FICTION", 'Special Interest' => "SPECIAL_INT", 'Sports' => "SPORTS", 'Stock Footage' => "STOCK_FOOTAGE", 'Thriller' => "THRILLER", 'Travel' => "TRAVEL", 'TV Show' => "TV_SHOW", 'Western' => "WESTERN" ); # various urls my $login_url = 'https://www.google.com/accounts/LoginAuth?continue=http%3A%2F%2Fvideo.google.com%2F&hl=en'; my $upload_url = 'http://www.google.com/video/uploader/form/videoonline'; unless (@ARGV) { HELP_MESSAGE(); exit 1; } my %opts; getopts('l:p:f:c:t:d:', \%opts); # if -l or -p are not given, try to use YT_LOGIN and YT_PASS constants unless (defined $opts{l}) { unless (length YT_LOGIN) { preamble(); print "Google Video username/login as neither defined nor passed as an argument\n"; print "Use -l switch to specify the username\n"; print "Example: -l joe_random\n"; exit 1; } else { $opts{l} = YT_LOGIN; } } unless (defined $opts{p}) { unless (length YT_PASS) { preamble(); print "Password was neither defined nor passed as an argument\n"; print "Use -p switch to specify the password\n"; print "Example: -p secretPass\n"; exit 1; } else { $opts{p} = YT_PASS; } } unless (defined $opts{f} && length $opts{f}) { preamble(); print "No video file was specified\n"; print "Use -f switch to specify the video file\n"; print 'Example: -f "C:\Program Files\movie.avi"', "\n"; print 'Example: -f "/home/pkrumins/super.cool.video.wmv"', "\n"; exit 1; } unless (-r $opts{f}) { preamble(); print "Video file is not readable or does not exist\n"; print "Check the permissions and the path to the file\n"; exit 1; } unless (defined $opts{c} && length $opts{c}) { preamble(); print "No video category was specified\n"; print "Use -c switch to set the category of the video\n"; print "Example: -c SCI_TECH would set category to \"Science & Technology\"\n\n"; print_cats(); exit 1; } unless (defined $cats{$opts{c}}) { preamble(); print "Category '$opts{c}' does not exist\n\n"; print_cats(); exit 1; } unless (defined $opts{t} && length $opts{t}) { preamble(); print "No video title was specified\n"; print "Use -t switch to set the title of the video\n"; print 'Example: -t "Super Cool Video Title"', "\n"; exit 1; } unless (defined $opts{d} && length $opts{d}) { preamble(); print "No video description was specified\n"; print "Use -d switch to set the description of the video\n"; print 'Example: -d "The coolest video description"', "\n"; exit 1; } # create the user agent, have it store cookies and # pretend to be a cool windows firefox browser my $ua = LWP::UserAgent->new( cookie_jar => {}, agent => 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.4) '. 'Gecko/20070515 Firefox/2.0.0.4' ); # let the user agent follow redirects after a POST request push @{$ua->requests_redirectable}, 'POST'; print "Logging in to Google Video...\n"; login(); print "Uploading the video ($opts{t})...\n"; upload(); print "Done!\n"; sub login { # submit the login form my $res = $ua->post($login_url, { continue => 'http://video.google.com/', skipvpage => 'true', sendvemail => 'false', hl => 'en', Email => $opts{l}, Passwd => $opts{p}, PersistentCookie => 'yes', rmShown => '1', signIn => 'Sign in' } ); unless ($res->is_success) { die "Failed logging in: failed submitting login form: ", $res->status_line; } # We have no good way to check if we really logged in. # I found that when you have logged in the upper right menu changes # and you have access to 'Log Out' option. # We check for this string to see if we have logged in. unless ($res->content =~ /Redirecting/) { die "Failed logging in: username/password incorrect"; } } sub upload { # let's prepare the video field hash which we will need in both steps my %vid_fields = ( baseurl => 'http://video.google.com', domain => 'video.google.com', countrycode => 'FI', hl => 'en', title => $opts{t}, description => $opts{d}, genre => $opts{c}, language => "FI", access => "unlisted", tos => "agree", ignore_broadcast_settings => 0, s => "Upload video" ); $vid_fields{'video-file'} = [ $opts{f} ]; my $resp = upload_step($upload_url, \%vid_fields); # After the video has been uploaded, youtube thanks the user # for uploading the vid. Let's test for this thanks message # to see if the upload was successful print $resp->content; unless ($resp->content =~ /Upload Complete/) { die "Upload might have failed, no 'thanks for upload' message ", "was found!.\n", "Or Google Video might have redesigned!"; } } sub extract_session_token { my $content = shift; if ($content =~ m{token_elem\.setAttribute\('value', '(.+?)'\);}) { return $1; } return; } sub upload_step { my ($url, $vid_fields) = @_; my $resp = $ua->post($url, $vid_fields, "Content_Type" => "form-data"); print $resp->content; unless ($resp->is_success) { die "Upload step failed: ", $resp->status_line; } return $resp; } sub HELP_MESSAGE { preamble(); print "Usage: $0 ", "-l [login] ", "-p [password] ", "-f ", "-c ", "-t ", "-d "; print_cats(); } sub print_cats { print "Possible categories (for -c switch):\n"; printf "%-4s - %s\n", $_, $cats{$_} foreach (sort { $cats{$a} cmp $cats{$b} } keys %cats); } sub VERSION_MESSAGE { preamble(); print "Version: v", VERSION, "\n"; } sub preamble { print "Google Video uploader by Mika Helander\n"; print "Modified from Peteris Krumins YouTube version (peter\@catonmat.net)\n"; print "http://www.catonmat.net - good coders code, great reuse\n"; print "\n" }Cheers,
-Mika
Oh, and couple things to got fixed:
is the right example for category, and
access => “unlistedâ€,is pending cmd-line option for public/unlisted.
And you can quess I'm Finn because of used country/language "FI" in couple of places ;)
-Mika
Hey, Mika! Thanks for your Google uploader! I wrote a universal video uploader recently. Can't wait to put it out - it uploads videos to 20 video websites. All done in Perl.
Oh, great!
I'm willing beta-tester if such is needed.
Gonna write some "workflow" on top of this and my copy-from-hdd-camcoder and transcoding stuff later on (for Linux :)
-Mika
Hey All,
These scripts seem to fail for me at the second phase:
I also get a similar error using the Firefox universal uploader addon.
(and the form does work).
They must have changed something!
I am not sure though what, I didn't go over the post information in the form.
Guy
GuySoft
42
Update: seems to be a problem in this network. worked great outside it.
Thanks,
Guy
P.S
Maybe I'll Write a wxpython GUI for this :-)
GuySoft, that would be great! Let me know when you make this tool, so I can link to you! :)
Any way to see the code for the 20 web site ?
I do video about nature and would like to send it to many web site
Fabrice
France
For large files, I had to use
use HTTP::Request::Common qw(:DEFAULT $DYNAMIC_FILE_UPLOAD);
$DYNAMIC_FILE_UPLOAD = 1;
To make it work
Did you have to do the same or is there another way to do it ?
Another thing, do you know haow to pass a gif with a code detection ?
Fabrice
France
hey, like your work, when will this 20 site version be available. also is there a new version of the google mod of this as it is giving me quite afew problems.
this also sort of works for megavideo however it needs alot of improvement because my first test uploaded it twice. then said gave error however it had uploaded.
#!/usr/bin/perl
#
# 2007.07.30
#
# Peteris Krumins (peter@catonmat.net)
# http://www.catonmat.net - good coders code, great reuse
#
use constant VERSION => "1.1";
use strict;
use warnings;
use LWP::UserAgent;
use Getopt::Std;
$Getopt::Std::STANDARD_HELP_VERSION = 1;
# set these values for default -l (login) and -p (pass) values
#
use constant YT_LOGIN => "jam3s2k";
use constant YT_PASS => "fux0ryou";
# video categories
#
my %cats = (
2 => 'Autos & Vehicles',
23 => 'Comedy',
24 => 'Entertainment',
1 => 'Film & Animation',
20 => 'Gadgets & Games',
26 => 'Howto & DIY',
10 => 'Music',
25 => 'News & Politics',
22 => 'People & Blogs',
15 => 'Pets & Animals',
17 => 'Sports',
19 => 'Travel & Places'
);
# various urls
my $login_url = 'http://www.megavideo.com/?s=signup';
my $upload_url = 'http://www.megavideo.com/?c=upload';
unless (@ARGV) {
HELP_MESSAGE();
exit 1;
}
my %opts;
getopts('l:p:f:c:t:d:x:', \%opts);
# if -l or -p are not given, try to use YT_LOGIN and YT_PASS constants
unless (defined $opts{l}) {
unless (length YT_LOGIN) {
preamble();
print "Youtube username/login as neither defined nor passed as an argument\n";
print "Use -l switch to specify the username\n";
print "Example: -l joe_random\n";
exit 1;
}
else {
$opts{l} = YT_LOGIN;
}
}
unless (defined $opts{p}) {
unless (length YT_PASS) {
preamble();
print "Password was neither defined nor passed as an argument\n";
print "Use -p switch to specify the password\n";
print "Example: -p secretPass\n";
exit 1;
}
else {
$opts{p} = YT_PASS;
}
}
unless (defined $opts{f} && length $opts{f}) {
preamble();
print "No video file was specified\n";
print "Use -f switch to specify the video file\n";
print 'Example: -f "C:\Program Files\movie.avi"', "\n";
print 'Example: -f "/home/pkrumins/super.cool.video.wmv"', "\n";
exit 1;
}
unless (-r $opts{f}) {
preamble();
print "Video file is not readable or does not exist\n";
print "Check the permissions and the path to the file\n";
exit 1;
}
unless (defined $opts{c} && length $opts{c}) {
preamble();
print "No video category was specified\n";
print "Use -c switch to set the category of the video\n";
print "Example: -c 20, would set category to Gadgets & Games\n\n";
print_cats();
exit 1;
}
unless (defined $cats{$opts{c}}) {
preamble();
print "Category '$opts{c}' does not exist\n\n";
print_cats();
exit 1;
}
unless (defined $opts{t} && length $opts{t}) {
preamble();
print "No video title was specified\n";
print "Use -t switch to set the title of the video\n";
print 'Example: -t "Super Cool Video Title"', "\n";
exit 1;
}
unless (defined $opts{d} && length $opts{d}) {
preamble();
print "No video description was specified\n";
print "Use -d switch to set the description of the video\n";
print 'Example: -d "The coolest video description"', "\n";
exit 1;
}
unless (defined $opts{x} && length $opts{x}) {
preamble();
print "No tags were specified\n";
print "Use -x switch to set the tags\n";
print 'Example: -x "foo, bar, baz, hacker, purl"', "\n";
exit 1;
}
# tags should be at least two chars, can't be just numbers
my @tags = split /,\s+/, $opts{x};
my @filtered_tags = grep { length > 2 && !/^\d+$/ } @tags;
unless (@filtered_tags) {
preamble();
print "Tags must at least two chars in length and must not be numeric!\n";
print "For example, 'foo', 'bar', 'yo29' are valid tags, but ";
print "'22222', 'hi', 'b9' are invalid\n";
exit 1;
}
$opts{x} = join ', ', @filtered_tags;
# create the user agent, have it store cookies and
# pretend to be a cool windows firefox browser
my $ua = LWP::UserAgent->new(
cookie_jar => {},
agent => 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.4) '.
'Gecko/20070515 Firefox/2.0.0.4'
);
# let the user agent follow redirects after a POST request
push @{$ua->requests_redirectable}, 'POST';
print "Logging in to YouTube...\n";
login();
print "Uploading the video ($opts{t})...\n";
upload();
print "Done!\n";
sub login {
# submit the login form
my $res = $ua->post($login_url,
{
current_form => 'loginForm',
nickname => $opts{l},
password => $opts{p},
action => 'login'
}
);
unless ($res->is_success) {
die "Failed logging in: failed submitting login form: ",
$res->status_line;
}
# We have no good way to check if we really logged in.
# I found that when you have logged in the upper right menu changes
# and you have access to 'Log Out' option.
# We check for this string to see if we have logged in.
unless ($res->content =~ /Log Out/) {
die "Failed logging in: username/password incorrect";
}
}
sub upload {
# upload is actually a two step process, first we set the video info,
# and then we post the actual video file
#
my $resp = $ua->get($upload_url);
unless ($resp->is_success) {
die "Failed getting $upload_url: ", $resp->status_line;
}
# let's prepare the video field hash which we will need in both steps
my %vid_fields = (
action => "step2",
title => $opts{t},
description => $opts{d},
tags => $opts{x},
channel => $opts{c},
language => "1",
#ignore_broadcast_settings => 0,
);
$resp = upload_step_one(\%vid_fields);
# now add additional video fields, the new session token,
$vid_fields{action} = "submit";
$vid_fields{message} = [ $opts{d} ];
$vid_fields{file} = [ $opts{f} ];
$vid_fields{private} = "1";
$vid_fields{channels} = "24";
# the upload form's action URL at step 2 changes, we need to extract it
my $action_url;
if ($resp->content =~ m{enctype="multipart/form-data" action="(.+?)"}) {
$action_url = $1;
}
else {
die "Failed extracting action URL, YouTube might have redesigned!";
}
$resp = upload_step_two($action_url, \%vid_fields);
# After the video has been uploaded, youtube thanks the user
# for uploading the vid. Let's test for this thanks message
# to see if the upload was successful
unless ($resp->content =~ /Upload Complete/) {
die "Upload might have failed, no 'thanks for upload' message ",
"was found!.\n",
"Or YouTube might have redesigned!";
}
}
sub extract_session_token {
my $content = shift;
if ($content =~ m{token = "(.+?)"}) {
return $1;
}
return;
}
sub upload_step_one {
my $vid_fields = shift;
my $resp = $ua->post($upload_url, $vid_fields,
"Content_Type" => "form-data");
unless ($resp->is_success) {
die "First upload step failed: ", $resp->status_line;
}
return $resp;
}
sub upload_step_two {
my ($url, $vid_fields) = @_;
my $resp = $ua->post($url, $vid_fields,
"Content_Type" => "form-data");
unless ($resp->is_success) {
die "Second upload step failed: ", $resp->status_line;
}
return $resp;
}
sub HELP_MESSAGE {
preamble();
print "Usage: $0 ",
"-l [login] ",
"-p [password] ",
"-f ",
"-c ",
"-t ",
"-d ",
"-x \n\n";
print_cats();
}
sub print_cats {
print "Possible categories (for -c switch):\n";
printf "%-4s - %s\n", $_, $cats{$_} foreach (sort {
$cats{$a} cmp $cats{$b}
} keys %cats);
}
sub VERSION_MESSAGE {
preamble();
print "Version: v", VERSION, "\n";
}
sub preamble {
print "YouTube video uploader by Peteris Krumins (peter\@catonmat.net)\n";
print "http://www.catonmat.net - good coders code, great reuse\n";
print "\n"
}
any ideas why this uploads twice? :S
Hi
I try to do one for liveleak, login form is ok but the upload form have a field called file[] (with brackets), how can I use this in my perl script ?
Fabrice
France
Hi,
I try to translate the Perl Script to a javascript because I'm using javascript in my voice application (VXML) to upload a video into Youtube.
Can any one give me a Help.
Thanks,
Seems to be a nice script, but it fails for me now:
Failed extracting session token, YouTube might have redesigned! at ytup.pl line 202.
Does it work for anyone else?
Hi, all! I have updated the youtube uploader and it works like a charm now!
But the version string is still the same. 8=]
Failed extracting 'addresser' id
It says, anything changed on youtube?
Hi,
Nice script. Unfortunately when I tried to upload video through this PERL script, following error message received:
Second upload step failed: 500 Server closed connection... at ytup.pl line 303.
Please help. Thanks.
Roman, I'll update the version string soon :)
Fs, hmm, I just tested the script and it works fine:
RTL2, can you try again? Seems to work for me...
dear sir,
I tried to modify your code to VC++ and got some problem, when i sent the step one field data to youtube server, i cant get the correct response from the server for the "addresser and URL" ,it seems the www.youtube.com\my_videos_upload form not be submited, can you give me some suggestion?
Greate thanks to you
// YTBUploaderDlg.cpp : implementation file//#include "stdafx.h"#include "YTBUploader.h"#include "YTBUploaderDlg.h"#include #include "w3c.h"#ifdef _DEBUG#define new DEBUG_NEW#undef THIS_FILEstatic char THIS_FILE[] = __FILE__;#endif/////////////////////////////////////////////////////////////////////////////// CAboutDlg dialog used for App Aboutclass CAboutDlg : public CDialog{public: CAboutDlg();// Dialog Data //{{AFX_DATA(CAboutDlg) enum { IDD = IDD_ABOUTBOX }; //}}AFX_DATA // ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CAboutDlg) protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support //}}AFX_VIRTUAL// Implementationprotected: //{{AFX_MSG(CAboutDlg) //}}AFX_MSG DECLARE_MESSAGE_MAP()};CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD){ //{{AFX_DATA_INIT(CAboutDlg) //}}AFX_DATA_INIT}void CAboutDlg::DoDataExchange(CDataExchange* pDX){ CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(CAboutDlg) //}}AFX_DATA_MAP}BEGIN_MESSAGE_MAP(CAboutDlg, CDialog) //{{AFX_MSG_MAP(CAboutDlg) // No message handlers //}}AFX_MSG_MAPEND_MESSAGE_MAP()/////////////////////////////////////////////////////////////////////////////// CYTBUploaderDlg dialogCYTBUploaderDlg::CYTBUploaderDlg(CWnd* pParent /*=NULL*/) : CDialog(CYTBUploaderDlg::IDD, pParent){ //{{AFX_DATA_INIT(CYTBUploaderDlg) m_sUsername = _T(""); m_sPassword = _T(""); m_sResult = _T(""); m_sVidefile = _T(""); m_pIEsession = NULL; //m_pHtCon = NULL; //}}AFX_DATA_INIT // Note that LoadIcon does not require a subsequent DestroyIcon in Win32 m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);}void CYTBUploaderDlg::DoDataExchange(CDataExchange* pDX){ CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(CYTBUploaderDlg) DDX_Text(pDX, IDC_ACOUNT, m_sUsername); DDX_Text(pDX, IDC_PASSWORD, m_sPassword); DDX_Text(pDX, IDC_RESULT, m_sResult); DDX_Text(pDX, IDC_FILEPATH, m_sVidefile); //}}AFX_DATA_MAP}BEGIN_MESSAGE_MAP(CYTBUploaderDlg, CDialog) //{{AFX_MSG_MAP(CYTBUploaderDlg) ON_WM_SYSCOMMAND() ON_WM_PAINT() ON_WM_QUERYDRAGICON() ON_BN_CLICKED(IDC_LOGIN, OnLogin) ON_BN_CLICKED(IDC_BROWSFILE, OnBrowsfile) ON_BN_CLICKED(IDC_UPLOAD, OnUpload) //}}AFX_MSG_MAPEND_MESSAGE_MAP()/////////////////////////////////////////////////////////////////////////////// CYTBUploaderDlg message handlersBOOL CYTBUploaderDlg::OnInitDialog(){ CDialog::OnInitDialog(); // Add "About..." menu item to system menu. // IDM_ABOUTBOX must be in the system command range. ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX); ASSERT(IDM_ABOUTBOX AppendMenu(MF_SEPARATOR); pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu); } } // Set the icon for this dialog. The framework does this automatically // when the application's main window is not a dialog SetIcon(m_hIcon, TRUE); // Set big icon SetIcon(m_hIcon, FALSE); // Set small icon //xDumphtml("afdf"); //TODO: Add extra initialization here xGenonefieldPostdata("asdfsdfsdsdfsd", "field_myvideo_title","title" ); return TRUE; // return TRUE unless you set the focus to a control}void CYTBUploaderDlg::OnSysCommand(UINT nID, LPARAM lParam){ if ((nID & 0xFFF0) == IDM_ABOUTBOX) { CAboutDlg dlgAbout; dlgAbout.DoModal(); } else { CDialog::OnSysCommand(nID, lParam); }}// If you add a minimize button to your dialog, you will need the code below// to draw the icon. For MFC applications using the document/view model,// this is automatically done for you by the framework.void CYTBUploaderDlg::OnPaint() { if (IsIconic()) { CPaintDC dc(this); // device context for painting SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0); // Center icon in client rectangle int cxIcon = GetSystemMetrics(SM_CXICON); int cyIcon = GetSystemMetrics(SM_CYICON); CRect rect; GetClientRect(&rect); int x = (rect.Width() - cxIcon + 1) / 2; int y = (rect.Height() - cyIcon + 1) / 2; // Draw the icon dc.DrawIcon(x, y, m_hIcon); } else { CDialog::OnPaint(); }}// The system calls this to obtain the cursor to display while the user drags// the minimized window.HCURSOR CYTBUploaderDlg::OnQueryDragIcon(){ return (HCURSOR) m_hIcon;}void CYTBUploaderDlg::OnLogin() { // TODO: Add your control notification handler code here UpdateData(); m_sUsername = _T("jackxie76"); m_sPassword = _T("jack760220"); CString szAddress; szAddress.Format("http://youtube.com/login?username=%s&password=%s%s",m_sUsername,m_sPassword,"&next=/index¤t_form=loginForm&action_login=1"); CString strServer; CString strObject; DWORD dwType = 0; INTERNET_PORT wPort; AfxParseURL(szAddress, dwType, strServer, strObject, wPort); if( m_pIEsession == NULL ) m_pIEsession = new CInternetSession(); CHttpConnection* pHttpConnection = NULL; CHttpFile *pIEFile = NULL; m_pIEsession->SetOption(INTERNET_OPTION_CONNECT_TIMEOUT, 2000); m_pIEsession->SetOption(INTERNET_OPTION_CONNECT_RETRIES, 1); try { DWORD dwErr = 0; CString szLoginfoCookie; dwErr = GetLastError(); //m_pHtCon = m_pIEsession->GetHttpConnection( strServer ,wPort,m_sUsername,m_sPassword ); pHttpConnection = m_pIEsession->GetHttpConnection( strServer ,wPort,m_sUsername,m_sPassword ); m_pIEsession->SetCookie(szAddress,"LOGIN_INFO","test"); dwErr = GetLastError(); if( pHttpConnection == NULL ) AfxMessageBox("Login failed at get connection!"); //Don't care the return error code 122 from OpenRequest function pIEFile = pHttpConnection->OpenRequest("GET",strObject);//CHttpConnection::HTTP_VERB_GET dwErr = GetLastError(); if( pIEFile == NULL ) AfxMessageBox("Login failed at open request!"); if( pIEFile->SendRequest() == FALSE ) AfxMessageBox("Login failed at open request!"); dwErr = GetLastError();//return 2, can't find specify file? DWORD dwStatusCode = -1; pIEFile->QueryInfoStatusCode( dwStatusCode ); if( dwStatusCode >= 500) AfxMessageBox("Login failed for server error!"); //Must delete cookie first,how to? BOOL bret = FALSE; bret = m_pIEsession->GetCookie(szAddress,"LOGIN_INFO",szLoginfoCookie ); //Verify code,jack + xDumphtml( xGetresponse( pIEFile ) ); pIEFile->Close(); delete pIEFile; pHttpConnection->Close(); delete pHttpConnection; m_pIEsession->Close(); delete m_pIEsession; m_pIEsession = NULL; if( bret == FALSE ) dwErr = GetLastError(); if( !szLoginfoCookie.IsEmpty()) { int nPos = szLoginfoCookie.Find("LOGIN_INFO="); char next = szLoginfoCookie.GetAt( nPos+11 ); int nLength = szLoginfoCookie.GetLength(); if( nPos != -1 && nPos GetErrorMessage(sz, 25); CString str; str.Format("InternetException occur!\r\n%s", sz); AfxMessageBox(str); } catch(...) { m_pIEsession->Close(); delete m_pIEsession; m_pIEsession = NULL; AfxMessageBox("Login failed unexpected!"); }}void CYTBUploaderDlg::OnBrowsfile() { // TODO: Add your control notification handler code here CFileDialog dlg(TRUE, "wmv", NULL, OFN_HIDEREADONLY, "All|*.wmv;*.mpg;*.3gp;*.avi|WMV (*.wmv)|*.wmv|MPG (*.mpg;*.mpeg)|*.mpg;*.mpeg|3GP (*.3gp)|*.3gp|AVI(*.avi)|*.avi|"); if(dlg.DoModal()==IDOK ) { m_sVidefile = dlg.GetPathName(); UpdateData( FALSE ); } }void CYTBUploaderDlg::OnUpload() { // TODO: Add your control notification handler code here BOOL bResult = FALSE; bResult = xGetSessionToken(); if( bResult == FALSE ) AfxMessageBox( "Get Session failed!"); bResult = xGetURL_Addresser(); if( bResult == FALSE ) AfxMessageBox( "Get addresser failed!"); bResult = xUpload_files(); if( bResult == FALSE ) AfxMessageBox( "Upload file failed!");}//Make the try post data, through send this data, we//can get the true upload web site and its addresser...CString CYTBUploaderDlg::MakePostArgments( CString szBoundary ){ //CString szBoundary = xGenBoundary(); CString szPostdata; szPostdata += xGenonefieldPostdata(szBoundary,"field_myvideo_title","mytitle"); szPostdata += xGenonefieldPostdata(szBoundary,"field_myvideo_keywords","Animation"); szPostdata += xGenonefieldPostdata(szBoundary,"field_myvideo_descr","descripty"); szPostdata += xGenonefieldPostdata(szBoundary,"language","EN"); szPostdata += xGenonefieldPostdata(szBoundary,"field_myvideo_categories","Comedy"); szPostdata += xGenonefieldPostdata(szBoundary,"field_privacy","public"); szPostdata += xGenonefieldPostdata(szBoundary,"allow_embedding","YES"); szPostdata += xGenonefieldPostdata(szBoundary,"allow_ratings","YES"); szPostdata += xGenonefieldPostdata(szBoundary,"allow_responses","YES"); szPostdata += xGenonefieldPostdata(szBoundary,"allow_comments","YES"); szPostdata += xGenonefieldPostdata(szBoundary,"ignore_broadcast_settings","0"); szPostdata += xGenonefieldPostdata(szBoundary,"MAX_FILE_SIZE","104857600"); szPostdata += xGenonefieldPostdata(szBoundary,"field_date_mon","0"); szPostdata += xGenonefieldPostdata(szBoundary,"field_date_day","0"); szPostdata += xGenonefieldPostdata(szBoundary,"field_date_yr","0"); szPostdata += xGenonefieldPostdata(szBoundary,"field_date_yr","0"); szPostdata += xGenonefieldPostdata(szBoundary,"location",""); szPostdata += xGenonefieldPostdata(szBoundary,"action_upload","Upload a video..."); szPostdata += xGenonefieldPostdata(szBoundary,"session_token",m_sSessiontoken ); CString szEnd; szEnd.Format("--%s\r\n",szBoundary); szPostdata += szEnd; return szPostdata;}//strBoundary = _T("--MULTI-PARTS-FORM-DATA-BOUNDARY-");CString CYTBUploaderDlg::MakeRequestHeaders(CString &strBoundary){ CString strFormat; CString strData; strFormat = _T("Content-Type: multipart/form-data; boundary=%s\r\n"); //strFormat = _T("Content-Type: form-data; boundary=%s\r\n"); strData.Format(strFormat, strBoundary); return strData;}//make the boundary string base on the GUID//CString CYTBUploaderDlg::xGenBoundary(){ GUID guid; HRESULT hr = CoCreateGuid( &guid ); CString szGUID = _T("f50f5b68265f4e5eb70103c40fd3b7c2"); if( hr == S_OK ) { szGUID.Format("%08X%04X%04X%02X%02X%02X%02X%02X%02X%02X%02X",guid.Data1,guid.Data2,guid.Data3,guid.Data4[0],guid.Data4[1],guid.Data4[2],guid.Data4[3],guid.Data4[4],guid.Data4[5],guid.Data4[6],guid.Data4[7] ); return szGUID; } return szGUID;}//generate every field value for the post data..CString CYTBUploaderDlg::xGenonefieldPostdata(CString szBoundary,CString fieldname, CString value){ CString strFormat; CString strData; strFormat += _T("--"); strFormat += szBoundary; strFormat += _T("\r\n"); strFormat += _T("Content-Disposition: form-data; name=\"fieldspec\""); strFormat.Replace("fieldspec",fieldname); if( fieldname.Compare("field_uploadfile") != 0 ) { strFormat += _T("\r\n\r\n"); strFormat += value; strFormat += _T("\r\n"); } else { //; filename=\"goodbye.wmv\" CString szTmp = _T("; filename=\"filename.abc\""); szTmp.Replace("filename.abc",m_sVidefile); strFormat += szTmp; strFormat += _T("\r\nContent-Type: application/octet-stream\r\n\r\n"); } return strFormat;}CString CYTBUploaderDlg::xMakepostdata( CString szBoundary ){ //CString szBoundary = xGenBoundary(); CString szPostdata; szPostdata += xGenonefieldPostdata(szBoundary,"field_myvideo_title","YTB_TITLE"); szPostdata += xGenonefieldPostdata(szBoundary,"field_myvideo_keywords","YTB_TAG"); szPostdata += xGenonefieldPostdata(szBoundary,"field_myvideo_descr","YTB_DESC"); szPostdata += xGenonefieldPostdata(szBoundary,"language","EN"); szPostdata += xGenonefieldPostdata(szBoundary,"field_myvideo_categories","2"); szPostdata += xGenonefieldPostdata(szBoundary,"field_privacy","public"); szPostdata += xGenonefieldPostdata(szBoundary,"allow_embedding","YES"); szPostdata += xGenonefieldPostdata(szBoundary,"allow_ratings","YES"); szPostdata += xGenonefieldPostdata(szBoundary,"allow_responses","YES"); szPostdata += xGenonefieldPostdata(szBoundary,"allow_comments","YES"); szPostdata += xGenonefieldPostdata(szBoundary,"session_token",""); szPostdata += xGenonefieldPostdata(szBoundary,"action_upload","1"); szPostdata += xGenonefieldPostdata(szBoundary,"addresser",m_szAddress); szPostdata += xGenonefieldPostdata(szBoundary,"field_command","myvideo_submit"); //szPostdata += xGenonefieldPostdata(szBoundary,"field_uploadfile",m_sVidefile); CString szEnd = _T("\r\nContent-Type: application/octet-stream\r\n\r\n"); szPostdata += szEnd; return szPostdata;}//dump the response data to html file to do check...//void CYTBUploaderDlg::xDumphtml(CString response){ CString szPath; CString szfilename = xGenBoundary(); szPath.Format("C:\\%s.html",szfilename); HANDLE file=(HANDLE)::CreateFile(szPath, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); unsigned long nw=0; WriteFile(file, (const char*)response, response.GetLength(), &nw, NULL); ::CloseHandle(file);}BOOL CYTBUploaderDlg::xGetSessionToken(){ CString szURI = "http://youtube.com/my_videos_upload"; if( m_pIEsession == NULL ) m_pIEsession = new CInternetSession(); m_pIEsession->SetOption(INTERNET_OPTION_CONNECT_TIMEOUT, 2000); m_pIEsession->SetOption(INTERNET_OPTION_CONNECT_RETRIES, 1); CString strServer; CString strObject; DWORD dwType = 0; INTERNET_PORT wPort; AfxParseURL(szURI, dwType, strServer, strObject, wPort); CHttpConnection* pHttpConnection = NULL; CHttpFile *pIEFile = NULL; try { pHttpConnection = m_pIEsession->GetHttpConnection( strServer ,wPort,m_sUsername,m_sPassword ); pIEFile = pHttpConnection->OpenRequest("GET",strObject);//CHttpConnection::HTTP_VERB_GET DWORD dwErr = GetLastError(); pIEFile->SendRequest(); dwErr = GetLastError(); //Verify code,jack + CString szRes = xGetresponse( pIEFile ); xDumphtml( szRes ); //find the var gXSRF_token = ' in the szRes and set to m_sSessiontoken int nPos1 = szRes.Find( "var gXSRF_token = '" ); int nPos2 = szRes.Find("'", nPos1 + 5 ); int nPos3 = szRes.Find("'", nPos2 + 5 ); m_sSessiontoken = szRes.Mid(nPos2+1,nPos3-nPos2-1 ); m_pIEsession->Close(); delete m_pIEsession; m_pIEsession = NULL; pHttpConnection->Close(); delete pHttpConnection; pIEFile->Close(); delete pIEFile; return TRUE; } catch(CInternetException * pEx) { char sz[256] = ""; pEx->GetErrorMessage(sz, 25); CString str; str.Format("%s", sz); AfxMessageBox(str); } return FALSE;}CString CYTBUploaderDlg::xGetresponse(CHttpFile *phtFile){ DWORD dwResponseLength = phtFile->GetLength(); DWORD dwErr = GetLastError(); LPSTR szResponse; CString strResponse; while ( 0 != dwResponseLength ) { szResponse = (LPSTR)malloc(dwResponseLength + 1); memset(szResponse,0x00,dwResponseLength + 1); szResponse[dwResponseLength] = ''; phtFile->Read(szResponse, dwResponseLength); strResponse += szResponse; free(szResponse); dwResponseLength = phtFile->GetLength(); dwErr = GetLastError(); } return strResponse;}BOOL CYTBUploaderDlg::xGetURL_Addresser(){ CString szURI = "http://youtube.com/my_videos_upload"; if( m_pIEsession == NULL ) m_pIEsession = new CInternetSession(); m_pIEsession->SetOption(INTERNET_OPTION_CONNECT_TIMEOUT, 2000); m_pIEsession->SetOption(INTERNET_OPTION_CONNECT_RETRIES, 1); CString strServer; CString strObject; DWORD dwType = 0; INTERNET_PORT wPort; AfxParseURL(szURI, dwType, strServer, strObject, wPort); CHttpConnection* pHttpConnection = NULL; CHttpFile *pIEFile = NULL; try { pHttpConnection = m_pIEsession->GetHttpConnection( strServer ,wPort,m_sUsername,m_sPassword ); DWORD dwErr = GetLastError(); pIEFile = pHttpConnection->OpenRequest("POST",strObject);//CHttpConnection::HTTP_VERB_POST dwErr = GetLastError(); //pIEFile->SendRequest(); //dwErr = GetLastError(); CString strBoundary = xGenBoundary(); CString szPostdata = MakePostArgments( strBoundary ); pIEFile->AddRequestHeaders( MakeRequestHeaders( strBoundary ) ); dwErr = GetLastError(); //1.Write pre post data /* CFile ofile; ofile.Open("C:\\postdataforURL.dat",CFile::modeRead | CFile::shareDenyWrite ); int nLength = ofile.GetLength(); BYTE* pByte = new BYTE( nLength ); ofile.Read(pByte,nLength); */ int nLen = MultiByteToWideChar(CP_UTF8, 0,szPostdata, -1, NULL, NULL); LPWSTR lpszW = new WCHAR[nLen]; MultiByteToWideChar(CP_UTF8, 0, szPostdata, -1, lpszW, nLen); DWORD dwTotalLength = nLen; pIEFile->SendRequestEx(dwTotalLength, HSR_SYNC | HSR_INITIATE);//dwTotalLength dwErr = GetLastError(); pIEFile->Write(lpszW, nLen ); pIEFile->Flush(); //pIEFile->Write(pByte, nLength );//nLen dwErr = GetLastError(); delete lpszW; pIEFile->EndRequest(HSR_SYNC); CString szRes = xGetresponse( pIEFile ); //Verify code,jack + xDumphtml( szRes ); int nURLpos2 = szRes.Find("name=\"theForm\""); int nURLpos1 = szRes.Find("action=\""); int noldpos = nURLpos1; while( nURLpos1 ",nAdrpos1); m_szAddress = szRes.Mid(nAdrpos1+24,nAdrpos2-nAdrpos1); //Please get URL and addresser after the above write action... //Get response and write dresser info to server... m_pIEsession->Close(); delete m_pIEsession; m_pIEsession = NULL; pHttpConnection->Close(); delete pHttpConnection; pIEFile->Close(); delete pIEFile; return TRUE; } catch(CInternetException * pEx) { char sz[256] = ""; pEx->GetErrorMessage(sz, 25); CString str; str.Format("%s", sz); AfxMessageBox(str); } return FALSE;}BOOL CYTBUploaderDlg::xUpload_files(){ CString szURI = "http://youtube.com/my_videos_upload"; if( m_pIEsession == NULL ) m_pIEsession = new CInternetSession(); m_pIEsession->SetOption(INTERNET_OPTION_CONNECT_TIMEOUT, 2000); m_pIEsession->SetOption(INTERNET_OPTION_CONNECT_RETRIES, 1); CString strServer; CString strObject; DWORD dwType = 0; INTERNET_PORT wPort; AfxParseURL(szURI, dwType, strServer, strObject, wPort); CHttpConnection* pHttpConnection = NULL; CHttpFile *pIEFile = NULL; CFile fUploadfile; if (FALSE == fUploadfile.Open(m_sVidefile, CFile::modeRead | CFile::shareDenyWrite)) { AfxMessageBox(_T("Unable to open the file.")); return FALSE; } try { pHttpConnection = m_pIEsession->GetHttpConnection( strServer ,wPort,m_sUsername,m_sPassword ); DWORD dwErr = GetLastError(); pIEFile = pHttpConnection->OpenRequest("POST",strObject);//CHttpConnection::HTTP_VERB_POST dwErr = GetLastError(); //pIEFile->SendRequest(); //dwErr = GetLastError(); CString strBoundary = xGenBoundary(); pIEFile->AddRequestHeaders( MakeRequestHeaders( strBoundary ) ); CString szPostdata2 = xMakepostdata( strBoundary ); CString szEndstrem; szEndstrem.Format("\r\n--%s--\r\n",strBoundary ); dwErr = GetLastError(); //1.Write pre post data int nLen = MultiByteToWideChar(CP_UTF8, 0,szPostdata2, -1, NULL, NULL); LPWSTR lpszW = new WCHAR[nLen]; MultiByteToWideChar(CP_UTF8, 0, szPostdata2, -1, lpszW, nLen); int nLen2 = MultiByteToWideChar(CP_UTF8, 0,szEndstrem, -1, NULL, NULL); LPWSTR lpszW2 = new WCHAR[nLen2]; MultiByteToWideChar(CP_UTF8, 0, szEndstrem, -1, lpszW2, nLen2); DWORD dwTotalLength = nLen + fUploadfile.GetLength() + nLen2; m_pIEsession->Close(); delete m_pIEsession; m_pIEsession = NULL; pIEFile->SendRequestEx(dwTotalLength, HSR_SYNC | HSR_INITIATE); dwErr = GetLastError(); pIEFile->Write(lpszW, nLen ); dwErr = GetLastError(); delete lpszW; //File transfer { DWORD dwReadLength = -1; DWORD dwChunkLength = 32 * 1024; void* pBuffer; pBuffer = malloc(dwChunkLength); while ( 0 != dwReadLength ) { CString strDebugMessage; strDebugMessage.Format(_T("%u / %u\n"), fUploadfile.GetPosition(), fUploadfile.GetLength()); TRACE(strDebugMessage); dwReadLength = fUploadfile.Read(pBuffer, dwChunkLength); if ( 0 != dwReadLength ) { pIEFile->Write(pBuffer, dwReadLength); } } free ( pBuffer ); } pIEFile->Write(lpszW2, nLen2 ); pIEFile->Flush(); dwErr = GetLastError(); delete lpszW2; pIEFile->EndRequest(HSR_SYNC); //Verify code,jack + CString szRes = xGetresponse( pIEFile ); xDumphtml( szRes ); if( szRes.Find("Video Upload - Upload Complete ") != -1 ) AfxMessageBox("Upload complete!"); pHttpConnection->Close(); delete pHttpConnection; pIEFile->Close(); delete pIEFile; return TRUE; } catch(CInternetException * pEx) { char sz[256] = ""; pEx->GetErrorMessage(sz, 25); CString str; str.Format("%s", sz); AfxMessageBox(str); } return FALSE;}Youtube appears to have changed their login. The form action is now http://www.youtube.com/signup and action_login is no longer a submit, it is hidden. Also the submit no longer has a name. How would the code need to be changed to deal with this?
Matt, I just tested the script and it works perfectly!
My script is logging in via http://www.youtube.com/login
Hey,
I am getting today:
Failed extracting 'addresser' id for upload step two
YouTube might have redesigned :( at ytup.pl line 242.
Does anyone get this too? Is there a way to fix this?
Hey All,
I fixed my problem, apparently it was because the script wont take newlines.
I wrote a script based on this one, I think you might want to see:
http://guysoft.wordpress.com/2008/04/17/a-script-to-slice-upload-videos-to-youtube/
It will Slice, Encode and Upload videos to Youtube.
Hey Peteris Krumins!
I'm also getting: "Failed extracting ‘addresser’ id for upload step two
YouTube might have redesigned :( at ytup.pl line 242." :((
Can you fix it?
Thanks.
Hi
Is it possible to download the scrpts you have for the 20 sites ?
Fabrice
That was my trackback 2 comments ago. I've been using this well -- though youtube changed enough that I had to change it. And I had to add parameter passing via the environment, so that I am not limited by any silly command line length limits! :) It also strips HTML out of the descriptions, as they are invalid, and my descriptions are coming from my own internal tag files which sometimes use HTML.
But today? I tried to upload one, and the response back said that I hadn't chosen any tags! Not true!
I'm not sure what the problem is. If you have a suggestion, could you forward it to clintjcl@gmail.com?
I am very interested in your script.
I am on a MAC OS X 10.5.4 (latest at time of writing).
Tried:
perl -MCPAN -e 'install LWP::UserAgent'
Got:
Can't locate object method "install" via package "LWP::UserAgent" at -e line 1.
Somewhere i read try installing LWP::Bundle. After a lot of work and questions I was told:
You might want to try
install Bundle::CPAN
and:
Warning: Cannot install LWP::Bundle, don't know what it is.
sudo perl -MCPAN -e 'install Bundle::CPAN'
also took a lot of time and asked a lot of questions, but finally came out with errors.
How can I install this package. Is there any way of installing with dependencies. It is many many years since I touched perl. Thanks in advance.
WOW good job!
I saved quite a lot of time with that!
Well done!
I found the youtube login was changed so you might need to update the login sub as follows:
sub login { my $resp = $ua->get($login_url); unless ($resp->is_success) { die "Failed getting $login_url: ", $resp->status_line; } my $session_token = extract_session_token($resp->content); print "Session Token: $session_token\n"; unless (defined $session_token) { die "Failed extracting session token, YouTube might have redesigned!"; } # submit the login form my $res = $ua->post($login_url, { current_form => 'loginForm', next => '/', username => $opts{l}, password => $opts{p}, action_login => 'Log In', session_token => $session_token } ); unless ($res->is_success) { die "Failed logging in: failed submitting login form: ", $res->status_line; } # We have no good way to check if we really logged in. # I found that when you have logged in the upper right menu changes # and you have access to 'Log Out' option. # We check for this string to see if we have logged in. unless ($res->content =~ /Sign Out/) { die "Failed logging in: username/password incorrect"; }}I downloaded the original link and updated the script with Patrick's new login function and it works! :))
Peteris - wicked cool utility! Thanks much for sharing it. I got it to work but was being told login failed. Once I commented out the check for "logout" it worked. Did they perhaps change something? Would mentioned you have app you are building for 20 sites - can we talk about that? I would like to see if we could use it for our application.
Hans
never mind about login - just read previous posts!
As of today, this script is no longer working. I even tried the new/improved &login posted above.
Failed getting http://www.youtube.com/login: 501 Protocol scheme 'https' is not supported (Crypt::SSLeay not installe
d) at C:\BAT\YouTube-uploader.pl line 245 [which is not the same as your line 245, I have added things to my code, but not to &login].
It died right at this part:
sub login {
my $resp = $ua->get($login_url);
unless ($resp->is_success) {
die "Failed getting $login_url: ", $resp->status_line;
}
Anybody have a clue? This is holding me up. Right now I am programmatically uploading to both YouTube and Flickr. But this is blowing me away :)
Actually, here is the full error:
Logging in to YouTube...
LWP will support https URLs if the Crypt::SSLeay module is installed.
More information at http://www.linpro.no/lwp/libwww-perl/README.SSL.
Dumping errors to logfile...
Odd number of elements in hash assignment at C:\BAT\YouTube-uploader.pl line 214.
Use of uninitialized value in concatenation (.) or string at C:\BAT\YouTube-uploader.pl line 216.
Failed logging in: failed submitting login form: 501 Protocol scheme 'https' is not supported (Crypt::SSLeay not inst
alled) at C:\BAT\YouTube-uploader.pl line 219.
.... Anyway... Crypt::SSLeay isn't available for ActiveState perl. I'm going to see about making the CPAN version and installing it and seeing if that fixes it. Will update later.
Update - Crypt::SSLeay is installable for ActiveState perl if you go into ppm and then do "install http://theoryx5.uwinnipeg.ca/ppms/Crypt-SSLeay.ppd".
Yup. That doesn't help. YouTube must have redesigned. I don't think I have the time to figure out how to fix it. All I can do is cross my fingers, be glad that Flickr video uploading works programmatically still, and hope that someone figures it out and posts an update! Sorry for so many comments in a row.
Yeah - I just got it working on March 19th! and now it's changed. Logging in to youtube.com/login now takes you first to a google login page with a redirect parameter in it.
Hey, fixed it again, works for me now. Youtube changed the login procedure, and some other things around again. Wish they could stop that.
#!/usr/bin/perl # # Version 1.0: 2007.07.30 # Version 1.1: 2007.10.12: youtube changed html # Version 1.2: 2008.03.13: youtube changed html # # Peteris Krumins (peter@catonmat.net) # http://www.catonmat.net - good coders code, great reuse # use constant VERSION => "1.1"; use strict; use warnings; use LWP::UserAgent; use Getopt::Std; use Net::SSLeay; use Data::Dumper; $Getopt::Std::STANDARD_HELP_VERSION = 1; # set these values for default -l (login) and -p (pass) values # use constant YT_LOGIN => ""; use constant YT_PASS => ""; # video categories # my %cats = ( 2 => 'Autos & Vehicles', 23 => 'Comedy', 24 => 'Entertainment', 1 => 'Film & Animation', 20 => 'Gadgets & Games', 26 => 'Howto & DIY', 10 => 'Music', 25 => 'News & Politics', 22 => 'People & Blogs', 15 => 'Pets & Animals', 17 => 'Sports', 19 => 'Travel & Places' ); # various urls my $login_url = 'https://www.google.com/accounts/ServiceLogin?service=youtube'; my $upload_url = 'http://www.youtube.com/my_videos_upload'; unless (@ARGV) { HELP_MESSAGE(); exit 1; } my %opts; getopts('l:p:f:c:t:d:x:', \%opts); # if -l or -p are not given, try to use YT_LOGIN and YT_PASS constants unless (defined $opts{l}) { unless (length YT_LOGIN) { preamble(); print "Youtube username/login as neither defined nor passed as an argument\n"; print "Use -l switch to specify the username\n"; print "Example: -l joe_random\n"; exit 1; } else { $opts{l} = YT_LOGIN; } } unless (defined $opts{p}) { unless (length YT_PASS) { preamble(); print "Password was neither defined nor passed as an argument\n"; print "Use -p switch to specify the password\n"; print "Example: -p secretPass\n"; exit 1; } else { $opts{p} = YT_PASS; } } unless (defined $opts{f} && length $opts{f}) { preamble(); print "No video file was specified\n"; print "Use -f switch to specify the video file\n"; print 'Example: -f "C:\Program Files\movie.avi"', "\n"; print 'Example: -f "/home/pkrumins/super.cool.video.wmv"', "\n"; exit 1; } unless (-r $opts{f}) { preamble(); print "Video file is not readable or does not exist\n"; print "Check the permissions and the path to the file\n"; exit 1; } unless (defined $opts{c} && length $opts{c}) { preamble(); print "No video category was specified\n"; print "Use -c switch to set the category of the video\n"; print "Example: -c 20, would set category to Gadgets & Games\n\n"; print_cats(); exit 1; } unless (defined $cats{$opts{c}}) { preamble(); print "Category '$opts{c}' does not exist\n\n"; print_cats(); exit 1; } unless (defined $opts{t} && length $opts{t}) { preamble(); print "No video title was specified\n"; print "Use -t switch to set the title of the video\n"; print 'Example: -t "Super Cool Video Title"', "\n"; exit 1; } unless (defined $opts{d} && length $opts{d}) { preamble(); print "No video description was specified\n"; print "Use -d switch to set the description of the video\n"; print 'Example: -d "The coolest video description"', "\n"; exit 1; } unless (defined $opts{x} && length $opts{x}) { preamble(); print "No tags were specified\n"; print "Use -x switch to set the tags\n"; print 'Example: -x "foo, bar, baz, hacker, purl"', "\n"; exit 1; } # tags should be at least two chars, can't be just numbers my @tags = split /,\s+/, $opts{x}; my @filtered_tags = grep { length > 2 && !/^\d+$/ } @tags; unless (@filtered_tags) { preamble(); print "Tags must at least two chars in length and must not be numeric!\n"; print "For example, 'foo', 'bar', 'yo29' are valid tags, but "; print "'22222', 'hi', 'b9' are invalid\n"; exit 1; } $opts{x} = join ', ', @filtered_tags; # create the user agent, have it store cookies and # pretend to be an even cooler Linux firefox browser my $ua = LWP::UserAgent->new( cookie_jar => {}, agent => 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.6) Gecko/20070802 SeaMonkey/1.1.4' ); # let the user agent follow redirects after a POST request push @{$ua->requests_redirectable}, 'POST'; print "Logging in to YouTube...\n"; login(); print "Uploading the video ($opts{t})...\n"; upload(); print "Done!\n"; sub login { # submit the login form my $res = $ua->post('https://www.google.com/accounts/ServiceLoginAuth?service=youtube', { Email => $opts{l}, Passwd => $opts{p}, action_login => 'signIn' } ); unless ($res->is_success){ die "Failed logging in: failed submitting login form: ", $res->status_line; } # We have no good way to check if we really logged in. # I found that when you have logged in the upper right menu changes # and you have access to 'Log Out' option. # We check for this string to see if we have logged in. unless ($res->content =~ /Sign Out/){ die "Failed logging in: username/password incorrect"; } } sub upload { # upload is actually a two step process, first we set the video info, # and then we post the actual video file # my $resp = $ua->get($upload_url); unless ($resp->is_success) { die "Failed getting $upload_url: ", $resp->status_line; } my $session_token = extract_session_token($resp->content); unless (defined $session_token) { die "Failed extracting session token, YouTube might have redesigned!"; } # let's prepare the video field hash which we will need in both steps my %vid_fields = ( field_myvideo_title => $opts{t}, field_myvideo_descr => $opts{d}, field_myvideo_keywords => $opts{x}, field_myvideo_categories => $opts{c}, language => "EN", action_upload => "Upload a video...", allow_embeddings => "Yes", allow_responses => "Yes", allow_comments => "Yes", allow_ratings => "Yes", location => "", field_date_mon => 0, field_date_day => 0, field_date_yr => 0, field_privacy => "public", ignore_broadcast_settings => 0, session_token => $session_token ); $resp = upload_step_one(\%vid_fields); # now add additional video fields, the new session token, # the addresser field (no idea what it is), and some more fields $vid_fields{session_token} = extract_session_token($resp->content); unless (defined $vid_fields{session_token}) { die "Failed extracting session token for upload step two\n", "YouTube might have redesigned :("; } #if ($resp->content =~ m{name="addresser" value="(.+?)">}) { # $vid_fields{addresser} = $1; #} #else { # die "Failed extracting 'addresser' id for upload step two, YouTube might have redesigned :("; #} $vid_fields{contact} = ""; $vid_fields{field_command} = "myvideo_submit"; $vid_fields{field_uploadfile} = [ $opts{f} ]; $vid_fields{field_private_share_entities} = ""; $vid_fields{action_upload} = "Upload Video"; # the upload form's action URL at step 2 changes, we need to extract it my $action_url; if ($resp->content =~ m{enctype="multipart/form-data" action="(.+?)"}) { $action_url = $1; } else { die "Failed extracting action URL, YouTube might have redesigned!"; } $resp = upload_step_two($action_url, \%vid_fields); # After the video has been uploaded, youtube thanks the user # for uploading the vid. Let's test for this thanks message # to see if the upload was successful unless ($resp->content =~ /Upload Complete/) { die "Upload might have failed, no 'thanks for upload' message ", "was found!.\n", "Or YouTube might have redesigned!"; } } sub extract_session_token { my $content = shift; #if ($content =~ m{token = "(.+?)"}) { # return $1; #} #if ($content =~ m{_token = '(.+?)'}) { if ($content =~ m{_token = '(.+?)'}) { return $1; } return; } sub upload_step_one { my $vid_fields = shift; my $resp = $ua->post($upload_url, $vid_fields, "Content_Type" => "form-data"); unless ($resp->is_success) { die "First upload step failed: ", $resp->status_line; } return $resp; } sub upload_step_two { my ($url, $vid_fields) = @_; my $resp = $ua->post($url, $vid_fields, "Content_Type" => "form-data"); unless ($resp->is_success) { die "Second upload step failed: ", $resp->status_line; } return $resp; } sub HELP_MESSAGE { preamble(); print "Usage: $0 ", "-l [login] ", "-p [password] ", "-f ", "-c ", "-t ", "-d ", "-x \n\n"; print_cats(); } sub print_cats { print "Possible categories (for -c switch):\n"; printf "%-4s - %s\n", $_, $cats{$_} foreach (sort { $cats{$a} cmp $cats{$b} } keys %cats); } sub VERSION_MESSAGE { preamble(); print "Version: v", VERSION, "\n"; } sub preamble { print "YouTube video uploader by Peteris Krumins (peter\@catonmat.net)\n"; print "http://www.catonmat.net - good coders code, great reuse\n"; print "\n" }Ok, basically using the program to upload a largish Movie File, in this Case 307MB, basically crashes with this Error Message:
I assume this is a Process Limit on my System, FreeBSD 7.1 . I think it would be better if we would read a File to be uploaded chunk by chunk, and send it chunk by chunk, so that the Amount of Memory used stays the same, even if we upload a 1GB Movie or whatever Youtubes Limit is. I'll read up on how to fix this, hope others will work on it too.
Bye
hi how r u
can u please delete this video from youtube?
i will pay u any price u want.
after you delete this file i want to delete anther file and i will pay you also
http://www.youtube.com/watch?v=s8PciedDNpA&feature=related
Overrider:
Your patch does not seem to work with the recent version of YT:
Failed logging in: username/password incorrect at ./ytup.pl line 188.
Hello,
I am working on creating the same script for other sites (DailyMotion, MySpace, Metacafe, etc) but I got stuck on the DailyMotion script.
I've tried creating this script also in Java and I get the exactly same error (Error 500: Connection reset).
Here is the perl version of the dailymotion upload script. I've tried to figure out for more than 12 hours what is the problem but I couldn't figure out.
Any help would be highly appreaciated.
#!/usr/bin/perl use strict; use warnings; use LWP::UserAgent; use Getopt::Std; use Net::SSLeay; use Data::Dumper; use HTML::Entities; $Getopt::Std::STANDARD_HELP_VERSION = 1; # various urls my $login_url = 'http://www.dailymotion.com/login'; my $upload_url = 'http://www.dailymotion.com/xupload'; # create the user agent, have it store cookies and # pretend to be an even cooler Linux firefox browser my $ua = LWP::UserAgent->new( cookie_jar => {}, agent => 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.6) Gecko/20070802 SeaMonkey/1.1.4' ); # let the user agent follow redirects after a POST request push @{$ua->requests_redirectable}, 'POST'; print "Logging in to DailyMotion…\n"; login(); print "Uploading the video…\n"; upload(); print "Done!\n"; sub login { # submit the login form my $res = $ua->post($login_url, { username => "dmtesting", password => "dmtesting", login_submit => "Login", form_name => "dm_pageitem_login" } ); unless ($res->is_success){ die "Failed logging in: failed submitting login form: ", $res->status_line; } # We have no good way to check if we really logged in. # I found that when you have logged in the upper right menu changes # and you have access to ‘Log Out’ option. # We check for this string to see if we have logged in. unless ($res->content =~ /logout/){ die $res->content; die "Failed logging in: username/password incorrect"; } } sub upload { # upload is actually a two step process, first we set the video info, # and then we post the actual video file # my $resp = $ua->get($upload_url); unless ($resp->is_success) { die "Failed getting $upload_url: ", $resp->status_line; } my $form_action = extract_form_action($resp->content); unless (defined $form_action) { die "Failed extracting form action, DailyMotion might have redesigned!"; } #print $form_action; # let’s prepare the video field hash which we will need in both steps my %vid_fields = ( file => [ "testmovie.avi" ], send => "Upload" ); $resp = upload_step_one(\%vid_fields, $form_action); } sub extract_form_action { my $content = shift; if ($content =~ m{}) { return $1; } return; } sub upload_step_one { my ($vid_fields, $action_url) = @_; my $resp = $ua->post($action_url, $vid_fields, "Content_Type" => "form-data"); unless ($resp->is_success) { die "First upload step failed: ", $resp->status_line; } return $resp; }PS: I'll post all the upload scripts here after I'll finish with them.
They have changed it twice in the last month! I almost had it working after about 20 hours of work and they changed it again!
Perl and Ruby both have libraries for the youtube dev api. You can upload videos that way but you have to sign up for a free developer ID key. Not hard. But trying to figure out the youtube api is hard....
I made the script below in a rush, so it's not the fix of the original code but something that just works (it does upload the video but doesn't check for successful login, nor fills the separate form for title and such, sorry!), tested only under Linux on 17th Nov 2009. Can maybe someone with more time update the original code as well?
Note that the reason I needed this script is because my Internet connection isn't fast and stable, and I had to upload my video with various tricks; this script is one of those. I have no intention of abusing it and you shouldn't either.
Regarding the YouTube API: I think it was designed for different purposes, correct me if I'm wrong. I find it hard to grasp, too.
#!/usr/bin/perl use strict; use warnings; use LWP::UserAgent; use HTTP::Request::Common; use Net::SSLeay; # ==== CONFIGURATION START === my $username = 'username'; my $password = 'password'; my $file = './test.avi'; # ==== CONFIGURATION END === # TODO: $HTTP::Request::Common::DYNAMIC_FILE_UPLOAD = 1; my $loginPage = 'https://www.google.com/accounts/ServiceLogin?uilel=3&service=youtube&passive=true&' . 'continue=http%3A%2F%2Fwww.youtube.com%2Fsignin%3Faction_handle_signin%3Dtrue%26nom' . 'obiletemp%3D1%26hl%3Den_US%26next%3D%252Findex&hl=en_US<mpl=sso'; my $loginForm = 'https://www.google.com/accounts/ServiceLoginAuth?service=youtube'; my $uploadPage = 'http://www.youtube.com/my_videos_upload?nobeta'; my $uploadForm = 'http://upload.youtube.com/my_streaming_videos_post'; my $GALX; sub findGALX { $GALX = $_[2] if $_[1] eq "GALX"; } my $ua = LWP::UserAgent->new ( cookie_jar => {}, agent => 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3' ); push @{$ua->requests_redirectable}, 'POST'; $ua->show_progress(1); print "\n--- Requesting login page...\n"; my $resp = $ua->get($loginPage); unless ($resp->is_success) { die "Unable to get login page: ", $resp->status_line; } print "\n--- Logging in...\n"; $ua->cookie_jar->scan(\&findGALX); my $req = POST $loginForm, [ 'ltmpl' => 'sso', 'continue' => 'http://www.youtube.com/signin?action_handle_signin=true&nomobiletemp=1&hl=en_US&next=%2F', 'service' => 'youtube', 'uilel' => '3', 'hl' => 'en_US', 'GALX' => $GALX, 'Email' => $username, 'Passwd' => $password, 'PersistentCookie' => 'yes', 'rmShown' => '1', 'signIn' => 'Sign in', 'asts' => '' ]; $req->referer($loginPage); $resp = $ua->request($req); unless ($resp->is_success) { die "Couldn't log in: ", $resp->status_line; } print "\n--- Requesting the page after login...\n"; my ($nextPage) = $resp->header('Refresh') =~ /0; url='([^']+)'/; print $nextPage . "\n"; my $req = GET $nextPage; $req->referer($loginForm); $resp = $ua->request($req); print "\n--- Requesting upload page...\n"; my $req = GET $uploadPage; $req->referer($nextPage); $resp = $ua->request($req); print "\n--- Uploading...\n"; my ($uploadKey) = $resp->content =~ /yt\.www\.upload\.UploadSettings\.Keys\.UPLOAD_KEY, "([^"]+)"/m; my ($sessionKey) = $resp->content =~ /yt\.www\.upload\.UploadSettings\.Keys\.SESSION_KEY, "([^"]+)"/m; my ($sessionToken) = $resp->content =~ /'XSRF_TOKEN': '([^']+)'/m; my $req = POST $uploadForm, Content_Type => 'form-data', Content => [ 'uploader_type' => 'Web_HTML', 'return_address' => 'www.youtube.com', 'upload_key' => $uploadKey, 'action_postvideo' => '1', 'live_thumbnail_id' => $sessionKey, 'up_locale' => 'en_US', 'up_content_region' => 'US', 'up_country' => 'US', 'field_uploadfile' => [ $file ], 'session_token' => $sessionToken, ]; $req->referer($uploadPage); $resp = $ua->request($req); # open (MYFILE, '>content.html') || die $!; # print MYFILE $resp->content . "\n"; # close MYFILE;this code is not working for me,i changed all details in that file.but
the error is $uploadForm url is not found.then how i change that video upload url..?plz give me a solution or give any other code for upload a video to Youtube..i need urgently.plz...
Thanks for this code!It works!!but how to get the result URL (like http://www.youtube.com/watch?v=xxxxxxxx
) ? any one can help? thanks.
hai this code is not working for me.plz send me an email to senthilmurugangsm@gmail.com for perl code to upload a video o Youtube
Nice work!
Problem: Looks like YT ignores the lang-setting in the script.
Replace
unless ($res->content =~ />Sign Outcontent =~ />Sign Outcontent =~ />Abmelden
lizard, yes that's a known problem. I haven't yet fixed it because I use it in English and it works for me. I'll try to make it universal the next time YouTube breaks the uploader. Then I'll have to spend some time digging through their html and I will then look for a pattern that is not localized.
ok, was bored. here is a diff with non localized logincheck, fix for big files and an dirty-progress counter ;)
--- ytup.perl 2009-12-05 08:40:50.000000000 +0100 +++ ytup.pl 2010-01-18 21:40:52.000000000 +0100 @@ -8,6 +8,7 @@ # Peteris Krumins (peter@catonmat.net) # http://www.catonmat.net -- good coders code, great reuse # +$|=1; use constant VERSION => "1.3"; @@ -15,7 +16,10 @@ use warnings; use LWP::UserAgent; +use HTTP::Request::Common qw(POST); +$HTTP::Request::Common::DYNAMIC_FILE_UPLOAD++; use HTML::Entities 'decode_entities'; + use Getopt::Std; $Getopt::Std::STANDARD_HELP_VERSION = 1; @@ -182,9 +186,9 @@ login(); print "Uploading the video ($opts{t})...\n"; -upload(); +my $video_id = upload(); -print "Done!\n"; +print "Done! http://www.youtube.com/watch?v=$video_id\n"; sub login { # go to login page to get redirected to google sign in page @@ -243,7 +247,7 @@ # I found that when you have logged in the upper right menu changes # and you have access to 'Sign Out' option. # We check for this string to see if we have logged in. - unless ($res->content =~ />Sign Out</) { + unless ($res->content =~ /<form name="logoutForm"/) { die "Failed logging in: username/password incorrect"; } } @@ -304,12 +308,31 @@ } # now lets post the video - $resp = $ua->post($file_upload_url, + my $starttime = time(); + my $size = -s $opts{f}; + print "\n"; + my $req = POST($file_upload_url, { Filedata => [ $opts{f} ] }, "Content_Type" => "form-data" ); + + + # wrap content generator sub + my $gen = $req->content; + die unless ref($gen) eq "CODE"; + my $total = 0; + + $req->content(sub { + my $chunk = &$gen(); + $total += length($chunk); + print "\r$total / $size bytes (".int($total/$size*100)."%) sent, ".int($total/1000/(time()-$starttime+1)) . " k / sec "; + return $chunk; + }); + + $resp = $ua->request($req); + print "\n"; unless ($resp->is_success) { die "Failed uploading the file: ", $resp->status_line; @@ -345,6 +368,8 @@ if ($resp->content =~ /"errors":\s*\[(.+?)\]/) { die "The video uploaded OK, but there were errors setting the video info:\n", $1; } + + return $video_id; } sub prepare_upload_urls {Hello,first said thanks for this script.
i was trying do some similar in bash but i was unable :(
But i have a problem,it said :
Failed logging in: username/password incorrect at ./ytup2.perl line 247.
I use the same username and passwd with
--location https://www.google.com/youtube/accounts/ClientLogin --data 'Email=cualquiercosa327&Passwd=mypass&service=youtube&source=Test' --header 'Content-type:application/x-www-form-urlencoded'
and it works fine:
Auth=Y4Ia2djW9ju9jwMhwYcc9RjFOOHSNf1bUPEyzn6sybZl_lnqtflaRCQU_b7uuiZ_2Q9yehY8rSEYouTubeUser=cualquiercosa327
thanks
dani
some help
lizard, hey, this is cool, thanks!
daniel, someone will have to fix it. I am retiring these projects as they are no longer interesting for me. Please understand, life is too short to be messing around with these old scripts, there are more important things to learn and solve.
All the projects are in my repository in github. You may fork them.
hi,i understand.thanks
mmm ... ok, heres a patched version, ready to download http://liztv.net/s/ytup.pl
have fun
Hey lizard. Thanks for the patch! I have included it in my repo:
http://github.com/pkrumins/youtube-uploader.
thanks lizard i like this patch..
Thanks a lot. It is working fine. I only changed line 247 to "Esci" for the italian youtube page.
I know a couple programming languages, but zilch about PERL. However, you made this so detailed I picked it up fairly quickly! My question though, is how can I set it so that when uploading the video its Private instead of public.
I tried changing the value privacy => 'Private' on line 337 but that didn't seem to do anything. Any insight would be greatly appreciated!
mmm ... broken aganin ;(
Failed extracting sessionKey. YouTube might have redesigned! at ytup.pl line 267
if you need a command line youtube upload:
http://code.google.com/p/youtube-upload/
it's made with youtube API so it's gonna last a lot longer than this :)
(it's in python though)
i tried to fix this perl script but it looks like they changed something really major this time... maybe i just suck at perl though
command line youtube upload is restricted by qouta :(
in last version (February 01, 2010) ytub.pl is necessary to change only 2 strings: to extract sessionKey & uploadKey, to replace quotes to single quotes.
I'm not sure that this blog will properly display regexp,
you can download the correct version here:
http://arjlover.net/ytup-20100621.pl
Thank you, Peteris Krumins!
I've made a youtube uploader in dot net. Just click on my name if your curious =D. Its realy easy and everthing is realy wel documented on googles api site
it broken again! :( after uploading the file youtube answer:
Failed uploading the file: 404 Not Found at ytup.pl line 337.
Author, thanks for commit my little changes about quotes, but now it too complicated for me!
i used this code(version 1.1).but there is a problem in session key.send me a new or changed full code in perl to upload a video to youtube.i need urgently.any help will be appreciated....
Yeah this is broken. It's a shame, cus it looks sweet.
Leave a new comment