This commit is contained in:
2019-09-29 03:39:18 -07:00
commit 82d79df388
9 changed files with 610 additions and 0 deletions

5
CHANGELOG.md Normal file
View File

@@ -0,0 +1,5 @@
# Changelog
All notable changes to this project will be documented in this file.
## [0.4.0]
- Initial repo version

33
README.md Normal file
View File

@@ -0,0 +1,33 @@
<img align="left" src="https://www.profiteroles.org/assets/rollup.png" style="float: left; margin-right: 30px;" width="120">
# Rollup
**Rollup** is a tool for renaming image files from digital cameras using EXIF tags. It was originally written to improve the arbitrary filenames in the iPhone Camera Roll.
* **⇩ [Download Latest Version](https://www.profiteroles.org/git/profiteroles/Rollup/raw/master/Rollup.app.zip)**
## Features
* Recursive directory scanning
* Custom rename template
* Reverse geocoding (lookup place name from GPS coordinates)
## Screenshots
<img src="https://www.profiteroles.org/assets/rollup_1.png" width="600">
<img src="https://www.profiteroles.org/assets/rollup_2.png" width="600">
## Notes
Running Rollup requires disabling Gatekeeper by running `sudo spctl --master-disable` in the Terminal.
## Changelog
https://www.profiteroles.org/git/profiteroles/Rollup/src/master/CHANGELOG.md
## Building from source
Building Rollup requires Platypus and Pashua
* https://github.com/sveinbjornt/Platypus
* https://github.com/BlueM/Pashua

BIN
Rollup.app.zip Normal file

Binary file not shown.

157
Rollup.php Executable file
View File

@@ -0,0 +1,157 @@
<?php
// Rollup
$version = file_get_contents(__DIR__."/version.txt");
// Includes
require (__DIR__."/functions.php");
// Load preferences
$prefs_file = "/Users/".get_current_user()."/Library/Preferences/org.profiteroles.Rollup.php";
if (!file_exists($prefs_file)) {
if (!copy(__DIR__."/prefs.php",$prefs_file)) {
echo "Error creating preferences file";
die;
}
}
$p = unserialize(file_get_contents($prefs_file));
$p['etbin'] = __DIR__."/exiftool";
$p['pfiletypes'] = array("jpg","jpeg","dng","heic","png","gif");
$p['vfiletypes'] = array("mov","mp4","m4v");
$p['screenshot_dims'] = array(320,640,750,828,1125,1242);
// Enable this option to verify newnames against those made with a prior version of rollup
$p['debug_verify_names'] = 0;
// No files dragged
array_shift($argv);
switch (@$argv[0]) {
case "Preferences...":
if (!exec("php ".__DIR__."/RollupPrefs.php")) {
echo "\nALERT:Warning|Google did not accept your API key\n";
}
die;
case "Check for Updates...":
$curr_version = @file_get_contents("https://www.profiteroles.org/git/profiteroles/Rollup/raw/master/version.txt");
if (!$curr_version) {
echo "\nALERT:Can't connect|Error checking for latest version\n";
die;
}
if ($curr_version > $version) {
if(askMulti("Rollup ".$curr_version." is available (you have ".$version.")", array("Cancel","Download")) == 1) {
exec("open https://www.profiteroles.org/git/profiteroles/Rollup");
echo "QUITAPP\n";
} else {
die;
}
} else {
echo "\nALERT:Up-to-date|".$version." is the latest version.\n";
die;
}
}
function alert($string, $title = "Warning") {
echo "\nALERT:".$title."|".$string."\n";
}
// Build list of valid dropped files
foreach ($argv as $target) {
if (is_dir($target)) {
$it = new RecursiveDirectoryIterator($target);
foreach(new RecursiveIteratorIterator($it) as $file) {
if (in_array(strtolower(pathinfo($file,PATHINFO_EXTENSION)), $p['allowed'])) {
$files[] = $file->getpathname();
}
}
} elseif (in_array(strtolower(pathinfo($target,PATHINFO_EXTENSION)), $p['allowed'])) {
$files[] = $target;
}
}
if (!@$files) { echo "\nALERT:No supported files|Supported filetypes: ".implode(", ",$p['allowed'])."\n"; die; } else { sort($files); }
if (count($files) > $p['limit']) { if (!ask("Really process ".count($files)." files?")) { die; } }
// Process files
$i = 0;
foreach ($files as $file) {
unset($parts, $ext, $mparts, $z, $e);
$parts = pathinfo($file);
$ext = strtolower($parts['extension']);
$z['%FILE'] = $parts['filename'];
$z['%EXT'] = $parts['extension'];
if ($p['debug_verify_names']) {
$p['maxlength'] = 999;
$z['%FILE'] = substr($parts['filename'],-8,8);
echo "[VERIFY]";
} else {
echo $parts['basename'];
}
if (strlen($parts['basename']) > $p['maxlength']) {
echo "-> SKIP\n";
} else {
$raw = eval("return ".shell_exec($p['etbin']." -php ".escapeshellarg($file)));
if (is_array($raw[0])) { $e = $raw[0]; } else { echo "Exiftool error\n"; die; }
$z['%DTO'] = formatDateTime($e);
$z['%DUR'] = formatDuration($e);
$z['%SW'] = formatSoftware($e,$p);
$z['%MODEL'] = formatModel($e);
$z['%PLACE'] = formatPlace($e,$p);
if (in_array($ext,$p['pfiletypes'])) {
$newname = str_replace(array_keys($z),array_values($z),$p['pt']);
} elseif (in_array($ext,$p['vfiletypes'])) {
$newname = str_replace(array_keys($z),array_values($z),$p['vt']);
}
$figureoutabetterwaytodothis_a = array("____","___","__");
$figureoutabetterwaytodothis_b = array("_","_","_");
$newname = str_replace($figureoutabetterwaytodothis_a,$figureoutabetterwaytodothis_b,$newname);
if (!@$newname) {
echo " -> ERROR\n";
} else {
echo " -> ".$newname."\n";
if ($p['debug_verify_names']) {
if ($parts['basename'] != $newname) {
echo "FAIL";
echo "\nALERT:Filename changed|".$parts['basename']." VS ".$newname;;
} else {
echo "pass";
}
} else {
if (!@$p['dry_run']) {
rename($file,$parts['dirname']."/".$newname);
} else {
echo "\nDisable dry run to rename files!";
}
}
}
echo updateProgress($i,count($files));
$i++;
}
}
?>

121
RollupPrefs.php Executable file
View File

@@ -0,0 +1,121 @@
<?
// RollupPrefs
function testKey($key) {
// https://developers.google.com/maps/documentation/geocoding/start
$streamContext = stream_context_create([ "ssl" => ["verify_peer" => false, "verify_peer_name" => false] ]);
$result = json_decode(file_get_contents("https://maps.googleapis.com/maps/api/geocode/json?key=".$key."&latlng=0,4", false, $streamContext));
if ($result->status == "REQUEST_DENIED") { return 0; } else { return 1; }
}
function makeWindowString($p) {
$conf = "
# Set window title
*.title = Preferences
*.floating = 1
# Allowed Filetypes
allowed.type = textfield
allowed.mandatory = 1
allowed.label = Allowed filetypes
allowed.default = ".implode(", ",$p['allowed'])."
allowed.placeholder = jpg, jpeg, png, mov, mp4, m4v, dng, heic
allowed.width = 400
allowed.tooltip = Process files matching these extensions
# Photo template
pt.type = textfield
pt.mandatory = 1
pt.label = Photo renaming template
pt.default = ".$p['pt']."
pt.placeholder = %DTO_%MODEL_%PLACE_%SW_%FILE.%EXT
pt.width = 400
pt.tooltip = Rename photo files according to this template
# Video template
vt.type = textfield
vt.mandatory = 1
vt.label = Video renaming template
vt.default = ".$p['vt']."
vt.placeholder = %DTO_%DUR_%PLACE_%SW_%FILE.%EXT
vt.width = 400
vt.tooltip = Rename video files according to this template
# API Key
key.type = textfield
key.label = Google Geocoding API key
key.default = ".$p['key']."
key.placeholder = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
key.width = 400
key.tooltip = An API key is required for reverse-geocoding
# Limit
limit.type = textfield
limit.mandatory = 1
limit.label = Max files to process without warning
limit.default = ".$p['limit']."
limit.width = 80
# Maxlength
maxlength.type = textfield
maxlength.mandatory = 1
maxlength.label = Max input filename length
maxlength.tooltip = Use this value to skip files that have already been renamed
maxlength.default = ".$p['maxlength']."
maxlength.width = 80
# Dry run
dry_run.type = checkbox
dry_run.label = Dry run (don't rename files)
dry_run.default = ".$p['dry_run']."
# Buttons
#gb.type = button
#gb.label = Detect Key...
cb.type = cancelbutton
db.type = defaultbutton
db.label = Save
";
return $conf;
}
// Read Prefs
$prefs_file = "/Users/".get_current_user()."/Library/Preferences/org.profiteroles.Rollup.php";
$p = unserialize(file_get_contents($prefs_file));
// Launch Pashua and parse results
$path = __DIR__."/Pashua.app/Contents/MacOS/Pashua";
$raw = shell_exec("echo ".escapeshellarg(makeWindowString($p))." | ".escapeshellarg($path)." - ");
$result = array();
foreach (explode("\n", $raw) as $line) {
preg_match('/^(\w+)=(.*)$/', $line, $matches);
if (empty($matches) or empty($matches[1])) {
continue;
}
$result[$matches[1]] = $matches[2];
}
// User cancelled
if (@$result['cb']) { echo 1; die; }
// Write Prefs
$result['allowed'] = explode(", ",$result['allowed']);
file_put_contents($prefs_file,serialize($result));
// Test API Key
if (@$result['key'] && ($result['key'] != $p['key'])) {
echo testKey($result['key']);
} else {
echo 1;
}
?>

292
functions.php Executable file
View File

@@ -0,0 +1,292 @@
<?php
function formatDateTime($e) {
if (@$e['DateTimeOriginal']) {
$dto = $e['DateTimeOriginal'];
} else {
$dto = substr($e['FileModifyDate'],0,19);
}
return str_replace(array(":"," "), array("-","_"), $dto);
}
function formatPlace($e, $p) {
if (!@$p['key'] || !@$e['GPSPosition']) { return ""; }
return get_formatted_place(get_gps($e['GPSPosition']),$p['key']);
}
function formatSoftware($e, $p) {
if (($e['FileTypeExtension'] == "png") && in_array($e['ImageWidth'], $p['screenshot_dims'])) {
return "Screenshot";
}
if (!@$e['Software'] && $e['ImageSize'] == "1280x720") {
return "Webcam";
}
if (@$e['Software']) {
$sw = $e['Software'];
if (is_numeric(substr($sw,0,3))) {
$sw = "iOS_".$sw;
}
if (strpos($sw, "Lightroom") !== false) {
$sw = "Lightroom";
}
if (strpos($sw, "Photoshop") !== false) {
$sw = "Photoshop";
}
return str_replace(" ","_",$sw);
} else {
return "";
}
}
function formatModel($e) {
if (!@$e['Model']) { return ""; }
if ($e['Model'] == "VLUU NV10, NV10") {
return "Samsung_NV10";
} else {
return str_replace(" ","-",$e['Model']);
}
}
function formatDuration($e) {
if (!@$e['MediaDuration']) { return ""; }
if (substr($e['MediaDuration'], -1) == "s") {
$time = floor($e['MediaDuration'])."s";
} else {
$time = "";
$mparts = explode(":",$e['MediaDuration']);
if ($mparts[0] != 0) {
$time = $mparts[0]."h";
}
$time .= $mparts[1]."m";
if ($mparts[2] != 0) {
$time .= $mparts[2]."s";
}
}
return $time;
}
function ask($string) {
$result = exec("osascript -e \"display dialog \\\"".$string."\\\"\" 2>&1");
if (strpos($result,"canceled") !== false) {
return 0;
} else {
return 1;
}
}
function askMulti($string, $buttons) {
$buttonstring = "buttons {\\\"".implode("\\\", \\\"",$buttons)."\\\"} default button ".count($buttons);
$result = exec("osascript -e \"display dialog \\\"".$string."\\\" ".$buttonstring."\" | cut -f2 -d':'");
return array_search($result,$buttons);
}
function updateProgress($num = 0, $total = 100) {
return "\nPROGRESS:".floor(($num/$total)*100)."\n";
}
function get_gps($gps_pos) {
# convert:
# 40 deg 44' 49.36" N, 73 deg 56' 28.18" W
# to:
# 40.7470444,-073.9411611
$parts = explode(" ",str_replace(array("deg ",",","'","\""),"",$gps_pos));
$lat_deg = $parts[0];
$lat_min = $parts[1];
$lat_sec = $parts[2];
$lat_dir = $parts[3];
$lon_deg = $parts[4];
$lon_min = $parts[5];
$lon_sec = $parts[6];
$lon_dir = $parts[7];
if ($lat_dir == "N") {
$lat_sin = "+";
} else {
$lat_sin = "-";
}
if ($lon_dir == "E") {
$lon_sin = "+";
} else {
$lon_sin = "-";
}
$latitiude = $lat_sin.($lat_deg+($lat_min/60)+($lat_sec/3600));
$longitude = $lon_sin.($lon_deg+($lon_min/60)+($lon_sec/3600));
return $latitiude.",".$longitude;
}
function get_formatted_place($gps,$key) {
$streamContext = stream_context_create([ "ssl" => ["verify_peer" => false, "verify_peer_name" => false] ]);
$raw = file_get_contents("https://maps.googleapis.com/maps/api/geocode/json?key=".$key."&latlng=".$gps, false, $streamContext);
$return = json_decode($raw);
if (!is_array($return->results[0]->address_components)) { return false; }
foreach ($return->results[0]->address_components as $key => $value) {
$out[$value->types[0]] = $value->short_name;
}
$country = $out['country'];
$state = $out['administrative_area_level_1'];
if ($out['locality'] == "NY" | $out['locality'] == "LA") {
$place = $out['neighborhood'];
} else {
$place = $out['locality'];
}
$addr = $out['route'];
if ($country) {
$dirty[] = $country;
}
if ($state) {
$dirty[] = $state;
}
if ($place) {
$dirty[] = $place;
}
if ($addr) {
$dirty[] = $addr;
}
foreach ($dirty as $item) {
$clean[] = remove_accents(str_replace(" ","_",$item));
}
return str_replace(array("_/_","'","\"",".",",","/"), array("-","","","","","-"), implode($clean,"-"));
}
/**
* Converts all accent characters to ASCII characters.
*
* If there are no accent characters, then the string given is just returned.
*
* @since 1.2.1
*
* @param string $string Text that might have accent characters
* @return string Filtered string with replaced "nice" characters.
*/
function reset_mbstring_encoding() {
mbstring_binary_safe_encoding( true );
}
function mbstring_binary_safe_encoding( $reset = false ) {
static $encodings = array();
static $overloaded = null;
if ( is_null( $overloaded ) )
$overloaded = function_exists( 'mb_internal_encoding' ) && ( ini_get( 'mbstring.func_overload' ) & 2 );
if ( false === $overloaded )
return;
if ( ! $reset ) {
$encoding = mb_internal_encoding();
array_push( $encodings, $encoding );
mb_internal_encoding( 'ISO-8859-1' );
}
if ( $reset && $encodings ) {
$encoding = array_pop( $encodings );
mb_internal_encoding( $encoding );
}
}
function seems_utf8( $str ) {
mbstring_binary_safe_encoding();
$length = strlen($str);
reset_mbstring_encoding();
for ($i=0; $i < $length; $i++) {
$c = ord($str[$i]);
if ($c < 0x80) $n = 0; // 0bbbbbbb
elseif (($c & 0xE0) == 0xC0) $n=1; // 110bbbbb
elseif (($c & 0xF0) == 0xE0) $n=2; // 1110bbbb
elseif (($c & 0xF8) == 0xF0) $n=3; // 11110bbb
elseif (($c & 0xFC) == 0xF8) $n=4; // 111110bb
elseif (($c & 0xFE) == 0xFC) $n=5; // 1111110b
else return false; // Does not match any model
for ($j=0; $j<$n; $j++) { // n bytes matching 10bbbbbb follow ?
if ((++$i == $length) || ((ord($str[$i]) & 0xC0) != 0x80))
return false;
}
}
return true;
}
function remove_accents($string) {
if ( !preg_match('/[\x80-\xff]/', $string) )
return $string;
if (seems_utf8($string)) {
$chars = array(
// Decompositions for Latin-1 Supplement
chr(194).chr(170) => 'a', chr(194).chr(186) => 'o', chr(195).chr(128) => 'A', chr(195).chr(129) => 'A', chr(195).chr(130) => 'A', chr(195).chr(131) => 'A', chr(195).chr(132) => 'A', chr(195).chr(133) => 'A', chr(195).chr(134) => 'AE',chr(195).chr(135) => 'C', chr(195).chr(136) => 'E', chr(195).chr(137) => 'E', chr(195).chr(138) => 'E', chr(195).chr(139) => 'E', chr(195).chr(140) => 'I', chr(195).chr(141) => 'I', chr(195).chr(142) => 'I', chr(195).chr(143) => 'I', chr(195).chr(144) => 'D', chr(195).chr(145) => 'N', chr(195).chr(146) => 'O', chr(195).chr(147) => 'O', chr(195).chr(148) => 'O', chr(195).chr(149) => 'O', chr(195).chr(150) => 'O', chr(195).chr(153) => 'U', chr(195).chr(154) => 'U', chr(195).chr(155) => 'U', chr(195).chr(156) => 'U', chr(195).chr(157) => 'Y', chr(195).chr(158) => 'TH',chr(195).chr(159) => 's', chr(195).chr(160) => 'a', chr(195).chr(161) => 'a', chr(195).chr(162) => 'a', chr(195).chr(163) => 'a', chr(195).chr(164) => 'a', chr(195).chr(165) => 'a', chr(195).chr(166) => 'ae',chr(195).chr(167) => 'c', chr(195).chr(168) => 'e', chr(195).chr(169) => 'e', chr(195).chr(170) => 'e', chr(195).chr(171) => 'e', chr(195).chr(172) => 'i', chr(195).chr(173) => 'i', chr(195).chr(174) => 'i', chr(195).chr(175) => 'i', chr(195).chr(176) => 'd', chr(195).chr(177) => 'n', chr(195).chr(178) => 'o', chr(195).chr(179) => 'o', chr(195).chr(180) => 'o', chr(195).chr(181) => 'o', chr(195).chr(182) => 'o', chr(195).chr(184) => 'o', chr(195).chr(185) => 'u', chr(195).chr(186) => 'u', chr(195).chr(187) => 'u', chr(195).chr(188) => 'u', chr(195).chr(189) => 'y', chr(195).chr(190) => 'th', chr(195).chr(191) => 'y', chr(195).chr(152) => 'O',
// Decompositions for Latin Extended-A
chr(196).chr(128) => 'A', chr(196).chr(129) => 'a', chr(196).chr(130) => 'A', chr(196).chr(131) => 'a', chr(196).chr(132) => 'A', chr(196).chr(133) => 'a', chr(196).chr(134) => 'C', chr(196).chr(135) => 'c', chr(196).chr(136) => 'C', chr(196).chr(137) => 'c', chr(196).chr(138) => 'C', chr(196).chr(139) => 'c', chr(196).chr(140) => 'C', chr(196).chr(141) => 'c', chr(196).chr(142) => 'D', chr(196).chr(143) => 'd', chr(196).chr(144) => 'D', chr(196).chr(145) => 'd', chr(196).chr(146) => 'E', chr(196).chr(147) => 'e', chr(196).chr(148) => 'E', chr(196).chr(149) => 'e', chr(196).chr(150) => 'E', chr(196).chr(151) => 'e', chr(196).chr(152) => 'E', chr(196).chr(153) => 'e', chr(196).chr(154) => 'E', chr(196).chr(155) => 'e', chr(196).chr(156) => 'G', chr(196).chr(157) => 'g', chr(196).chr(158) => 'G', chr(196).chr(159) => 'g', chr(196).chr(160) => 'G', chr(196).chr(161) => 'g', chr(196).chr(162) => 'G', chr(196).chr(163) => 'g', chr(196).chr(164) => 'H', chr(196).chr(165) => 'h', chr(196).chr(166) => 'H', chr(196).chr(167) => 'h', chr(196).chr(168) => 'I', chr(196).chr(169) => 'i', chr(196).chr(170) => 'I', chr(196).chr(171) => 'i', chr(196).chr(172) => 'I', chr(196).chr(173) => 'i', chr(196).chr(174) => 'I', chr(196).chr(175) => 'i', chr(196).chr(176) => 'I', chr(196).chr(177) => 'i', chr(196).chr(178) => 'IJ',chr(196).chr(179) => 'ij', chr(196).chr(180) => 'J', chr(196).chr(181) => 'j', chr(196).chr(182) => 'K', chr(196).chr(183) => 'k', chr(196).chr(184) => 'k', chr(196).chr(185) => 'L', chr(196).chr(186) => 'l', chr(196).chr(187) => 'L', chr(196).chr(188) => 'l', chr(196).chr(189) => 'L', chr(196).chr(190) => 'l', chr(196).chr(191) => 'L', chr(197).chr(128) => 'l', chr(197).chr(129) => 'L', chr(197).chr(130) => 'l', chr(197).chr(131) => 'N', chr(197).chr(132) => 'n', chr(197).chr(133) => 'N', chr(197).chr(134) => 'n', chr(197).chr(135) => 'N', chr(197).chr(136) => 'n', chr(197).chr(137) => 'N', chr(197).chr(138) => 'n', chr(197).chr(139) => 'N', chr(197).chr(140) => 'O', chr(197).chr(141) => 'o', chr(197).chr(142) => 'O', chr(197).chr(143) => 'o', chr(197).chr(144) => 'O', chr(197).chr(145) => 'o', chr(197).chr(146) => 'OE',chr(197).chr(147) => 'oe', chr(197).chr(148) => 'R',chr(197).chr(149) => 'r', chr(197).chr(150) => 'R',chr(197).chr(151) => 'r', chr(197).chr(152) => 'R',chr(197).chr(153) => 'r', chr(197).chr(154) => 'S',chr(197).chr(155) => 's', chr(197).chr(156) => 'S',chr(197).chr(157) => 's', chr(197).chr(158) => 'S',chr(197).chr(159) => 's', chr(197).chr(160) => 'S', chr(197).chr(161) => 's', chr(197).chr(162) => 'T', chr(197).chr(163) => 't', chr(197).chr(164) => 'T', chr(197).chr(165) => 't', chr(197).chr(166) => 'T', chr(197).chr(167) => 't', chr(197).chr(168) => 'U', chr(197).chr(169) => 'u', chr(197).chr(170) => 'U', chr(197).chr(171) => 'u', chr(197).chr(172) => 'U', chr(197).chr(173) => 'u', chr(197).chr(174) => 'U', chr(197).chr(175) => 'u', chr(197).chr(176) => 'U', chr(197).chr(177) => 'u', chr(197).chr(178) => 'U', chr(197).chr(179) => 'u', chr(197).chr(180) => 'W', chr(197).chr(181) => 'w', chr(197).chr(182) => 'Y', chr(197).chr(183) => 'y', chr(197).chr(184) => 'Y', chr(197).chr(185) => 'Z', chr(197).chr(186) => 'z', chr(197).chr(187) => 'Z', chr(197).chr(188) => 'z', chr(197).chr(189) => 'Z', chr(197).chr(190) => 'z', chr(197).chr(191) => 's',
// Decompositions for Latin Extended-B
chr(200).chr(152) => 'S', chr(200).chr(153) => 's', chr(200).chr(154) => 'T', chr(200).chr(155) => 't',
// Euro Sign
chr(226).chr(130).chr(172) => 'E',
// GBP (Pound) Sign
chr(194).chr(163) => '',
// Vowels with diacritic (Vietnamese)
// unmarked
chr(198).chr(160) => 'O', chr(198).chr(161) => 'o', chr(198).chr(175) => 'U', chr(198).chr(176) => 'u',
// grave accent
chr(225).chr(186).chr(166) => 'A', chr(225).chr(186).chr(167) => 'a', chr(225).chr(186).chr(176) => 'A', chr(225).chr(186).chr(177) => 'a', chr(225).chr(187).chr(128) => 'E', chr(225).chr(187).chr(129) => 'e', chr(225).chr(187).chr(146) => 'O', chr(225).chr(187).chr(147) => 'o', chr(225).chr(187).chr(156) => 'O', chr(225).chr(187).chr(157) => 'o', chr(225).chr(187).chr(170) => 'U', chr(225).chr(187).chr(171) => 'u', chr(225).chr(187).chr(178) => 'Y', chr(225).chr(187).chr(179) => 'y',
// hook
chr(225).chr(186).chr(162) => 'A', chr(225).chr(186).chr(163) => 'a', chr(225).chr(186).chr(168) => 'A', chr(225).chr(186).chr(169) => 'a', chr(225).chr(186).chr(178) => 'A', chr(225).chr(186).chr(179) => 'a', chr(225).chr(186).chr(186) => 'E', chr(225).chr(186).chr(187) => 'e', chr(225).chr(187).chr(130) => 'E', chr(225).chr(187).chr(131) => 'e', chr(225).chr(187).chr(136) => 'I', chr(225).chr(187).chr(137) => 'i', chr(225).chr(187).chr(142) => 'O', chr(225).chr(187).chr(143) => 'o', chr(225).chr(187).chr(148) => 'O', chr(225).chr(187).chr(149) => 'o', chr(225).chr(187).chr(158) => 'O', chr(225).chr(187).chr(159) => 'o', chr(225).chr(187).chr(166) => 'U', chr(225).chr(187).chr(167) => 'u', chr(225).chr(187).chr(172) => 'U', chr(225).chr(187).chr(173) => 'u', chr(225).chr(187).chr(182) => 'Y', chr(225).chr(187).chr(183) => 'y',
// tilde
chr(225).chr(186).chr(170) => 'A', chr(225).chr(186).chr(171) => 'a', chr(225).chr(186).chr(180) => 'A', chr(225).chr(186).chr(181) => 'a', chr(225).chr(186).chr(188) => 'E', chr(225).chr(186).chr(189) => 'e', chr(225).chr(187).chr(132) => 'E', chr(225).chr(187).chr(133) => 'e', chr(225).chr(187).chr(150) => 'O', chr(225).chr(187).chr(151) => 'o', chr(225).chr(187).chr(160) => 'O', chr(225).chr(187).chr(161) => 'o', chr(225).chr(187).chr(174) => 'U', chr(225).chr(187).chr(175) => 'u', chr(225).chr(187).chr(184) => 'Y', chr(225).chr(187).chr(185) => 'y',
// acute accent
chr(225).chr(186).chr(164) => 'A', chr(225).chr(186).chr(165) => 'a', chr(225).chr(186).chr(174) => 'A', chr(225).chr(186).chr(175) => 'a', chr(225).chr(186).chr(190) => 'E', chr(225).chr(186).chr(191) => 'e', chr(225).chr(187).chr(144) => 'O', chr(225).chr(187).chr(145) => 'o', chr(225).chr(187).chr(154) => 'O', chr(225).chr(187).chr(155) => 'o', chr(225).chr(187).chr(168) => 'U', chr(225).chr(187).chr(169) => 'u',
// dot below
chr(225).chr(186).chr(160) => 'A', chr(225).chr(186).chr(161) => 'a', chr(225).chr(186).chr(172) => 'A', chr(225).chr(186).chr(173) => 'a', chr(225).chr(186).chr(182) => 'A', chr(225).chr(186).chr(183) => 'a', chr(225).chr(186).chr(184) => 'E', chr(225).chr(186).chr(185) => 'e', chr(225).chr(187).chr(134) => 'E', chr(225).chr(187).chr(135) => 'e', chr(225).chr(187).chr(138) => 'I', chr(225).chr(187).chr(139) => 'i', chr(225).chr(187).chr(140) => 'O', chr(225).chr(187).chr(141) => 'o', chr(225).chr(187).chr(152) => 'O', chr(225).chr(187).chr(153) => 'o', chr(225).chr(187).chr(162) => 'O', chr(225).chr(187).chr(163) => 'o', chr(225).chr(187).chr(164) => 'U', chr(225).chr(187).chr(165) => 'u', chr(225).chr(187).chr(176) => 'U', chr(225).chr(187).chr(177) => 'u', chr(225).chr(187).chr(180) => 'Y', chr(225).chr(187).chr(181) => 'y',
// Vowels with diacritic (Chinese, Hanyu Pinyin)
chr(201).chr(145) => 'a',
// macron
chr(199).chr(149) => 'U', chr(199).chr(150) => 'u',
// acute accent
chr(199).chr(151) => 'U', chr(199).chr(152) => 'u',
// caron
chr(199).chr(141) => 'A', chr(199).chr(142) => 'a', chr(199).chr(143) => 'I', chr(199).chr(144) => 'i', chr(199).chr(145) => 'O', chr(199).chr(146) => 'o', chr(199).chr(147) => 'U', chr(199).chr(148) => 'u', chr(199).chr(153) => 'U', chr(199).chr(154) => 'u',
// grave accent
chr(199).chr(155) => 'U', chr(199).chr(156) => 'u',
);
// Used for locale-specific rules
$locale = "en_US";
if ( 'de_DE' == $locale ) {
$chars[ chr(195).chr(132) ] = 'Ae'; $chars[ chr(195).chr(164) ] = 'ae'; $chars[ chr(195).chr(150) ] = 'Oe'; $chars[ chr(195).chr(182) ] = 'oe'; $chars[ chr(195).chr(156) ] = 'Ue'; $chars[ chr(195).chr(188) ] = 'ue'; $chars[ chr(195).chr(159) ] = 'ss';
} elseif ( 'da_DK' === $locale ) {
$chars[ chr(195).chr(134) ] = 'Ae'; $chars[ chr(195).chr(166) ] = 'ae'; $chars[ chr(195).chr(152) ] = 'Oe'; $chars[ chr(195).chr(184) ] = 'oe'; $chars[ chr(195).chr(133) ] = 'Aa'; $chars[ chr(195).chr(165) ] = 'aa';
}
$string = strtr($string, $chars);
} else {
// Assume ISO-8859-1 if not UTF-8
$chars['in'] = chr(128).chr(131).chr(138).chr(142).chr(154).chr(158).chr(159).chr(162).chr(165).chr(181).chr(192).chr(193).chr(194).chr(195).chr(196).chr(197).chr(199).chr(200).chr(201).chr(202).chr(203).chr(204).chr(205).chr(206).chr(207).chr(209).chr(210).chr(211).chr(212).chr(213).chr(214).chr(216).chr(217).chr(218).chr(219).chr(220).chr(221).chr(224).chr(225).chr(226).chr(227).chr(228).chr(229).chr(231).chr(232).chr(233).chr(234).chr(235).chr(236).chr(237).chr(238).chr(239).chr(241).chr(242).chr(243).chr(244).chr(245).chr(246).chr(248).chr(249).chr(250).chr(251).chr(252).chr(253).chr(255);
$chars['out'] = "EfSZszYcYuAAAAAACEEEEIIIINOOOOOOUUUUYaaaaaaceeeeiiiinoooooouuuuyy";
$string = strtr($string, $chars['in'], $chars['out']);
$double_chars['in'] = array(chr(140), chr(156), chr(198), chr(208), chr(222), chr(223), chr(230), chr(240), chr(254));
$double_chars['out'] = array('OE', 'oe', 'AE', 'DH', 'TH', 'ss', 'ae', 'dh', 'th');
$string = str_replace($double_chars['in'], $double_chars['out'], $string);
}
return $string;
}
?>

BIN
icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

1
prefs.php Normal file
View File

@@ -0,0 +1 @@
a:8:{s:7:"allowed";a:8:{i:0;s:3:"jpg";i:1;s:4:"jpeg";i:2;s:3:"png";i:3;s:3:"mov";i:4;s:3:"mp4";i:5;s:3:"m4v";i:6;s:3:"dng";i:7;s:4:"heic";}s:5:"limit";i:2000;s:9:"maxlength";i:30;s:6:"review";i:1;s:2:"pt";s:33:"%DTO_%MODEL_%PLACE_%SW_%FILE.%EXT";s:2:"vt";s:31:"%DTO_%DUR_%PLACE_%SW_%FILE.%EXT";s:3:"key";s:0:"";s:7:"dry_run";i:1;}

1
version.txt Normal file
View File

@@ -0,0 +1 @@
0.4.0