summaryrefslogtreecommitdiff
path: root/rapid
diff options
context:
space:
mode:
authorJulien Valroff <julien@kirya.net>2009-11-15 08:17:38 +0000
committerJulien Valroff <julien@kirya.net>2009-11-15 08:17:38 +0000
commit39b3722a11483b0f1322986528e77d01b4c92183 (patch)
treea96b7ccd7d56f1320916e62a962c5b220edefb2a /rapid
parent94193fdc006f406f0e09b0d9b45a002b36ca49a8 (diff)
New beta release
Diffstat (limited to 'rapid')
-rw-r--r--rapid/ChangeLog52
-rw-r--r--rapid/INSTALL18
-rw-r--r--rapid/config.py2
-rw-r--r--rapid/glade3/rapid.glade405
-rwxr-xr-xrapid/metadata.py28
-rwxr-xr-xrapid/rapid.py350
-rw-r--r--rapid/renamesubfolderprefs.py70
7 files changed, 770 insertions, 155 deletions
diff --git a/rapid/ChangeLog b/rapid/ChangeLog
index 430aab8..4d38ed2 100644
--- a/rapid/ChangeLog
+++ b/rapid/ChangeLog
@@ -1,3 +1,55 @@
+Version 0.1.0 beta 1
+--------------------
+
+2009-11-14
+
+This code is ready for full release, but given the magnitude of changes, a beta
+seems like a good idea, simply to catch any undetected bugs.
+
+Added a "Job codes" option. Like the "text" option in image and subfolder name
+generation, this allows you to specify text that will be placed into the file
+and subfolder names. However, unlike the "text" option, which requires that the
+text be directly entered via the program preferences, when using the "Job code"
+option, the program will prompt for it each time a download begins.
+
+Made Download button the default button. Hitting enter while the main window
+has focus will now start the download.
+
+Fixed bug #387002: added dependency in Ubuntu packages for librsvg2-common.
+Thanks go to user hasp for this fix.
+
+Fixed bug #478620: problem with corrupted image files. Thanks go to user Katrin
+Krieger for tracking this one down.
+
+Fixed bug #479424: some camera model names do not have numbers, but it still
+makes sense to return a shortened name. Thanks go to user Wesley Harp for
+highlighting this problem.
+
+Fixed bug #482831: program no longer crashes when auto-download is off, and a
+device is inserted before another download has completed
+
+Added Czech translation by Tomas Novak.
+
+Added French translation by Julien Valroff, Michel Ange, and Cenwen.
+
+Added Hungarian translation by Balazs Oveges and Andras Lorincz.
+
+Added Slovak translation by Tomas Novak.
+
+Added Swedish translation by Ulf Urden and Michal Predotka.
+
+Added dependency on gnome-icon-theme in Ubuntu packages.
+
+Added additional hour, minute and second options in image renaming and subfolder
+creation. Thanks to Art Zemon for the patch.
+
+Malformed image date time exif values have are minimally checked to see if they
+can still be used for subfolder and image renaming. Some software programs seem
+to make a mess of them.
+
+Update man page, including a bug fix by Julien Valroff.
+
+
Version 0.0.10
--------------
diff --git a/rapid/INSTALL b/rapid/INSTALL
index 27f9b07..7972a01 100644
--- a/rapid/INSTALL
+++ b/rapid/INSTALL
@@ -1,14 +1,14 @@
Rapid Photo Downloader depends on the following software:
-- GNOME 2.18
-- GTK+ 2.10
-- Python 2.5
-- pygtk 2.10
-- python-gconf 2.18
-- python-glade2 2.10
-- gnome-python 2.10
-- libexiv2 0.15
-- pyexiv2 0.1.1
+- GNOME 2.18 or higher
+- GTK+ 2.10 or higher
+- Python 2.5 or 2.6
+- pygtk 2.10 or higher
+- python-gconf 2.18 or higher
+- python-glade2 2.10 or higher
+- gnome-python 2.10 or higher
+- libexiv2 0.15 or higher
+- pyexiv2 0.1.1 or higher
To run Rapid Photo Downloader you will need all the software mentioned above. To
start from a fairly basic system, I suggest the following:
diff --git a/rapid/config.py b/rapid/config.py
index 2e59841..7325538 100644
--- a/rapid/config.py
+++ b/rapid/config.py
@@ -15,7 +15,7 @@
### along with this program; if not, write to the Free Software
### Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-version = '0.0.10'
+version = '0.1.0~b1'
GCONF_KEY="/apps/rapid-photo-downloader"
GLADE_FILE = "glade3/rapid.glade"
diff --git a/rapid/glade3/rapid.glade b/rapid/glade3/rapid.glade
index be01fa4..92c533b 100644
--- a/rapid/glade3/rapid.glade
+++ b/rapid/glade3/rapid.glade
@@ -589,14 +589,14 @@
</packing>
</child>
<child>
- <widget class="GtkVBox" id="vbox12">
+ <widget class="GtkVBox" id="reame_options_vbox">
<property name="visible">True</property>
<child>
- <widget class="GtkLabel" id="label29">
+ <widget class="GtkLabel" id="sequence_number_label">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="xpad">12</property>
- <property name="label" translatable="yes">&lt;b&gt;Sequence numbers&lt;/b&gt;</property>
+ <property name="label" translatable="yes">&lt;b&gt;Sequence Numbers&lt;/b&gt;</property>
<property name="use_markup">True</property>
</widget>
<packing>
@@ -605,11 +605,11 @@
</packing>
</child>
<child>
- <widget class="GtkHBox" id="hbox21">
+ <widget class="GtkHBox" id="sequence_number_hbox">
<property name="visible">True</property>
<property name="spacing">12</property>
<child>
- <widget class="GtkLabel" id="label46">
+ <widget class="GtkLabel" id="spacer_seq_label">
<property name="visible">True</property>
<property name="xpad">12</property>
</widget>
@@ -619,7 +619,7 @@
</packing>
</child>
<child>
- <widget class="GtkVBox" id="vbox13">
+ <widget class="GtkVBox" id="seq_vbox">
<property name="visible">True</property>
<property name="spacing">12</property>
<child>
@@ -796,7 +796,7 @@
</packing>
</child>
<child>
- <widget class="GtkLabel" id="label44">
+ <widget class="GtkLabel" id="compatibility_label">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="xpad">12</property>
@@ -805,15 +805,15 @@
</widget>
<packing>
<property name="expand">False</property>
- <property name="position">2</property>
+ <property name="position">3</property>
</packing>
</child>
<child>
- <widget class="GtkHBox" id="hbox16">
+ <widget class="GtkHBox" id="compatibility_hbox">
<property name="visible">True</property>
<property name="spacing">12</property>
<child>
- <widget class="GtkLabel" id="label32">
+ <widget class="GtkLabel" id="compatibility_spacer_label">
<property name="visible">True</property>
<property name="xpad">12</property>
</widget>
@@ -825,7 +825,7 @@
<child>
<widget class="GtkTable" id="compatibility_table">
<property name="visible">True</property>
- <property name="n_rows">9</property>
+ <property name="n_rows">2</property>
<property name="n_columns">2</property>
<child>
<widget class="GtkLabel" id="label9">
@@ -854,48 +854,6 @@
<property name="bottom_attach">2</property>
</packing>
</child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
</widget>
<packing>
<property name="position">1</property>
@@ -914,7 +872,7 @@
<packing>
<property name="expand">False</property>
<property name="padding">12</property>
- <property name="position">3</property>
+ <property name="position">4</property>
</packing>
</child>
</widget>
@@ -940,6 +898,211 @@
</packing>
</child>
<child>
+ <widget class="GtkVBox" id="job_codes_tab">
+ <property name="visible">True</property>
+ <property name="spacing">12</property>
+ <child>
+ <widget class="GtkVBox" id="job_codes_header_vbox">
+ <property name="visible">True</property>
+ <child>
+ <widget class="GtkHBox" id="hbox188">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <widget class="GtkImage" id="image77">
+ <property name="visible">True</property>
+ <property name="icon_name">emblem-photos</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label1340">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">&lt;span weight="bold" size="x-large"&gt;Job Codes&lt;/span&gt;</property>
+ <property name="use_markup">True</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkHSeparator" id="hseparator44">
+ <property name="visible">True</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkVBox" id="job_codes_vbox">
+ <property name="visible">True</property>
+ <child>
+ <widget class="GtkVBox" id="job_code_vbox">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <widget class="GtkLabel" id="job_code_label">
+ <property name="xalign">0</property>
+ <property name="xpad">12</property>
+ <property name="label" translatable="yes">&lt;b&gt;Job Codes&lt;/b&gt;</property>
+ <property name="use_markup">True</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkHBox" id="job_code_hbox">
+ <property name="visible">True</property>
+ <property name="spacing">12</property>
+ <child>
+ <widget class="GtkLabel" id="job_code_spacer_label">
+ <property name="visible">True</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkScrolledWindow" id="job_code_scrolledwindow">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">automatic</property>
+ <property name="vscrollbar_policy">automatic</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <widget class="GtkTreeView" id="job_code_treeview">
+ <property name="width_request">250</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_visible">False</property>
+ <property name="rubber_banding">True</property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkHBox" id="job_code_button_hbox">
+ <property name="visible">True</property>
+ <child>
+ <widget class="GtkVButtonBox" id="job_code_vbuttonbox">
+ <property name="visible">True</property>
+ <property name="spacing">12</property>
+ <property name="layout_style">start</property>
+ <child>
+ <widget class="GtkButton" id="add_job_code_button">
+ <property name="label">gtk-add</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ <signal name="clicked" handler="on_add_job_code_button_clicked"/>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkButton" id="remove_job_code_button">
+ <property name="label">gtk-remove</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ <signal name="clicked" handler="on_remove_job_code_button_clicked"/>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkButton" id="clear_job_code_button">
+ <property name="label" translatable="yes" comments="The underscore after the C signifies that the l is the accelerator key. This is the standard 'Clear' button, but I needed to change the accelerator from the standard 'c' to 'l' because the close button also used 'c'">C_lear</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ <signal name="clicked" handler="on_clear_job_code_button_clicked"/>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="job_codes_tab_label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Job Codes</property>
+ </widget>
+ <packing>
+ <property name="position">3</property>
+ <property name="tab_fill">False</property>
+ <property name="type">tab</property>
+ </packing>
+ </child>
+ <child>
<widget class="GtkVBox" id="device_tab">
<property name="visible">True</property>
<property name="spacing">12</property>
@@ -1187,7 +1350,7 @@ You can download photos from multiple image devices simultaneously.
</child>
</widget>
<packing>
- <property name="position">3</property>
+ <property name="position">4</property>
</packing>
</child>
<child>
@@ -1196,7 +1359,7 @@ You can download photos from multiple image devices simultaneously.
<property name="label" translatable="yes">Image Devices</property>
</widget>
<packing>
- <property name="position">3</property>
+ <property name="position">4</property>
<property name="tab_fill">False</property>
<property name="type">tab</property>
</packing>
@@ -1505,7 +1668,7 @@ You can download photos from multiple image devices simultaneously.
</child>
</widget>
<packing>
- <property name="position">4</property>
+ <property name="position">5</property>
</packing>
</child>
<child>
@@ -1514,7 +1677,7 @@ You can download photos from multiple image devices simultaneously.
<property name="label" translatable="yes">Backup</property>
</widget>
<packing>
- <property name="position">4</property>
+ <property name="position">5</property>
<property name="tab_fill">False</property>
<property name="type">tab</property>
</packing>
@@ -1720,7 +1883,7 @@ You can download photos from multiple image devices simultaneously.
</child>
</widget>
<packing>
- <property name="position">5</property>
+ <property name="position">6</property>
</packing>
</child>
<child>
@@ -1730,7 +1893,7 @@ You can download photos from multiple image devices simultaneously.
<property name="label" translatable="yes">Automation</property>
</widget>
<packing>
- <property name="position">5</property>
+ <property name="position">6</property>
<property name="tab_fill">False</property>
<property name="type">tab</property>
</packing>
@@ -2116,7 +2279,7 @@ You can download photos from multiple image devices simultaneously.
</child>
</widget>
<packing>
- <property name="position">6</property>
+ <property name="position">7</property>
</packing>
</child>
<child>
@@ -2126,7 +2289,7 @@ You can download photos from multiple image devices simultaneously.
<property name="label" translatable="yes">Error Handling</property>
</widget>
<packing>
- <property name="position">6</property>
+ <property name="position">7</property>
<property name="tab_fill">False</property>
<property name="type">tab</property>
</packing>
@@ -2208,16 +2371,23 @@ Rapid Photo Downloader is distributed in the hope that it will be useful, but WI
You should have received a copy of the GNU General Public License along with Rapid Photo Downloader; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.</property>
<property name="authors">Damon Lynch &lt;damonlynch@gmail.com&gt;</property>
- <property name="translator_credits" comments="TRANSLATORS: Replace this string with your names, one name per line.">Martin Egger &lt;martin.egger@gmx.net&gt;
+ <property name="translator_credits" comments="TRANSLATORS: Replace this string with your names, one name per line.">Michel Ange &lt;michelange@wanadoo.fr&gt;
+Martin Egger &lt;martin.egger@gmx.net&gt;
+Andras Lorincz
Jose Luis Navarro &lt;jlnavarro111@gmail.com&gt;
+Tomas Novak &lt;kuvaly@seznam.cz&gt;
Abel O'Rian &lt;abel.orian@gmail.com&gt;
+Balazs Oveges
Daniel Paessler &lt;daniel@paessler.org&gt;
Michal Predotka &lt;mpredotka@googlemail.com&gt;
Luca Reverberi &lt;thereve@gmail.com&gt;
Mikko Ruohola &lt;polarfox@polarfox.net&gt;
Sergei Sedov &lt;sedov@webmail.perm.ru&gt;
Marco Solari &lt;marcosolari@gmail.com&gt;
-Julien Valroff &lt;julien@kirya.net&gt;</property>
+Ulf Urd&#xE9;n &lt;ulf.urden@purplescout.com&gt;
+Julien Valroff &lt;julien@kirya.net&gt;
+
+</property>
<property name="logo">rapid-photo-downloader-about.png</property>
<property name="wrap_license">True</property>
<child internal-child="vbox">
@@ -2550,58 +2720,65 @@ Julien Valroff &lt;julien@kirya.net&gt;</property>
</packing>
</child>
<child>
- <widget class="GtkLabel" id="speed_label">
- <property name="visible">True</property>
- <property name="label" translatable="yes"> </property>
- <property name="width_chars">9</property>
- </widget>
- <packing>
- <property name="expand">False</property>
- <property name="position">1</property>
- </packing>
- </child>
- </widget>
- <packing>
- <property name="expand">False</property>
- <property name="position">3</property>
- </packing>
- </child>
- <child>
- <widget class="GtkVSeparator" id="vseparator2">
- <property name="visible">True</property>
- </widget>
- <packing>
- <property name="expand">False</property>
- <property name="position">4</property>
- </packing>
- </child>
- <child>
- <widget class="GtkEventBox" id="error_eventbox">
- <property name="visible">True</property>
- <signal name="button_press_event" handler="on_error_eventbox_button_press_event"/>
- <child>
- <widget class="GtkHBox" id="hbox1">
+ <widget class="GtkHBox" id="hbox2">
<property name="visible">True</property>
<child>
- <widget class="GtkImage" id="warning_image">
+ <widget class="GtkLabel" id="speed_label">
<property name="visible">True</property>
- <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
- <property name="xpad">3</property>
- <property name="stock">gtk-dialog-warning</property>
- <property name="icon-size">1</property>
+ <property name="label" translatable="yes"> </property>
+ <property name="width_chars">9</property>
</widget>
<packing>
<property name="expand">False</property>
- <property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
- <widget class="GtkImage" id="error_image">
+ <widget class="GtkEventBox" id="error_eventbox">
<property name="visible">True</property>
- <property name="xpad">3</property>
- <property name="stock">gtk-dialog-error</property>
- <property name="icon-size">1</property>
+ <signal name="button_press_event" handler="on_error_eventbox_button_press_event"/>
+ <child>
+ <widget class="GtkHBox" id="hbox1">
+ <property name="visible">True</property>
+ <child>
+ <widget class="GtkVSeparator" id="warning_vseparator">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkImage" id="error_image">
+ <property name="visible">True</property>
+ <property name="xpad">3</property>
+ <property name="stock">gtk-dialog-error</property>
+ <property name="icon-size">1</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkImage" id="warning_image">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="xpad">3</property>
+ <property name="stock">gtk-dialog-warning</property>
+ <property name="icon-size">1</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
</widget>
<packing>
<property name="expand">False</property>
@@ -2610,12 +2787,24 @@ Julien Valroff &lt;julien@kirya.net&gt;</property>
</packing>
</child>
</widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
</child>
</widget>
<packing>
<property name="expand">False</property>
- <property name="fill">False</property>
- <property name="position">5</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkVSeparator" id="vseparator2">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">4</property>
</packing>
</child>
<child>
@@ -2626,7 +2815,7 @@ Julien Valroff &lt;julien@kirya.net&gt;</property>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
- <property name="position">6</property>
+ <property name="position">5</property>
</packing>
</child>
</widget>
diff --git a/rapid/metadata.py b/rapid/metadata.py
index 8fc6820..1d4b2df 100755
--- a/rapid/metadata.py
+++ b/rapid/metadata.py
@@ -220,11 +220,13 @@ class MetaData(pyexiv2.Image):
Returns missing if the metadata value is not present.
- The short format is determined by the first occurence of a digit in the
+ The short format is determined by the first occurrence of a digit in the
camera model, including all alphaNumeric characters before and after
- that digit up till a non-alphanumeric character.
+ that digit up till a non-alphanumeric character, but with these interventions:
- Canon "Mark" designations are shortened prior to conversion.
+ 1. Canon "Mark" designations are shortened prior to conversion.
+ 2. Names like "Canon EOS DIGITAL REBEL XSi" do not have a number and must
+ and treated differently (see below)
Examples:
Canon EOS 300D DIGITAL -> 300D
@@ -233,6 +235,20 @@ class MetaData(pyexiv2.Image):
NIKON D2X -> D2X
NIKON D70 -> D70
X100,D540Z,C310Z -> X100
+ Canon EOS DIGITAL REBEL XSi -> XSi
+ Canon EOS Digital Rebel XS -> XS
+ Canon EOS Digital Rebel XTi -> XTi
+ Canon EOS Kiss Digital X -> Digital
+ Canon EOS Digital Rebel XT -> XT
+ EOS Kiss Digital -> Digital
+ Canon Digital IXUS Wireless -> Wireless
+ Canon Digital IXUS i zoom -> zoom
+ Canon EOS Kiss Digital N -> N
+ Canon Digital IXUS IIs -> IIs
+ IXY Digital L -> L
+ Digital IXUS i -> i
+ IXY Digital -> Digital
+ Digital IXUS -> IXUS
The optional includeCharacters allows additional characters to appear
before and after the digits.
@@ -246,8 +262,7 @@ class MetaData(pyexiv2.Image):
includeCharacters = '\-':
DSC-P92 -> DSC-P92
- If a digit is not found in the camera model, the full length camera
- model is returned.
+ If a digit is not found in the camera model, the last word is returned.
Note: assume exif values are in ENGLISH, regardless of current platform
"""
@@ -260,7 +275,8 @@ class MetaData(pyexiv2.Image):
if r:
return r.group("model")
else:
- return m.strip()
+ head, space, model = m.strip().rpartition(' ')
+ return model
else:
return missing
diff --git a/rapid/rapid.py b/rapid/rapid.py
index 0dcbb21..0511e07 100755
--- a/rapid/rapid.py
+++ b/rapid/rapid.py
@@ -146,6 +146,8 @@ class Queue(tube.Tube):
display_queue = Queue()
media_collection_treeview = image_hbox = log_dialog = None
+job_code = None
+need_job_code = False
class ThreadManager:
_workers = []
@@ -287,6 +289,11 @@ class ThreadManager:
if w.downloadStarted and not w.running:
yield w
+ def getWaitingForJobCodeWorkers(self):
+ for w in self._workers:
+ if w.waitingForJobCode:
+ yield w
+
def getFinishedWorkers(self):
for w in self._workers:
if self._isFinished(w):
@@ -364,6 +371,11 @@ class RapidPreferences(prefs.Preferences):
"day_start": prefs.Value(prefs.STRING, "03:00"),
"downloads_today": prefs.ListValue(prefs.STRING_LIST, [today(), '0']),
"stored_sequence_no": prefs.Value(prefs.INT, 0),
+ "job_codes": prefs.ListValue(prefs.STRING_LIST, [_('New York'),
+ _('Manila'), _('Prague'), _('Helsinki'), _('Wellington'),
+ _('Tehran'), _('Kampala'), _('Paris'), _('Berlin'), _('Sydney'),
+ _('Budapest'), _('Rome'), _('Moscow'), _('Delhi'), _('Warsaw'),
+ _('Jakarta'), _('Madrid'), _('Stockholm')])
}
def __init__(self):
@@ -434,7 +446,12 @@ class RapidPreferences(prefs.Preferences):
self.day_start = "0:0"
return 0, 0
-
+ def getSampleJobCode(self):
+ if self.job_codes:
+ return self.job_codes[0]
+ else:
+ return ''
+
class ImageRenameTable(tpm.TablePlusMinus):
def __init__(self, parentApp, adjustScrollWindow):
@@ -534,8 +551,6 @@ class ImageRenameTable(tpm.TablePlusMinus):
def getParentAppPrefs(self):
self.prefList = self.parentApp.prefs.image_rename
-
-
def getPrefsFactory(self):
self.prefsFactory = rn.ImageRenamePreferences(self.prefList, self,
@@ -544,6 +559,12 @@ class ImageRenameTable(tpm.TablePlusMinus):
def updateParentAppPrefs(self):
self.parentApp.prefs.image_rename = self.prefList
+ def updateExampleJobCode(self):
+ job_code = self.parentApp.prefs.getSampleJobCode()
+ if not job_code:
+ job_code = _('Job code')
+ self.prefsFactory.setJobCode(job_code)
+
def updateExample(self):
self.parentApp.updateImageRenameExample()
@@ -654,6 +675,7 @@ class PreferencesDialog(gnomeglade.Component):
self._setupDownloadFolderTab()
self._setupImageRenameTab()
self._setupRenameOptionsTab()
+ self._setupJobCodeTab()
self._setupDeviceTab()
self._setupBackupTab()
self._setupAutomationTab()
@@ -768,6 +790,8 @@ class PreferencesDialog(gnomeglade.Component):
self.updateImageRenameExample()
def _setupRenameOptionsTab(self):
+
+ # sequence numbers
self.downloads_today_entry = ValidatedEntry.ValidatedEntry(ValidatedEntry.bounded(ValidatedEntry.v_int, int, 0))
self.stored_number_entry = ValidatedEntry.ValidatedEntry(ValidatedEntry.bounded(ValidatedEntry.v_int, int, 1))
self.downloads_today_entry.connect('changed', self.on_downloads_today_entry_changed)
@@ -785,9 +809,31 @@ class PreferencesDialog(gnomeglade.Component):
self.hour_spinbutton.set_value(float(hour))
self.minute_spinbutton.set_value(float(minute))
+ #compatibility
self.strip_characters_checkbutton.set_active(
self.prefs.strip_characters)
+ def _setupJobCodeTab(self):
+ self.job_code_liststore = gtk.ListStore(str)
+ column = gtk.TreeViewColumn()
+ rentext = gtk.CellRendererText()
+ rentext.connect('edited', self.on_job_code_edited)
+ rentext .set_property('editable', True)
+
+ column.pack_start(rentext, expand=0)
+ column.set_attributes(rentext, text=0)
+ self.job_code_treeview_column = column
+ self.job_code_treeview.append_column(column)
+ self.job_code_treeview.props.model = self.job_code_liststore
+ for code in self.prefs.job_codes:
+ self.job_code_liststore.append((code, ))
+
+ # set multiple selections
+ self.job_code_treeview.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
+
+ self.clear_job_code_button.set_image(gtk.image_new_from_stock(
+ gtk.STOCK_CLEAR,
+ gtk.ICON_SIZE_BUTTON))
def _setupDeviceTab(self):
self.device_location_filechooser_button = gtk.FileChooserButton(
_("Select an image folder"))
@@ -889,6 +935,7 @@ class PreferencesDialog(gnomeglade.Component):
"""
if hasattr(self, 'rename_table'):
+ self.rename_table.updateExampleJobCode()
name, problem = self.rename_table.prefsFactory.generateNameUsingPreferences(
self.sampleImage, self.sampleImageName,
self.prefs.strip_characters, sequencesPreliminary=False)
@@ -901,7 +948,7 @@ class PreferencesDialog(gnomeglade.Component):
if problem:
text += "\n"
# Translators: please do not modify or leave out html formatting tags like <i> and <b>. These are used to format the text the users sees
- text += _("<i><b>Warning:</b> There is insufficient image metatdata to fully generate the name. Please use other renaming options.</i>")
+ text += _("<i><b>Warning:</b> There is insufficient image metadata to fully generate the name. Please use other renaming options.</i>")
self.new_name_label.set_markup(text)
@@ -911,6 +958,7 @@ class PreferencesDialog(gnomeglade.Component):
"""
if hasattr(self, 'subfolder_table'):
+ self.subfolder_table.updateExampleJobCode()
path, problem = self.subfolder_table.prefsFactory.generateNameUsingPreferences(
self.sampleImage, self.sampleImageName,
self.prefs.strip_characters)
@@ -921,7 +969,7 @@ class PreferencesDialog(gnomeglade.Component):
# since this is markup, escape it
path = common.escape(text)
if problem:
- warning = _("<i><b>Warning:</b> There is insufficient image metatdata to fully generate subfolders. Please use other subfolder naming options.</i>" )
+ warning = _("<i><b>Warning:</b> There is insufficient image metadata to fully generate subfolders. Please use other subfolder naming options.</i>" )
else:
warning = ""
# Translators: you should not modify or leave out the %s. This is a code used by the programming language python to insert a value that thes user will see
@@ -993,6 +1041,75 @@ class PreferencesDialog(gnomeglade.Component):
self.widget.destroy()
+ def on_add_job_code_button_clicked(self, button):
+ j = JobCodeDialog(self.widget, self.prefs.job_codes, None)
+ if j.run() == gtk.RESPONSE_OK:
+ self.add_job_code(j.get_job_code())
+ j.destroy()
+
+ def add_job_code(self, job_code):
+ if job_code and job_code not in self.prefs.job_codes:
+ self.job_code_liststore.prepend((job_code, ))
+ self.update_job_codes()
+ selection = self.job_code_treeview.get_selection()
+ selection.unselect_all()
+ selection.select_path((0, ))
+ #scroll to the top
+ adjustment = self.job_code_scrolledwindow.get_vadjustment()
+ adjustment.set_value(adjustment.lower)
+
+ def on_remove_job_code_button_clicked(self, button):
+ """ remove selected job codes (can be multiple selection)"""
+ selection = self.job_code_treeview.get_selection()
+ model, selected = selection.get_selected_rows()
+ iters = [model.get_iter(path) for path in selected]
+ # only delete if a jobe code is selected
+ if iters:
+ no = len(iters)
+ path = None
+ for i in range(0, no):
+ iter = iters[i]
+ if i == no - 1:
+ path = model.get_path(iter)
+ model.remove(iter)
+
+ # now that we removed the selection, play nice with
+ # the user and select the next item
+ selection.select_path(path)
+
+ # if there was no selection that meant the user
+ # removed the last entry, so we try to select the
+ # last item
+ if not selection.path_is_selected(path):
+ row = path[0]-1
+ # test case for empty lists
+ if row >= 0:
+ selection.select_path((row,))
+
+ self.update_job_codes()
+ self.updateImageRenameExample()
+ self.updateDownloadFolderExample()
+
+ def on_clear_job_code_button_clicked(self, button):
+ self.job_code_liststore.clear()
+ self.update_job_codes()
+ self.updateImageRenameExample()
+ self.updateDownloadFolderExample()
+
+ def on_job_code_edited(self, widget, path, new_text):
+ iter = self.job_code_liststore.get_iter(path)
+ self.job_code_liststore.set_value(iter, 0, new_text)
+ self.update_job_codes()
+ self.updateImageRenameExample()
+ self.updateDownloadFolderExample()
+
+ def update_job_codes(self):
+ """ update preferences with list of job codes"""
+ job_codes = []
+ for row in self.job_code_liststore:
+ job_codes.append(row[0])
+ self.prefs.job_codes = job_codes
+
def on_auto_startup_checkbutton_toggled(self, checkbutton):
self.prefs.auto_download_at_startup = checkbutton.get_active()
@@ -1150,6 +1267,7 @@ class CopyPhotos(Thread):
self.hasStarted = False
self.doNotStart = False
+ self.waitingForJobCode = False
self.autoStart = autoStart
self.cardMedia = cardMedia
@@ -1371,16 +1489,23 @@ class CopyPhotos(Thread):
skipImage = True
imageMetadata = newName = newFile = path = subfolder = None
else:
- imageMetadata.readMetadata()
- if not imageMetadata.exifKeys() and (needMetaDataToCreateUniqueSubfolderName or
- (needMetaDataToCreateUniqueImageName and
- not addUniqueIdentifier)):
- logError(config.SERIOUS_ERROR, _("Image has no metadata"),
- _("Metadata is essential for generating subfolders / image names.\nSource: %s") % image,
- IMAGE_SKIPPED)
+ try:
+ # this step can fail if the source image is corrupt
+ imageMetadata.readMetadata()
+ except:
skipImage = True
+
+ if not skipImage:
+ if not imageMetadata.exifKeys() and (needMetaDataToCreateUniqueSubfolderName or
+ (needMetaDataToCreateUniqueImageName and
+ not addUniqueIdentifier)):
+ skipImage = True
+
+ if skipImage:
+ logError(config.SERIOUS_ERROR, _("Image has no metadata"),
+ _("Metadata is essential for generating subfolders / image names.\nSource: %s") % image,
+ IMAGE_SKIPPED)
newName = newFile = path = subfolder = None
-
else:
subfolder, problem = self.subfolderPrefsFactory.generateNameUsingPreferences(
imageMetadata, name,
@@ -1650,6 +1775,14 @@ class CopyPhotos(Thread):
self.running = False
self.lock.release()
return
+ elif self.autoStart and need_job_code:
+ if job_code == None:
+ self.waitingForJobCode = True
+ display_queue.put((self.parentApp.getJobCode, ()))
+ self.running = False
+ self.lock.acquire()
+ self.running = True
+ self.waitingForJobCode = False
elif not self.autoStart:
# halt thread, waiting to be restarted so download proceeds
self.running = False
@@ -1669,10 +1802,19 @@ class CopyPhotos(Thread):
self.running = False
display_queue.close("rw")
return
-
+
+
self.downloadStarted = True
cmd_line(_("Download has started from %s") % self.cardMedia.prettyName(limit=0))
+ if need_job_code and job_code == None:
+ sys.stderr.write(str(self.thread_id ) + ": job code should never be None\n")
+ self.imageRenamePrefsFactory.setJobCode('unknown-job-code')
+ self.subfolderPrefsFactory.setJobCode('unknown-job-code')
+ else:
+ self.imageRenamePrefsFactory.setJobCode(job_code)
+ self.subfolderPrefsFactory.setJobCode(job_code)
+
# Some images may not have metadata (this
# is unlikely for images straight out of a
# camera, but it is possible for images that have been edited). If
@@ -1802,7 +1944,7 @@ class CopyPhotos(Thread):
self.lock.release()
except thread_error:
- sys.stderr.write(self.thread_id + " thread error\n")
+ sys.stderr.write(str(self.thread_id) + " thread error\n")
def quit(self):
"""
@@ -2012,6 +2154,80 @@ class ImageHBox(gtk.HBox):
adjustment.set_value(adjustment.upper)
+
+
+class JobCodeDialog(gtk.Dialog):
+ """ Dialog prompting for a job code"""
+
+ def __init__(self, parent_window, job_codes, default_job_code, postJobCodeEntryCB, autoStart):
+ gtk.Dialog.__init__(self, _('Enter a Job Code'), None,
+ gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
+ (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
+ gtk.STOCK_OK, gtk.RESPONSE_OK))
+
+
+ self.set_icon_from_file(paths.share_dir('glade3/rapid-photo-downloader-about.png'))
+ self.postJobCodeEntryCB = postJobCodeEntryCB
+ self.autoStart = autoStart
+
+ self.combobox = gtk.combo_box_entry_new_text()
+ for text in job_codes:
+ self.combobox.append_text(text)
+
+ self.job_code_hbox = gtk.HBox(False)
+
+ label = gtk.Label(_('Job Code:'))
+ self.job_code_hbox.pack_start(label, padding=6)
+ self.job_code_hbox.pack_start(self.combobox, True, True, padding=6)
+
+ self.set_border_width(6)
+ self.set_has_separator(False)
+
+ # make entry box have entry completion
+ self.entry = self.combobox.child
+
+ completion = gtk.EntryCompletion()
+ completion.set_match_func(self.match_func)
+ completion.connect("match-selected",
+ self.on_completion_match)
+ completion.set_model(self.combobox.get_model())
+ completion.set_text_column(0)
+ self.entry.set_completion(completion)
+
+ # when user hits enter, close the dialog window
+ self.set_default_response(gtk.RESPONSE_OK)
+ self.entry.set_activates_default(True)
+
+ if default_job_code:
+ self.entry.set_text(default_job_code)
+
+ self.vbox.pack_start(self.job_code_hbox, padding=12)
+
+ self.set_transient_for(parent_window)
+ self.show_all()
+ self.connect('response', self.on_job_code_resp)
+
+ def match_func(self, completion, key, iter):
+ model = completion.get_model()
+ return model[iter][0].startswith(self.entry.get_text())
+
+ def on_completion_match(self, completion, model, iter):
+ self.entry.set_text(model[iter][0])
+ self.entry.set_position(-1)
+
+ def get_job_code(self):
+ return self.combobox.child.get_text()
+
+ def on_job_code_resp(self, jc_dialog, response):
+ userChoseCode = False
+ if response == gtk.RESPONSE_OK:
+ userChoseCode = True
+ cmd_line(_("Job Code entered"))
+ else:
+ cmd_line(_("Job Code not entered - download to be cancelled"))
+ self.postJobCodeEntryCB(self, userChoseCode, self.get_job_code(), self.autoStart)
+
+
class LogDialog(gnomeglade.Component):
"""
Displays a log of errors, warnings or other information to the user
@@ -2041,11 +2257,13 @@ class LogDialog(gnomeglade.Component):
self.parentApp.error_image.show()
elif severity == config.WARNING:
self.parentApp.warning_image.show()
+ self.parentApp.warning_vseparator.show()
iter = self.textbuffer.get_end_iter()
self.textbuffer.insert_with_tags(iter, problem +"\n", self.problemTag)
- iter = self.textbuffer.get_end_iter()
- self.textbuffer.insert(iter, details + "\n")
+ if details:
+ iter = self.textbuffer.get_end_iter()
+ self.textbuffer.insert(iter, details + "\n")
if resolution:
iter = self.textbuffer.get_end_iter()
self.textbuffer.insert_with_tags(iter, resolution +"\n", self.resolutionTag)
@@ -2063,6 +2281,7 @@ class LogDialog(gnomeglade.Component):
pass
self.parentApp.error_image.hide()
self.parentApp.warning_image.hide()
+ self.parentApp.warning_vseparator.hide()
self.parentApp.prefs.show_log_dialog = False
self.widget.hide()
return True
@@ -2106,8 +2325,10 @@ class RapidApp(gnomeglade.GnomeApp, dbus.service.Object):
self._resetDownloadInfo()
self.statusbar_context_id = self.rapid_statusbar.get_context_id("progress")
+ # hide display of warning and error symbols in the taskbar until they are needed
self.error_image.hide()
self.warning_image.hide()
+ self.warning_vseparator.hide()
if not displayPreferences:
displayPreferences = not self.checkPreferencesOnStartup()
@@ -2122,6 +2343,9 @@ class RapidApp(gnomeglade.GnomeApp, dbus.service.Object):
# control sequence numbers and letters
global sequences
+
+ # whether we need to prompt for a job code
+ global need_job_code
duplicate_files = {}
@@ -2149,7 +2373,7 @@ class RapidApp(gnomeglade.GnomeApp, dbus.service.Object):
self.startVolumeMonitor()
- # set up tree view display
+ # set up tree view display to display image devices and download status
media_collection_treeview = MediaTreeView(self)
self.media_collection_vbox.pack_start(media_collection_treeview)
@@ -2173,11 +2397,16 @@ class RapidApp(gnomeglade.GnomeApp, dbus.service.Object):
# menus
-# self.menu_resequence.set_sensitive(False)
self.menu_display_thumbnails.set_active(self.prefs.display_thumbnails)
self.menu_clear.set_sensitive(False)
+ #job code initialization
+ need_job_code = self.needJobCode()
+ self.last_chosen_job_code = None
+ self.prompting_for_job_code = False
+
+ #setup download and backup mediums, initiating scans
self.setupAvailableImageAndBackupMedia(onStartup=True, onPreferenceChange=False, doNotAllowAutoStart = displayPreferences)
#adjust viewport size for displaying media
@@ -2186,6 +2415,8 @@ class RapidApp(gnomeglade.GnomeApp, dbus.service.Object):
height = self.media_collection_viewport.size_request()[1]
self.media_collection_scrolledwindow.set_size_request(-1, height)
+ self.download_button.grab_default()
+ # for some reason, the grab focus command is not working... unsure why
self.download_button.grab_focus()
if displayPreferences:
@@ -2225,6 +2456,63 @@ class RapidApp(gnomeglade.GnomeApp, dbus.service.Object):
# misc.run_dialog(title, msg)
return prefsOk
+ def needJobCode(self):
+ return rn.usesJobCode(self.prefs.image_rename) or rn.usesJobCode(self.prefs.subfolder)
+
+ def assignJobCode(self, code):
+ """ assign job code (which may be empty) to global variable and update user preferences
+
+ Update preferences only if code is not empty. Do not duplicate job code.
+ """
+ global job_code
+ if code == None:
+ code = ''
+ job_code = code
+
+ if job_code:
+ #add this value to job codes preferences
+ #delete any existing value which is the same
+ #(this way it comes to the front, which is where it should be)
+ #never modify self.prefs.job_codes in place! (or prefs become screwed up)
+
+ jcs = self.prefs.job_codes
+ while code in jcs:
+ jcs.remove(code)
+
+ self.prefs.job_codes = [code] + jcs
+
+
+
+
+ def _getJobCode(self, postJobCodeEntryCB, autoStart):
+ cmd_line(_("Prompting for Job Code"))
+ self.prompting_for_job_code = True
+ j = JobCodeDialog(self.widget, self.prefs.job_codes, self.last_chosen_job_code, postJobCodeEntryCB, autoStart)
+
+ def getJobCode(self, autoStart=True):
+ """ called from the copyphotos thread"""
+
+ self._getJobCode(self.gotJobCode, autoStart)
+
+ def gotJobCode(self, dialog, userChoseCode, code, autoStart):
+ dialog.destroy()
+ self.prompting_for_job_code = False
+
+ if userChoseCode:
+ self.assignJobCode(code)
+ self.last_chosen_job_code = code
+ if autoStart:
+ cmd_line(_("Starting downloads that have been waiting for a Job Code"))
+ for w in workers.getWaitingForJobCodeWorkers():
+ w.startStop()
+ else:
+ cmd_line(_("Starting downloads"))
+ self.startDownload()
+
+
+ # FIXME: what happens to these workers that are waiting? How will the user start their download?
+ # check if need to add code to start button
+
def checkForUpgrade(self, runningVersion):
""" Checks if the running version of the program is different from the version recorded in the preferences.
@@ -2307,7 +2595,7 @@ class RapidApp(gnomeglade.GnomeApp, dbus.service.Object):
def usingVolumeMonitor(self):
"""
- Returns True if programs needs to use gnomevfs volume monitor
+ Returns True if programs needs to use gio or gnomevfs volume monitor
"""
return (self.prefs.device_autodetection or
@@ -2573,6 +2861,7 @@ class RapidApp(gnomeglade.GnomeApp, dbus.service.Object):
self.download_button_is_download = True
self.download_button = gtk.Button()
self.download_button.set_use_underline(True)
+ self.download_button.set_flags(gtk.CAN_DEFAULT)
self._set_download_button()
self.download_button.connect('clicked', self.on_download_button_clicked)
self.download_hbutton_box.set_layout(gtk.BUTTONBOX_START)
@@ -2596,7 +2885,10 @@ class RapidApp(gnomeglade.GnomeApp, dbus.service.Object):
self.startTime = None
self.totalDownloadSize = self.totalDownloadedSoFar = 0
self.totalDownloadSizeThisRun = self.totalDownloadedSoFarThisRun = 0
- self.timeRemaining.clear()
+ # there is no need to clear self.timeRemaining, as when each thread is completed, it removes itself
+
+ global job_code
+ job_code = None
def addToTotalDownloadSize(self, size):
self.totalDownloadSize += size
@@ -2845,12 +3137,18 @@ class RapidApp(gnomeglade.GnomeApp, dbus.service.Object):
If pause, a click indicates to pause all running downloads.
"""
if self.download_button_is_download:
- self.startDownload()
+ if need_job_code and job_code == None and not self.prompting_for_job_code:
+ self.getJobCode(autoStart=False)
+ else:
+ self.startDownload()
else:
self.pauseDownload()
def on_preference_changed(self, key, value):
-
+ """
+ Called when user changes the program's preferences
+ """
+
if key == 'display_thumbnails':
self.set_display_thumbnails(value)
elif key == 'show_log_dialog':
@@ -2860,11 +3158,17 @@ class RapidApp(gnomeglade.GnomeApp, dbus.service.Object):
if self.usingVolumeMonitor():
self.startVolumeMonitor()
cmd_line("\n" + _("Preferences were changed."))
+
self.setupAvailableImageAndBackupMedia(onStartup = False, onPreferenceChange = True, doNotAllowAutoStart = False)
if is_beta and verbose:
print "Current worker status:"
workers.printWorkerStatus()
+ elif key in ['subfolder', 'image_rename']:
+ global need_job_code
+ need_job_code = self.needJobCode()
+
+
def on_error_eventbox_button_press_event(self, widget, event):
self.prefs.show_log_dialog = True
log_dialog.widget.show()
diff --git a/rapid/renamesubfolderprefs.py b/rapid/renamesubfolderprefs.py
index 9804c59..f28e068 100644
--- a/rapid/renamesubfolderprefs.py
+++ b/rapid/renamesubfolderprefs.py
@@ -85,6 +85,7 @@ TEXT = 'Text'
FILENAME = 'Filename'
METADATA = 'Metadata'
SEQUENCES = 'Sequences'
+JOB_CODE = 'Job code'
SEPARATOR = os.sep
@@ -161,7 +162,8 @@ LIST_DATE_TIME_L2 = ['YYYYMMDD', 'YYYY-MM-DD','YYMMDD', 'YY-MM-DD',
'MMDDYYYY', 'MMDDYY', 'MMDD',
'DDMMYYYY', 'DDMMYY', 'YYYY', 'YY',
'MM', 'DD',
- 'HHMMSS', 'HHMM']
+ 'HHMMSS', 'HHMM', 'HH-MM-SS', 'HH-MM', 'HH', 'MM', 'SS']
+
LIST_IMAGE_DATE_TIME_L2 = LIST_DATE_TIME_L2 + [SUBSECONDS]
@@ -178,6 +180,8 @@ class i18TranslateMeThanks:
_('Filename')
_('Metadata')
_('Sequences')
+ # Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#jobcode
+ _('Job code')
_('Image date')
_('Today')
_('Yesterday')
@@ -275,7 +279,17 @@ class i18TranslateMeThanks:
_('HHMMSS')
# Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#renamedateandtime
_('HHMM')
-
+ # Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#renamedateandtime
+ _('HH-MM-SS')
+ # Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#renamedateandtime
+ _('HH-MM')
+ # Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#renamedateandtime
+ _('HH')
+ # Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#renamedateandtime
+ _('MM')
+ # Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#renamedateandtime
+ _('SS')
+
# Convenience values for python datetime conversion using values in
# LIST_DATE_TIME_L2. Obviously the two must remain synchronized.
@@ -284,7 +298,8 @@ DATE_TIME_CONVERT = ['%Y%m%d', '%Y-%m-%d','%y%m%d', '%y-%m-%d',
'%m%d%Y', '%m%d%y', '%m%d',
'%d%m%Y', '%d%m%y', '%Y', '%y',
'%m', '%d',
- '%H%M%S', '%H%M']
+ '%H%M%S', '%H%M', '%H-%M-%S', '%H-%M',
+ '%H', '%M', '%S']
LIST_IMAGE_NUMBER_L2 = [IMAGE_NUMBER_ALL, IMAGE_NUMBER_1, IMAGE_NUMBER_2,
@@ -392,7 +407,7 @@ DICT_SEQUENCE_L1 = {
LIST_IMAGE_RENAME_L0 = [DATE_TIME, TEXT, FILENAME, METADATA,
- SEQUENCES]
+ SEQUENCES, JOB_CODE]
DICT_IMAGE_RENAME_L0 = {
@@ -401,16 +416,18 @@ DICT_IMAGE_RENAME_L0 = {
FILENAME: DICT_FILENAME_L1,
METADATA: DICT_METADATA_L1,
SEQUENCES: DICT_SEQUENCE_L1,
+ JOB_CODE: None,
ORDER_KEY: LIST_IMAGE_RENAME_L0
}
-LIST_SUBFOLDER_L0 = [DATE_TIME, TEXT, FILENAME, METADATA, SEPARATOR]
+LIST_SUBFOLDER_L0 = [DATE_TIME, TEXT, FILENAME, METADATA, JOB_CODE, SEPARATOR]
DICT_SUBFOLDER_L0 = {
DATE_TIME: DICT_DATE_TIME_L1,
TEXT: None,
FILENAME: DICT_SUBFOLDER_FILENAME_L1,
METADATA: DICT_METADATA_L1,
+ JOB_CODE: None,
SEPARATOR: None,
ORDER_KEY: LIST_SUBFOLDER_L0
}
@@ -556,6 +573,14 @@ def upgradePreferencesToCurrent(imageRenamePrefs, subfolderPrefs, previousVers
# only check image rename, for now....
upgraded, imageRenamePrefs = _upgradePreferencesToCurrent(imageRenamePrefs, previousVersion)
return (upgraded, imageRenamePrefs , subfolderPrefs)
+
+
+def usesJobCode(prefs):
+ """ Returns True if the preferences contain a job code, else returns False"""
+ for i in range(0, len(prefs), 3):
+ if prefs[i] == JOB_CODE:
+ return True
+ return False
def checkPreferencesForValidity(imageRenamePrefs, subfolderPrefs, version=config.version):
"""Returns true if the passed in preferences are valid"""
@@ -748,6 +773,8 @@ class ImageRenamePreferences:
self.fileSequenceLock = fileSequenceLock
self.sequences = sequences
+
+ self.job_code = ''
# derived classes will have their own definitions, do not overwrite
if not hasattr(self, "prefsDefnL0"):
@@ -771,7 +798,12 @@ class ImageRenamePreferences:
v = ''
for i in range(0, len(self.prefList), 3):
- s = "%s: " % self.prefList[i]
+ if (self.prefList[i+1] or self.prefList[i+2]):
+ c = ':'
+ else:
+ c = ''
+ s = "%s%s " % (self.prefList[i], c)
+
if self.prefList[i+1]:
s = "%s%s" % (s, self.prefList[i+1])
if self.prefList[i+2]:
@@ -781,6 +813,9 @@ class ImageRenamePreferences:
return v
+ def setJobCode(self, job_code):
+ self.job_code = job_code
+
def _getDateComponent(self):
"""
Returns portion of new image / subfolder name based on date time
@@ -804,6 +839,23 @@ class ImageRenamePreferences:
if d:
if self.L2 <> SUBSECONDS:
+
+ if type(d) == type('string'):
+ # will be a string only if the date time could not be converted in the datetime type
+ # try to massage badly formed date / times into a valid value
+ _datetime = d.strip()
+ # remove any weird characters at the end of the string
+ while _datetime and not _datetime[-1].isdigit():
+ _datetime = _datetime[:-1]
+ _date, _time = _datetime.split(' ')
+ _datetime = "%s %s" % (_date.replace(":", "-") , _time.replace("-", ":"))
+ try:
+ d = datetime.datetime.strptime(_datetime, '%Y-%m-%d %H:%M:%S')
+ except:
+ v = ''
+ problem = _('Error in date time component. Value %s appears invalid') % ''
+ return (v, problem)
+
try:
return (d.strftime(convertDateForStrftime(self.L2)), None)
except:
@@ -1011,7 +1063,7 @@ class ImageRenamePreferences:
return self._getStoredSequenceNo()
elif self.L1 == SEQUENCE_LETTER:
return self._getSequenceLetter()
-
+
def _getComponent(self):
try:
if self.L0 == DATE_TIME:
@@ -1024,6 +1076,8 @@ class ImageRenamePreferences:
return self._getMetadataComponent()
elif self.L0 == SEQUENCES:
return self._getSequencesComponent()
+ elif self.L0 == JOB_CODE:
+ return (self.job_code, None)
elif self.L0 == SEPARATOR:
return (os.sep, None)
except:
@@ -1197,7 +1251,7 @@ class ImageRenamePreferences:
widgets.append(widget1)
widgets.append(None)
return
- elif key == SEPARATOR:
+ elif key in [SEPARATOR, JOB_CODE]:
widgets.append(None)
widgets.append(None)
return