Browse Source

Release: d4bf62e70e

master
Dirk Alders 4 years ago
parent
commit
aa39140bc8
7 changed files with 4801 additions and 744 deletions
  1. 156
    8
      __init__.py
  2. 249
    117
      _testresults_/coverage.xml
  3. 4296
    532
      _testresults_/unittest.json
  4. BIN
      _testresults_/unittest.pdf
  5. 19
    0
      common.py
  6. 35
    0
      convert.py
  7. 46
    87
      metadata.py

+ 156
- 8
__init__.py View File

@@ -16,6 +16,7 @@ media (Media Tools)
16 16
 **Submodules:**
17 17
 
18 18
 * :func:`media.get_media_data`
19
+* :class:`media.image`
19 20
 
20 21
 **Unittest:**
21 22
 
@@ -24,7 +25,7 @@ media (Media Tools)
24 25
 __DEPENDENCIES__ = []
25 26
 
26 27
 import logging
27
-from media import metadata
28
+from PIL import Image, ImageEnhance
28 29
 
29 30
 logger_name = 'MEDIA'
30 31
 logger = logging.getLogger(logger_name)
@@ -37,14 +38,161 @@ __INTERPRETER__ = (3, )
37 38
 """The Tested Interpreter-Versions"""
38 39
 
39 40
 
41
+KEY_ALBUM = 'album'
42
+KEY_APERTURE = 'aperture'
43
+KEY_ARTIST = 'artist'
44
+KEY_BITRATE = 'bitrate'
45
+KEY_CAMERA = 'camera'
46
+KEY_DURATION = 'duration'
47
+KEY_EXPOSURE_PROGRAM = 'exposure_program'
48
+KEY_EXPOSURE_TIME = 'exposure_time'
49
+KEY_FLASH = 'flash'
50
+KEY_FOCAL_LENGTH = 'focal_length'
51
+KEY_GENRE = 'genre'
52
+KEY_GPS = 'gps'
53
+KEY_HEIGHT = 'height'
54
+KEY_ISO = 'iso'
55
+KEY_ORIENTATION = 'orientation'
56
+KEY_RATIO = 'ratio'
57
+KEY_SIZE = 'size'
58
+KEY_TIME = 'time'   # USE time.localtime(value) or datetime.fromtimestamp(value) to convert the timestamp
59
+KEY_TIME_IS_SUBSTITUTION = 'tm_is_subst'
60
+KEY_TITLE = 'title'
61
+KEY_TRACK = 'track'
62
+KEY_WIDTH = 'width'
63
+KEY_YEAR = 'year'
64
+
65
+
40 66
 def get_media_data(full_path):
41
-    ft = metadata.get_filetype(full_path)
67
+    from media.metadata import get_audio_data, get_image_data, get_video_data
68
+    from media.common import get_filetype, FILETYPE_AUDIO, FILETYPE_IMAGE, FILETYPE_VIDEO
69
+    #
70
+    ft = get_filetype(full_path)
42 71
     #
43
-    if ft == metadata.FILETYPE_AUDIO:
44
-        return metadata.get_audio_data(full_path)
45
-    elif ft == metadata.FILETYPE_IMAGE:
46
-        return metadata.get_image_data(full_path)
47
-    elif ft == metadata.FILETYPE_VIDEO:
48
-        return metadata.get_video_data(full_path)
72
+    if ft == FILETYPE_AUDIO:
73
+        return get_audio_data(full_path)
74
+    elif ft == FILETYPE_IMAGE:
75
+        return get_image_data(full_path)
76
+    elif ft == FILETYPE_VIDEO:
77
+        return get_video_data(full_path)
49 78
     else:
50 79
         logger.warning('Filetype not known: %s', full_path)
80
+
81
+
82
+ORIENTATION_NORMAL = 1
83
+ORIENTATION_VERTICAL_MIRRORED = 2
84
+ORIENTATION_HALF_ROTATED = 3
85
+ORIENTATION_HORIZONTAL_MIRRORED = 4
86
+ORIENTATION_LEFT_ROTATED = 6
87
+ORIENTATION_RIGHT_ROTATED = 8
88
+
89
+JOIN_TOP_LEFT = 1
90
+JOIN_TOP_RIGHT = 2
91
+JOIN_BOT_LEFT = 3
92
+JOIN_BOT_RIGHT = 4
93
+JOIN_CENTER = 5
94
+
95
+
96
+class image(object):
97
+    def __init__(self, media_instance=None):
98
+        if media_instance is not None:
99
+            self.load_from_file(media_instance)
100
+        else:
101
+            self._im = None
102
+
103
+    def load_from_file(self, media_instance):
104
+        from media.convert import get_pil_image
105
+        #
106
+        self._im = get_pil_image(media_instance)
107
+        if self._im is None:
108
+            return False
109
+        return True
110
+
111
+    def save(self, full_path):
112
+        if self._im is None:
113
+            logger.warning('No image available to be saved (%s)', repr(full_path))
114
+            return False
115
+        else:
116
+            logger.debug('Saving image to %s', repr(full_path))
117
+            with open(full_path, 'w') as fh:
118
+                im = self._im.convert('RGB')
119
+                im.save(fh, 'JPEG')
120
+        return True
121
+
122
+    def resize(self, max_size):
123
+        if self._im is None:
124
+            logger.warning('No image available to be resized')
125
+            return False
126
+        else:
127
+            logger.debug('Resizing picture to max %d pixel in whatever direction', max_size)
128
+            x, y = self._im.size
129
+            xy_max = max(x, y)
130
+            self._im = self._im.resize((int(x * float(max_size) / xy_max), int(y * float(max_size) / xy_max)), Image.NEAREST).rotate(0)
131
+        return True
132
+
133
+    def rotate_by_orientation(self, orientation):
134
+        if self._im is None:
135
+            logger.warning('No image available, rotation not possible')
136
+            return False
137
+
138
+        if orientation == ORIENTATION_HALF_ROTATED:
139
+            angle = 180
140
+        elif orientation == ORIENTATION_LEFT_ROTATED:
141
+            angle = 270
142
+        elif orientation == ORIENTATION_RIGHT_ROTATED:
143
+            angle = 90
144
+        else:
145
+            logger.warning('Orientation %s unknown for rotation', repr(orientation))
146
+            return False
147
+        logger.debug('Rotating picture by %d°', angle)
148
+        self._im = self._im.rotate(angle, expand=True)
149
+        return True
150
+
151
+    def join(self, join_image, join_pos=JOIN_TOP_RIGHT, opacity=0.7):
152
+        from media.convert import get_pil_image
153
+
154
+        def rgba_copy(im):
155
+            if im.mode != 'RGBA':
156
+                return im.convert('RGBA')
157
+            else:
158
+                return im.copy()
159
+
160
+        if self._im is None:
161
+            logger.warning('No image available, joining not possible')
162
+            return False
163
+
164
+        # ensure type of join_image is PIL.Image
165
+        join_image = get_pil_image(join_image)
166
+        if join_image is None:
167
+            logger.warning('Image to be joined is not supported %s', repr(join_image))
168
+            return False
169
+
170
+        im2 = rgba_copy(join_image)
171
+        # change opacity of im2
172
+        alpha = im2.split()[3]
173
+        alpha = ImageEnhance.Brightness(alpha).enhance(opacity)
174
+        im2.putalpha(alpha)
175
+
176
+        self._im = rgba_copy(self._im)
177
+
178
+        # create a transparent layer
179
+        layer = Image.new('RGBA', self._im.size, (0, 0, 0, 0))
180
+        # draw im2 in layer
181
+        if join_pos == JOIN_TOP_LEFT:
182
+            layer.paste(im2, (0, 0))
183
+        elif join_pos == JOIN_TOP_RIGHT:
184
+            layer.paste(im2, ((self._im.size[0] - im2.size[0]), 0))
185
+        elif join_pos == JOIN_BOT_LEFT:
186
+            layer.paste(im2, (0, (self._im.size[1] - im2.size[1])))
187
+        elif join_pos == JOIN_BOT_RIGHT:
188
+            layer.paste(im2, ((self._im.size[0] - im2.size[0]), (self._im.size[1] - im2.size[1])))
189
+        elif join_pos == JOIN_CENTER:
190
+            layer.paste(im2, (int((self._im.size[0] - im2.size[0]) / 2), int((self._im.size[1] - im2.size[1]) / 2)))
191
+        else:
192
+            logger.warning("Join position value %s is not supported", join_pos)
193
+            return False
194
+
195
+        logger.debug('Joining two images')
196
+        self._im = Image.composite(layer, self._im, layer)
197
+
198
+        return True

