1. #!/usr/bin/perl
  2. #
  3. # 2007.07.30
  4. #
  5. # EMP
  6. #
  7. #
  8.  
  9. use constant VERSION => "1";
  10.  
  11. use strict;
  12. use warnings;
  13. use LWP::UserAgent;
  14. use Getopt::Std;
  15.  
  16. $Getopt::Std::STANDARD_HELP_VERSION = 1;
  17.  
  18. # set these values for default -l (login) and -p (pass) values
  19. #
  20. use constant YT_LOGIN => "";
  21. use constant YT_PASS => "";
  22.  
  23. # video categories
  24. #
  25. my %cats = (
  26. 2 => 'Autos & Vehicles',
  27. 23 => 'Comedy',
  28. 24 => 'Entertainment',
  29. 1 => 'Film & Animation',
  30. 20 => 'Gadgets & Games',
  31. 26 => 'Howto & DIY',
  32. 10 => 'Music',
  33. 25 => 'News & Politics',
  34. 22 => 'People & Blogs',
  35. 15 => 'Pets & Animals',
  36. 17 => 'Sports',
  37. 19 => 'Travel & Places'
  38. );
  39.  
  40. # various urls
  41. my $login_url = 'http://www.youtube.com/login';
  42. my $upload_url = 'http://www.youtube.com/my_videos_upload';
  43.  
  44. unless (@ARGV) {
  45. HELP_MESSAGE();
  46. exit 1;
  47. }
  48.  
  49. my %opts;
  50. getopts('l:p:f:c:t:d:x:', \%opts);
  51.  
  52. # if -l or -p are not given, try to use YT_LOGIN and YT_PASS constants
  53. unless (defined $opts{l}) {
  54. unless (length YT_LOGIN) {
  55. preamble();
  56. print "Youtube username/login as neither defined nor passed as an argument\n";
  57. print "Use -l switch to specify the username\n";
  58. print "Example: -l joe_random\n";
  59. exit 1;
  60. }
  61. else {
  62. $opts{l} = YT_LOGIN;
  63. }
  64. }
  65.  
  66. unless (defined $opts{p}) {
  67. unless (length YT_PASS) {
  68. preamble();
  69. print "Password was neither defined nor passed as an argument\n";
  70. print "Use -p switch to specify the password\n";
  71. print "Example: -p secretPass\n";
  72. exit 1;
  73. }
  74. else {
  75. $opts{p} = YT_PASS;
  76. }
  77. }
  78.  
  79. unless (defined $opts{f} && length $opts{f}) {
  80. preamble();
  81. print "No video file was specified\n";
  82. print "Use -f switch to specify the video file\n";
  83. print 'Example: -f "C:\Program Files\movie.avi"', "\n";
  84. print 'Example: -f "/home/pkrumins/super.cool.video.wmv"', "\n";
  85. exit 1;
  86. }
  87.  
  88. unless (-r $opts{f}) {
  89. preamble();
  90. print "Video file is not readable or does not exist\n";
  91. print "Check the permissions and the path to the file\n";
  92. exit 1;
  93. }
  94.  
  95. unless (defined $opts{c} && length $opts{c}) {
  96. preamble();
  97. print "No video category was specified\n";
  98. print "Use -c switch to set the category of the video\n";
  99. print "Example: -c 20, would set category to Gadgets & Games\n\n";
  100. print_cats();
  101. exit 1;
  102. }
  103.  
  104. unless (defined $cats{$opts{c}}) {
  105. preamble();
  106. print "Category '$opts{c}' does not exist\n\n";
  107. print_cats();
  108. exit 1;
  109. }
  110.  
  111. unless (defined $opts{t} && length $opts{t}) {
  112. preamble();
  113. print "No video title was specified\n";
  114. print "Use -t switch to set the title of the video\n";
  115. print 'Example: -t "Super Cool Video Title"', "\n";
  116. exit 1;
  117. }
  118.  
  119. unless (defined $opts{d} && length $opts{d}) {
  120. preamble();
  121. print "No video description was specified\n";
  122. print "Use -d switch to set the description of the video\n";
  123. print 'Example: -d "The coolest video description"', "\n";
  124. exit 1;
  125. }
  126.  
  127. unless (defined $opts{x} && length $opts{x}) {
  128. preamble();
  129. print "No tags were specified\n";
  130. print "Use -x switch to set the tags\n";
  131. print 'Example: -x "foo, bar, baz, hacker, purl"', "\n";
  132. exit 1;
  133. }
  134.  
  135. # tags should be at least two chars, can't be just numbers
  136. my @tags = split /,\s+/, $opts{x};
  137. my @filtered_tags = grep { length > 2 && !/^\d+$/ } @tags;
  138. unless (@filtered_tags) {
  139. preamble();
  140. print "Tags must at least two chars in length and must not be numeric!\n";
  141. print "For example, 'foo', 'bar', 'yo29' are valid tags, but ";
  142. print "'22222', 'hi', 'b9' are invalid\n";
  143. exit 1;
  144. }
  145.  
  146. $opts{x} = join ', ', @filtered_tags;
  147. # create the user agent, have it store cookies and
  148. # pretend to be a cool windows firefox browser
  149. my $ua = LWP::UserAgent->new(
  150. cookie_jar => {},
  151. agent => 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.4) '.
  152. 'Gecko/20070515 Firefox/2.0.0.4'
  153. );
  154.  
  155. # let the user agent follow redirects after a POST request
  156. push @{$ua->requests_redirectable}, 'POST';
  157.  
  158. print "Logging in to YouTube...\n";
  159. login();
  160.  
  161. print "Uploading the video ($opts{t})...\n";
  162. upload();
  163.  
  164. print "Done!\n";
  165.  
  166. sub login {
  167. # submit the login form
  168. my $res = $ua->post($login_url,
  169. {
  170. current_form => 'loginForm',
  171. username => $opts{l},
  172. password => $opts{p},
  173. action_login => 'Log In'
  174. }
  175. );
  176. unless ($res->is_success) {
  177. die "Failed logging in: failed submitting login form: ",
  178. $res->status_line;
  179. }
  180.  
  181. # We have no good way to check if we really logged in.
  182. # I found that when you have logged in the upper right menu changes
  183. # and you have access to 'Log Out' option.
  184. # We check for this string to see if we have logged in.
  185. unless ($res->content =~ /Log Out/) {
  186. die "Failed logging in: username/password incorrect";
  187. }
  188. }
  189.  
  190. sub upload {
  191. # upload is actually a two step process, first we set the video info,
  192. # and then we post the actual video file
  193. #
  194.  
  195. my $resp = $ua->get($upload_url);
  196. unless ($resp->is_success) {
  197. die "Failed getting $upload_url: ", $resp->status_line;
  198. }
  199.  
  200. my $session_token = extract_session_token($resp->content);
  201. unless (defined $session_token) {
  202. die "Failed extracting session token, YouTube might have redesigned!";
  203. }
  204.  
  205. # let's prepare the video field hash which we will need in both steps
  206. my %vid_fields = (
  207. field_myvideo_title => $opts{t},
  208. field_myvideo_descr => $opts{d},
  209. field_myvideo_keywords => $opts{x},
  210. field_myvideo_categories => $opts{c},
  211. language => "EN",
  212. action_upload => "Upload a video...",
  213. allow_embeddings => "Yes",
  214. allow_responses => "Yes",
  215. allow_comments => "Yes",
  216. allow_ratings => "Yes",
  217. location => "",
  218. field_date_mon => 0,
  219. field_date_day => 0,
  220. field_date_yr => 0,
  221. field_privacy => "public",
  222. ignore_broadcast_settings => 0,
  223. session_token => $session_token
  224. );
  225.  
  226. $resp = upload_step_one(\%vid_fields);
  227.  
  228. # now add additional video fields, the new session token,
  229. # the addresser field (no idea what it is), and some more fields
  230. $vid_fields{session_token} = extract_session_token($resp->content);
  231. unless (defined $vid_fields{session_token}) {
  232. die "Failed extracting session token for upload step two\n",
  233. "YouTube might have redesigned :(";
  234. }
  235.  
  236. if ($resp->content =~ m{name="addresser" value="(.+?)">}) {
  237. $vid_fields{addresser} = $1;
  238. }
  239. else {
  240. die "Failed extracting 'addresser' id for upload step two\n",
  241. "YouTube might have redesigned :(";
  242. }
  243. $vid_fields{contact} = "";
  244. $vid_fields{field_command} = "myvideo_submit";
  245. $vid_fields{field_uploadfile} = [ $opts{f} ];
  246. $vid_fields{field_private_share_entities} = "";
  247. $vid_fields{action_upload} = "Upload Video";
  248.  
  249. # the upload form's action URL at step 2 changes, we need to extract it
  250. my $action_url;
  251. if ($resp->content =~ m{enctype="multipart/form-data" action="(.+?)"}) {
  252. $action_url = $1;
  253. }
  254. else {
  255. die "Failed extracting action URL, YouTube might have redesigned!";
  256. }
  257.  
  258. $resp = upload_step_two($action_url, \%vid_fields);
  259.  
  260. # After the video has been uploaded, youtube thanks the user
  261. # for uploading the vid. Let's test for this thanks message
  262. # to see if the upload was successful
  263. unless ($resp->content =~ /Upload Complete/) {
  264. die "Upload might have failed, no 'thanks for upload' message ",
  265. "was found!.\n",
  266. "Or YouTube might have redesigned!";
  267. }
  268. }
  269.  
  270. sub extract_session_token {
  271. my $content = shift;
  272.  
  273. if ($content =~ m{token = "(.+?)"}) {
  274. return $1;
  275. }
  276.  
  277. return;
  278. }
  279.  
  280. sub upload_step_one {
  281. my $vid_fields = shift;
  282. my $resp = $ua->post($upload_url, $vid_fields,
  283. "Content_Type" => "form-data");
  284.  
  285. unless ($resp->is_success) {
  286. die "First upload step failed: ", $resp->status_line;
  287. }
  288.  
  289. return $resp;
  290. }
  291.  
  292. sub upload_step_two {
  293. my ($url, $vid_fields) = @_;
  294. my $resp = $ua->post($url, $vid_fields,
  295. "Content_Type" => "form-data");
  296.  
  297. unless ($resp->is_success) {
  298. die "Second upload step failed: ", $resp->status_line;
  299. }
  300.  
  301. return $resp;
  302. }
  303.  
  304. sub HELP_MESSAGE {
  305. preamble();
  306. print "Usage: $0 ",
  307. "-l [login] ",
  308. "-p [password] ",
  309. "-f <video file> ",
  310. "-c <category> ",
  311. "-t <title> ",
  312. "-d <description> ",
  313. "-x <comma, separated, tags>\n\n";
  314. print_cats();
  315. }
  316.  
  317. sub print_cats {
  318. print "Possible categories (for -c switch):\n";
  319. printf "%-4s - %s\n", $_, $cats{$_} foreach (sort {
  320. $cats{$a} cmp $cats{$b}
  321. } keys %cats);
  322. }
  323.  
  324. sub VERSION_MESSAGE {
  325. preamble();
  326. print "Version: v", VERSION, "\n";
  327. }
  328.  
  329. sub preamble {
  330. print "EMP"\n";
  331. print "\n"
  332. }
  333.  
  334.