You're viewing a comment by overrider and its responses.

May 02, 2009, 15:25

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"
}

Reply To This Comment

(why do I need your e-mail?)

(Your twitter name, if you have one. (I'm @pkrumins, btw.))

Type the first letter of your name: (just to make sure you're a human)

Please preview the comment before submitting to make sure it's OK.