+ 249
- 117
_testresults_/coverage.xml View File

@@ -1,34 +1,168 @@
1 1
 <?xml version="1.0" ?>
2
-<coverage branch-rate="0.9762" branches-covered="41" branches-valid="42" complexity="0" line-rate="0.9857" lines-covered="207" lines-valid="210" timestamp="1580454618005" version="4.5">
2
+<coverage branch-rate="0.9643" branches-covered="81" branches-valid="84" complexity="0" line-rate="0.9759" lines-covered="324" lines-valid="332" timestamp="1580584174451" version="4.5">
3 3
 	<!-- Generated by coverage.py: https://coverage.readthedocs.io -->
4 4
 	<!-- Based on https://raw.githubusercontent.com/cobertura/web/master/htdocs/xml/coverage-04.dtd -->
5 5
 	<sources/>
6 6
 	<packages>
7
-		<package branch-rate="0.9762" complexity="0" line-rate="0.9857" name=".user_data.data.dirk.prj.unittest.media.pylibs.media">
7
+		<package branch-rate="0.9643" complexity="0" line-rate="0.9759" name=".user_data.data.dirk.prj.unittest.media.pylibs.media">
8 8
 			<classes>
9
-				<class branch-rate="1" complexity="0" filename="/user_data/data/dirk/prj/unittest/media/pylibs/media/__init__.py" line-rate="1" name="__init__.py">
9
+				<class branch-rate="0.9737" complexity="0" filename="/user_data/data/dirk/prj/unittest/media/pylibs/media/__init__.py" line-rate="0.9924" name="__init__.py">
10 10
 					<methods/>
11 11
 					<lines>
12 12
 						<line hits="1" number="4"/>
13
-						<line hits="1" number="24"/>
14
-						<line hits="1" number="26"/>
13
+						<line hits="1" number="25"/>
15 14
 						<line hits="1" number="27"/>
16
-						<line hits="1" number="29"/>
15
+						<line hits="1" number="28"/>
17 16
 						<line hits="1" number="30"/>
18
-						<line hits="1" number="33"/>
19
-						<line hits="1" number="36"/>
20
-						<line hits="1" number="40"/>
17
+						<line hits="1" number="31"/>
18
+						<line hits="1" number="34"/>
19
+						<line hits="1" number="37"/>
21 20
 						<line hits="1" number="41"/>
22
-						<line branch="true" condition-coverage="100% (2/2)" hits="1" number="43"/>
21
+						<line hits="1" number="42"/>
22
+						<line hits="1" number="43"/>
23 23
 						<line hits="1" number="44"/>
24
-						<line branch="true" condition-coverage="100% (2/2)" hits="1" number="45"/>
24
+						<line hits="1" number="45"/>
25 25
 						<line hits="1" number="46"/>
26
-						<line branch="true" condition-coverage="100% (2/2)" hits="1" number="47"/>
26
+						<line hits="1" number="47"/>
27 27
 						<line hits="1" number="48"/>
28
+						<line hits="1" number="49"/>
28 29
 						<line hits="1" number="50"/>
