This repository was archived by the owner on Jan 30, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathbpg-srv.php
More file actions
2493 lines (2172 loc) · 95.8 KB
/
Copy pathbpg-srv.php
File metadata and controls
2493 lines (2172 loc) · 95.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?php
/*
* Copyright 2013 Benjamin Roy
* This program is distributed under the terms of the GNU General Public License version 2.0
*
* Program name: Ben's Picture Gallery (BPG)
* Web site: https://github.com/benroy73/bpg
* Author: Benjamin Roy, email: benroy@7373.us
* License: GPLv2
* Version Release Date: May 2013
* Version: 3.0
*
*/
// ---------------------------------------------------------------------
// Customize these variables for your own site in your config.php file
// The config.php file will override these values.
// ---------------------------------------------------------------------
// The real path on the server to the original photos
// it can be relative to this bpg.php script or absolute.
$original_photos_dir = 'photos'; // (no trailing slash)
// The name of your gallery
$gallery_name = 'Picture Gallery';
// The name to put in the copyright tag of the RSS feeds
$copyright_owner_name = '';
// The font to use for watermarking images
$watermark_font_file = "images/FreeSansBoldOblique.ttf"; // you can change this to any TrueType font file
$watermark_font_size = 32;
// The name to use for the top level breadcrumb and the RSS feed.
// It will be linked to the parent directory of this script.
$home_site_name = 'My Website';
// Sort the pictures in the browser by filename or EXIF Date
$sort_style = 'file_mtime'; // filename or file_mtime
// The bits per second for your outgoing bandwith. Used to calculate downloads from the cart.
$upstream_bandwidth_bits = 1000000;
// The user that the web server runs as. www-data on a Debian or Ubuntu system.
// This is used to try to make sure file permissions get set properly in the cache.
$webserver_user = 'www-data';
// The location of the cached files, it needs to be writable by the webserver and browsable by visitors.
// It's location is relative to this script and is where all the generated files are stored.
// If PHP can't create this directory you will need to create it manually like "mkdir _cache; chmod 777 _cache;"
$cache_dir = '_cache'; // (no trailing slash)
// What size should the pictures be resized to?
$thumbnail_image_dimension = '160';
$small_image_dimension = '640';
$medium_image_dimension = '1280';
$large_image_dimension = '1920';
// How good should the resized pictures look on a scall from 1-100 where 100 is perfect and 1 is terrible
// the jpeg compression quality 0-100 (75 seems like a good balance)
$jpeg_image_quality = '75';
// What features should be enabled?
$enable_ffmpeg_video_feature = TRUE;
$enable_exif_editing_feature = TRUE;
$enable_site_stats_feature = TRUE; // display site statistics on the top page
$enable_zip_download_feature = TRUE;
$enable_buy_prints_feature = TRUE;
$enable_load_balancing_feature = FALSE;
$enable_watermark_feature = FALSE;
// External commands that this program depends on. They must be excutable by the web server.
$exiftool_cmd = "/usr/bin/exiftool"; // this program writes the Exif metadata
$zip_cmd = '/usr/bin/zip'; // this is used to make a downloadable zip file from the files in the cart
$id3v2_cmd = '/usr/bin/id3v2'; // shell command to use if PHP is missing the id3_get_tag function
$echo_cmd = 'echo'; // shell command used for statistics
$grep_cmd = 'grep'; // shell command used for statistics
$du_cmd = 'du'; // shell command used for statistics
$find_cmd = 'find'; // shell command used for statistics
$wc_cmd = 'wc'; // shell command used for statistics
$awk_cmd = 'awk'; // shell command used for statistics
// instructions to install ffmpeg on Ubuntu http://ubuntuforums.org/showthread.php?t=786095
$ffmpeg_cmd = '/usr/local/bin/ffmpeg'; // this converts videos to the H.264 MP4 format
$ffmpeg_metadata_cmd = '/usr/local/bin/qt-faststart'; // command for adding metadata to mp4 file
// on a Mac ffmpeg is often located at /opt/local/bin/ffmpeg
// The URL where the RSS summary announcement feed will be available publicly
// RSS readers like Google Reader can subscribe to this to see when new photos are added
$photo_public_rss_url = '/photos/rss.xml';
/*
If your site is password protected and you want visitors to be able to get updates in a RSS reader, then
make Apache serve the rss.xml link without restrictions like this
RewriteEngine On
RewriteRule ^/photos/rss.xml$ /photos/index.php?view=rsspub
<Location /photos/rss.xml>
Allow from all
</Location>
*/
// URL that photo lab will use to access the copies of the original photos for printing.
// Original files are temporarily copied to ./$cache_dir/_orders/ when buying prints,
// this URL needs to be accessible to the lab's servers without authentication.
$print_lab_orders_url = 'http://localhost/print-orders'; // (no trailing slash)
// To edit jpeg comments or delete the original files the user name must be in this $admin_users array
// (the original files must be writable by the webserver for this to work).
// The user name is identified by the by the web server's basic auth (.htaccess), so you'll need to use
// Apache's htpasswd and .htaccess files to make these users login
$admin_users = array(); // example: array( 'alice', 'bob' )
// the list of sites to use for load balancing
$load_balance_hosts = array( 'http://localhost/photos1', 'http://localhost/photos2' );
// what subnet is local and fast and should get higher quality videos
// usually this is '192.168.'
$local_subnet = '192.168.';
// The google analytics id code to use, if you want to enable it
//$google_analytics_tracking_code = '';
// The URL to the AWStats js file, if you want to enable it
//$awstats_url = '';
// =============================================================================
//
// Don't change anything below here unless you know what you are doing
//
// =============================================================================
// override the variables above so settings can be saved between upgrades in a local config.php file
if ( file_exists('config.php') ) include('config.php');
$google_analytics_js = '';
if (isset($google_analytics_tracking_code)) {
$google_analytics_js = "
var _gaq = _gaq || [];
_gaq.push(['_setAccount', '$google_analytics_tracking_code']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
";
}
$awstats_js = '';
if (isset($awstats_url)) {
$awstats_js = "
(function() {
var node = document.createElement('script'); node.type = 'text/javascript'; node.async = true;
node.src = '$awstats_url';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(node, s);
})();
";
}
$html_page_top = <<<"EOD"
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>$gallery_name</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Photo gallery">
<meta name="author" content="$copyright_owner_name">
<!-- CSS -->
<link type='text/css' rel='stylesheet' href="bootstrap/css/bootstrap.css"/>
<link type='text/css' rel='stylesheet' href="bootstrap/css/bootstrap-responsive.css"/>
<link type='text/css' rel='stylesheet' href='photoswipe-3.0.5.1/photoswipe.css'/>
<link type='text/css' rel='stylesheet' href='mediaelement-2.10.3-benroy73/mediaelementplayer.css'/>
<link type='text/css' rel='stylesheet' href='bpg.css'/>
<!-- HTML5 shim, for IE6-8 support of HTML5 elements https://code.google.com/p/html5shiv/ -->
<!--[if lt IE 9]>
<script type='text/javascript' src="bootstrap/js/html5shiv.js"></script>
<![endif]-->
<!-- Fav and touch icons -->
<link rel="apple-touch-icon-precomposed" sizes="144x144" href="/img/apple-touch-icon-144-precomposed.png">
<link rel="apple-touch-icon-precomposed" sizes="114x114" href="/img/apple-touch-icon-114-precomposed.png">
<link rel="apple-touch-icon-precomposed" sizes="72x72" href="/img/apple-touch-icon-72-precomposed.png">
<link rel="apple-touch-icon-precomposed" href="/img/apple-touch-icon-57-precomposed.png">
<link rel="icon" type="image/png" href="/img/favicon.png">
<link type='application/rss+xml' rel='alternate' href='$photo_public_rss_url' title='announcements of new photos'/>
<link type='application/rss+xml' rel='alternate' href='bpg-srv?view=rssnew' title='latest photo folder'/>
</head>
<body>
<!-- Part 1: Wrap all page content here -->
<div id="wrap">
<!-- Fixed navbar -->
<div class="navbar navbar-fixed-top">
<div class="navbar-inner">
<div class="container">
<button type="button" class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="brand" href="/">$home_site_name</a>
<div class="nav-collapse collapse">
<ul class="nav">
<li id="top_level_breadcrumb"><a href="?">Photos</a></li>
<!--<li class="divider-vertical"></li>-->
</ul>
<ul class="nav pull-right">
<li class="pull-right" id="ui_admin_button"><a id="admin_mode_button" href="#">Admin</a></li>
<li class="dropdown pull-right" id="ui_cart_menu">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><i class="icon-shopping-cart"></i> Cart <b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a id="add_some_to_cart" href="#">Add some pictures</a></li>
<li><a id="add_all_to_cart" href="#">Add all pictures</a></li>
<li><a id="remove_some_from_cart" href="#">Remove some pictures</a></li>
<li><a id="remove_all_from_cart" href="#">Empty cart</a></li>
<li class="divider"></li>
<li><a id="view_cart_menu_option" href="?dir=cart">View cart</a></li>
</ul>
</li>
</ul>
</div><!--/.nav-collapse -->
<button id="buy_prints_button" class="btn pull-right">Buy Prints<button>
<button id="download_files_button" class="btn pull-right">Download Files<button>
</div>
</div>
</div>
<!-- Begin page content -->
<div id="content_media" class="container">
EOD;
$html_page_bottom = <<<"EOD"
</div>
<div id="push"></div>
</div>
<div id="footer">
<div class="container">
<p id='muted credit'>Powered by <a href='https://github.com/benroy73/bpg'>BPG</a> - <span id='site_stats'></span></p>
</div>
</div>
<div id="videoPlayerModal" class="modal hide" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-body">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">x</button>
<div id="slideshow_screen"></div>
</div>
</div>
<!-- Placed at the end of the document so the pages load faster -->
<script type='text/javascript' src='//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js'></script>
<script type='text/javascript' src="bootstrap/js/bootstrap.min.js"></script>
<script type='text/javascript' src='mediaelement-2.10.3-benroy73/mediaelement-and-player.min.js'></script>
<script type='text/javascript' src='photoswipe-3.0.5.1/lib/klass.min.js'></script>
<script type='text/javascript' src='photoswipe-3.0.5.1/code.photoswipe.jquery-3.0.5.1.min.js'></script>
<script type='text/javascript' src='jquery.jeditable.js'></script>
<script type='text/javascript' src='bpg.js'></script>
<script type='text/javascript'>
$google_analytics_js
$awstats_js
</script>
</body>
</html>
EOD;
// =============================================================================
//
// Really don't mess with stuff below here unless you understand what you are doing
//
// =============================================================================
// disable load balancing within the LAN
if ( isset($_SERVER['REMOTE_ADDR']) && strpos($_SERVER['REMOTE_ADDR'], $local_subnet) !== false ) {
$enable_load_balancing_feature = FALSE;
}
ini_set('session.cookie_httponly', 1); // override the default setting and don't let javascript used this cookie
session_start(); // use PHP session to track users shopping cart
if (isset($_SESSION['cart'])) {
$cart = $_SESSION['cart'];
}
$balance_iterator = 0; //placeholder for which load balancing host to use next
function isAdminUser() { // decide if the current user has admin privileges
global $admin_users;
if ( isset($_SERVER["PHP_AUTH_USER"]) ) {
return in_array($_SERVER["PHP_AUTH_USER"], $admin_users);
}
else {
return FALSE;
}
}
function getUiSettings() {
global $enable_zip_download_feature, $enable_exif_editing_feature, $enable_buy_prints_feature;
$ui_settings['isAdmin'] = isAdminUser();
$ui_settings['download_enabled'] = $enable_zip_download_feature;
$ui_settings['exif_editing_enabled'] = $enable_exif_editing_feature;
$ui_settings['buy_prints_enabled'] = $enable_buy_prints_feature;
return $ui_settings;
}
function ajaxError( $msg ) {
header("HTTP/1.1 500 Internal Server Error");
print $msg;
exit(1);
}
function ajaxJsonResponse( $obj ) {
header('Content-Type: application/json');
print json_encode($obj);
exit(0);
}
if (!function_exists('id3_get_tag')) { // use the id3v2 command if PHP is missing this function
function id3_get_tag( $filepath ) {
global $id3v2_cmd;
if (!is_executable($id3v2_cmd) && is_executable('/opt/local/bin/id3v2')) { // try the Mac location
$id3v2_cmd = '/opt/local/bin/id3v2';
}
$tags['comments'] = exec("$id3v2_cmd -l \"$filepath\" |grep '^COMM (Comments): (Recording notes'|awk -F': ' '{print \$3}' ");
$tags['artist'] = exec("$id3v2_cmd -l \"$filepath\" |grep '^TPE1'|awk -F': ' '{print \$2}'");
return $tags;
}
}
function handled_filetype( $file ) { //return the type of file if it is supported, or false if unsupported
$type = FALSE;
$extension = strtolower( substr($file, -4, 4) );
switch ($extension) {
case '.jpg':
$type = 'photo';
break;
case 'jpeg':
$type = 'photo';
break;
case '.avi':
$type = 'video';
break;
case '.mov':
$type = 'video';
break;
case '.mp4':
$type = 'video';
break;
case '.3gp':
$type = 'video';
break;
case '.mp3':
$type = 'audio';
break;
case '.wav':
$type = 'audio';
break;
}
return $type;
}
function safe_path( $path ) { // scrub a path provided by the browser to make it safe
/* data provided by a user can't be trusted so we need to check it here
the rules we enforce here are
1. no '..' allowed in path
2. don't start with /
*/
$path = stripslashes (urldecode( stripslashes($path) ));
$path = trim ( $path, '/\\' );
if ( strpos($path, '..') !== false ) {
$path = ''; // don't allow ..
print "don't mess with the dir parameter!\n";
}
if ( substr($path, 0, 1) == '/' ) {
$path = ''; // don't start with /
print "Don't mess with the dir parameter!\n";
}
return $path;
}
function urlencode_path( $string ) { // encode a file path so it can be used in URLs
$result = implode("/", array_map("rawurlencode", explode("/", $string)));
$result = str_replace(' ', '%20', $result);
return $result;
}
function file_mtime_sorter($a, $b) {
global $current_path;
$a_filepath = "$current_path/$a";
$b_filepath = "$current_path/$b";
$a_time = filemtime( $a_filepath );
$b_time = filemtime( $b_filepath );
if ($a_time == $b_time) {
return 0;
}
else {
return ($a_time < $b_time) ? -1 : 1;
}
}
function get_files_and_dirs( $path, $only_handled_filetypes=TRUE ) { // get all the files and folders in the path requested
global $cache_dir, $sort_style, $current_path;
$files = array();
$dirs = array();
$files_and_dirs = array();
if ($path == '' || !$path) $path = '.'; // don't allow an empty path
if (is_dir($path)) {
$files = scandir( $path ); // get the directory list for a path
}
if (is_array($files)) {
foreach( $files as $i => $file ) { // separate the file and dirs
if (substr($file, 0, 1)==".") { // ignore files and dirs that start with '.'
unset( $files[$i] );
}
elseif (is_dir("$path/$file")) { // dirs
if ( $file != $cache_dir ) $dirs[] = $file ;
unset( $files[$i] );
}
elseif ($only_handled_filetypes && (!handled_filetype($file))) {
// we only want handled file types, and this isn't one of them
unset( $files[$i] );
}
}
}
$current_path = $path; // set this global variable for use in the sort function
//default sort method is alphabetic by filename
if ($sort_style == "file_mtime") {
usort($files, "file_mtime_sorter");
}
rsort($dirs);
$files_and_dirs['dirs'] = $dirs;
$files_and_dirs['files'] = $files;
return $files_and_dirs;
}
function get_files_grouped_by_type($files) {
$files_by_type = array();
$files_by_type['photo'] = array();
$files_by_type['video'] = array();
$files_by_type['audio'] = array();
foreach( $files as $file ) {
if (handled_filetype($file)) {
$files_by_type[handled_filetype($file)][] = $file;
}
}
return $files_by_type;
}
function mkdir_r($dir_name, $rights=0777){ // take a path and make all the directories if they don't exist yet
$dirs = explode('/', $dir_name);
$dir = '';
$blank_html_page =
"<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
<title>blank page</title>
</head>
<body>
<div style='text-align: center; margin-top: 20%;'>This page is intentionally blank.</div>
</body>
</html>
";
foreach ($dirs as $part) {
$dir .= $part . '/';
if (!is_dir($dir) && strlen($dir)>0) {
mkdir($dir, $rights);
chmod($dir, 0777);
}
if (!is_file($dir ."index.html")) {
file_put_contents($dir ."index.html", $blank_html_page);
}
}
}
function makeScaled($im, $size, $new_size, $rotation=0) { // resize the image keeping the same aspect ratio
$width = $size[0];
$height = $size[1];
if ($width<=$new_size && $height<=$new_size) return $im;
if ($width>$height) { //fat images, normal landscape format
$ratio = $width/$new_size;
$newWidth=$new_size;
$newHeight = round($height/$ratio,0);
}
elseif ($width<$height) { //tall images, portrait format
$ratio = $height/$new_size;
$newHeight = $new_size;
$newWidth= round($width/$ratio,0);
}
else { //a square image
$newWidth = $new_size;
$newHeight = $new_size;
}
//make the new image
$destImage = ImageCreateTrueColor( $newWidth, $newHeight);
//copy the passed image into the new image at the proper scale
// Resized is about 30% faster, Resampled is better quality
ImageCopyResized( $destImage, $im, 0, 0, 0, 0, $newWidth+1, $newHeight+1, $width, $height );
// // Resampled is better quality but about 30% slower
// ImageCopyResampled( $destImage, $im, 0, 0, 0, 0, $newWidth+1, $newHeight+1, $width, $height );
//
// ImageMagik is about 10% slower than PHP/GD Resized
//exec("/usr/bin/convert -size 800x800 \"$filepath\" -resize 800x800 \"$cached_mediumfile\"");
//if ($rotation > 0)
// exec("/usr/bin/convert -rotate $rotation \"$cached_mediumfile\" \"$cached_mediumfile\"");
//exec("/usr/bin/convert -size 160x160 \"$cached_mediumfile\" -resize 160x160 \"$cached_thumbfile\"");
if ($rotation>0) {
$color = ImageColorAllocate($destImage,255,255,255);
$destImage = ImageRotate($destImage,$rotation,$color);
}
return $destImage;
}
function get_original_path( $cache_file ) {
// this might help with videos in the cart
}
function get_cache_path( $filepath, $size='_thumbnails' ) {
// get the path for a _large, _medium, _small or _thumbnail images in the cache
global $cache_dir, $original_photos_dir;
$file = basename($filepath);
$dir = dirname($filepath);
if ($size == 'original' ) {
$path = "$original_photos_dir/$dir/$file";
}
elseif (handled_filetype($filepath) == 'audio' && $size == '_thumbnails') {
$path = "images/sound-icon.png";
}
else {
$path = "$cache_dir/$size/$dir/$file";
}
if ( handled_filetype($filepath) == 'video' ) {
if ($size == '_thumbnails') {
$path = substr_replace($path, '.jpg', -4);
}
else {
$path = substr_replace($path, '.mp4', -4);
}
}
return $path;
}
function get_load_balanced_url( $filepath ) { // returns a load balanced URL
global $load_balance_hosts, $balance_iterator;
$filepath = urlencode_path($filepath);
if (load_balancing_feature_enabled()) {
$mirror_url = $load_balance_hosts[$balance_iterator] .'/'. $filepath;
$balance_iterator++;
if ($balance_iterator > count($load_balance_hosts)-1) {
$balance_iterator = 0;
}
return $mirror_url;
}
else {
return $filepath;
}
}
function generate_cached_audio_files( $filepath ) {
global $original_photos_dir;
$original_file = "$original_photos_dir/$filepath";
copy($original_file, get_cache_path( $filepath, '_large' ));
copy($original_file, get_cache_path( $filepath, '_medium' ));
copy($original_file, get_cache_path( $filepath, '_small' ));
// thumbnails are all directed to images/sound-icon.png in get_cache_path function so I don't need to create it here
}
function get_video_dimension( $filepath ) {
global $ffmpeg_cmd, $grep_cmd, $awk_cmd;
$forig_es = escapeshellarg($filepath);
// # get the video size
// # Stream #0.0: Video: mjpeg, yuvj422p, 640x480, 15 tbr, 15 tbn, 15 tbc
// # Stream #0.0(eng): Video: h264, yuvj420p, 1280x720, 23182 kb/s, 30 fps, 30 tbr, 3k tbn, 6k tbc
$exec_cmd = "$ffmpeg_cmd -i $forig_es -vstats 2>&1 |$grep_cmd 'Stream.*Video' |$awk_cmd -F, '{print $3}' |$awk_cmd '{print $1}'";
$video_dimensions = '';
$video_dimensions = trim(exec($exec_cmd));
//error_log( $exec_cmd . "\n" . $video_dimensions );
return $video_dimensions;
}
function generate_cached_video_files( $filepath, $overwrite_existing_files=TRUE ) {
global $original_photos_dir, $jpeg_image_quality, $thumbnail_image_dimension;
global $ffmpeg_cmd, $grep_cmd, $awk_cmd;
$cached_thumbfile = get_cache_path( $filepath, '_thumbnails' );
$cached_smallfile = get_cache_path( $filepath, '_small' );
$cached_mediumfile = get_cache_path( $filepath, '_medium' );
$cached_largefile = get_cache_path( $filepath, '_large' );
if ( ! ffmpeg_video_feature_enabled() ) {
return FALSE;
}
$filepath = "$original_photos_dir/$filepath";
$forig_es = escapeshellarg($filepath);
if (!is_file($cached_thumbfile) || $overwrite_existing_files) {
// use the camera's thm files for movies if possible
// check for lower or upper case file names
if (is_file( substr_replace($filepath, '.thm', -4))) {
$thmfile = substr_replace($filepath, '.thm', -4);
}
elseif (is_file( substr_replace($filepath, '.THM', -4))) {
$thmfile = substr_replace($filepath, '.THM', -4);
}
else {
// no .THM file so need to use ffmpeg to get tumbnail image
//ffmpeg -y -i MVI_6640.AVI -s qcif -f mjpeg -t 0.001 movie.jpg
$tempfile = $cached_thumbfile . '.temp.jpg';
$tempfile_es = escapeshellarg($tempfile);
// exec("$ffmpeg_cmd -y -v 0 -i $forig_es -s 160x120 -f mjpeg -t 0.001 $thmfile_es 2>&1"); // this does not work on all video files
// exec("$ffmpeg_cmd -y -v 0 -i $forig_es -s 160x120 -ss 00:00:01.00 -vcodec mjpeg -vframes 1 ". escapeshellarg($cached_thumbfile) ." 2>&1"); // unfortunately this stretches the image proportions
// get a full sized frame
$exec_cmd = "$ffmpeg_cmd -y -v 0 -i $forig_es -ss 00:00:01.00 -vcodec mjpeg -vframes 1 $tempfile_es 2>&1";
$last_error = exec($exec_cmd, $output, $retvar);
if ( $retvar != 0 ) {
error_log( $exec_cmd );
error_log( $last_error );
error_log( print_r(array_pop($output), true) );
}
// then resize it to the thumbnail size
$im = ImageCreateFromJpeg($tempfile); // read the image into memory
$size = array(imagesx($im), imagesy($im));
$im = makeScaled($im, $size, $thumbnail_image_dimension); // scale the image in memory
ImageJpeg($im, $cached_thumbfile, 90); // save the image file
ImageDestroy($im); // release the image in memory
unlink($tempfile); // delete the temp file
$thmfile = $cached_thumbfile;
}
// add the filmstrip border to the thumbnail in the cache
// just email yourself an image and it will be base64 encoded like this
$image = 'iVBORw0KGgoAAAANSUhEUgAAABQAAAB4CAYAAADyv9IsAAAACXBIWXMAAAsT
AAALEwEAmpwYAAAAYElEQVRo3u3UMQ7AIBADwXOU/3/Z+UAKCrobSiSmwNJm
ZjoXzzOXz0Lw/btsm5PHSeoPgSvAyBdQYIFAgQUKrFGAAmsUgRVYIFBggQIL
BAosUGCNAhRYowiswAKBWwP7AQXJJO+EVoJJAAAAAElFTkSuQmCC';
$image = base64_decode($image);
$filmstrip = imageCreateFromString($image);
//$filmstrip = imageCreateFromPNG('images/filmstrip.png');
$filmstrip_width = imagesx($filmstrip);
$filmstrip_height = imagesy($filmstrip);
$image = imageCreateFromJpeg($thmfile);
$size = getimagesize($thmfile);
$dest_x = $size[0] - $filmstrip_width;
$dest_y = $size[1] - $filmstrip_height;
imagecopymerge($image, $filmstrip, 0, 0, 0, 0, $filmstrip_width, $filmstrip_height, 50);
imagecopymerge($image, $filmstrip, $dest_x, $dest_y, 0, 0, $filmstrip_width, $filmstrip_height, 50);
ImageJpeg($image, $cached_thumbfile, $jpeg_image_quality);
imagedestroy($image);
imagedestroy($filmstrip);
}
// video encoding takes too long so only generate the video files when run from the command line
// this should be run from cron periodically (perhaps hourly)
// php bpg-dev.php -g -d "all" -t all
if ( PHP_SAPI == 'cli' ) { // command line execution
// http://developer.apple.com/safari/library/documentation/AudioVideo/Conceptual/Using_HTML5_Audio_Video/AudioandVideoTagBasics/AudioandVideoTagBasics.html#//apple_ref/doc/uid/TP40009523-CH2-SW1
// https://developer.apple.com/library/safari/#documentation/AppleApplications/Reference/SafariWebContent/CreatingVideoforSafarioniPhone/CreatingVideoforSafarioniPhone.html
// http://ubuntuforums.org/showthread.php?t=786095
// http://rob.opendot.cl/index.php/useful-stuff/ffmpeg-x264-encoding-guide/
// http://rob.opendot.cl/index.php/useful-stuff/ipod-video-guide/
// http://ffmpeg.org/trac/ffmpeg/wiki/FilteringGuide
// http://enddl22.net/wordpress/?p=2499
// http://ffmpeg.org/trac/ffmpeg/ticket/309
// # get the video size
// # Stream #0.0: Video: mjpeg, yuvj422p, 640x480, 15 tbr, 15 tbn, 15 tbc
// # Stream #0.0(eng): Video: h264, yuvj420p, 1280x720, 23182 kb/s, 30 fps, 30 tbr, 3k tbn, 6k tbc
//$exec_cmd = "$ffmpeg_cmd -i $forig_es -vstats 2>&1 |$grep_cmd Video |$awk_cmd -F, '{print $3}'";
//$video_dimensions = '';
//$video_dimensions = trim(exec($exec_cmd));
////error_log( $exec_cmd . "\n" . $video_dimensions );
// adding -preset slow after baseline would improve quality but make the encodeing much slower
// iphone max is 640x480
// ipad & iphone4 max is 1280x720
if (!is_file($cached_smallfile) || $overwrite_existing_files) {
//
// generate the small size H.264 MP4 version of the file
//
$h264options = '-acodec libfaac -aq 100 -r 15 -vcodec libx264 -pix_fmt yuv420p -vprofile baseline -crf 31 -vf scale="320:trunc(ow/a/2)*2" -threads 0';
run_ffmpeg_command($filepath, $cached_smallfile, $h264options);
}
if (!is_file("$cached_smallfile.jpg") || $overwrite_existing_files) {
save_video_poster_jpg($cached_smallfile, 'small');
}
if (!is_file($cached_mediumfile) || $overwrite_existing_files) {
//
// generate the medium size H.264 MP4 version of the file
//
$h264options = '-acodec libfaac -aq 100 -r 15 -vcodec libx264 -pix_fmt yuv420p -vprofile baseline -crf 29 -vf scale="640:trunc(ow/a/2)*2" -threads 0';
run_ffmpeg_command($filepath, $cached_mediumfile, $h264options);
}
if (!is_file("$cached_mediumfile.jpg") || $overwrite_existing_files) {
save_video_poster_jpg($cached_mediumfile, 'medium');
}
if (!is_file($cached_largefile) || $overwrite_existing_files) {
//
// generate the large size H.264 version of the file
//
// baseline should allow more hardware decoders to play it
$h264options = "-acodec libfaac -aq 150 -r 30 -vcodec libx264 -pix_fmt yuv420p -vprofile baseline -crf 28 -threads 0";
run_ffmpeg_command($filepath, $cached_largefile, $h264options);
}
if (!is_file("$cached_largefile.jpg") || $overwrite_existing_files) {
save_video_poster_jpg($cached_largefile, 'large');
}
}
/*
ffmpeg -y -i INPUT.MOV -acodec libfaac -ab 128k -ac 2 -s 480x270 -vcodec mpeg4 -b 378k -flags +aic+mv4+trell -mbd 2 -cmp 2 -subcmp 2 -g 250 -maxrate 512k -bufsize 2M output.mp4
works on ipod touch:
ffmpeg -y -i INPUT.MOV -acodec libfaac -ab 48k -ac 2 -s 480x270 -vcodec mpeg4 -b 378k -mbd 2 -cmp 2 -subcmp 2 -g 250 -maxrate 512k -bufsize 2M output.mp4
works on ipod touch:
ffmpeg -y -i INPUT.MOV \
-acodec libfaac -ab 48k -ac 2 \
-r 15 -s 480x270 -vcodec mpeg4 \
-flags +aic+mv4 -trellis 1 -mbd 2 -cmp 2 -subcmp 2 -g 250 -maxrate 512k -bufsize 2M -metadata title="test video" output.mp4
/usr/local/share/ffmpeg/libx264-ipod320.ffpreset
/usr/local/share/ffmpeg/libx264-ipod640.ffpreset
works on ipod touch:
ffmpeg -y -i INPUT.MOV -acodec libfaac -aq 100 -ac 2 -vcodec libx264 \
-vpre ipod640 -crf 30 -vf scale=640:-1 -threads 0 output.mp4
works on ipod touch:
ffmpeg -y -i INPUT.MOV -acodec libfaac -aq 100 -ac 2 -r 15 -vcodec libx264 \
-vpre ipod320 -crf 31 -vf scale=320:-1 -threads 0 output.mp4
ffmpeg -y -i INPUT.MOV -acodec libfaac -aq 100 -r 15 -vcodec libx264 -vpre ipod320 -crf 31 -vf scale=320:-1 -threads 0 output.mp4
ffmpeg -y -i INPUT.MOV -acodec libfaac -aq 100 -r 15 -vcodec libx264 -vpre ipod640 -crf 29 -vf scale=640:-1 -threads 0 output-medium.mp4
ffmpeg -y -i INPUT.MOV -acodec libfaac -aq 100 -vcodec libx264 -crf 28 -threads 0 output-high.mp4
ffmpeg -y -i INPUT.MOV -acodec libfaac -aq 100 -vcodec libx264 -vprofile baseline -crf 28 -threads 0 output-high-baseline.mp4
*/
}
function run_ffmpeg_command($original_file, $cache_file, $h264options) {
global $ffmpeg_cmd, $ffmpeg_metadata_cmd;
$forig_es = escapeshellarg($original_file);
$ftemp = substr_replace($cache_file, '.tmp.mp4', -4);
$ftemp_es = escapeshellarg($ftemp);
$fnew_es = escapeshellarg($cache_file);
$exec_cmd = "$ffmpeg_cmd -v 0 -y -i $forig_es $h264options $ftemp_es";
//error_log( $exec_cmd );
$last_error = exec($exec_cmd . " 2>&1", $output, $retvar);
if ( $retvar != 0 ) {
error_log( $exec_cmd );
error_log( $last_error );
error_log( print_r(array_pop($output), true) );
}
if ( !is_file( $ftemp ) ) {
$out = print_r($output, true);
print "<pre>ffmpeg failed to create file for cache ($cache_file).\n$exec_cmd\n$out</pre>";
return;
}
if ( !is_executable($ffmpeg_metadata_cmd) && is_executable('/usr/local/bin/qtfaststart.py') ) {
$ffmpeg_metadata_cmd = '/usr/local/bin/qtfaststart.py';
}
if ( is_executable($ffmpeg_metadata_cmd) ) {
exec("$ffmpeg_metadata_cmd $ftemp_es $fnew_es 2>&1"); // add metadata for progressive download/fast-start
unlink($ftemp);
}
else {
rename($ftemp, $cache_file);
}
}
function save_video_poster_jpg($cache_file, $size) {
global $ffmpeg_cmd;
$video_file = escapeshellarg($cache_file);
$poster_file = escapeshellarg("$cache_file.jpg");
list($x, $y) = explode('x', get_video_dimension( $cache_file ), 2);
if ($x <= 150 || $y <= 150) {
$size = 'small';
}
if ($size == 'small') {
$button_file = 'images/play_button_small.png';
}
else if ($size == 'medium') {
$button_file = 'images/play_button_medium.png';
}
else {
$button_file = 'images/play_button.png';
}
$ffmpeg_filter_options = "-ss 00:00:01.00 -vcodec mjpeg -vframes 1 -f image2 -vf 'movie=$button_file [wm]; [in][wm] overlay=main_w/2-overlay_w/2:main_h/2-overlay_w/2 [out]'";
// http://www.idude.net/index.php/how-to-watermark-a-video-using-ffmpeg/
/*
/usr/local/bin/ffmpeg -y -i '_cache/_small/2005/test'\''s ! ~ weird chars/MVI_0019.mp4' -ss 00:00:01.00 -vcodec mjpeg -vframes 1 -f image2 -vf 'movie=images/play_button.png [wm]; [in][wm] overlay=main_w-overlay_w-10:main_h-overlay_h-10 [out]' '_cache/_small/2005/test'\''s ! ~ weird chars/MVI_0019.mp4'.jpg
/usr/local/bin/ffmpeg -y -i '_cache/_small/2005/test'\''s ! ~ weird chars/MVI_0019.mp4' -ss 00:00:01.00 -vcodec mjpeg -vframes 1 -f image2 -vf 'movie=images/play_button_small.png [wm]; [in][wm] overlay=main_w/2-overlay_w/2:main_h/2-overlay_w/2 [out]' '_cache/_small/2005/test'\''s ! ~ weird chars/MVI_0019.mp4'.jpg
*/
// save the first frame as the video poster image with a play button watermark
$exec_cmd = "$ffmpeg_cmd -v 0 -y -i $video_file $ffmpeg_filter_options $poster_file";
$last_error = exec($exec_cmd . " 2>&1", $output, $retvar);
if ( $retvar != 0 ) {
error_log( $exec_cmd );
error_log( $last_error );
error_log( print_r(array_pop($output), true) );
}
}
function generate_cached_jpeg_files( $filepath ) { // make versions of JPEG files for the cache
global $original_photos_dir;
global $large_image_dimension, $medium_image_dimension, $small_image_dimension, $thumbnail_image_dimension, $jpeg_image_quality;
global $watermark_font_file, $watermark_font_size, $copyright_owner_name;
$cached_thumbfile = get_cache_path( $filepath, '_thumbnails' );
$cached_smallfile = get_cache_path( $filepath, '_small' );
$cached_mediumfile = get_cache_path( $filepath, '_medium' );
$cached_largefile = get_cache_path( $filepath, '_large' );
$filepath = "$original_photos_dir/$filepath";
// find out how the image needs to be rotated
$exif = exif_read_data($filepath);
$rotation = 0;
if (!empty($exif['Orientation'])) {
switch($exif['Orientation']) {
case 1: $rotation = 0; break;
case 8: $rotation = 90; break;
case 3: $rotation = 180; break;
case 6: $rotation = 270; break;
default: $rotation = 0; break;
}
}
// read the image into memory
$im = ImageCreateFromJpeg($filepath);
$size = getimagesize($filepath);
$im = makeScaled($im, $size, $large_image_dimension, $rotation); // create the cached large file and rotate it
ImageJpeg($im, $cached_largefile, 90); // save the large image file
$size = array(imagesx($im), imagesy($im));
$im = makeScaled($im, $size, $medium_image_dimension); // reduce and create the cached medium file
if ( watermark_feature_enabled() ) { // only watermarking the medium size images
$year = date( 'Y', timestamp_from_exif_DateTimeOriginal($filepath) );
$string = "© copyright $copyright_owner_name $year";
$font_color = imagecolorallocate($im, 0, 0, 0); // color for watermark font
$font_background_color = imagecolorallocate($im, 255, 255, 255);
$x = 10;
$y = imagesy($im) - 20;
//imagestring($im, 5, $x, $y, $string, $color); // draw horizontally
//imagestringup($im, 5, $px, $py, $string, $color); // draw vertically
imagettftext($im, $watermark_font_size, 0, $x, $y+1, $font_background_color, $watermark_font_file, $string );
imagettftext($im, $watermark_font_size, 0, $x, $y-1, $font_background_color, $watermark_font_file, $string );
imagettftext($im, $watermark_font_size, 0, $x+1, $y, $font_background_color, $watermark_font_file, $string );
imagettftext($im, $watermark_font_size, 0, $x-1, $y, $font_background_color, $watermark_font_file, $string );
imagettftext($im, $watermark_font_size, 0, $x, $y, $font_color, $watermark_font_file, $string );
}
ImageJpeg($im, $cached_mediumfile, $jpeg_image_quality); // save the medium image file
$size = array(imagesx($im), imagesy($im));
$im = makeScaled($im, $size, $small_image_dimension); // now reduce it again to the cached small file
ImageJpeg($im, $cached_smallfile, $jpeg_image_quality); // save the small image file
$size = array(imagesx($im), imagesy($im));
$im = makeScaled($im, $size, $thumbnail_image_dimension); // now reduce it again to the cached thumbnail file
ImageJpeg($im, $cached_thumbfile, $jpeg_image_quality); // save the thumbnail image file
ImageDestroy($im); //clean up the old image
}
function generate_cached_files( $filepath, $overwrite_existing_files=FALSE ) { // make the cached version of the file
// return 0 if no files were generated, or 1 if one was generated
global $webserver_user;
$file = basename($filepath);
$dir = dirname($filepath);
$cached_thumbfile = get_cache_path( $filepath, '_thumbnails' );
$cached_smallfile = get_cache_path( $filepath, '_small' );
$cached_mediumfile = get_cache_path( $filepath, '_medium' );
$cached_largefile = get_cache_path( $filepath, '_large' );
$cache_thumb_dir = dirname($cached_thumbfile);
$cache_small_dir = dirname($cached_smallfile);
$cache_medium_dir = dirname($cached_mediumfile);
$cache_large_dir = dirname($cached_largefile);
if (!is_file($cached_thumbfile) ||
!is_file($cached_smallfile) ||
!is_file($cached_mediumfile) ||
!is_file($cached_largefile) ||
(handled_filetype($file) == 'video' && (!is_file("$cached_smallfile.jpg") || !is_file("$cached_mediumfile.jpg") || !is_file("$cached_largefile.jpg"))) ||
$overwrite_existing_files) {
mkdir_r($cache_thumb_dir);
mkdir_r($cache_small_dir);
mkdir_r($cache_medium_dir);
mkdir_r($cache_large_dir);
switch ( handled_filetype($file) ) {
case 'photo':
generate_cached_jpeg_files( $filepath );
break;
case 'video':
generate_cached_video_files( $filepath, $overwrite_existing_files );
break;
case 'audio':
generate_cached_audio_files( $filepath );
break;
}
if ( @chown($cached_thumbfile, $webserver_user) &&
@chown($cached_smallfile, $webserver_user) &&
@chown($cached_mediumfile, $webserver_user) &&
@chown($cached_largefile, $webserver_user) ) {
chgrp( $cached_smallfile, $webserver_user );
chmod( $cached_smallfile, 0664);
chgrp( $cached_mediumfile, $webserver_user );
chmod( $cached_mediumfile, 0664);
chgrp( $cached_largefile, $webserver_user );
chmod( $cached_largefile, 0664);
chgrp( $cached_thumbfile, $webserver_user );
chmod( $cached_thumbfile, 0664);
}
elseif (handled_filetype($file) != 'audio') {
//error_log( "$file -- $cached_mediumfile -- $cached_thumbfile" );
@chmod( $cached_largefile, 0666);
@chmod( $cached_mediumfile, 0666);
@chmod( $cached_smallfile, 0666);
@chmod( $cached_thumbfile, 0666);
}
return 1;
}
return 0;
}