/
media_utils.py
448 lines (388 loc) · 14.9 KB
/
media_utils.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# media_utils.py @ CIFONAUTA
# Copyleft 2010-2011 - Bruno C. Vellutini | organelas.com
#
#TODO Definir licença.
#
# Atualizado: 16 Feb 2011 01:09AM
'''Biblioteca de apoio para arquivos multimídia.
Guarda funções úteis para a manipulação de fotos e imagens. Criar thumbnails,
extrair duração, otimizar, etc.
Centro de Biologia Marinha da Universidade de São Paulo.
'''
import logging
import os
import pyexiv2
import random
import re
import subprocess
from shutil import copy2, move
from iptcinfo import IPTCInfo
# Instancia logger.
logger = logging.getLogger('cifonauta.utils')
__author__ = 'Bruno Vellutini'
__copyright__ = 'Copyright 2010-2011, CEBIMar-USP'
__credits__ = 'Bruno C. Vellutini'
__license__ = 'DEFINIR'
__version__ = '0.1'
__maintainer__ = 'Bruno Vellutini'
__email__ = 'organelas@gmail.com'
__status__ = 'Development'
# build_call
# process_video
# create_meta?
# process image
# optimize
# rename_file
# get_initials
# create_id
# prepare_meta
#TODO Checar se o ImageMagick está instalado.
#TODO Checar se o FFmpeg está instalado.
def read_iptc(abspath, charset='utf-8', new=False):
'''Parses IPTC metadata from a photo with iptcinfo.py'''
info = IPTCInfo(abspath, True, charset)
if len(info.data) < 4:
print('IPTC is empty for %s' % abspath)
return None
return info
def create_thumb(filepath, destination):
'''Cria thumbnail para foto.'''
# Confere argumentos.
if not os.path.isfile(filepath):
logger.critical('%s não é um arquivo válido.', filepath)
if not os.path.isdir(destination):
logger.critical('%s não é um destino válido.', destination)
# Define nome do thumbnail.
filename = os.path.basename(filepath)
# Checa se nome do arquivo tem pontos.
if filename.count('.') > 1:
logger.critical('Mais de um "." em %s', filename)
else:
filename = filename.split('.')[0] + '.jpg'
# Define caminho para pasta local.
localpath = os.path.join(destination, filename)
# Define comando a ser executado.
magick_call = [
'convert', '-define', 'jpeg:size=200x150', filepath, '-thumbnail',
'120x90^', '-gravity', 'center', '-extent', '120x90', localpath
]
# Executando o ImageMagick
try:
subprocess.call(magick_call)
logger.debug('Thumb criado em %s', localpath)
return localpath
except IOError:
logger.warning('Erro ao criar thumb %s', localpath)
return None
def build_call(filepath):
'''Constrói o subprocesso para converter o vídeo com o FFmpeg.
#webm HD
ffmpeg -y -i hd.m2ts -i marca.png -metadata title="A WEBM HD" -metadata author="AN AUTHOR"
-b:v 600k -threads 0 -acodec libvorbis -b:a 128k -ac 2 -ar 44100 -vcodec libvpx
-filter_complex "scale=512x288,overlay=0:main_h-overlay_h-0" hd.webm
#webm DV
ffmpeg -y -i dv.avi -i marca.png -metadata title="A WEBM DV" -metadata author="AN AUTHOR"
-b:v 600k -threads 0 -acodec libvorbis -b:a 128k -ac 2 -ar 44100 -vcodec libvpx
-filter_complex "scale=512x384,overlay=0:main_h-overlay_h-0" dv.webm
#ogg HD
ffmpeg -y -i hd.m2ts -i marca.png -metadata title="AN OGG HD" -metadata author="AN AUTHOR"
-b:v 600k -threads 0 -acodec libvorbis -b:a 128k -ac 2 -ar 44100 -vcodec libtheora
-filter_complex "scale=512x288,overlay=0:main_h-overlay_h-0" hd.ogv
#ogg DV
ffmpeg -y -i dv.avi -i marca.png -metadata title="AN OGG DV" -metadata author="AN AUTHOR"
-b:v 600k -threads 0 -acodec libvorbis -b:a 128k -ac 2 -ar 44100 -vcodec libtheora
-filter_complex "scale=512x384,overlay=0:main_h-overlay_h-0" dv.ogv
#mp4 HD
ffmpeg -y -i hd.m2ts -i marca.png -metadata title="A MP4 HD" -metadata author="AN AUTHOR"
-b:v 600k -threads 0 -acodec libfaac -b:a 128k -ac 2 -ar 44100 -vcodec libx264
-filter_complex "scale=512x288,overlay=0:main_h-overlay_h-0" hd.mp4
#mp4 DV
ffmpeg -y -i dv.avi -i marca.png -metadata title="A MP4 DV" -metadata author="AN AUTHOR"
-b:v 600k -threads 0 -acodec libfaac -b:a 128k -ac 2 -ar 44100 -vcodec libx264
-filter_complex "scale=512x384,overlay=0:main_h-overlay_h-0" dv.mp4
'''
# FFMPEG command.
call = [
'ffmpeg', '-y', '-i', filepath, '-i', 'marca.png',
'-b:v', '600k', '-threads', '0',
]
if filepath.endswith('m2ts'):
call.extend([
'-filter_complex', 'overlay=0:main_h-overlay_h-0',
'-s', '512x288', '-aspect', '16:9',
])
else:
call.extend([
'-filter_complex', 'overlay=0:main_h-overlay_h-0',
'-s', '512x384', '-aspect', '4:3',
])
# Audio codec
if filepath.endswith('mp4'):
call.extend(['-acodec', 'libfaac', '-b:a', '128k',
'-ac', '2', '-ar', '44100'])
else:
call.extend(['-acodec', 'libvorbis', '-b:a', '128k',
'-ac', '2', '-ar', '44100'])
# Video codec
if filepath.endswith('webm'):
call.extend(['-vcodec', 'libvpx'])
elif filepath.endswith('mp4'):
call.extend(['-vcodec', 'libx264'])
if filepath.endswith('ogv'):
call.extend(['-vcodec', 'libtheora'])
return call
def grab_still(filepath):
'''Grab video's first frame.'''
# Path to still image.
stillpath = os.path.splitext(filepath)[0] + '.jpg'
# Command to be called.
if filepath.endswith('m2ts'):
ffmpeg_call = [ 'ffmpeg', '-y', '-i', filepath, '-vframes', '1',
'-filter_complex', 'scale=512:288', '-aspect', '16:9', '-ss',
'1', '-f', 'image2', stillpath ]
else:
ffmpeg_call = ['ffmpeg', '-y', '-i', filepath, '-vframes', '1',
'-filter_complex', 'scale=512:384', '-ss', '1', '-f', 'image2',
stillpath]
# Executing ffmpeg.
try:
subprocess.call(ffmpeg_call)
logger.debug('Still criado em %s', stillpath)
return stillpath
except IOError:
logger.warning('Erro ao criar still %s', stillpath)
return None, None
def create_still(filepath, destination):
'''Cria still para o vídeo e thumbnail em seguida.'''
# Confere argumentos.
if not os.path.isfile(filepath):
logger.critical('%s não é um arquivo válido.', filepath)
if not os.path.isdir(destination):
logger.critical('%s não é um destino válido.', destination)
# Define nome do thumbnail.
filename = os.path.basename(filepath)
# Checa se nome do arquivo tem pontos.
if filename.count('.') > 1:
logger.critical('Mais de um "." em %s', filename)
else:
thumbname = filename.split('.')[0] + '.jpg'
stillname = filename.split('.')[0] + '_still.jpg'
# Define caminho para pasta local.
thumbpath = os.path.join(destination, thumbname)
stillpath = os.path.join(destination, stillname)
# Define comando a ser executado.
if filepath.endswith('m2ts'):
ffmpeg_call = [
'ffmpeg', '-y', '-i', filepath, '-vframes', '1', '-filter_complex',
'scale=512:288', '-aspect', '16:9', '-ss', '1', '-f', 'image2',
stillpath
]
else:
ffmpeg_call = [
'ffmpeg', '-y', '-i', filepath, '-vframes', '1', '-filter_complex',
'scale=512:384', '-ss', '1', '-f', 'image2', stillpath
]
# Executando o ffmpeg
try:
subprocess.call(ffmpeg_call)
logger.debug('Still criado em %s', stillpath)
# Copia still para servir de template para thumb.
copy2(stillpath, thumbpath)
# Cria thumbnail normal.
thumbpath = create_thumb(thumbpath, destination)
return thumbpath, stillpath
except IOError:
logger.warning('Erro ao criar still %s', stillpath)
return None, None
def convert_to_web(filepath, finalpath):
'''Redimensiona e otimiza fotos para a rede.'''
convert_call = ['convert', filepath, '-density', '72', '-format', 'jpg',
'-quality', '70', '-resize', '800x800>', finalpath]
try:
subprocess.call(convert_call)
logger.debug('%s processado com sucesso.', finalpath)
return finalpath
except:
logger.critical('Erro ao converter %s', filepath)
return None
def watermarker(filepath):
'''Insere marca d'água em foto.'''
# Arquivo com marca d'água.
watermark = u'marca.png'
# Constrói chamada para canto esquerdo embaixo.
mark_call = ['composite', '-gravity', 'southwest', watermark, filepath,
filepath]
try:
subprocess.call(mark_call)
logger.debug('Marca adicionada em %s', filepath)
return True
except:
logger.warning('Erro ao adicionar marca em %s', filepath)
return False
def get_exif(filepath):
'''Extrai o exif da foto usando o pyexiv2 0.3.0.'''
logger.debug('Extraindo EXIF de %s', filepath)
exif = pyexiv2.ImageMetadata(filepath)
exif.read()
return exif
def get_exif_date(exif):
'''Extrai a data em que foi criada a foto do EXIF.'''
try:
date = exif['Exif.Photo.DateTimeOriginal']
except:
try:
date = exif['Exif.Photo.DateTimeDigitized']
except:
try:
date = exif['Exif.Image.DateTime']
except:
return False
return date.value
def get_date(exif):
'''Retorna a data da foto já pronta para metadados.'''
# Extrai data do exif.
date = get_exif_date(exif)
# Faz parsing do valor extraído.
try:
date_string = date.strftime('%Y-%m-%d %I:%M:%S')
except:
date_string = ''
if date_string and date_string != '0000:00:00 00:00:00':
return date
else:
return '1900-01-01 01:01:01'
def get_gps(exif):
'''Extrai coordenadas guardadas no EXIF.'''
# Para guardar valores.
gps = {}
gps_data = {}
try:
# Latitude.
gps['latref'] = exif['Exif.GPSInfo.GPSLatitudeRef'].value
gps['latdeg'] = exif['Exif.GPSInfo.GPSLatitude'].value[0]
gps['latmin'] = exif['Exif.GPSInfo.GPSLatitude'].value[1]
gps['latsec'] = exif['Exif.GPSInfo.GPSLatitude'].value[2]
latitude = get_decimal(gps['latref'], gps['latdeg'], gps['latmin'],
gps['latsec'])
# Longitude.
gps['longref'] = exif['Exif.GPSInfo.GPSLongitudeRef'].value
gps['longdeg'] = exif['Exif.GPSInfo.GPSLongitude'].value[0]
gps['longmin'] = exif['Exif.GPSInfo.GPSLongitude'].value[1]
gps['longsec'] = exif['Exif.GPSInfo.GPSLongitude'].value[2]
longitude = get_decimal(gps['longref'], gps['longdeg'], gps['longmin'],
gps['longsec'])
# Gravando valores prontos.
gps_data['geolocation'] = '%s %d°%d\'%d" %s %d°%d\'%d"' % (
gps['latref'], gps['latdeg'], gps['latmin'], gps['latsec'],
gps['longref'], gps['longdeg'], gps['longmin'], gps['longsec'])
gps_data['latitude'] = '%f' % latitude
gps_data['longitude'] = '%f' % longitude
except:
logger.debug('Foto sem dados de GPS.')
# Gravando valores prontos.
gps_data['geolocation'] = ''
gps_data['latitude'] = ''
gps_data['longitude'] = ''
return gps_data
def get_decimal(ref, deg, min, sec):
'''Descobre o valor decimal das coordenadas.'''
decimal_min = (min * 60.0 + sec) / 60.0
decimal = (deg * 60.0 + decimal_min) / 60.0
negatives = ['S', 'W']
if ref in negatives:
decimal = -decimal
return decimal
def get_info(video):
'''Pega informações do vídeo na marra e retorna dicionário.
Os valores são extraídos do stderr do ffmpeg usando expressões
regulares.
'''
try:
call = subprocess.Popen(['ffmpeg', '-i', video],
stderr=subprocess.PIPE)
except:
logger.warning('Não conseguiu abrir o arquivo %s', video)
return None
# Necessário converter pra string pra ser objeto permanente.
info = str(call.stderr.read())
# Encontra a duração do arquivo.
length_re = re.search('(?<=Duration: )\d+:\d+:\d+', info)
# Encontra o codec e dimensões.
precodec_re = re.search('(?<=Video: ).+, .+, \d+x\d+', info)
# Processando os outputs brutos.
#XXX Melhorar isso e definir o formato oficial dos valores.
# Exemplo (guardar em segundos e converter depois):
# >>> import datetime
# >>> str(datetime.timedelta(seconds=666))
# '0:11:06'
duration = length_re.group(0)
codecs = precodec_re.group(0).split(', ')
codec = codecs[0].split(' ')[0]
dimensions = codecs[-1]
# Salvando valores limpos em um dicionário.
details = {
'duration': duration,
'dimensions': dimensions,
'codec': codec,
}
return details
def dir_ready(*dirs):
'''Verifica se diretório(s) existe(m), criando caso não exista.'''
for dir in dirs:
if os.path.isdir(dir) is False:
logger.debug('Criando diretório %s', dir)
os.makedirs(dir)
def check_file(filepath):
'''Checa se arquivo existe.'''
media_file = os.path.isfile(filepath)
return media_file
def rename_file(filename, authors):
'''Renomeia arquivo com iniciais e identificador.'''
logger.debug('Renomeando %s', filename)
if authors:
initials = get_initials(authors)
else:
initials = 'cbm'
id = create_id()
new_filename = initials + '_' + id + os.path.splitext(filename)[1].lower()
return new_filename
def get_initials(authors):
'''Extrai iniciais dos autores.
Faz split por vírgulas, caso tenha mais de 1 autor; para cada autor faz
split por espaços em branco e pega apenas a primeira letra; junta iniciais
em sequência; junta autores separados por hífen.
Não está muito legível, mas é isso aí.
'''
initials = '-'.join([''.join([sil[:1] for sil in word.strip().split(' ')]) for word in authors.split(',')]).lower()
return initials
def create_id():
'''Cria identificador único para nome do arquivo.'''
chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
id = ''.join([random.choice(chars) for x in xrange(6)])
return id
def fix_filename(root, filename):
'''Checa validade do nome do arquivo.'''
# Verifica a existência de pontos extras.
dotcount = filename.count('.')
if dotcount == 0:
filepath = os.path.join(root, filename)
logger.warning('%s sem extensão!', filepath)
elif dotcount > 1:
splitname = filename.split('.')
extension = splitname.pop()
basename = ''.join(splitname)
fixedname = basename + '.' + extension
filepath = os.path.join(root, fixedname)
oldpath = os.path.join(root, filename)
try:
move(oldpath, filepath)
logger.debug('Corrigido: %s >> %s', filename, fixedname)
except:
logger.warning('%s não foi corrigido!', oldpath)
filepath = oldpath
else:
filepath = os.path.join(root, filename)
return filepath