30
+						<line hits="1" number="51"/>
31
+						<line hits="1" number="52"/>
32
+						<line hits="1" number="53"/>
33
+						<line hits="1" number="54"/>
34
+						<line hits="1" number="55"/>
35
+						<line hits="1" number="56"/>
36
+						<line hits="1" number="57"/>
37
+						<line hits="1" number="58"/>
38
+						<line hits="1" number="59"/>
39
+						<line hits="1" number="60"/>
40
+						<line hits="1" number="61"/>
41
+						<line hits="1" number="62"/>
42
+						<line hits="1" number="63"/>
43
+						<line hits="1" number="66"/>
44
+						<line hits="1" number="67"/>
45
+						<line hits="1" number="68"/>
46
+						<line hits="1" number="70"/>
47
+						<line branch="true" condition-coverage="100% (2/2)" hits="1" number="72"/>
48
+						<line hits="1" number="73"/>
49
+						<line branch="true" condition-coverage="100% (2/2)" hits="1" number="74"/>
50
+						<line hits="1" number="75"/>
51
+						<line branch="true" condition-coverage="100% (2/2)" hits="1" number="76"/>
52
+						<line hits="1" number="77"/>
53
+						<line hits="1" number="79"/>
54
+						<line hits="1" number="82"/>
55
+						<line hits="1" number="83"/>
56
+						<line hits="1" number="84"/>
57
+						<line hits="1" number="85"/>
58
+						<line hits="1" number="86"/>
59
+						<line hits="1" number="87"/>
60
+						<line hits="1" number="89"/>
61
+						<line hits="1" number="90"/>
62
+						<line hits="1" number="91"/>
63
+						<line hits="1" number="92"/>
64
+						<line hits="1" number="93"/>
65
+						<line hits="1" number="96"/>
66
+						<line hits="1" number="97"/>
67
+						<line branch="true" condition-coverage="100% (2/2)" hits="1" number="98"/>
68
+						<line hits="1" number="99"/>
69
+						<line hits="1" number="101"/>
70
+						<line hits="1" number="103"/>
71
+						<line hits="1" number="104"/>
72
+						<line hits="1" number="106"/>
73
+						<line branch="true" condition-coverage="100% (2/2)" hits="1" number="107"/>
74
+						<line hits="1" number="108"/>
75
+						<line hits="1" number="109"/>
76
+						<line hits="1" number="111"/>
77
+						<line branch="true" condition-coverage="100% (2/2)" hits="1" number="112"/>
78
+						<line hits="1" number="113"/>
79
+						<line hits="1" number="114"/>
80
+						<line hits="1" number="116"/>
81
+						<line hits="1" number="117"/>
82
+						<line hits="1" number="118"/>
83
+						<line hits="1" number="119"/>
84
+						<line hits="1" number="120"/>
85
+						<line hits="1" number="122"/>
86
+						<line branch="true" condition-coverage="100% (2/2)" hits="1" number="123"/>
87
+						<line hits="1" number="124"/>
88
+						<line hits="1" number="125"/>
89
+						<line hits="1" number="127"/>
90
+						<line hits="1" number="128"/>
91
+						<line hits="1" number="129"/>
92
+						<line hits="1" number="130"/>
93
+						<line hits="1" number="131"/>
94
+						<line hits="1" number="133"/>
95
+						<line branch="true" condition-coverage="100% (2/2)" hits="1" number="134"/>
96
+						<line hits="1" number="135"/>
97
+						<line hits="1" number="136"/>
98
+						<line branch="true" condition-coverage="100% (2/2)" hits="1" number="138"/>
99
+						<line hits="1" number="139"/>
100
+						<line branch="true" condition-coverage="100% (2/2)" hits="1" number="140"/>
101
+						<line hits="1" number="141"/>
102
+						<line branch="true" condition-coverage="100% (2/2)" hits="1" number="142"/>
103
+						<line hits="1" number="143"/>
104
+						<line hits="1" number="145"/>
105
+						<line hits="1" number="146"/>
106
+						<line hits="1" number="147"/>
107
+						<line hits="1" number="148"/>
108
+						<line hits="1" number="149"/>
109
+						<line hits="1" number="151"/>
110
+						<line hits="1" number="152"/>
111
+						<line hits="1" number="154"/>
112
+						<line branch="true" condition-coverage="50% (1/2)" hits="1" missing-branches="158" number="155"/>
113
+						<line hits="1" number="156"/>
114
+						<line hits="0" number="158"/>
115
+						<line branch="true" condition-coverage="100% (2/2)" hits="1" number="160"/>
116
+						<line hits="1" number="161"/>
117
+						<line hits="1" number="162"/>
118
+						<line hits="1" number="165"/>
119
+						<line branch="true" condition-coverage="100% (2/2)" hits="1" number="166"/>
120
+						<line hits="1" number="167"/>
121
+						<line hits="1" number="168"/>
122
+						<line hits="1" number="170"/>
123
+						<line hits="1" number="172"/>
124
+						<line hits="1" number="173"/>
125
+						<line hits="1" number="174"/>
126
+						<line hits="1" number="176"/>
127
+						<line hits="1" number="179"/>
128
+						<line branch="true" condition-coverage="100% (2/2)" hits="1" number="181"/>
129
+						<line hits="1" number="182"/>
130
+						<line branch="true" condition-coverage="100% (2/2)" hits="1" number="183"/>
131
+						<line hits="1" number="184"/>
132
+						<line branch="true" condition-coverage="100% (2/2)" hits="1" number="185"/>
133
+						<line hits="1" number="186"/>
134
+						<line branch="true" condition-coverage="100% (2/2)" hits="1" number="187"/>
135
+						<line hits="1" number="188"/>
136
+						<line branch="true" condition-coverage="100% (2/2)" hits="1" number="189"/>
137
+						<line hits="1" number="190"/>
138
+						<line hits="1" number="192"/>
139
+						<line hits="1" number="193"/>
140
+						<line hits="1" number="195"/>
141
+						<line hits="1" number="196"/>
142
+						<line hits="1" number="198"/>
29 143
 					</lines>
30 144
 				</class>
31
-				<class branch-rate="0.9722" complexity="0" filename="/user_data/data/dirk/prj/unittest/media/pylibs/media/metadata.py" line-rate="0.9845" name="metadata.py">
145
+				<class branch-rate="1" complexity="0" filename="/user_data/data/dirk/prj/unittest/media/pylibs/media/common.py" line-rate="1" name="common.py">
146
+					<methods/>
147
+					<lines>
148
+						<line hits="1" number="1"/>
149
+						<line hits="1" number="3"/>
150
+						<line hits="1" number="4"/>
151
+						<line hits="1" number="5"/>
152
+						<line hits="1" number="7"/>
153
+						<line hits="1" number="8"/>
154
+						<line hits="1" number="9"/>
155
+						<line hits="1" number="12"/>
156
+						<line hits="1" number="13"/>
157
+						<line branch="true" condition-coverage="100% (2/2)" hits="1" number="14"/>
158
+						<line hits="1" number="15"/>
159
+						<line branch="true" condition-coverage="100% (2/2)" hits="1" number="16"/>
160
+						<line hits="1" number="17"/>
161
+						<line branch="true" condition-coverage="100% (2/2)" hits="1" number="18"/>
162
+						<line hits="1" number="19"/>
163
+					</lines>
164
+				</class>
165
+				<class branch-rate="0.9" complexity="0" filename="/user_data/data/dirk/prj/unittest/media/pylibs/media/convert.py" line-rate="0.8667" name="convert.py">
32 166
 					<methods/>
