You are not logged in.
As jansucan said regarding limits. On my box, the max command line length seems to 64KB.
Here's a script which takes only the directory containing the image files as an argument. In Thunar custom actions, put %f on the command line and tick just Directories in Appearance Conditions.
#!/bin/bash
#expecting one arg, which is a directory containing images
ZENITY_X=0
ZENITY_Y=0
ZENITY_W=500
ZENITY_H=400
MAX_OUTPUT=10
#exit if $1 is null or can't enter directory $1
if [[ -z $1 ]] || ! cd "$1" 2>/dev/null; then exit; fi
function selectImages {
#run a loop in background to resize/move window when it's up
{ while
! wmctrl -l -G|grep "ZenityImageSorter" >/dev/null
do
sleep 0.05
done
wmctrl -r ZenityImageSorter -e 0,$ZENITY_X,$ZENITY_Y,$ZENITY_W,$ZENITY_H
} &
#loop through all files in directory
for file in *; do
p=(`identify -format "%w %h" "$file" 2>/dev/null`)
#skip file if identify didn't identify it
if [[ -z ${p[0]} ]] || [[ -z ${p[1]} ]]; then continue; fi
#write file data to stdout
printf "%05d\n%05d\n%s\n" ${p[0]} ${p[1]} "$file"
done | zenity --list --multiple --title="ZenityImageSorter" --print-column=3 \
--column "width" --column "height" --column="file name" 2>/dev/null \
|tr "|" "\n"
}
readarray -n $MAX_OUTPUT -t images <<< "`selectImages`"
#end if no files returned
[[ -z ${images[0]} ]] && exit
#run display command on each returned file
for file in "${images[@]}"; do
display "$file" &
done
#don't exit till all child processes have finished
wait
The script will work through all files in the dir and adds to the list in zenity any which identify "thinks" are images (beware: this includes text and pdf files! because display can show them). There is probably still a limit to the number of files it can handle, but it should be higher. I've used it successfully with 4000 test images.
I've also enabled multiple select (works the same as other GTK+ apps), and when you click OK, each file selected is opened with display. I put in a limit to how many files you can open (variable MAX_OUTPUT declared near the top): if you're dealing with thousands of image files in a dir, then you can easily jam up your desktop if you make this limit too big.
Zenity returns data to the script only when it exits, so it is not possible to return to how you had the list before you opened some files. And I think we're approaching the limit of what we can do with these tools
Offline
Tx jansucan and Simon!
Using Simon's script above it takes about 4 minutes to populate list of 4775 images and about 10 minutes to populate list of 10730 images. That's the most images I've tried. And it works with filenames of all sizes. Still that's a long time to wait for that many images to populate. If working with a directory that contains more than 1000-2000 images I might use the following Custom Action to break them up:
Name: Files into Folders
Description: Divide files in folder into many folders. Customizable by changing number of files.
Command: /home/$USER/Scripts/movefilesdir5.sh %N
Under Appearance Conditions check options depending on how you want to use the script. I use this script for all types of files so I have everything checked.
Bash Script:
#!/bin/bash
c=1; d=1; mkdir -p dir_${d}
for file in *
do
if [ $c -eq 1000 ]
then
d=$(( d + 1 )); c=0; mkdir -p dir_${d}
fi
mv "$file" dir_${d}/
c=$(( c + 1 ))
done
Change the number "1000" in the bash script to whatever number of images you want divided into each folder.
After using this Custom Action to divide, say, 5000 images into 5 subfolders, then it will be easier to use Simon's Custom Action.
Of course, I could use XNViewMP to view the dimensions of images in large directories, but I'd rather avoid that as XNViewMP can be a pain to work with when dealing with large numbers of images.
It would be convenient, however, if there was a counter that showed at a glance how many images have been populated, to determine when it's done. Otherwise you have to keep checking to see if it's done. A popup box that indicates completion wouldn't be good, because in most cases i'll be using the script on image directories containing no more than 500 images, in which case the list is created almost instantly, and so a popup box would be a nuisance --assuming you have to click "OK" to get it to close. Some sort of counter would be handy.
I've found that if you open 30 images and wait about 90 seconds before trying to edit them or close them then they will work fine. But like you say, it's better to stick with no more than 10 most of the time.
If you try to work with that many images without waiting for 90 seconds then this warning box will pop up when trying to close them or edit them:
Any chance you could add a counter to the script? If not, then what you've done so far will work nicely.
Last edited by birch (2014-09-20 12:52:28)
Offline
You can achieve a great speed up by minimizing number of calls to identify. I have tested it with 1000 images. This code calls identify 1000 times - one call for one image (output was modified for clarity):
> time for i in *.jpg; do identify -format "%w %h" $i; done
real 0m36.646s
user 0m29.727s
sys 0m6.370s
And this code calls it only once for all 1000 images:
> time identify -format "%w %h" *.jpg
real 0m4.202s
user 0m4.078s
sys 0m0.091s
Last edited by jansucan (2014-09-20 13:28:45)
Offline
Thanks @jansucan for the hint on speed up. I modified the script to use just one call to identify, but it turns out it is zenity that causes most of the wait. My dir Pictures/test contains 4000 small png files.
time while read w h f; do printf "%05d\n%05d\n%s\n" $w $h "$f"; done < <(identify -format "%w %h %f\n" Pictures/test/*)
...
real 0m9.554s
user 0m8.452s
sys 0m0.956s
but piping this to zenity...
while read w h f; do printf "%05d\n%05d\n%s\n" $w $h "$f"; done < <(identify -format "%w %h %f\n" Pictures/test/*) | zenity --list --multiple --title="ZenityImageSorter" --print-column=3 --column "width" --column "height" --column="file name"
...waiting for the list to be fully populated, then clicking cancel immediately... this took 4 minutes!!
For what it's worth, the here's the modified script:
#!/bin/bash
#expecting one arg, which is a directory containing images
ZENITY_X=0
ZENITY_Y=0
ZENITY_W=500
ZENITY_H=400
MAX_OUTPUT=10
#exit if $1 is null or can't enter directory $1
if [[ -z $1 ]] || ! cd "$1" 2>/dev/null; then exit; fi
function selectImages {
#run a loop in background to resize/move window when it's up
{ while
! wmctrl -l -G|grep "ZenityImageSorter" >/dev/null
do
sleep 0.05
done
wmctrl -r ZenityImageSorter -e 0,$ZENITY_X,$ZENITY_Y,$ZENITY_W,$ZENITY_H
} &
while
read w h file
do
if [[ $w ]] && [[ $h ]]; then
#write file data to stdout
printf "%05d\n%05d\n%s\n" $w $h "$file"
fi
done < <(identify -format "%w %h %f\n" * 2>/dev/null) | \
zenity --list --multiple --title="ZenityImageSorter" --print-column=3 \
--column "width" --column "height" --column="file name" 2>/dev/null | \
tr "|" "\n"
}
readarray -n $MAX_OUTPUT -t images <<< "`selectImages`"
#end if no files returned
[[ -z ${images[0]} ]] && exit
#run display command on each returned file
for file in "${images[@]}"; do
display "$file" &
done
#don't exit till all child processes have finished
wait
Offline
I time tested your modified script and your previous script on 1100 images and it took about 1 minute for each. The only difference I detected was that there was be a slight delay in the list being populated after the zenity window pops up with the most recent script. But the time to populate was about the same.
Could a right-click context menu be incorporated for the populated list of files? I'd like to use the script to delete tiny pics by right-clicking on one or many of them and deleting. It would also be cool if you could use Custom Actions on the list, but that's probably not possible.
Offline
Unfortunately it isn't possible to add context menus, or even extra buttons, to zenity's windows. And I don't think there's a way to put the output from a script back into Thunar to use its Custom Action.
The only way I can think of to extend what I've done here is to have a script that takes an extra parameter to choose whether clicking OK button displays the selected files or deletes them. You would then have to set up two Custom Actions: one for Display, one for Delete. This is relatively simple to implement.
If, for a particular action, you are only interested in the largest or smallest images, the list of files the script passes to zenity could first go through sort and head/tail (like you were doing at the start of this thread) to speed up the display. For example, for 'select to delete' could show the smallest 100 files by width or height. This is more complicated - writing a script, setting up Custom Actions and choosing sensible numbers for each option.
Offline
Would there be a way to make these additions if you were using YAD?
If not, I'd like that second custom action for deleting pics.
The script for only sorting the 100 largest and smallest pics would be very useful.
Last edited by birch (2014-09-21 12:52:01)
Offline
Okay, this script needs an extra parameter in the command line. To set up Custom Action for display:
/path/to/script display %f
To set up for delete:
/path/to/script delete %f
After the we have the dimensions from identify, an initial sort is performed by sort. The first key of the sort is the figure (width+height). Then the first so many files from the sort are put into zenity. Unless you have very asymmetric images, this should be okay, and speeds things up. All the files are identified & sorted, so there is a delay for this before anything appears in zenity.
The action in use is shown in the window title as a reminder. There some variables to configure towards the top of the script, inside the first case block.
MAX_OUTPUT is the max number of files to either display or delete. NB I mean DELETE not 'send to trash' !!!!
MAX_INPUT is the max number of files to put in the list. If this is too restrictive, just make the figure bigger.
SORTING is command line options to the initial sort. I set it here so that in display mode zenity uses the biggest, in delete mode the smallest.
#!/bin/bash
#expecting 2 args:
# function display or delete
# a directory containing images
ZENITY_X=0
ZENITY_Y=0
ZENITY_W=500
ZENITY_H=400
#set options according to function in $1, if isn't recognised, exit
#MAX_INPUT is max number of files in list taken from sort by (width+height)
#MAX_OUTPUT is max number of files to display or delete
#SORTING is command line options for sort; data lines: w+h w h file
case $1 in
display) MAX_OUTPUT=10; MAX_INPUT=100; SORTING="-n -r" ;;
delete) MAX_OUTPUT=50; MAX_INPUT=100; SORTING="-n" ;;
*) exit ;;
esac
WIN_TITLE="ZenityImageSorter_$1"
#exit if $2 is null or can't enter directory $2
if [[ -z $2 ]] || ! cd "$2" 2>/dev/null; then exit; fi
function IDandSort {
while
read w h f
do
#skip file if identify didn't identify it
if [[ $w ]] && [[ $h ]]; then
printf "%05d %05d %05d %s\n" $((w+h)) $w $h "$f"
fi
done < <(identify -format "%w %h %f\n" *) | sort $SORTING | head -n $MAX_INPUT
}
function selectImages {
#run a loop in background to resize/move window when it's up
{ while
! wmctrl -l -G|grep "$WIN_TITLE" >/dev/null
do
sleep 0.05
done
wmctrl -r "$WIN_TITLE" -e 0,$ZENITY_X,$ZENITY_Y,$ZENITY_W,$ZENITY_H
} &
while
read s w h file
do
printf "%s\n%s\n%s\n" $w $h "$file"
done < <(IDandSort) | \
zenity --list --multiple --title="$WIN_TITLE" --print-column=3 \
--column "width" --column "height" --column="file name" 2>/dev/null | \
tr "|" "\n"
}
readarray -n $MAX_OUTPUT -t images <<< "`selectImages`"
#end if no files returned
[[ -z ${images[0]} ]] && exit
#run command on each returned file
case $1 in
display)
for file in "${images[@]}"; do
display "$file" &
done ;;
delete)
rm -f "${images[@]}" ;;
esac
#don't exit till all child processes have finished
wait
Offline
...waiting for the list to be fully populated, then clicking cancel immediately... this took 4 minutes!!
I have tried to to look at source codes for zenity 3.6.0 and commented out line 176 in tree.c. The results for this code
while read w h f; do printf "%05d\n%05d\n%s\n" $w $h "$f"; done < <(identify -format "%w %h %f\n" Pictures/test/*) \
| zenity --list --multiple --title="ZenityImageSorter" --print-column=3 --column "width" --column "height" --column="file name"
and 4000 images are:
Original version of zenity:
real 3m4.994s
user 1m0.411s
sys 0m1.006s
Modified version:
real 0m27.316s
user 0m25.333s
sys 0m0.596s
It will take a little time to think about a clean solution, but this goes for zenity developers.
Offline
@jansucan That's interesting, I'll take a look at the code.
Meanwhile, I had a look at yad (never used it before) and it has some useful features that will help here. The following script takes just a dir name as an argument, will:
1) display an image on double-clicking a row
2) using the check-boxes, files can be marked for deletion on exit.
The bad news is that yad is as slow a zenity; the good news is that you can start working on the files as soon as they appear in the list. If you exit before the list has finished loading, there may be a delay of several seconds before the window closes, but files marked for deletion will be deleted when it does.
#!/bin/bash
#expecting 1 arg:
# a directory containing images
#window size
YAD_W=500
YAD_H=400
#max number of files to delete
MAX_DELETE=50
#window title
WIN_TITLE="Image Sorter"
#exit if $1 is null or can't enter directory $1
if [[ -z $1 ]] || ! cd "$1" 2>/dev/null; then exit; fi
function dclick {
display "$4" &
}
export -f dclick
function selectImages {
returnList=`\
while
read w h file
do
if [[ $w ]] && [[ $h ]]; then
printf "\n%05d\n%05d\n%s\n" $w $h "$file"
fi
done < <(identify -format "%w %h %f\n" * 2>/dev/null) | \
yad --list --checklist --title="$WIN_TITLE" --print-column=4 \
--width=$YAD_W --height=$YAD_H --center \
--text="\tDouble-click a row to view the image\n\n\tTick the box to mark for deletion\n" \
--dclick-action="bash -c \"dclick %s\"" \
--button="Cancel:1" --button="Delete & Exit:0" \
--column="" --column="width:NUM" --column="height:NUM" --column="file name" 2>/dev/null |
sed -r "s/\|$//"`
}
selectImages
if [[ $returnList ]]; then
readarray -n $MAX_DELETE -t images <<< "$returnList"
rm -f "${images[@]}"
fi
#don't exit till all child processes have finished
wait
Double-clicking a row executes the function dclick; if you want to change this from display to something else, you can do it there.
Usual warning: make backups & try it out on test data until you are sure the script does what you expect.
EDIT: sometime between yad versions 0.25 and 0.27 support for pango mark-up was added. This means text in widgets now has to be quoted html-style. In the script above, the value for the second button would have to be written "Delete & Exit:0"
Last edited by Simon_P (2014-09-28 14:04:22)
Offline
I've timed the version above using yad with 4000 items:
real 1m53.516s
user 1m46.292s
sys 0m2.476s
That's better than twice as fast as the zenity version. identify runs at 100% for the first few seconds, then yad at 100% until the list is full. I looked at the source code and see yad doesn't have that crude cpu-usage limiter. Yad is looking good, despite the name.
Offline
Tx! The YAD script is the best one yet. I tested it with 7626 images (in various formats and cases), totalling 1.5 gigabytes, and it populated them in a little less than 2 minutes! This is definitely the way to go. The interface is nicer as well.
I replaced "display" with "xnviewmp".
Offline
@birch Nice to hear
BTW I just 'tweaked' the code in yad and it loaded 4000 in under 20sec! Not sure how stable the result is...
Offline
Can you post the tweaked code?
Offline
Just to be clear, I'm talking about the C source code for yad. I'm using the current release version 0.25.1. In the file src/list.c I replaced the while loop on lines 431 & 432 with
gtk_main_iteration_do(FALSE);
Offline
How do I find list.c and how to edit it?
Offline
While on one hand, I don't want to encourage anyone who isn't already familiar with compiling sources & possible consequences of modifying code to experiment with this; but on other hand, I don't want to discourage anyone who wants to learn. My suggestion is to find a good tutorial on compiling source packages, preferably with specifics of your distro. This ubuntu wiki is a reasonable example.
I was mistaken when I earlier said the lastest yad release is 0.25.1. That was from when the project was on GoogleCode. It moved to SourceForge and is now on 0.27.0. Download the source package there. In this version the relevant line numbers in src/list.c are 351/352 (list.c is a file you'll have when you've extracted the archive). You should build and test the unmodified version before changing anything.
I haven't had any adverse effects from playing with the code, but that doesn't mean you won't.
Offline
Okay, tx! I'll look into it.
Offline
I was formerly birch. I sometimes still find the sorting custom action useful, but I find myself using the following custom action much more, which renames the jpgs according to dimensions:
#!/bin/bash
for filename in *.jpg* *.JPG* *.jpeg*;
do
inname=`convert $filename -format "%t" info:`
size=`convert $filename -format "%wx%h" info:`
mv $filename "${size}_${inname}.jpg";
done
Offline
Ooh, nice turn of events, very nice read!!!
Offline
[ Generated in 0.012 seconds, 7 queries executed - Memory usage: 646.7 KiB (Peak: 679.54 KiB) ]