4 use Digest
::MD5
qw(md5_hex);
5 use HTTP
::Request
::Common qw
/GET/;
8 # name: LJ::fetch_userpic
9 # des: Fetch source content of userpic by url or post
10 # args: userpic, src, urlpic
11 # return: hashref: { content, size }
19 ## Read uploaded image
20 my $upload = LJ
::Request
->upload('userpic');
25 $size = $upload->size;
27 # upload image as temp file to mogileFS. this file is used in lj_upf_resize worker.
28 seek $upload->fh, 0,0;
29 read $upload->fh, $content, $upload->size; # read content
33 size
=> $upload->size,
36 } elsif ($args{'src'} eq "url") {
37 ## Get image somewhere from internet
39 my $ua = LJ
::get_useragent
(
41 max_size
=> $args{maxupload
} + 1024,
45 my $res = $ua->get($args{urlpic
});
46 if ($res && $res->is_success) {
47 # read downloaded file
48 $content = $res->content;
49 $size = length $content;
60 # name: LJ::load_userpics
61 # des: Loads a bunch of userpics at once.
62 # args: dbarg?, upics, idlist
63 # des-upics: hashref to load pictures into, keys being the picids.
64 # des-idlist: [$u, $picid] or [[$u, $picid], [$u, $picid], +] objects
65 # also supports deprecated old method, of an array ref of picids.
70 my ($upics, $idlist) = @_;
72 return undef unless ref $idlist eq 'ARRAY' && $idlist->[0];
74 # deal with the old calling convention, just an array ref of picids eg. [7, 4, 6, 2]
75 if (! ref $idlist->[0] && $idlist->[0]) { # assume we have an old style caller
76 my $in = join(',', map { $_+0 } @
$idlist);
77 my $dbr = LJ
::get_db_reader
();
78 my $sth = $dbr->prepare("SELECT userid, picid, width, height " .
79 "FROM userpic WHERE picid IN ($in)");
82 while ($_ = $sth->fetchrow_hashref) {
83 my $id = $_->{'picid'};
90 # $idlist needs to be an arrayref of arrayrefs,
91 # HOWEVER, there's a special case where it can be
92 # an arrayref of 2 items: $u (which is really an arrayref)
93 # as well due to 'fields' and picid which is an integer.
95 # [$u, $picid] needs to map to [[$u, $picid]] while allowing
96 # [[$u1, $picid1], [$u2, $picid2], [etc...]] to work.
97 if (scalar @
$idlist == 2 && ! ref $idlist->[1]) {
98 $idlist = [ $idlist ];
101 ## avoid requesting upic multiple times.
102 ## otherwise memcached returns it multiple times too.
104 ## don't load (non-existent) upics with negative id
105 ## see also LJSUP-5502 and hook 'control_default_userpic'
109 my $upicid = $_->[1];
110 $upicid>0 && not $uniq{$u}->{$upicid}++
114 foreach my $row (@
{$idlist})
116 my ($u, $id) = @
$row;
119 if ($LJ::CACHE_USERPIC
{$id}) {
120 $upics->{$id} = $LJ::CACHE_USERPIC
{$id};
122 push @load_list, [$u, $id+0];
125 return unless @load_list;
127 if (@LJ::MEMCACHE_SERVERS
) {
128 my @mem_keys = map { [$_->[1],"userpic.$_->[1]"] } @load_list;
129 my $mem = LJ
::MemCacheProxy
::get_multi
(@mem_keys) || {};
130 while (my ($k, $v) = each %$mem) {
131 next unless $v && $k =~ /(\d+)/;
133 $upics->{$id} = LJ
::MemCache
::array_to_hash
("userpic", $v);
135 @load_list = grep { ! $upics->{$_->[1]} } @load_list;
136 return unless @load_list;
141 foreach my $row (@load_list) {
142 # ignore users on clusterid 0
143 next unless $row->[0]->{clusterid
};
145 if ($row->[0]->{'dversion'} > 6) {
146 push @
{$db_load{$row->[0]->{'clusterid'}}}, $row;
148 push @load_list_d6, $row;
152 foreach my $cid (keys %db_load) {
153 my $dbcr = LJ
::get_cluster_def_reader
($cid);
155 print STDERR
"Error: LJ::load_userpics unable to get handle; cid = $cid\n";
159 my (@bindings, @data);
160 foreach my $row (@
{$db_load{$cid}}) {
161 push @bindings, "(userid=? AND picid=?)";
162 push @data, ($row->[0]->{userid
}, $row->[1]);
164 next unless @data && @bindings;
166 my $sth = $dbcr->prepare("SELECT userid, picid, width, height, fmt, state, ".
167 " UNIX_TIMESTAMP(picdate) AS 'picdate', location, flags ".
168 "FROM userpic2 WHERE " . join(' OR ', @bindings));
169 $sth->execute(@data);
171 while (my $ur = $sth->fetchrow_hashref) {
172 my $id = delete $ur->{'picid'};
175 # force into numeric context so they'll be smaller in memcache:
176 foreach my $k (qw(userid width height flags picdate)) {
179 $ur->{location
} = uc(substr($ur->{location
}, 0, 1));
181 $LJ::CACHE_USERPIC
{$id} = $ur;
182 LJ
::MemCacheProxy
::set
([$id,"userpic.$id"], LJ
::MemCache
::hash_to_array
("userpic", $ur));
186 # following path is only for old style d6 userpics... don't load any if we don't
188 return unless @load_list_d6;
190 my $dbr = LJ
::get_db_writer
();
191 my $picid_in = join(',', map { $_->[1] } @load_list_d6);
192 my $sth = $dbr->prepare("SELECT userid, picid, width, height, contenttype, state, ".
193 " UNIX_TIMESTAMP(picdate) AS 'picdate' ".
194 "FROM userpic WHERE picid IN ($picid_in)");
196 while (my $ur = $sth->fetchrow_hashref) {
197 my $id = delete $ur->{'picid'};
200 # force into numeric context so they'll be smaller in memcache:
201 foreach my $k (qw(userid width height picdate)) {
204 $ur->{location
} = "?";
205 $ur->{flags
} = undef;
210 }->{delete $ur->{contenttype
}};
212 $LJ::CACHE_USERPIC
{$id} = $ur;
213 LJ
::MemCacheProxy
::set
([$id,"userpic.$id"], LJ
::MemCache
::hash_to_array
("userpic", $ur));
218 # name: LJ::expunge_userpic
219 # des: Expunges a userpic so that the system will no longer deliver this userpic. If
220 # your site has off-site caching or something similar, you can also define a hook
221 # "expunge_userpic" which will be called with a picid and userid when a pic is
224 # des-picid: ID of the picture to expunge.
226 # returns: undef on error, or the userid of the picture owner on success.
228 sub expunge_userpic
{
229 # take in a picid and expunge it from the system so that it can no longer be used
230 my ($u, $picid) = @_;
232 return undef unless $picid && ref $u;
234 # get the pic information
237 if ($u->{'dversion'} > 6) {
238 my $dbcm = LJ
::get_cluster_master
($u);
239 return undef unless $dbcm && $u->writer;
241 $state = $dbcm->selectrow_array('SELECT state FROM userpic2 WHERE userid = ? AND picid = ?',
242 undef, $u->{'userid'}, $picid);
243 return undef unless $state; # invalid pic
244 return $u->{'userid'} if $state eq 'X'; # already expunged
247 $u->do("UPDATE userpic2 SET state='X' WHERE userid = ? AND picid = ?", undef, $u->{'userid'}, $picid);
248 return LJ
::error
($dbcm) if $dbcm->err;
249 $u->do("DELETE FROM userpicmap2 WHERE userid = ? AND picid = ?", undef, $u->{'userid'}, $picid);
251 my $dbr = LJ
::get_db_reader
();
252 return undef unless $dbr;
254 $state = $dbr->selectrow_array('SELECT state FROM userpic WHERE picid = ?',
256 return undef unless $state; # invalid pic
257 return $u->{'userid'} if $state eq 'X'; # already expunged
260 my $dbh = LJ
::get_db_writer
();
261 return undef unless $dbh;
262 $dbh->do("UPDATE userpic SET state='X' WHERE picid = ?", undef, $picid);
263 return LJ
::error
($dbh) if $dbh->err;
264 $dbh->do("DELETE FROM userpicmap WHERE userid = ? AND picid = ?", undef, $u->{'userid'}, $picid);
267 # now clear the user's memcache picture info
268 LJ
::Userpic
->delete_cache($u);
269 LJ
::MemCacheProxy
::delete([$picid, "userpic.$picid"]);
271 ## if this was the default userpic, "undefault" it
272 if ($u->{'defaultpicid'} && $u->{'defaultpicid'}==$picid) {
273 LJ
::update_user
($u, { defaultpicid
=> 0 });
276 # call the hook and get out of here
277 my @rval = LJ
::run_hooks
('expunge_userpic', $picid, $u->{'userid'});
278 return ($u->{'userid'}, map {$_->[0]} grep {$_ && @
$_ && $_->[0]} @rval);
282 # name: LJ::activate_userpics
283 # des: des: Wrapper around [func[LJ::User::activate_userpics]] for compatibility.
285 # returns: undef on failure 1 on success
287 sub activate_userpics
290 return undef unless LJ
::isu
($u);
292 # if a userid was given, get a real $u object
293 $u = LJ
::load_userid
($u, "force") unless isu
($u);
295 # should have a $u object now
296 return undef unless isu
($u);
298 return $u->activate_userpics;
302 # name: LJ::get_userpic_info
303 # des: Given a user, gets their userpic information.
305 # des-uuid: userid, or user object.
306 # des-opts: Optional; hash of options, 'load_comments'.
307 # returns: hash of userpicture information;
308 # for efficiency, we store the userpic structures
309 # in memcache in a packed format.
310 # info: memory format:
312 # version number of format,
314 # "packed string", which expands to an array of {width=>..., ...}
315 # "packed string", which expands to { 'kw1' => id, 'kw2' => id, ...}
321 my ($uuid, $opts) = @_;
322 return undef unless $uuid;
323 my $userid = LJ
::want_userid
($uuid);
324 my $u = LJ
::want_user
($uuid); # This should almost always be in memory already
325 return undef unless $u && $u->{clusterid
};
327 # in the cache, cool, well unless it doesn't have comments or urls
329 if (my $cachedata = $LJ::CACHE_USERPIC_INFO
{$userid}) {
331 if ($u->{'dversion'} > 6) {
332 $good = 0 if $opts->{'load_comments'} && ! $cachedata->{'_has_comments'};
333 $good = 0 if $opts->{'load_urls'} && ! $cachedata->{'_has_urls'};
335 return $cachedata if $good;
338 my $VERSION_PICINFO = 3;
340 my $memkey = [$u->{'userid'},"upicinf:$u->{'userid'}"];
343 if ($minfo = LJ
::MemCacheProxy
::get
($memkey)) {
344 # the pre-versioned memcache data was a two-element hash.
345 # since then, we use an array and include a version number.
347 if (ref $minfo eq 'HASH' ||
348 $minfo->[0] != $VERSION_PICINFO) {
349 # old data in the cache. delete.
350 LJ
::MemCacheProxy
::delete($memkey);
352 my (undef, $picstr, $kwstr) = @
$minfo;
357 while (length $picstr >= 7) {
358 my $pic = { userid
=> $u->{'userid'} };
360 $pic->{width
}, $pic->{height
},
361 $pic->{state}) = unpack "NCCA", substr($picstr, 0, 7, '');
362 $info->{pic
}->{$pic->{picid
}} = $pic;
367 while (($nulpos = index($kwstr, "\0", $pos)) > 0) {
368 my $kw = substr($kwstr, $pos, $nulpos-$pos);
369 my $id = unpack("N", substr($kwstr, $nulpos+1, 4));
370 $pos = $nulpos + 5; # skip NUL + 4 bytes.
371 $info->{kw
}->{$kw} = $info->{pic
}->{$id} if $info;
375 if ($u->{'dversion'} > 6) {
377 # Load picture comments
378 if ($opts->{'load_comments'}) {
379 my $commemkey = [$u->{'userid'}, "upiccom:$u->{'userid'}"];
380 my $comminfo = LJ
::MemCacheProxy
::get
($commemkey);
382 if (defined $comminfo) {
385 while (($nulpos = index($comminfo, "\0", $pos)) > 0) {
386 my $comment = substr($comminfo, $pos, $nulpos-$pos);
387 my $id = unpack("N", substr($comminfo, $nulpos+1, 4));
388 $pos = $nulpos + 5; # skip NUL + 4 bytes.
389 $info->{'pic'}->{$id}->{'comment'} = $comment;
390 $info->{'comment'}->{$id} = $comment;
392 $info->{'_has_comments'} = 1;
393 } else { # Requested to load comments, but they aren't in memcache
400 if ($opts->{'load_urls'} && $info) {
401 my $urlmemkey = [$u->{'userid'}, "upicurl:$u->{'userid'}"];
402 my $urlinfo = LJ
::MemCache
::get
($urlmemkey);
404 if (defined $urlinfo) {
407 while (($nulpos = index($urlinfo, "\0", $pos)) > 0) {
408 my $url = substr($urlinfo, $pos, $nulpos-$pos);
409 my $id = unpack("N", substr($urlinfo, $nulpos+1, 4));
410 $pos = $nulpos + 5; # skip NUL + 4 bytes.
411 $info->{'pic'}->{$id}->{'url'} = $url;
413 $info->{'_has_urls'} = 1;
414 } else { # Requested to load urls, but they aren't in memcache
422 my %minfocom; # need this in this scope
429 my ($picstr, $kwstr);
431 my $dbcr = LJ
::get_cluster_def_reader
($u);
432 my $db = @LJ::MEMCACHE_SERVERS ? LJ
::get_db_writer
() : LJ
::get_db_reader
();
433 return undef unless $dbcr && $db;
435 if ($u->{'dversion'} > 6) {
436 $sth = $dbcr->prepare("SELECT picid, width, height, state, userid, comment, url ".
437 "FROM userpic2 WHERE userid=?");
439 $sth = $db->prepare("SELECT picid, width, height, state, userid ".
440 "FROM userpic WHERE userid=?");
442 $sth->execute($u->{'userid'});
444 while (my $pic = $sth->fetchrow_hashref) {
445 next if $pic->{state} eq 'X'; # no expunged pics in list
447 $info->{'pic'}->{$pic->{'picid'}} = $pic;
448 $minfocom{int($pic->{picid
})} = $pic->{comment
} if $u->{'dversion'} > 6
449 && $opts->{'load_comments'} && $pic->{'comment'};
450 $minfourl{int($pic->{'picid'})} = $pic->{'url'} if $u->{'dversion'} > 6
451 && $opts->{'load_urls'} && $pic->{'url'};
455 $picstr = join('', map { pack("NCCA", $_->{picid
},
456 $_->{width
}, $_->{height
}, $_->{state}) } @pics);
458 if ($u->{'dversion'} > 6) {
459 $sth = $dbcr->prepare("SELECT k.keyword, m.picid FROM userpicmap2 m, userkeywords k ".
460 "WHERE k.userid=? AND m.kwid=k.kwid AND m.userid=k.userid");
462 $sth = $db->prepare("SELECT k.keyword, m.picid FROM userpicmap m, keywords k ".
463 "WHERE m.userid=? AND m.kwid=k.kwid");
465 $sth->execute($u->{'userid'});
467 while (my ($kw, $id) = $sth->fetchrow_array) {
468 next unless $info->{'pic'}->{$id};
469 next if $kw =~ /[\n\r\0]/; # used to be a bug that allowed these to get in.
470 $info->{'kw'}->{$kw} = $info->{'pic'}->{$id};
471 $minfokw{$kw} = int($id);
473 $kwstr = join('', map { pack("Z*N", $_, $minfokw{$_}) } keys %minfokw);
475 $memkey = [$u->{'userid'},"upicinf:$u->{'userid'}"];
476 $minfo = [ $VERSION_PICINFO, $picstr, $kwstr ];
477 LJ
::MemCache
::set
($memkey, $minfo);
479 if ($u->{'dversion'} > 6) {
481 if ($opts->{'load_comments'}) {
482 $info->{'comment'} = \
%minfocom;
483 my $commentstr = join('', map { pack("Z*N", $minfocom{$_}, $_) } keys %minfocom);
485 my $memkey = [$u->{'userid'}, "upiccom:$u->{'userid'}"];
486 LJ
::MemCache
::set
($memkey, $commentstr);
488 $info->{'_has_comments'} = 1;
491 if ($opts->{'load_urls'}) {
492 my $urlstr = join('', map { pack("Z*N", $minfourl{$_}, $_) } keys %minfourl);
494 my $memkey = [$u->{'userid'}, "upicurl:$u->{'userid'}"];
495 LJ
::MemCache
::set
($memkey, $urlstr);
497 $info->{'_has_urls'} = 1;
502 $LJ::CACHE_USERPIC_INFO
{$u->{'userid'}} = $info;
507 # name: LJ::get_pic_from_keyword
508 # des: Given a userid and keyword, returns the pic row hashref.
510 # des-keyword: The keyword of the userpic to fetch.
511 # returns: hashref of pic row found
513 sub get_pic_from_keyword
516 my $info = LJ
::get_userpic_info
($u) or
519 if (my $pic = $info->{'kw'}{$kw}) {
523 # the lame "pic#2343" thing when they didn't assign a keyword
524 if ($kw =~ /^pic\#(\d+)$/) {
526 if (my $pic = $info->{'pic'}{$picid}) {
534 sub get_picid_from_keyword
536 my ($u, $kw, $default) = @_;
537 $default ||= (ref $u ?
$u->{'defaultpicid'} : 0);
539 LJ
::run_hook
('control_default_userpic', \
$default);
541 return $default unless $kw;
543 my $pic = LJ
::get_pic_from_keyword
($u, $kw)
545 return $pic->{'picid'};
548 # this will return a user's userpicfactory image stored in mogile scaled down.
549 # if only $size is passed, will return image scaled so the largest dimension will
550 # not be greater than $size. If $x1, $y1... are set then it will return the image
551 # scaled so the largest dimension will not be greater than 100
552 # all parameters are optional, default size is 640.
554 # if maxfilesize option is passed, get_upf_scaled will decrease the image quality
555 # until it reaches maxfilesize, in kilobytes. (only applies to the 100x100 userpic)
557 # returns [imageref, mime, width, height] on success, undef on failure.
559 # note: this will always keep the image's original aspect ratio and not distort it.
563 my $gc = LJ
::gearman_client
();
565 # no gearman, do this in-process
566 return LJ
::_get_upf_scaled
(@args)
570 my $u = LJ
::get_remote
()
571 or die "No remote user";
572 unshift @args, "userid" => $u->id;
575 my $arg = Storable
::nfreeze
(\
@args);
576 my $task = Gearman
::Task
->new('lj_upf_resize', \
$arg,
582 $result = Storable
::thaw
($$res);
586 my $ts = $gc->new_task_set();
587 $ts->add_task($task);
588 $ts->wait(timeout
=> 30); # 30 sec timeout;
590 # job failed ... error reporting?
591 die "Could not resize image down\n" unless $result;
597 # name: _get_upf_scaled
598 # des: Crop and scale images
600 # size - max width of target image or [ width x height, ...]
601 # x1, x2, y1, y2 - coords in a source image for crop
602 # border (bool) - is it need to add border to target image
603 # save_to_FB (bool) - is it need to save to FB (FotoBilder)
604 # fb_gallery - gallery to save target image
605 # auto_crop - is it need to auto_crop image
606 # cancel_size - if size of picture is equal or smaller => cancel processing, caller will not use such small picture
607 # returns status 'small'
609 # Sample for use as auto-crop:
611 # my $res = LJ::get_upf_scaled(
612 # source => \$content,
613 # size => [ "140x105" ],
615 # fb_gallery => 'test_gal',
623 my $dataref = delete $opts{source
};
624 my $size = delete $opts{size
} || 640;
625 my $x1 = delete $opts{x1
};
626 my $y1 = delete $opts{y1
};
627 my $x2 = delete $opts{x2
};
628 my $y2 = delete $opts{y2
};
629 my $border = delete $opts{border
} || 0;
630 my $maxfilesize = delete $opts{maxfilesize
} || 38;
631 my $u = LJ
::want_user
(delete $opts{userid
} || delete $opts{u
}) || LJ
::get_remote
();
632 my $mogkey = delete $opts{mogkey
};
633 my $downsize_only = delete $opts{downsize_only
};
634 my $save_to_FB = delete $opts{save_to_FB
} || 0;
635 my $fb_gallery = delete $opts{fb_gallery
};
636 my $fb_username = delete $opts{fb_username
};
637 my $fb_password = delete $opts{fb_password
};
638 my $auto_crop = delete $opts{auto_crop
};
639 my $cancel_size = delete $opts{cancel_size
};
640 croak
"No userid or remote" unless $u || $mogkey || $dataref;
642 $maxfilesize *= 1024;
644 croak
"Invalid parameters to get_upf_scaled\n" if scalar keys %opts;
646 my $mode = ($x1 || $y1 || $x2 || $y2 || $auto_crop) ?
"crop" : "scale";
648 eval { require Image
::Magick
}
651 eval { require Image
::Size
}
654 $mogkey ||= 'upf:' . $u->{userid
};
655 $dataref = LJ
::mogclient
()->get_file_data($mogkey)
661 # original width/height
662 my ($ow, $oh) = Image
::Size
::imgsize
($dataref);
663 return undef unless $ow && $oh;
665 my @cancel_size = split /x/, $cancel_size;
666 return { status
=> 'small' }
667 if $cancel_size[0] and $ow < $cancel_size[0] or $cancel_size[1] and $oh < $cancel_size[1];
669 # converts an ImageMagick object to the form returned to our callers
670 my $imageParams = sub {
672 my $blob = $im->ImageToBlob;
673 return [\
$blob, $im->Get('MIME'), $im->Get('width'), $im->Get('height')];
676 # compute new width and height while keeping aspect ratio
677 my $getSizedCoords = sub {
685 $fromw = $img->Get('width');
686 $fromh = $img->Get('height');
689 return (int($newsize * $fromw/$fromh), $newsize) if $fromh > $fromw;
690 return ($newsize, int($newsize * $fromh/$fromw));
693 # get the "medium sized" width/height. this is the size which
694 # the user selects from
695 my @sizes = split /x/, $size;
696 my ($medw, $medh) = scalar @sizes > 1 ?
@sizes : $getSizedCoords->($size);
697 return undef unless $medw && $medh;
699 # simple scaling mode
700 if ($mode eq "scale") {
701 my $image = Image
::Magick
->new(size
=> "${medw}x${medh}")
704 $image->BlobToImage($$dataref);
705 unless ($downsize_only && ($medw > $ow || $medh > $oh)) {
706 $image->Resize(width
=> $medw, height
=> $medh);
708 return $imageParams->($image);
711 # else, we're in 100x100 cropping mode
713 # scale user coordinates up from the medium pixelspace to full pixelspace
719 # cropping dimensions from the full pixelspace
723 # but if their selected region in full pixelspace is 800x800 or something
724 # ridiculous, no point decoding the JPEG to its full size... we can
725 # decode to a smaller size so we get 100px when we crop
726 my $min_dim = $tw < $th ?
$tw : $th;
727 my ($decodew, $decodeh) = ($ow, $oh);
729 $decodew = $sizes[0];
730 $decodeh = $sizes[1];
732 my $wanted_size = 100;
733 if ($min_dim > $wanted_size) {
734 # then let's not decode the full JPEG down from its huge size
735 my $de_scale = $wanted_size / $min_dim;
737 $decodew = int($de_scale * $decodew);
738 $decodeh = int($de_scale * $decodeh);
740 $_ *= $de_scale foreach ($x1, $x2, $y1, $y2);
744 $_ = int($_) foreach ($x1, $x2, $y1, $y2, $tw, $th);
746 # make the pristine (uncompressed) 100x100 image
747 my $timage = $auto_crop ? Image
::Magick
->new() : Image
::Magick
->new(size
=> "${decodew}x${decodeh}");
748 return undef unless $timage;
750 $timage->BlobToImage($$dataref);
751 $timage->Set(magick
=> 'PNG');
754 my ($crop_w, $crop_h) = ();
755 if ($oh <= $decodeh and $ow <= $decodew) {
757 # else one (or two) size is bigger
758 } elsif ($oh < $decodeh) { # than ow > decodew
761 $x1 = ($ow - $crop_w) / 2;
763 } elsif ($ow < $decodew) { # than oh > decodeh
766 $y1 = ($oh - $crop_h) / 2;
768 } elsif ($ow / $decodew >= $oh / $decodeh) {
769 $crop_w = $oh * $decodew / $decodeh;
771 $x1 = ($ow - $crop_w) / 2;
775 $crop_h = $ow * $decodeh / $decodew;
776 $y1 = ($oh - $crop_h) / 2;
779 if ($oh > $decodeh or $ow > $decodew) {
780 $timage->Crop($crop_w."x".$crop_h."+$x1+$y1");
781 if ($crop_h > $decodeh or $crop_w > $decodew) {
782 $timage->Scale(width
=> $decodew, height
=> $decodeh);
788 $timage->Scale(width
=> $decodew, height
=> $decodeh);
789 $timage->Mogrify(crop
=> "${w}x${h}+$x1+$y1");
793 my $im_blob = $timage->ImageToBlob;
794 my $res = upload_to_fb
(
797 username
=> $fb_username,
798 password
=> $fb_password,
803 my $targetSize = $border ?
98 : 100;
805 my ($nw, $nh) = $getSizedCoords->($targetSize, $timage);
806 $timage->Scale(width
=> $nw, height
=> $nh);
808 # add border if desired
809 $timage->Border(geometry
=> "1x1", color
=> 'black') if $border;
812 # test, if we can skip compression
813 my $piccopy = $timage->Clone();
814 my $ret = $imageParams->($piccopy);
815 unless ( length(${ $ret->[0] }) < $maxfilesize ) {
816 $timage->Set(magick
=> 'JPG'); # need compression
819 foreach my $qual (qw(100 90 85 75)) {
820 # work off a copy of the image so we aren't recompressing it
821 $piccopy = $timage->Clone();
822 $piccopy->Set('quality' => $qual);
823 $ret = $imageParams->($piccopy);
824 last if length(${ $ret->[0] }) < $maxfilesize;
834 my $magic_ref = (ref $magic) ?
$magic : \
$magic;
835 my $mime = $default || 'text/plain'; # default value
837 $mime = 'image/jpeg' if $$magic_ref =~ /^\xff\xd8/; # JPEG
838 $mime = 'image/gif' if $$magic_ref =~ /^GIF8
/; # GIF
839 $mime = 'image/png' if $$magic_ref =~ /^\x89PNG
/; # PNG
846 my $username = shift;
848 my $ua = LWP
::UserAgent
->new;
849 $ua->agent("FotoBilder_Uploader/0.2");
851 my $req = HTTP
::Request
->new(GET
=> "$LJ::FB_SITEROOT/interface/simple");
852 $req->push_header("X-FB-Mode" => "GetChallenge");
853 $req->push_header("X-FB-User" => $username);
855 my $res = $ua->request($req);
856 die "HTTP error: " . $res->content . "\n"
857 unless $res->is_success;
859 my $xmlres = XML
::Simple
::XMLin
($res->content);
860 my $methres = $xmlres->{GetChallengeResponse
};
862 if (my $err = $xmlres->{Error
} || $methres->{Error
}) {
867 return $methres->{Challenge
};
873 my $password = shift;
874 return "crp:$chal:" . md5_hex
($chal . md5_hex
($password));
880 my $dataref = $opts{dataref
};
881 my $gals = $opts{gals
} || [];
883 my $username = $opts{username
} || $LJ::FB_USER
;
884 my $password = $opts{password
} || $LJ::FB_PASS
;
888 $chal = get_challenge
($username)
889 or die "No challenge string available.\n";
892 my $ua = LWP
::UserAgent
->new;
893 $ua->agent("FotoBilder_Uploader/0.2");
896 my $req = HTTP
::Request
->new(PUT
=> "$LJ::FB_SITEROOT/interface/simple");
897 $req->push_header("X-FB-Mode" => "UploadPic");
898 $req->push_header("X-FB-User" => $username);
899 $req->push_header("X-FB-Auth" => make_auth
($chal, $password));
900 $req->push_header("X-FB-GetChallenge" => 1);
903 my $sec = 255; ## public
904 $req->push_header("X-FB-UploadPic.PicSec" => $sec);
909 # initialize galleries struct array
910 $req->push_header(":X-FB-UploadPic.Gallery._size" => scalar(@
$gals));
912 # add individual galleries
913 foreach my $idx (0..@
$gals-1) {
914 my $gal = $gals->[$idx];
916 my @path = split(/\0/, $gal);
917 my $galname = pop @path;
920 ("X-FB-UploadPic.Gallery.$idx.GalName" => $galname);
922 ("X-FB-UploadPic.Gallery.$idx.GalDate" => time());
924 ("X-FB-UploadPic.Gallery.$idx.GalSec" => $sec);
928 (":X-FB-UploadPic.Gallery.$idx.Path._size" => scalar(@path));
929 foreach (0..@path-1) {
931 (":X-FB-UploadPic.Gallery.$idx.Path.$_" => $path[$_]);
938 $req->push_header("X-FB-UploadPic.ImageLength" => length($dataref));
939 $req->push_header("Content-Length" => length($dataref));
940 $req->content($dataref);
942 my $res = $ua->request($req);
943 die "HTTP error: " . $res->content . "\n"
944 unless $res->is_success;
946 my $xmlres = XML
::Simple
::XMLin
($res->content);
947 my $methres = $xmlres->{UploadPicResponse
};
948 my $chalres = $xmlres->{GetChallengeResponse
};
950 $chal = $chalres->{Challenge
};
952 if (my $err = $xmlres->{Error
} || $methres->{Error
} || $chalres->{Error
}) {
957 errstr
=> ref $err eq 'HASH' ?
$err->{content
} : (ref $err eq 'ARRAY' ?
$err->[0] : $err),
963 picid
=> $methres->{PicID
},
964 url
=> $methres->{URL
},
969 # get picture from internet $opts{source} and crop it to $opts{size},
970 # than save result into $opts{galleries} (arrayref) of scrapbook of $opts{username}, using $opts{password}
971 # returns result of &upload_to_fb
972 sub crop_picture_from_web
{
976 my $source = LJ
::trim
($opts{source
});
984 ## fetch a photo from Net
985 my $ua = LJ
::get_useragent
( role
=> 'crop_picture',
986 max_size
=> 10 * 1024 * 1024,
989 my $result = $ua->request(GET
($source));
990 unless ($result and $result->is_success) {
995 errstr
=> $result ?
$result->status_line : 'unknown error in downloading',
998 $data = $result->content;
999 $opts{data
} = $result->content;
1001 $data = ${$opts{'dataref'}};
1003 my $res = LJ
::_get_upf_scaled
(
1005 size
=> $opts{size
},
1006 cancel_size
=> $opts{cancel_size
},
1009 fb_username
=> $opts{username
},
1010 fb_password
=> $opts{password
},
1011 fb_gallery
=> $opts{galleries
},
1018 errstr
=> 'probably bad picture',
1021 # need to repeat? (because of bad auth in CentOS-32 ScrapBook)
1022 # DELETE THIS IN FUTURE!!!
1023 if ($res->{picid
} == -1) {
1024 warn $res->{errstr
} if $LJ::IS_DEV_SERVER
;
1025 return upload_to_fb
(%{$res->{opts
}});