33 167
 					<lines>
34 168
 						<line hits="1" number="1"/>
@@ -38,12 +172,46 @@
38 172
 						<line hits="1" number="5"/>
39 173
 						<line hits="1" number="8"/>
40 174
 						<line hits="1" number="9"/>
175
+						<line hits="1" number="10"/>
41 176
 						<line hits="1" number="11"/>
42 177
 						<line hits="1" number="12"/>
43
-						<line hits="1" number="13"/>
178
+						<line branch="true" condition-coverage="100% (2/2)" hits="1" number="14"/>
44 179
 						<line hits="1" number="15"/>
180
+						<line branch="true" condition-coverage="100% (2/2)" hits="1" number="16"/>
181
+						<line hits="1" number="17"/>
182
+						<line branch="true" condition-coverage="100% (2/2)" hits="1" number="18"/>
183
+						<line branch="true" condition-coverage="50% (1/2)" hits="1" missing-branches="22" number="19"/>
184
+						<line hits="1" number="20"/>
185
+						<line hits="0" number="22"/>
186
+						<line hits="1" number="23"/>
187
+						<line hits="1" number="24"/>
188
+						<line hits="0" number="25"/>
189
+						<line hits="0" number="26"/>
190
+						<line hits="0" number="27"/>
191
+						<line hits="1" number="28"/>
192
+						<line hits="1" number="29"/>
193
+						<line hits="1" number="30"/>
194
+						<line hits="1" number="31"/>
195
+						<line branch="true" condition-coverage="100% (2/2)" hits="1" number="32"/>
196
+						<line hits="1" number="33"/>
197
+						<line hits="1" number="35"/>
198
+					</lines>
199
+				</class>
200
+				<class branch-rate="0.9667" complexity="0" filename="/user_data/data/dirk/prj/unittest/media/pylibs/media/metadata.py" line-rate="0.9808" name="metadata.py">
201
+					<methods/>
202
+					<lines>
203
+						<line hits="1" number="1"/>
204
+						<line hits="1" number="2"/>
205
+						<line hits="1" number="3"/>
206
+						<line hits="1" number="4"/>
207
+						<line hits="1" number="5"/>
208
+						<line hits="1" number="6"/>
209
+						<line hits="1" number="9"/>
210
+						<line hits="1" number="12"/>
211
+						<line hits="1" number="13"/>
45 212
 						<line hits="1" number="16"/>
46 213
 						<line hits="1" number="17"/>
214
+						<line hits="1" number="18"/>
47 215
 						<line hits="1" number="19"/>
48 216
 						<line hits="1" number="20"/>
49 217
 						<line hits="1" number="21"/>
@@ -58,8 +226,6 @@
58 226
 						<line hits="1" number="30"/>
59 227
 						<line hits="1" number="31"/>
60 228
 						<line hits="1" number="32"/>
61
-						<line hits="1" number="33"/>
62
-						<line hits="1" number="34"/>
63 229
 						<line hits="1" number="35"/>
64 230
 						<line hits="1" number="36"/>
65 231
 						<line hits="1" number="37"/>
@@ -67,163 +233,129 @@
67 233
 						<line hits="1" number="39"/>
68 234
 						<line hits="1" number="40"/>
69 235
 						<line hits="1" number="41"/>
236
+						<line hits="1" number="42"/>
70 237
 						<line hits="1" number="43"/>
71 238
 						<line hits="1" number="44"/>
72 239
 						<line hits="1" number="47"/>
73 240
 						<line hits="1" number="48"/>
74
-						<line branch="true" condition-coverage="100% (2/2)" hits="1" number="49"/>
75
-						<line hits="1" number="50"/>
76
-						<line branch="true" condition-coverage="100% (2/2)" hits="1" number="51"/>
241
+						<line hits="1" number="51"/>
77 242
 						<line hits="1" number="52"/>
78
-						<line branch="true" condition-coverage="100% (2/2)" hits="1" number="53"/>
79
-						<line hits="1" number="54"/>
243
+						<line branch="true" condition-coverage="100% (2/2)" hits="1" number="54"/>
244
+						<line hits="1" number="55"/>
245
+						<line hits="1" number="56"/>
80 246
 						<line hits="1" number="57"/>
81
-						<line hits="1" number="58"/>
82
-						<line hits="1" number="59"/>
83
-						<line hits="1" number="60"/>
84
-						<line hits="1" number="61"/>
85
-						<line hits="1" number="62"/>
247
+						<line branch="true" condition-coverage="100% (2/2)" hits="1" number="59"/>
248
+						<line branch="true" condition-coverage="100% (2/2)" hits="1" number="60"/>
249
+						<line branch="true" condition-coverage="100% (2/2)" hits="1" number="61"/>
86 250
 						<line hits="1" number="63"/>
87 251
 						<line hits="1" number="64"/>
88
-						<line hits="1" number="65"/>
89 252
 						<line hits="1" number="66"/>
90 253
 						<line hits="1" number="67"/>
91
-						<line hits="1" number="68"/>
92 254
 						<line hits="1" number="69"/>
93 255
 						<line hits="1" number="70"/>
94 256
 						<line hits="1" number="71"/>
95
-						<line hits="1" number="72"/>
96
-						<line hits="1" number="73"/>
257
+						<line hits="1" number="74"/>
258
+						<line hits="1" number="75"/>
97 259
 						<line hits="1" number="76"/>
98
-						<line hits="1" number="77"/>
99 260
 						<line hits="1" number="78"/>
100 261
 						<line hits="1" number="79"/>
101
-						<line hits="1" number="80"/>
102 262
 						<line hits="1" number="81"/>
103 263
 						<line hits="1" number="82"/>
104 264
 						<line hits="1" number="83"/>
105 265
 						<line hits="1" number="84"/>
106 266
 						<line hits="1" number="85"/>
107
-						<line hits="1" number="88"/>
108
-						<line hits="1" number="89"/>
267
+						<line hits="0" number="86"/>
268
+						<line hits="0" number="87"/>
269
+						<line hits="0" number="88"/>
270
+						<line hits="1" number="90"/>
271
+						<line branch="true" condition-coverage="100% (2/2)" hits="1" number="91"/>
109 272
 						<line hits="1" number="92"/>
110
-						<line hits="1" number="93"/>
111
-						<line branch="true" condition-coverage="100% (2/2)" hits="1" number="95"/>
112
-						<line hits="1" number="96"/>
113
-						<line hits="1" number="97"/>
273
+						<line branch="true" condition-coverage="100% (2/2)" hits="1" number="93"/>
274
+						<line hits="1" number="94"/>
275
+						<line hits="1" number="95"/>
276
+						<line branch="true" condition-coverage="100% (2/2)" hits="1" number="97"/>
114 277
 						<line hits="1" number="98"/>
115
-						<line branch="true" condition-coverage="100% (2/2)" hits="1" number="100"/>
116
-						<line branch="true" condition-coverage="100% (2/2)" hits="1" number="101"/>
117
-						<line branch="true" condition-coverage="100% (2/2)" hits="1" number="102"/>
118
-						<line hits="1" number="104"/>
119
-						<line hits="1" number="105"/>
278
+						<line hits="1" number="99"/>
279
+						<line hits="1" number="100"/>
280
+						<line hits="1" number="101"/>
281
+						<line hits="1" number="102"/>
282
+						<line hits="1" number="103"/>
283
+						<line hits="1" number="106"/>
120 284
 						<line hits="1" number="107"/>
121 285
 						<line hits="1" number="108"/>
286
+						<line hits="1" number="109"/>
122 287
 						<line hits="1" number="110"/>
123 288
 						<line hits="1" number="111"/>
124 289
 						<line hits="1" number="112"/>
125
-						<line hits="1" number="115"/>
290
+						<line hits="1" number="114"/>
126 291
 						<line hits="1" number="116"/>
127 292
 						<line hits="1" number="117"/>
293
+						<line hits="1" number="118"/>
128 294
 						<line hits="1" number="119"/>
129 295
 						<line hits="1" number="120"/>
296
+						<line hits="1" number="121"/>
130 297
 						<line hits="1" number="122"/>
131 298
 						<line hits="1" number="123"/>
132 299
 						<line hits="1" number="124"/>
133 300
 						<line hits="1" number="125"/>
134 301
 						<line hits="1" number="126"/>
135
-						<line hits="0" number="127"/>
136
-						<line hits="0" number="128"/>
137
-						<line hits="0" number="129"/>
302
+						<line hits="1" number="127"/>
303
+						<line hits="1" number="128"/>
304
+						<line branch="true" condition-coverage="100% (2/2)" hits="1" number="129"/>
305
+						<line branch="true" condition-coverage="100% (2/2)" hits="1" number="130"/>
138 306
 						<line hits="1" number="131"/>
139
-						<line branch="true" condition-coverage="100% (2/2)" hits="1" number="132"/>
140
-						<line hits="1" number="133"/>
141
-						<line branch="true" condition-coverage="100% (2/2)" hits="1" number="134"/>
307
+						<line hits="1" number="132"/>
308
+						<line branch="true" condition-coverage="100% (2/2)" hits="1" number="133"/>
309
+						<line hits="1" number="134"/>
142 310
 						<line hits="1" number="135"/>
143
-						<line hits="1" number="136"/>
144
-						<line branch="true" condition-coverage="100% (2/2)" hits="1" number="138"/>
145 311
 						<line hits="1" number="139"/>
146 312
 						<line hits="1" number="140"/>
147 313
 						<line hits="1" number="141"/>
148
-						<line hits="1" number="142"/>
149
-						<line hits="1" number="143"/>
150 314
 						<line hits="1" number="144"/>
315
+						<line hits="1" number="145"/>
316
+						<line hits="1" number="146"/>
151 317
 						<line hits="1" number="147"/>
152 318
 						<line hits="1" number="148"/>
153 319
 						<line hits="1" number="149"/>
154
-						<line hits="1" number="150"/>
320
+						<line branch="true" condition-coverage="100% (2/2)" hits="1" number="150"/>
155 321
 						<line hits="1" number="151"/>
156 322
 						<line hits="1" number="152"/>
157 323
 						<line hits="1" number="153"/>
158
-						<line hits="1" number="155"/>
324
+						<line hits="1" number="156"/>
159 325
 						<line hits="1" number="157"/>
160
-						<line hits="1" number="158"/>
161
-						<line hits="1" number="159"/>
162
-						<line hits="1" number="160"/>
163
-						<line hits="1" number="161"/>
164
-						<line hits="1" number="162"/>
165
-						<line hits="1" number="163"/>
166
-						<line hits="1" number="164"/>
167
-						<line hits="1" number="165"/>
168
-						<line hits="1" number="166"/>
169
-						<line hits="1" number="167"/>
170
-						<line hits="1" number="168"/>
171
-						<line hits="1" number="169"/>
172
-						<line branch="true" condition-coverage="100% (2/2)" hits="1" number="170"/>
173
-						<line branch="true" condition-coverage="100% (2/2)" hits="1" number="171"/>
174
-						<line hits="1" number="172"/>
175
-						<line hits="1" number="173"/>
176
-						<line branch="true" condition-coverage="100% (2/2)" hits="1" number="174"/>
177
-						<line hits="1" number="175"/>
178
-						<line hits="1" number="176"/>
179
-						<line hits="1" number="180"/>
180
-						<line hits="1" number="181"/>
181
-						<line hits="1" number="182"/>
182
-						<line hits="1" number="185"/>
183
-						<line hits="1" number="186"/>
184
-						<line hits="1" number="187"/>
326
+						<line hits="1" number="170"/>
327
+						<line hits="1" number="171"/>
185 328
 						<line hits="1" number="188"/>
186 329
 						<line hits="1" number="189"/>
187 330
 						<line hits="1" number="190"/>
188
-						<line branch="true" condition-coverage="100% (2/2)" hits="1" number="191"/>
189
-						<line hits="1" number="192"/>
331
+						<line hits="1" number="191"/>
332
+						<line branch="true" condition-coverage="100% (2/2)" hits="1" number="192"/>
190 333
 						<line hits="1" number="193"/>
191
-						<line hits="1" number="194"/>
192
-						<line hits="1" number="197"/>
193
-						<line hits="1" number="198"/>
334
+						<line branch="true" condition-coverage="100% (2/2)" hits="1" number="194"/>
335
+						<line hits="1" number="195"/>
336
+						<line hits="1" number="196"/>
337
+						<line hits="1" number="199"/>
338
+						<line hits="1" number="200"/>
339
+						<line hits="1" number="201"/>
340
+						<line hits="1" number="204"/>
341
+						<line hits="1" number="205"/>
342
+						<line hits="1" number="206"/>
343
+						<line hits="1" number="207"/>
344
+						<line branch="true" condition-coverage="100% (2/2)" hits="1" number="208"/>
345
+						<line hits="1" number="209"/>
346
+						<line hits="1" number="210"/>
194 347
 						<line hits="1" number="211"/>
195 348
 						<line hits="1" number="212"/>
196
-						<line hits="1" number="229"/>
197
-						<line hits="1" number="230"/>
198
-						<line hits="1" number="231"/>
199
-						<line hits="1" number="232"/>
200
-						<line branch="true" condition-coverage="100% (2/2)" hits="1" number="233"/>
201
-						<line hits="1" number="234"/>
202
-						<line branch="true" condition-coverage="100% (2/2)" hits="1" number="235"/>
203
-						<line hits="1" number="236"/>
204
-						<line hits="1" number="237"/>
205
-						<line hits="1" number="240"/>
206
-						<line hits="1" number="241"/>
207
-						<line hits="1" number="242"/>
208
-						<line hits="1" number="245"/>
209
-						<line hits="1" number="246"/>
210
-						<line hits="1" number="247"/>
211
-						<line hits="1" number="248"/>
212
-						<line branch="true" condition-coverage="100% (2/2)" hits="1" number="249"/>
213
-						<line hits="1" number="250"/>
214
-						<line hits="1" number="251"/>
215
-						<line hits="1" number="252"/>
216
-						<line hits="1" number="253"/>
217
-						<line hits="1" number="254"/>
218
-						<line hits="1" number="255"/>
219
-						<line branch="true" condition-coverage="50% (1/2)" hits="1" missing-branches="exit" number="256"/>
220
-						<line hits="1" number="257"/>
221
-						<line hits="1" number="258"/>
222
-						<line hits="1" number="259"/>
223
-						<line hits="1" number="262"/>
224
-						<line hits="1" number="263"/>
225
-						<line hits="1" number="264"/>
226
-						<line hits="1" number="265"/>
349
+						<line hits="1" number="213"/>
350
+						<line hits="1" number="214"/>
351
+						<line branch="true" condition-coverage="50% (1/2)" hits="1" missing-branches="exit" number="215"/>
352
+						<line hits="1" number="216"/>
353
+						<line hits="1" number="217"/>
354
+						<line hits="1" number="218"/>
355
+						<line hits="1" number="221"/>
356
+						<line hits="1" number="222"/>
357
+						<line hits="1" number="223"/>
358
+						<line hits="1" number="224"/>
227 359
 					</lines>
228 360
 				</class>
229 361
 			</classes>

+ 4296
- 532
_testresults_/unittest.json
File diff suppressed because it is too large
View File


BIN
_testresults_/unittest.pdf View File


+ 19
- 0
common.py View File

@@ -0,0 +1,19 @@
1
+import os
2
+
3
+FILETYPE_AUDIO = 'audio'
4
+FILETYPE_IMAGE = 'image'
5
+FILETYPE_VIDEO = 'video'
6
+
7
+EXTENTIONS_AUDIO = ['.mp3', ]
8
+EXTENTIONS_IMAGE = ['.jpg', '.jpeg', '.jpe', '.png', '.tif', '.tiff', '.gif', ]
9
+EXTENTIONS_VIDEO = ['.avi', '.mpg', '.mpeg', '.mpe', '.mov', '.qt', '.mp4', '.webm', '.ogv', '.flv', '.3gp', ]
10
+
11
+
12
+def get_filetype(full_path):
13
+    ext = os.path.splitext(full_path.lower())[1]
14
+    if ext in EXTENTIONS_AUDIO:
15
+        return FILETYPE_AUDIO
16
+    elif ext in EXTENTIONS_IMAGE:
17
+        return FILETYPE_IMAGE
18
+    elif ext in EXTENTIONS_VIDEO:
19
+        return FILETYPE_VIDEO

+ 35
- 0
convert.py View File

@@ -0,0 +1,35 @@
1
+import io
2
+from media import common, logger
3
+from PIL import Image
4
+import subprocess
5
+import platform
6
+
7
+
8
+def get_pil_image(media_instance):
9
+    try:
10
+        media_instance = media_instance._im
11
+    except AttributeError:
12
+        pass
13
+    #
14
+    if type(media_instance) == str:
15
+        ft = common.get_filetype(media_instance)
16
+        if ft == common.FILETYPE_IMAGE:
17
+            return Image.open(media_instance).copy()
18
+        elif ft == common.FILETYPE_VIDEO:
19
+            if platform.system() == 'Linux':
20
+                cmd = 'ffmpeg -ss 0.5 -i "' + media_instance + '" -vframes 1 -f image2pipe pipe:1 2> /dev/null'
21
+            else:
22
+                cmd = 'ffmpeg -ss 0.5 -i "' + media_instance + '" -vframes 1 -f image2pipe pipe:1 2> NULL'
23
+            try:
24
+                data = subprocess.check_output(cmd, shell=True)
25
+            except subprocess.CalledProcessError:
26
+                logger.warning('ffmpeg seems to be not installed')
27
+                return None
28
+            ffmpeg_handle = io.BytesIO(data)
29
+            im = Image.open(ffmpeg_handle)
30
+            return im.copy()
31
+        logger.warning('Filetype is not supported (%s)', media_instance)
32
+    elif type(media_instance) == Image.Image:
33
+        return media_instance.copy()
34
+    else:
35
+        logger.warning('Instance type is not supported: %s' % type(media_instance))

+ 46
- 87
metadata.py View File

@@ -1,87 +1,46 @@
1 1
 import logging
2 2
 import os
3 3
 from PIL import Image
4
+import media
4 5
 import subprocess
5 6
 import time
6 7
 
7 8
 
8
-logger_name = 'MEDIA'
9
-logger = logging.getLogger(logger_name)
10
-
11
-FILETYPE_AUDIO = 'audio'
12
-FILETYPE_IMAGE = 'image'
13
-FILETYPE_VIDEO = 'video'
14
-
15
-EXTENTIONS_AUDIO = ['.mp3', ]
16
-EXTENTIONS_IMAGE = ['.jpg', '.jpeg', '.jpe', '.png', '.tif', '.tiff', '.gif', ]
17
-EXTENTIONS_VIDEO = ['.avi', '.mpg', '.mpeg', '.mpe', '.mov', '.qt', '.mp4', '.webm', '.ogv', '.flv', '.3gp', ]
18
-
19
-KEY_ALBUM = 'album'
20
-KEY_APERTURE = 'aperture'
21
-KEY_ARTIST = 'artist'
22
-KEY_BITRATE = 'bitrate'
23
-KEY_CAMERA = 'camera'
24
-KEY_DURATION = 'duration'
25
-KEY_EXPOSURE_PROGRAM = 'exposure_program'
26
-KEY_EXPOSURE_TIME = 'exposure_time'
27
-KEY_FLASH = 'flash'
28
-KEY_FOCAL_LENGTH = 'focal_length'
29
-KEY_GENRE = 'genre'
30
-KEY_GPS = 'gps'
31
-KEY_HEIGHT = 'height'
32
-KEY_ISO = 'iso'
33
-KEY_ORIENTATION = 'orientation'
34
-KEY_RATIO = 'ratio'
35
-KEY_SIZE = 'size'
36
-KEY_TIME = 'time'   # USE time.localtime(value) or datetime.fromtimestamp(value) to convert the timestamp
37
-KEY_TIME_IS_SUBSTITUTION = 'tm_is_subst'
38
-KEY_TITLE = 'title'
39
-KEY_TRACK = 'track'
40
-KEY_WIDTH = 'width'
41
-KEY_YEAR = 'year'
9
+logger = media.logger
10
+
42 11
 
43 12
 __KEY_CAMERA_VENDOR__ = 'camera_vendor'
44 13
 __KEY_CAMERA_MODEL__ = 'camera_model'
45 14
 
46 15
 
47
-def get_filetype(full_path):
48
-    ext = os.path.splitext(full_path.lower())[1]
49
-    if ext in EXTENTIONS_AUDIO:
50
-        return FILETYPE_AUDIO
51
-    elif ext in EXTENTIONS_IMAGE:
52
-        return FILETYPE_IMAGE
53
-    elif ext in EXTENTIONS_VIDEO:
54
-        return FILETYPE_VIDEO
55
-
56
-
57 16
 def get_audio_data(full_path):
58 17
     conv_key_dict = {}
59
-    conv_key_dict['album'] = (str, KEY_ALBUM)
60
-    conv_key_dict['TAG:album'] = (str, KEY_ALBUM)
61
-    conv_key_dict['TAG:artist'] = (str, KEY_ARTIST)
62
-    conv_key_dict['artist'] = (str, KEY_ARTIST)
63
-    conv_key_dict['bit_rate'] = (__int_conv__, KEY_BITRATE)
64
-    conv_key_dict['duration'] = (float, KEY_DURATION)
65
-    conv_key_dict['TAG:genre'] = (str, KEY_GENRE)
66
-    conv_key_dict['genre'] = (str, KEY_GENRE)
67
-    conv_key_dict['TAG:title'] = (str, KEY_TITLE)
68
-    conv_key_dict['title'] = (str, KEY_TITLE)
69
-    conv_key_dict['TAG:track'] = (__int_conv__, KEY_TRACK)
70
-    conv_key_dict['track'] = (__int_conv__, KEY_TRACK)
71
-    conv_key_dict['TAG:date'] = (__int_conv__, KEY_YEAR)
72
-    conv_key_dict['date'] = (__int_conv__, KEY_YEAR)
18
+    conv_key_dict['album'] = (str, media.KEY_ALBUM)
19
+    conv_key_dict['TAG:album'] = (str, media.KEY_ALBUM)
20
+    conv_key_dict['TAG:artist'] = (str, media.KEY_ARTIST)
21
+    conv_key_dict['artist'] = (str, media.KEY_ARTIST)
22
+    conv_key_dict['bit_rate'] = (__int_conv__, media.KEY_BITRATE)
23
+    conv_key_dict['duration'] = (float, media.KEY_DURATION)
24
+    conv_key_dict['TAG:genre'] = (str, media.KEY_GENRE)
25
+    conv_key_dict['genre'] = (str, media.KEY_GENRE)
26
+    conv_key_dict['TAG:title'] = (str, media.KEY_TITLE)
27
+    conv_key_dict['title'] = (str, media.KEY_TITLE)
28
+    conv_key_dict['TAG:track'] = (__int_conv__, media.KEY_TRACK)
29
+    conv_key_dict['track'] = (__int_conv__, media.KEY_TRACK)
30
+    conv_key_dict['TAG:date'] = (__int_conv__, media.KEY_YEAR)
31
+    conv_key_dict['date'] = (__int_conv__, media.KEY_YEAR)
73 32
     return __adapt__data__(__get_xxprobe_data__(full_path, conv_key_dict), full_path)
74 33
 
75 34
 
76 35
 def get_video_data(full_path):
77 36
     conv_key_dict = {}
78
-    conv_key_dict['creation_time'] = (__vid_datetime_conv__, KEY_TIME)
79
-    conv_key_dict['TAG:creation_time'] = (__vid_datetime_conv__, KEY_TIME)
80
-    conv_key_dict['bit_rate'] = (__int_conv__, KEY_BITRATE)
81
-    conv_key_dict['duration'] = (float, KEY_DURATION)
82
-    conv_key_dict['height'] = (__int_conv__, KEY_HEIGHT)
83
-    conv_key_dict['width'] = (__int_conv__, KEY_WIDTH)
84
-    conv_key_dict['display_aspect_ratio'] = (__ratio_conv__, KEY_RATIO)
37
+    conv_key_dict['creation_time'] = (__vid_datetime_conv__, media.KEY_TIME)
38
+    conv_key_dict['TAG:creation_time'] = (__vid_datetime_conv__, media.KEY_TIME)
39
+    conv_key_dict['bit_rate'] = (__int_conv__, media.KEY_BITRATE)
40
+    conv_key_dict['duration'] = (float, media.KEY_DURATION)
41
+    conv_key_dict['height'] = (__int_conv__, media.KEY_HEIGHT)
42
+    conv_key_dict['width'] = (__int_conv__, media.KEY_WIDTH)
43
+    conv_key_dict['display_aspect_ratio'] = (__ratio_conv__, media.KEY_RATIO)
85 44
     return __adapt__data__(__get_xxprobe_data__(full_path, conv_key_dict), full_path)
86 45
 
87 46
 
@@ -90,25 +49,25 @@ def get_image_data(full_path):
90 49
 
91 50
 
92 51
 def __adapt__data__(data, full_path):
93
-    data[KEY_SIZE] = os.path.getsize(full_path)
52
+    data[media.KEY_SIZE] = os.path.getsize(full_path)
94 53
     # Join Camera Vendor and Camera Model
95 54
     if __KEY_CAMERA_MODEL__ in data and __KEY_CAMERA_VENDOR__ in data:
96 55
         model = data.pop(__KEY_CAMERA_MODEL__)
97 56
         vendor = data.pop(__KEY_CAMERA_VENDOR__)
98
-        data[KEY_CAMERA] = '%s: %s' % (vendor, model)
57
+        data[media.KEY_CAMERA] = '%s: %s' % (vendor, model)
99 58
     # Add time if not exists
100
-    if KEY_TIME not in data:
101
-        if KEY_YEAR in data and KEY_TRACK in data:
102
-            if data[KEY_YEAR] != 0:  # ignore year 0 - must be wrong
59
+    if media.KEY_TIME not in data:
60
+        if media.KEY_YEAR in data and media.KEY_TRACK in data:
61
+            if data[media.KEY_YEAR] != 0:  # ignore year 0 - must be wrong
103 62
                 # Use a date where track 1 is the newest in the given year
104
-                minute = int(data[KEY_TRACK] / 60)
105
-                second = (data[KEY_TRACK] - 60 * minute) % 60
63
+                minute = int(data[media.KEY_TRACK] / 60)
64
+                second = (data[media.KEY_TRACK] - 60 * minute) % 60
106 65
                 #
107
-                data[KEY_TIME] = int(time.mktime((data[KEY_YEAR], 1, 1, 0, 59 - minute, 59 - second, 0, 0, 0)))
108
-                data[KEY_TIME_IS_SUBSTITUTION] = True
66
+                data[media.KEY_TIME] = int(time.mktime((data[media.KEY_YEAR], 1, 1, 0, 59 - minute, 59 - second, 0, 0, 0)))
67
+                data[media.KEY_TIME_IS_SUBSTITUTION] = True
109 68
         else:
110
-            data[KEY_TIME] = int(os.path.getmtime(full_path))
111
-            data[KEY_TIME_IS_SUBSTITUTION] = True
69
+            data[media.KEY_TIME] = int(os.path.getmtime(full_path))
70
+            data[media.KEY_TIME_IS_SUBSTITUTION] = True
112 71
     return data
113 72
 
114 73
 
@@ -154,19 +113,19 @@ def __get_exif_data__(full_path):
154 113
     else:
155 114
         conv_key_dict = {}
156 115
         # IMAGE
157
-        conv_key_dict[0x9003] = (__datetime_conv__, KEY_TIME)
158
-        conv_key_dict[0x8822] = (__exposure_program_conv__, KEY_EXPOSURE_PROGRAM)
159
-        conv_key_dict[0x829A] = (__num_denum_conv__, KEY_EXPOSURE_TIME)
160
-        conv_key_dict[0x9209] = (__flash_conv__, KEY_FLASH)
161
-        conv_key_dict[0x829D] = (__num_denum_conv__, KEY_APERTURE)
162
-        conv_key_dict[0x920A] = (__num_denum_conv__, KEY_FOCAL_LENGTH)
163
-        conv_key_dict[0x8825] = (__gps_conv__, KEY_GPS)
164
-        conv_key_dict[0xA003] = (__int_conv__, KEY_HEIGHT)
165
-        conv_key_dict[0x8827] = (__int_conv__, KEY_ISO)
116
+        conv_key_dict[0x9003] = (__datetime_conv__, media.KEY_TIME)
117
+        conv_key_dict[0x8822] = (__exposure_program_conv__, media.KEY_EXPOSURE_PROGRAM)
118
+        conv_key_dict[0x829A] = (__num_denum_conv__, media.KEY_EXPOSURE_TIME)
119
+        conv_key_dict[0x9209] = (__flash_conv__, media.KEY_FLASH)
120
+        conv_key_dict[0x829D] = (__num_denum_conv__, media.KEY_APERTURE)
121
+        conv_key_dict[0x920A] = (__num_denum_conv__, media.KEY_FOCAL_LENGTH)
122
+        conv_key_dict[0x8825] = (__gps_conv__, media.KEY_GPS)
123
+        conv_key_dict[0xA003] = (__int_conv__, media.KEY_HEIGHT)
124
+        conv_key_dict[0x8827] = (__int_conv__, media.KEY_ISO)
166 125
         conv_key_dict[0x010F] = (str, __KEY_CAMERA_VENDOR__)
167 126
         conv_key_dict[0x0110] = (str, __KEY_CAMERA_MODEL__)
168
-        conv_key_dict[0x0112] = (__int_conv__, KEY_ORIENTATION)
169
-        conv_key_dict[0xA002] = (__int_conv__, KEY_WIDTH)
127
+        conv_key_dict[0x0112] = (__int_conv__, media.KEY_ORIENTATION)
128
+        conv_key_dict[0xA002] = (__int_conv__, media.KEY_WIDTH)
170 129
         for key in conv_key_dict:
171 130
             if key in exif:
172 131
                 tp, name = conv_key_dict[key]

Loading…
Cancel
Save