Added som features for audio handling (cddb, rip, encode)
This commit is contained in:
parent
abcf63d02e
commit
402e837551
138
CDDB.py
Normal file
138
CDDB.py
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
import urllib
|
||||||
|
import socket
|
||||||
|
import os
|
||||||
|
import urllib.parse
|
||||||
|
import urllib.request
|
||||||
|
import subprocess
|
||||||
|
import logging
|
||||||
|
from .common import KEY_ALBUM, KEY_ARTIST, KEY_GENRE, KEY_TITLE, KEY_TRACK, KEY_YEAR
|
||||||
|
|
||||||
|
try:
|
||||||
|
from config import APP_NAME as ROOT_LOGGER_NAME
|
||||||
|
except ImportError:
|
||||||
|
ROOT_LOGGER_NAME = 'root'
|
||||||
|
logger = logging.getLogger(ROOT_LOGGER_NAME).getChild(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
version = 2.0
|
||||||
|
if 'EMAIL' in os.environ:
|
||||||
|
(default_user, hostname) = os.environ['EMAIL'].split('@')
|
||||||
|
else:
|
||||||
|
default_user = os.environ['USER'] or os.geteuid() or 'user'
|
||||||
|
hostname = socket.gethostname() or 'host'
|
||||||
|
|
||||||
|
proto = 6
|
||||||
|
default_server = 'http://gnudb.gnudb.org/~cddb/cddb.cgi'
|
||||||
|
|
||||||
|
|
||||||
|
def my_disc_metadata(**kwargs):
|
||||||
|
"""Generate my disc metadata
|
||||||
|
|
||||||
|
kwargs needs to include the following data:
|
||||||
|
* KEY_ARTIST (str)
|
||||||
|
* KEY_ALBUM (str)
|
||||||
|
* KEY_YEAR (int) - will be converted here
|
||||||
|
* KEY_GENRE (str)
|
||||||
|
* "track_xx" (str) - where xx is the track number which will be converted to int here
|
||||||
|
"""
|
||||||
|
main_dict = {}
|
||||||
|
for key in [KEY_ARTIST, KEY_ALBUM, KEY_YEAR, KEY_GENRE]:
|
||||||
|
try:
|
||||||
|
value = kwargs.pop(key)
|
||||||
|
except KeyError:
|
||||||
|
logger.error("Information is missing in kwargs - key=%s", key)
|
||||||
|
return None
|
||||||
|
if key in [KEY_YEAR]:
|
||||||
|
try:
|
||||||
|
main_dict[key] = int(value)
|
||||||
|
except ValueError:
|
||||||
|
logger.error("Can't convert %s (key=%s) to integer value", value, key)
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
main_dict[key] = value
|
||||||
|
rv = dict(main_dict)
|
||||||
|
rv["tracks"] = []
|
||||||
|
for key in list(kwargs):
|
||||||
|
value = kwargs.pop(key)
|
||||||
|
if key.startswith("track_"):
|
||||||
|
track = dict(main_dict)
|
||||||
|
try:
|
||||||
|
track[KEY_TRACK] = int(key[6:])
|
||||||
|
except ValueError:
|
||||||
|
logger.warning("Useless information kwargs - kwargs[%s] = %s", key, repr(value))
|
||||||
|
track[KEY_TITLE] = value
|
||||||
|
rv["tracks"].append(track)
|
||||||
|
else:
|
||||||
|
logger.warning("Useless information kwargs - key=%s", key)
|
||||||
|
return rv
|
||||||
|
|
||||||
|
|
||||||
|
def query(data_str, server_url=default_server, user=default_user, host=hostname, client_name=ROOT_LOGGER_NAME, client_version=version):
|
||||||
|
url = f"{server_url}?cmd=cddb+query+{data_str}&hello={user}+{host}+{client_name}+{client_version}&proto={proto}"
|
||||||
|
response = urllib.request.urlopen(url)
|
||||||
|
header = response.readline().decode("utf-8").rstrip().split(" ", 3)
|
||||||
|
header[0] = int(header[0])
|
||||||
|
|
||||||
|
if header[0] not in (210, ):
|
||||||
|
logger.error("Error while querying cddb entry: \"%d - %s\"", header[0], header[3])
|
||||||
|
return None
|
||||||
|
|
||||||
|
rv = {}
|
||||||
|
for line in response.readlines():
|
||||||
|
line = line.decode("utf-8").rstrip()
|
||||||
|
if line == '.': # end of matches
|
||||||
|
break
|
||||||
|
dummy, did, txt = line.split(" ", 2)
|
||||||
|
rv[did] = txt
|
||||||
|
return rv
|
||||||
|
|
||||||
|
|
||||||
|
def cddb(disc_id, server_url=default_server, user=default_user, host=hostname, client_name=ROOT_LOGGER_NAME, client_version=version):
|
||||||
|
KEY_TRANSLATOR = {
|
||||||
|
"DGENRE": KEY_GENRE,
|
||||||
|
"DYEAR": KEY_YEAR
|
||||||
|
}
|
||||||
|
#
|
||||||
|
url = f"{server_url}?cmd=cddb+read+data+{disc_id}&hello={default_server}+{hostname}+{client_name}+{client_version}&proto={proto}"
|
||||||
|
response = urllib.request.urlopen(url)
|
||||||
|
|
||||||
|
header = response.readline().decode("utf-8").rstrip().split(" ", 3)
|
||||||
|
header[0] = int(header[0])
|
||||||
|
|
||||||
|
if header[0] not in (210, ):
|
||||||
|
logger.error("Error while reading cddb entry: \"%d - %s\"", header[1], header[3])
|
||||||
|
return None
|
||||||
|
data = {}
|
||||||
|
for line in response.readlines():
|
||||||
|
line = line.decode("utf-8").rstrip()
|
||||||
|
if line == '.': # end of matches
|
||||||
|
break
|
||||||
|
if not line.startswith("#"):
|
||||||
|
match = line.split('=', 2)
|
||||||
|
key = KEY_TRANSLATOR.get(match[0])
|
||||||
|
value = match[1].strip()
|
||||||
|
if key:
|
||||||
|
if key == KEY_YEAR:
|
||||||
|
value = int(value)
|
||||||
|
data[key] = value
|
||||||
|
elif match[0] == "DTITLE":
|
||||||
|
art_tit = value.split("/", 2)
|
||||||
|
data[KEY_ARTIST] = art_tit[0].strip()
|
||||||
|
data[KEY_ALBUM] = art_tit[1].strip()
|
||||||
|
elif match[0].startswith("TTITLE"):
|
||||||
|
data["track_%02d" % (int(match[0][6:]) + 1)] = value
|
||||||
|
else:
|
||||||
|
logger.debug("cddb line ignored: \"%s\"", line)
|
||||||
|
return my_disc_metadata(**data)
|
||||||
|
|
||||||
|
|
||||||
|
def discid():
|
||||||
|
discid_cmd = subprocess.getoutput("which cd-discid")
|
||||||
|
if not discid_cmd:
|
||||||
|
logger.error("cd-discid is required for encoding. You need to install it to your system.")
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
return subprocess.check_output(discid_cmd).decode("utf-8").strip().replace(" ", "+")
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
return None
|
233
__init__.py
233
__init__.py
@ -22,203 +22,52 @@ media (Media Tools)
|
|||||||
|
|
||||||
See also the :download:`unittest <../../media/_testresults_/unittest.pdf>` documentation.
|
See also the :download:`unittest <../../media/_testresults_/unittest.pdf>` documentation.
|
||||||
"""
|
"""
|
||||||
|
from .common import CALLBACK_CDDB_CHOICE
|
||||||
|
from .common import CALLBACK_MAN_INPUT
|
||||||
|
from .common import get_disc_device
|
||||||
|
from .common import KEY_ALBUM
|
||||||
|
from .common import KEY_APERTURE
|
||||||
|
from .common import KEY_ARTIST
|
||||||
|
from .common import KEY_BITRATE
|
||||||
|
from .common import KEY_CAMERA
|
||||||
|
from .common import KEY_DURATION
|
||||||
|
from .common import KEY_EXPOSURE_PROGRAM
|
||||||
|
from .common import KEY_EXPOSURE_TIME
|
||||||
|
from .common import KEY_FLASH
|
||||||
|
from .common import KEY_FOCAL_LENGTH
|
||||||
|
from .common import KEY_GENRE
|
||||||
|
from .common import KEY_GPS
|
||||||
|
from .common import KEY_HEIGHT
|
||||||
|
from .common import KEY_ISO
|
||||||
|
from .common import KEY_ORIENTATION
|
||||||
|
from .common import KEY_RATIO
|
||||||
|
from .common import KEY_SIZE
|
||||||
|
from .common import KEY_TIME
|
||||||
|
from .common import KEY_TIME_IS_SUBSTITUTION
|
||||||
|
from .common import KEY_TITLE
|
||||||
|
from .common import KEY_TRACK
|
||||||
|
from .common import KEY_WIDTH
|
||||||
|
from .common import KEY_YEAR
|
||||||
|
from .convert import disc_track_rip
|
||||||
|
from .convert import wav_to_mp3
|
||||||
|
from .convert import track_to_targetpath
|
||||||
|
from .image import image
|
||||||
|
from .image import ORIENTATION_HALF_ROTATED
|
||||||
|
from .image import ORIENTATION_HORIZONTAL_MIRRORED
|
||||||
|
from .image import ORIENTATION_LEFT_ROTATED
|
||||||
|
from .image import ORIENTATION_NORMAL
|
||||||
|
from .image import ORIENTATION_RIGHT_ROTATED
|
||||||
|
from .image import ORIENTATION_VERTICAL_MIRRORED
|
||||||
|
from .image import JOIN_BOT_LEFT, JOIN_BOT_RIGHT
|
||||||
|
from .image import JOIN_CENTER
|
||||||
|
from .image import JOIN_TOP_LEFT
|
||||||
|
from .image import JOIN_TOP_RIGHT
|
||||||
|
from .metadata import get_media_data
|
||||||
__DEPENDENCIES__ = []
|
__DEPENDENCIES__ = []
|
||||||
|
|
||||||
import io
|
|
||||||
import logging
|
|
||||||
from PIL import Image, ImageEnhance, ExifTags
|
|
||||||
|
|
||||||
try:
|
|
||||||
from config import APP_NAME as ROOT_LOGGER_NAME
|
|
||||||
except ImportError:
|
|
||||||
ROOT_LOGGER_NAME = 'root'
|
|
||||||
logger = logging.getLogger(ROOT_LOGGER_NAME).getChild(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
__DESCRIPTION__ = """The Module {\\tt %s} is designed to help on all issues with media files, like tags (e.g. exif, id3) and transformations.
|
__DESCRIPTION__ = """The Module {\\tt %s} is designed to help on all issues with media files, like tags (e.g. exif, id3) and transformations.
|
||||||
For more Information read the documentation.""" % __name__.replace('_', '\_')
|
For more Information read the documentation.""" % __name__.replace('_', '\_')
|
||||||
"""The Module Description"""
|
"""The Module Description"""
|
||||||
__INTERPRETER__ = (3, )
|
__INTERPRETER__ = (3, )
|
||||||
"""The Tested Interpreter-Versions"""
|
"""The Tested Interpreter-Versions"""
|
||||||
|
|
||||||
|
|
||||||
KEY_ALBUM = 'album'
|
|
||||||
KEY_APERTURE = 'aperture'
|
|
||||||
KEY_ARTIST = 'artist'
|
|
||||||
KEY_BITRATE = 'bitrate'
|
|
||||||
KEY_CAMERA = 'camera'
|
|
||||||
KEY_DURATION = 'duration'
|
|
||||||
KEY_EXPOSURE_PROGRAM = 'exposure_program'
|
|
||||||
KEY_EXPOSURE_TIME = 'exposure_time'
|
|
||||||
KEY_FLASH = 'flash'
|
|
||||||
KEY_FOCAL_LENGTH = 'focal_length'
|
|
||||||
KEY_GENRE = 'genre'
|
|
||||||
KEY_GPS = 'gps'
|
|
||||||
KEY_HEIGHT = 'height'
|
|
||||||
KEY_ISO = 'iso'
|
|
||||||
KEY_ORIENTATION = 'orientation'
|
|
||||||
KEY_RATIO = 'ratio'
|
|
||||||
KEY_SIZE = 'size'
|
|
||||||
KEY_TIME = 'time' # USE time.localtime(value) or datetime.fromtimestamp(value) to convert the timestamp
|
|
||||||
KEY_TIME_IS_SUBSTITUTION = 'tm_is_subst'
|
|
||||||
KEY_TITLE = 'title'
|
|
||||||
KEY_TRACK = 'track'
|
|
||||||
KEY_WIDTH = 'width'
|
|
||||||
KEY_YEAR = 'year'
|
|
||||||
|
|
||||||
|
|
||||||
def get_media_data(full_path):
|
|
||||||
from media.metadata import get_audio_data, get_image_data, get_video_data
|
|
||||||
from media.common import get_filetype, FILETYPE_AUDIO, FILETYPE_IMAGE, FILETYPE_VIDEO
|
|
||||||
#
|
|
||||||
ft = get_filetype(full_path)
|
|
||||||
#
|
|
||||||
if ft == FILETYPE_AUDIO:
|
|
||||||
return get_audio_data(full_path)
|
|
||||||
elif ft == FILETYPE_IMAGE:
|
|
||||||
return get_image_data(full_path)
|
|
||||||
elif ft == FILETYPE_VIDEO:
|
|
||||||
return get_video_data(full_path)
|
|
||||||
else:
|
|
||||||
logger.warning('Filetype not known: %s', full_path)
|
|
||||||
|
|
||||||
|
|
||||||
ORIENTATION_NORMAL = 1
|
|
||||||
ORIENTATION_VERTICAL_MIRRORED = 2
|
|
||||||
ORIENTATION_HALF_ROTATED = 3
|
|
||||||
ORIENTATION_HORIZONTAL_MIRRORED = 4
|
|
||||||
ORIENTATION_LEFT_ROTATED = 6
|
|
||||||
ORIENTATION_RIGHT_ROTATED = 8
|
|
||||||
|
|
||||||
JOIN_TOP_LEFT = 1
|
|
||||||
JOIN_TOP_RIGHT = 2
|
|
||||||
JOIN_BOT_LEFT = 3
|
|
||||||
JOIN_BOT_RIGHT = 4
|
|
||||||
JOIN_CENTER = 5
|
|
||||||
|
|
||||||
|
|
||||||
class image(object):
|
|
||||||
def __init__(self, media_instance=None):
|
|
||||||
if media_instance is not None:
|
|
||||||
self.load_from_file(media_instance)
|
|
||||||
else:
|
|
||||||
self._im = None
|
|
||||||
|
|
||||||
def load_from_file(self, media_instance):
|
|
||||||
from media.convert import get_pil_image
|
|
||||||
#
|
|
||||||
self._im = get_pil_image(media_instance)
|
|
||||||
if self._im is None:
|
|
||||||
return False
|
|
||||||
try:
|
|
||||||
self._exif = dict(self._im._getexif().items())
|
|
||||||
except AttributeError:
|
|
||||||
self._exif = {}
|
|
||||||
if type(self._im) is not Image.Image:
|
|
||||||
self._im = self._im.copy()
|
|
||||||
logger.debug('loading image from %s', repr(media_instance))
|
|
||||||
return True
|
|
||||||
|
|
||||||
def save(self, full_path):
|
|
||||||
if self._im is None:
|
|
||||||
logger.warning('No image available to be saved (%s)', repr(full_path))
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
logger.debug('Saving image to %s', repr(full_path))
|
|
||||||
with open(full_path, 'w') as fh:
|
|
||||||
im = self._im.convert('RGB')
|
|
||||||
im.save(fh, 'JPEG')
|
|
||||||
return True
|
|
||||||
|
|
||||||
def image_data(self):
|
|
||||||
im = self._im.copy().convert('RGB')
|
|
||||||
output = io.BytesIO()
|
|
||||||
im.save(output, format='JPEG')
|
|
||||||
return output.getvalue()
|
|
||||||
|
|
||||||
def resize(self, max_size):
|
|
||||||
if self._im is None:
|
|
||||||
logger.warning('No image available to be resized')
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
logger.debug('Resizing picture to max %d pixel in whatever direction', max_size)
|
|
||||||
x, y = self._im.size
|
|
||||||
xy_max = max(x, y)
|
|
||||||
self._im = self._im.resize((int(x * float(max_size) / xy_max), int(y * float(max_size) / xy_max)), Image.NEAREST).rotate(0)
|
|
||||||
return True
|
|
||||||
|
|
||||||
def rotate_by_orientation(self, orientation=None):
|
|
||||||
if self._im is None:
|
|
||||||
logger.warning('No image available, rotation not possible')
|
|
||||||
return False
|
|
||||||
|
|
||||||
if orientation is None:
|
|
||||||
exif_tags = dict((v, k) for k, v in ExifTags.TAGS.items())
|
|
||||||
try:
|
|
||||||
orientation = self._exif[exif_tags['Orientation']]
|
|
||||||
logger.debug("No orientation given, orientation %s extract from exif data", repr(orientation))
|
|
||||||
except KeyError:
|
|
||||||
return False
|
|
||||||
|
|
||||||
if orientation == ORIENTATION_HALF_ROTATED:
|
|
||||||
angle = 180
|
|
||||||
elif orientation == ORIENTATION_LEFT_ROTATED:
|
|
||||||
angle = 270
|
|
||||||
elif orientation == ORIENTATION_RIGHT_ROTATED:
|
|
||||||
angle = 90
|
|
||||||
else:
|
|
||||||
if type(orientation) == int and orientation > 8:
|
|
||||||
logger.warning('Orientation %s unknown for rotation', repr(orientation))
|
|
||||||
return False
|
|
||||||
logger.debug('Rotating picture by %d (deg)', angle)
|
|
||||||
self._im = self._im.rotate(angle, expand=True)
|
|
||||||
return True
|
|
||||||
|
|
||||||
def join(self, join_image, join_pos=JOIN_TOP_RIGHT, opacity=0.7):
|
|
||||||
from media.convert import get_pil_image
|
|
||||||
|
|
||||||
def rgba_copy(im):
|
|
||||||
if im.mode != 'RGBA':
|
|
||||||
return im.convert('RGBA')
|
|
||||||
else:
|
|
||||||
return im.copy()
|
|
||||||
|
|
||||||
if self._im is None:
|
|
||||||
logger.warning('No image available, joining not possible')
|
|
||||||
return False
|
|
||||||
|
|
||||||
# ensure type of join_image is PIL.Image
|
|
||||||
join_image = get_pil_image(join_image)
|
|
||||||
if join_image is None:
|
|
||||||
logger.warning('Image to be joined is not supported %s', repr(join_image))
|
|
||||||
return False
|
|
||||||
|
|
||||||
im2 = rgba_copy(join_image)
|
|
||||||
# change opacity of im2
|
|
||||||
alpha = im2.split()[3]
|
|
||||||
alpha = ImageEnhance.Brightness(alpha).enhance(opacity)
|
|
||||||
im2.putalpha(alpha)
|
|
||||||
|
|
||||||
self._im = rgba_copy(self._im)
|
|
||||||
|
|
||||||
# create a transparent layer
|
|
||||||
layer = Image.new('RGBA', self._im.size, (0, 0, 0, 0))
|
|
||||||
# draw im2 in layer
|
|
||||||
if join_pos == JOIN_TOP_LEFT:
|
|
||||||
layer.paste(im2, (0, 0))
|
|
||||||
elif join_pos == JOIN_TOP_RIGHT:
|
|
||||||
layer.paste(im2, ((self._im.size[0] - im2.size[0]), 0))
|
|
||||||
elif join_pos == JOIN_BOT_LEFT:
|
|
||||||
layer.paste(im2, (0, (self._im.size[1] - im2.size[1])))
|
|
||||||
elif join_pos == JOIN_BOT_RIGHT:
|
|
||||||
layer.paste(im2, ((self._im.size[0] - im2.size[0]), (self._im.size[1] - im2.size[1])))
|
|
||||||
elif join_pos == JOIN_CENTER:
|
|
||||||
layer.paste(im2, (int((self._im.size[0] - im2.size[0]) / 2), int((self._im.size[1] - im2.size[1]) / 2)))
|
|
||||||
else:
|
|
||||||
logger.warning("Join position value %s is not supported", join_pos)
|
|
||||||
return False
|
|
||||||
|
|
||||||
logger.debug('Joining two images')
|
|
||||||
self._im = Image.composite(layer, self._im, layer)
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
36
common.py
36
common.py
@ -1,12 +1,42 @@
|
|||||||
import os
|
import os
|
||||||
|
import discid
|
||||||
|
|
||||||
|
KEY_ALBUM = 'album'
|
||||||
|
KEY_APERTURE = 'aperture'
|
||||||
|
KEY_ARTIST = 'artist'
|
||||||
|
KEY_BITRATE = 'bitrate'
|
||||||
|
KEY_CAMERA = 'camera'
|
||||||
|
KEY_DURATION = 'duration'
|
||||||
|
KEY_EXPOSURE_PROGRAM = 'exposure_program'
|
||||||
|
KEY_EXPOSURE_TIME = 'exposure_time'
|
||||||
|
KEY_FLASH = 'flash'
|
||||||
|
KEY_FOCAL_LENGTH = 'focal_length'
|
||||||
|
KEY_GENRE = 'genre'
|
||||||
|
KEY_GPS = 'gps'
|
||||||
|
KEY_HEIGHT = 'height'
|
||||||
|
KEY_ISO = 'iso'
|
||||||
|
KEY_ORIENTATION = 'orientation'
|
||||||
|
KEY_RATIO = 'ratio'
|
||||||
|
KEY_SIZE = 'size'
|
||||||
|
KEY_TIME = 'time' # USE time.localtime(value) or datetime.fromtimestamp(value) to convert the timestamp
|
||||||
|
KEY_TIME_IS_SUBSTITUTION = 'tm_is_subst'
|
||||||
|
KEY_TITLE = 'title'
|
||||||
|
KEY_TRACK = 'track'
|
||||||
|
KEY_WIDTH = 'width'
|
||||||
|
KEY_YEAR = 'year'
|
||||||
|
|
||||||
FILETYPE_AUDIO = 'audio'
|
FILETYPE_AUDIO = 'audio'
|
||||||
FILETYPE_IMAGE = 'image'
|
FILETYPE_IMAGE = 'image'
|
||||||
FILETYPE_VIDEO = 'video'
|
FILETYPE_VIDEO = 'video'
|
||||||
|
FILETYPE_DISC = 'disc'
|
||||||
|
|
||||||
|
CALLBACK_CDDB_CHOICE = 0
|
||||||
|
CALLBACK_MAN_INPUT = 1
|
||||||
|
|
||||||
EXTENTIONS_AUDIO = ['.mp3', ]
|
EXTENTIONS_AUDIO = ['.mp3', ]
|
||||||
EXTENTIONS_IMAGE = ['.jpg', '.jpeg', '.jpe', '.png', '.tif', '.tiff', '.gif', ]
|
EXTENTIONS_IMAGE = ['.jpg', '.jpeg', '.jpe', '.png', '.tif', '.tiff', '.gif', ]
|
||||||
EXTENTIONS_VIDEO = ['.avi', '.mpg', '.mpeg', '.mpe', '.mov', '.qt', '.mp4', '.webm', '.ogv', '.flv', '.3gp', ]
|
EXTENTIONS_VIDEO = ['.avi', '.mpg', '.mpeg', '.mpe', '.mov', '.qt', '.mp4', '.webm', '.ogv', '.flv', '.3gp', ]
|
||||||
|
PREFIX_DISC = '/dev/'
|
||||||
|
|
||||||
|
|
||||||
def get_filetype(full_path):
|
def get_filetype(full_path):
|
||||||
@ -17,3 +47,9 @@ def get_filetype(full_path):
|
|||||||
return FILETYPE_IMAGE
|
return FILETYPE_IMAGE
|
||||||
elif ext in EXTENTIONS_VIDEO:
|
elif ext in EXTENTIONS_VIDEO:
|
||||||
return FILETYPE_VIDEO
|
return FILETYPE_VIDEO
|
||||||
|
elif full_path.startswith(PREFIX_DISC):
|
||||||
|
return FILETYPE_DISC
|
||||||
|
|
||||||
|
|
||||||
|
def get_disc_device():
|
||||||
|
return discid.get_default_device()
|
||||||
|
102
convert.py
102
convert.py
@ -1,8 +1,17 @@
|
|||||||
import io
|
import io
|
||||||
from media import common, logger
|
from media import common
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
import subprocess
|
import subprocess
|
||||||
import platform
|
import platform
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
try:
|
||||||
|
from config import APP_NAME as ROOT_LOGGER_NAME
|
||||||
|
except ImportError:
|
||||||
|
ROOT_LOGGER_NAME = 'root'
|
||||||
|
logger = logging.getLogger(ROOT_LOGGER_NAME).getChild(__name__)
|
||||||
|
|
||||||
|
|
||||||
def get_pil_image(media_instance):
|
def get_pil_image(media_instance):
|
||||||
@ -33,3 +42,94 @@ def get_pil_image(media_instance):
|
|||||||
return media_instance.copy()
|
return media_instance.copy()
|
||||||
else:
|
else:
|
||||||
logger.warning('Instance type is not supported: %s' % type(media_instance))
|
logger.warning('Instance type is not supported: %s' % type(media_instance))
|
||||||
|
|
||||||
|
|
||||||
|
def FilenameFilter(filename: str) -> str:
|
||||||
|
# WHITELIST = [os.path.sep, os.path.extsep]
|
||||||
|
WHITELIST = [chr(x) for x in range(ord('0'), ord('9') + 1)]
|
||||||
|
WHITELIST += [chr(x) for x in range(ord('a'), ord('z') + 1)]
|
||||||
|
WHITELIST += ["ä", "ö", "ü", "ß"]
|
||||||
|
#
|
||||||
|
rv = ""
|
||||||
|
for c in filename.lower():
|
||||||
|
rv += c if c in WHITELIST else '_'
|
||||||
|
return rv
|
||||||
|
|
||||||
|
|
||||||
|
def track_to_targetpath(basepath: str, track: dict, ext: str):
|
||||||
|
return os.path.join(
|
||||||
|
basepath,
|
||||||
|
FilenameFilter(track[common.KEY_ARTIST]),
|
||||||
|
"%04d_" % track[common.KEY_YEAR] + FilenameFilter(track[common.KEY_ALBUM]),
|
||||||
|
"%02d_" % track[common.KEY_TRACK] + FilenameFilter(track[common.KEY_TITLE]) + "." + ext
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def disc_track_rip(track_num: int, target_file: str, progress_callback):
|
||||||
|
FAC_SEC_VAL = 1224
|
||||||
|
#
|
||||||
|
cdp_cmd = subprocess.getoutput("which cdparanoia")
|
||||||
|
if not cdp_cmd:
|
||||||
|
logger.error("cdparanoia is required for ripping. You need to install it to your system.")
|
||||||
|
else:
|
||||||
|
cmd = [cdp_cmd, "-e", "-X", "%d" % track_num, target_file]
|
||||||
|
cdp = subprocess.Popen(cmd, text=True, stderr=subprocess.PIPE)
|
||||||
|
#
|
||||||
|
rval = 0
|
||||||
|
min_sec = None
|
||||||
|
max_sec = None
|
||||||
|
min_read = None
|
||||||
|
while (out := cdp.stderr.readline()) != "":
|
||||||
|
out = out.strip()
|
||||||
|
# identify minimum sector
|
||||||
|
if ("Ripping from sector" in out):
|
||||||
|
min_sec = int(list(filter(None, out.split(" ")))[3])
|
||||||
|
# identify maximum sector
|
||||||
|
if ("to sector" in out):
|
||||||
|
max_sec = int(list(filter(None, out.split(" ")))[2])
|
||||||
|
# identify progress
|
||||||
|
if "[read]" in out:
|
||||||
|
val = int(out.split(" ")[-1])
|
||||||
|
if not min_read:
|
||||||
|
min_read = val
|
||||||
|
rval = max(val, rval)
|
||||||
|
try:
|
||||||
|
dsec = max_sec - min_sec
|
||||||
|
except TypeError:
|
||||||
|
logger.exception("Error while parsing cdparanoia. Start and End sector could not be detrmined.")
|
||||||
|
else:
|
||||||
|
p = (rval - min_read) / FAC_SEC_VAL / dsec
|
||||||
|
p = min(p, 1)
|
||||||
|
progress_callback(p)
|
||||||
|
progress_callback(1)
|
||||||
|
return cdp.wait()
|
||||||
|
|
||||||
|
|
||||||
|
def wav_to_mp3(infile: str, basepath: str, track_information, progress_callback, bitrate=256, vbr=0, quaulity=0):
|
||||||
|
lame_parameter = {
|
||||||
|
common.KEY_ARTIST: '--ta',
|
||||||
|
common.KEY_ALBUM: '--tl',
|
||||||
|
common.KEY_YEAR: '--ty',
|
||||||
|
common.KEY_GENRE: '--tg',
|
||||||
|
common.KEY_TRACK: '--tn',
|
||||||
|
common.KEY_TITLE: '--tt'
|
||||||
|
}
|
||||||
|
lame_cmd = subprocess.getoutput("which lame")
|
||||||
|
if not lame_cmd:
|
||||||
|
logger.error("lame is required for encoding. You need to install it to your system.")
|
||||||
|
else:
|
||||||
|
outfile = track_to_targetpath(basepath, track_information, 'mp3')
|
||||||
|
cmd = [lame_cmd, "-b", str(bitrate), "-V", str(vbr), "--vbr-old", "-q", str(quaulity), infile, outfile]
|
||||||
|
cmd.extend(["--tc", "Encoded by lame"])
|
||||||
|
for key in track_information:
|
||||||
|
cmd.extend([lame_parameter[key], str(track_information[key])])
|
||||||
|
lame = subprocess.Popen(cmd, text=True, stderr=subprocess.PIPE)
|
||||||
|
while (out := lame.stderr.readline()) != "":
|
||||||
|
out = out.strip()
|
||||||
|
posb = out.find("(")
|
||||||
|
posp = out.find("%")
|
||||||
|
if posb >= 0 and posp >= 0:
|
||||||
|
p = int(out[posb+1:posp]) / 100
|
||||||
|
progress_callback(p)
|
||||||
|
progress_callback(1)
|
||||||
|
return lame.wait()
|
||||||
|
150
image.py
Normal file
150
image.py
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
import io
|
||||||
|
import logging
|
||||||
|
from PIL import Image, ImageEnhance, ExifTags
|
||||||
|
|
||||||
|
try:
|
||||||
|
from config import APP_NAME as ROOT_LOGGER_NAME
|
||||||
|
except ImportError:
|
||||||
|
ROOT_LOGGER_NAME = 'root'
|
||||||
|
logger = logging.getLogger(ROOT_LOGGER_NAME).getChild(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
ORIENTATION_NORMAL = 1
|
||||||
|
ORIENTATION_VERTICAL_MIRRORED = 2
|
||||||
|
ORIENTATION_HALF_ROTATED = 3
|
||||||
|
ORIENTATION_HORIZONTAL_MIRRORED = 4
|
||||||
|
ORIENTATION_LEFT_ROTATED = 6
|
||||||
|
ORIENTATION_RIGHT_ROTATED = 8
|
||||||
|
|
||||||
|
JOIN_TOP_LEFT = 1
|
||||||
|
JOIN_TOP_RIGHT = 2
|
||||||
|
JOIN_BOT_LEFT = 3
|
||||||
|
JOIN_BOT_RIGHT = 4
|
||||||
|
JOIN_CENTER = 5
|
||||||
|
|
||||||
|
|
||||||
|
class image(object):
|
||||||
|
def __init__(self, media_instance=None):
|
||||||
|
if media_instance is not None:
|
||||||
|
self.load_from_file(media_instance)
|
||||||
|
else:
|
||||||
|
self._im = None
|
||||||
|
|
||||||
|
def load_from_file(self, media_instance):
|
||||||
|
from media.convert import get_pil_image
|
||||||
|
#
|
||||||
|
self._im = get_pil_image(media_instance)
|
||||||
|
if self._im is None:
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
self._exif = dict(self._im._getexif().items())
|
||||||
|
except AttributeError:
|
||||||
|
self._exif = {}
|
||||||
|
if type(self._im) is not Image.Image:
|
||||||
|
self._im = self._im.copy()
|
||||||
|
logger.debug('loading image from %s', repr(media_instance))
|
||||||
|
return True
|
||||||
|
|
||||||
|
def save(self, full_path):
|
||||||
|
if self._im is None:
|
||||||
|
logger.warning('No image available to be saved (%s)', repr(full_path))
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
logger.debug('Saving image to %s', repr(full_path))
|
||||||
|
with open(full_path, 'w') as fh:
|
||||||
|
im = self._im.convert('RGB')
|
||||||
|
im.save(fh, 'JPEG')
|
||||||
|
return True
|
||||||
|
|
||||||
|
def image_data(self):
|
||||||
|
im = self._im.copy().convert('RGB')
|
||||||
|
output = io.BytesIO()
|
||||||
|
im.save(output, format='JPEG')
|
||||||
|
return output.getvalue()
|
||||||
|
|
||||||
|
def resize(self, max_size):
|
||||||
|
if self._im is None:
|
||||||
|
logger.warning('No image available to be resized')
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
logger.debug('Resizing picture to max %d pixel in whatever direction', max_size)
|
||||||
|
x, y = self._im.size
|
||||||
|
xy_max = max(x, y)
|
||||||
|
self._im = self._im.resize((int(x * float(max_size) / xy_max), int(y * float(max_size) / xy_max)), Image.NEAREST).rotate(0)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def rotate_by_orientation(self, orientation=None):
|
||||||
|
if self._im is None:
|
||||||
|
logger.warning('No image available, rotation not possible')
|
||||||
|
return False
|
||||||
|
|
||||||
|
if orientation is None:
|
||||||
|
exif_tags = dict((v, k) for k, v in ExifTags.TAGS.items())
|
||||||
|
try:
|
||||||
|
orientation = self._exif[exif_tags['Orientation']]
|
||||||
|
logger.debug("No orientation given, orientation %s extract from exif data", repr(orientation))
|
||||||
|
except KeyError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if orientation == ORIENTATION_HALF_ROTATED:
|
||||||
|
angle = 180
|
||||||
|
elif orientation == ORIENTATION_LEFT_ROTATED:
|
||||||
|
angle = 270
|
||||||
|
elif orientation == ORIENTATION_RIGHT_ROTATED:
|
||||||
|
angle = 90
|
||||||
|
else:
|
||||||
|
if type(orientation) == int and orientation > 8:
|
||||||
|
logger.warning('Orientation %s unknown for rotation', repr(orientation))
|
||||||
|
return False
|
||||||
|
logger.debug('Rotating picture by %d (deg)', angle)
|
||||||
|
self._im = self._im.rotate(angle, expand=True)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def join(self, join_image, join_pos=JOIN_TOP_RIGHT, opacity=0.7):
|
||||||
|
from media.convert import get_pil_image
|
||||||
|
|
||||||
|
def rgba_copy(im):
|
||||||
|
if im.mode != 'RGBA':
|
||||||
|
return im.convert('RGBA')
|
||||||
|
else:
|
||||||
|
return im.copy()
|
||||||
|
|
||||||
|
if self._im is None:
|
||||||
|
logger.warning('No image available, joining not possible')
|
||||||
|
return False
|
||||||
|
|
||||||
|
# ensure type of join_image is PIL.Image
|
||||||
|
join_image = get_pil_image(join_image)
|
||||||
|
if join_image is None:
|
||||||
|
logger.warning('Image to be joined is not supported %s', repr(join_image))
|
||||||
|
return False
|
||||||
|
|
||||||
|
im2 = rgba_copy(join_image)
|
||||||
|
# change opacity of im2
|
||||||
|
alpha = im2.split()[3]
|
||||||
|
alpha = ImageEnhance.Brightness(alpha).enhance(opacity)
|
||||||
|
im2.putalpha(alpha)
|
||||||
|
|
||||||
|
self._im = rgba_copy(self._im)
|
||||||
|
|
||||||
|
# create a transparent layer
|
||||||
|
layer = Image.new('RGBA', self._im.size, (0, 0, 0, 0))
|
||||||
|
# draw im2 in layer
|
||||||
|
if join_pos == JOIN_TOP_LEFT:
|
||||||
|
layer.paste(im2, (0, 0))
|
||||||
|
elif join_pos == JOIN_TOP_RIGHT:
|
||||||
|
layer.paste(im2, ((self._im.size[0] - im2.size[0]), 0))
|
||||||
|
elif join_pos == JOIN_BOT_LEFT:
|
||||||
|
layer.paste(im2, (0, (self._im.size[1] - im2.size[1])))
|
||||||
|
elif join_pos == JOIN_BOT_RIGHT:
|
||||||
|
layer.paste(im2, ((self._im.size[0] - im2.size[0]), (self._im.size[1] - im2.size[1])))
|
||||||
|
elif join_pos == JOIN_CENTER:
|
||||||
|
layer.paste(im2, (int((self._im.size[0] - im2.size[0]) / 2), int((self._im.size[1] - im2.size[1]) / 2)))
|
||||||
|
else:
|
||||||
|
logger.warning("Join position value %s is not supported", join_pos)
|
||||||
|
return False
|
||||||
|
|
||||||
|
logger.debug('Joining two images')
|
||||||
|
self._im = Image.composite(layer, self._im, layer)
|
||||||
|
|
||||||
|
return True
|
186
metadata.py
186
metadata.py
@ -1,47 +1,131 @@
|
|||||||
import logging
|
import media.CDDB
|
||||||
import os
|
|
||||||
from PIL import Image
|
|
||||||
import math
|
|
||||||
import media
|
|
||||||
import subprocess
|
|
||||||
import time
|
import time
|
||||||
|
import subprocess
|
||||||
|
from media import common
|
||||||
|
import math
|
||||||
|
from PIL import Image
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
logger = media.logger
|
try:
|
||||||
|
from config import APP_NAME as ROOT_LOGGER_NAME
|
||||||
|
except ImportError:
|
||||||
|
ROOT_LOGGER_NAME = 'root'
|
||||||
|
logger = logging.getLogger(ROOT_LOGGER_NAME).getChild(__name__)
|
||||||
|
try:
|
||||||
|
import discid
|
||||||
|
except OSError:
|
||||||
|
logger.exception("You might install python3-libdiscid")
|
||||||
|
|
||||||
__KEY_CAMERA_VENDOR__ = 'camera_vendor'
|
__KEY_CAMERA_VENDOR__ = 'camera_vendor'
|
||||||
__KEY_CAMERA_MODEL__ = 'camera_model'
|
__KEY_CAMERA_MODEL__ = 'camera_model'
|
||||||
|
|
||||||
|
|
||||||
|
def get_media_data(full_path, user_callback=None):
|
||||||
|
#
|
||||||
|
ft = common.get_filetype(full_path)
|
||||||
|
#
|
||||||
|
if ft == common.FILETYPE_AUDIO:
|
||||||
|
return get_audio_data(full_path)
|
||||||
|
elif ft == common.FILETYPE_IMAGE:
|
||||||
|
return get_image_data(full_path)
|
||||||
|
elif ft == common.FILETYPE_VIDEO:
|
||||||
|
return get_video_data(full_path)
|
||||||
|
elif ft == common.FILETYPE_DISC:
|
||||||
|
return get_disc_data(full_path, user_callback)
|
||||||
|
else:
|
||||||
|
logger.warning('Filetype not known: %s', full_path)
|
||||||
|
|
||||||
|
|
||||||
def get_audio_data(full_path):
|
def get_audio_data(full_path):
|
||||||
conv_key_dict = {}
|
conv_key_dict = {}
|
||||||
conv_key_dict['album'] = (str, media.KEY_ALBUM)
|
conv_key_dict['album'] = (str, common.KEY_ALBUM)
|
||||||
conv_key_dict['TAG:album'] = (str, media.KEY_ALBUM)
|
conv_key_dict['TAG:album'] = (str, common.KEY_ALBUM)
|
||||||
conv_key_dict['TAG:artist'] = (str, media.KEY_ARTIST)
|
conv_key_dict['TAG:artist'] = (str, common.KEY_ARTIST)
|
||||||
conv_key_dict['artist'] = (str, media.KEY_ARTIST)
|
conv_key_dict['artist'] = (str, common.KEY_ARTIST)
|
||||||
conv_key_dict['bit_rate'] = (__int_conv__, media.KEY_BITRATE)
|
conv_key_dict['bit_rate'] = (__int_conv__, common.KEY_BITRATE)
|
||||||
conv_key_dict['duration'] = (float, media.KEY_DURATION)
|
conv_key_dict['duration'] = (float, common.KEY_DURATION)
|
||||||
conv_key_dict['TAG:genre'] = (str, media.KEY_GENRE)
|
conv_key_dict['TAG:genre'] = (str, common.KEY_GENRE)
|
||||||
conv_key_dict['genre'] = (str, media.KEY_GENRE)
|
conv_key_dict['genre'] = (str, common.KEY_GENRE)
|
||||||
conv_key_dict['TAG:title'] = (str, media.KEY_TITLE)
|
conv_key_dict['TAG:title'] = (str, common.KEY_TITLE)
|
||||||
conv_key_dict['title'] = (str, media.KEY_TITLE)
|
conv_key_dict['title'] = (str, common.KEY_TITLE)
|
||||||
conv_key_dict['TAG:track'] = (__int_conv__, media.KEY_TRACK)
|
conv_key_dict['TAG:track'] = (__int_conv__, common.KEY_TRACK)
|
||||||
conv_key_dict['track'] = (__int_conv__, media.KEY_TRACK)
|
conv_key_dict['track'] = (__int_conv__, common.KEY_TRACK)
|
||||||
conv_key_dict['TAG:date'] = (__int_conv__, media.KEY_YEAR)
|
conv_key_dict['TAG:date'] = (__int_conv__, common.KEY_YEAR)
|
||||||
conv_key_dict['date'] = (__int_conv__, media.KEY_YEAR)
|
conv_key_dict['date'] = (__int_conv__, common.KEY_YEAR)
|
||||||
return __adapt__data__(__get_xxprobe_data__(full_path, conv_key_dict), full_path)
|
return __adapt__data__(__get_xxprobe_data__(full_path, conv_key_dict), full_path)
|
||||||
|
|
||||||
|
|
||||||
|
def get_disc_data(full_path, user_callback):
|
||||||
|
#
|
||||||
|
# Read Information from CDDB database
|
||||||
|
#
|
||||||
|
did = media.CDDB.discid()
|
||||||
|
if did is None:
|
||||||
|
logger.error("Could not determine disc id...")
|
||||||
|
sys.exit(1)
|
||||||
|
q = media.CDDB.query(did)
|
||||||
|
if q is None:
|
||||||
|
data = {
|
||||||
|
common.KEY_ARTIST: None,
|
||||||
|
common.KEY_ALBUM: None,
|
||||||
|
common.KEY_YEAR: None,
|
||||||
|
common.KEY_GENRE: None
|
||||||
|
}
|
||||||
|
for i in range(0, int(did.split('+')[1])):
|
||||||
|
data["track_%02d" % (i + 1)] = None
|
||||||
|
data = user_callback(common.CALLBACK_MAN_INPUT, data)
|
||||||
|
return media.CDDB.my_disc_metadata(**data)
|
||||||
|
|
||||||
|
if len(q) == 1:
|
||||||
|
# Only one entry
|
||||||
|
did = tuple(q.keys())[0]
|
||||||
|
else:
|
||||||
|
# multiple entries (choose)
|
||||||
|
if user_callback is None:
|
||||||
|
logger.warning("No usercallback to handle multiple cddb choices...")
|
||||||
|
sys.exit(1)
|
||||||
|
did = user_callback(common.CALLBACK_CDDB_CHOICE, q)
|
||||||
|
|
||||||
|
return media.CDDB.cddb(did)
|
||||||
|
"""
|
||||||
|
musicbrainzngs.set_useragent("pyrip", "0.1", "your@mail")
|
||||||
|
disc = discid.read()
|
||||||
|
disc_id = disc.id
|
||||||
|
disc_data = {}
|
||||||
|
try:
|
||||||
|
result = musicbrainzngs.get_releases_by_discid(disc_id, includes=["artists", "recordings"])
|
||||||
|
except musicbrainzngs.ResponseError:
|
||||||
|
logger.exception("disc not found or bad response")
|
||||||
|
sys.exit(1)
|
||||||
|
else:
|
||||||
|
disc_data[common.KEY_ARTIST] = result["disc"]["release-list"][0]["artist-credit-phrase"]
|
||||||
|
disc_data[common.KEY_ALBUM] = result["disc"]["release-list"][0]["title"]
|
||||||
|
disc_data[common.KEY_YEAR] = int(result["disc"]["release-list"][0]["date"][:4])
|
||||||
|
data_copy = dict(disc_data)
|
||||||
|
disc_data["id"] = result["disc"]["release-list"][0]["id"]
|
||||||
|
disc_data["tracks"] = []
|
||||||
|
# get tracklist
|
||||||
|
for entry in result["disc"]["release-list"][0]["medium-list"][0]["track-list"]:
|
||||||
|
track = dict(data_copy)
|
||||||
|
track[common.KEY_TITLE] = entry["recording"]["title"]
|
||||||
|
track[common.KEY_TRACK] = int(entry['number'])
|
||||||
|
disc_data["tracks"].append(track)
|
||||||
|
return disc_data
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
def get_video_data(full_path):
|
def get_video_data(full_path):
|
||||||
conv_key_dict = {}
|
conv_key_dict = {}
|
||||||
conv_key_dict['creation_time'] = (__vid_datetime_conv__, media.KEY_TIME)
|
conv_key_dict['creation_time'] = (__vid_datetime_conv__, common.KEY_TIME)
|
||||||
conv_key_dict['TAG:creation_time'] = (__vid_datetime_conv__, media.KEY_TIME)
|
conv_key_dict['TAG:creation_time'] = (__vid_datetime_conv__, common.KEY_TIME)
|
||||||
conv_key_dict['bit_rate'] = (__int_conv__, media.KEY_BITRATE)
|
conv_key_dict['bit_rate'] = (__int_conv__, common.KEY_BITRATE)
|
||||||
conv_key_dict['duration'] = (float, media.KEY_DURATION)
|
conv_key_dict['duration'] = (float, common.KEY_DURATION)
|
||||||
conv_key_dict['height'] = (__int_conv__, media.KEY_HEIGHT)
|
conv_key_dict['height'] = (__int_conv__, common.KEY_HEIGHT)
|
||||||
conv_key_dict['width'] = (__int_conv__, media.KEY_WIDTH)
|
conv_key_dict['width'] = (__int_conv__, common.KEY_WIDTH)
|
||||||
conv_key_dict['display_aspect_ratio'] = (__ratio_conv__, media.KEY_RATIO)
|
conv_key_dict['display_aspect_ratio'] = (__ratio_conv__, common.KEY_RATIO)
|
||||||
return __adapt__data__(__get_xxprobe_data__(full_path, conv_key_dict), full_path)
|
return __adapt__data__(__get_xxprobe_data__(full_path, conv_key_dict), full_path)
|
||||||
|
|
||||||
|
|
||||||
@ -50,25 +134,25 @@ def get_image_data(full_path):
|
|||||||
|
|
||||||
|
|
||||||
def __adapt__data__(data, full_path):
|
def __adapt__data__(data, full_path):
|
||||||
data[media.KEY_SIZE] = os.path.getsize(full_path)
|
data[common.KEY_SIZE] = os.path.getsize(full_path)
|
||||||
# Join Camera Vendor and Camera Model
|
# Join Camera Vendor and Camera Model
|
||||||
if __KEY_CAMERA_MODEL__ in data and __KEY_CAMERA_VENDOR__ in data:
|
if __KEY_CAMERA_MODEL__ in data and __KEY_CAMERA_VENDOR__ in data:
|
||||||
model = data.pop(__KEY_CAMERA_MODEL__)
|
model = data.pop(__KEY_CAMERA_MODEL__)
|
||||||
vendor = data.pop(__KEY_CAMERA_VENDOR__)
|
vendor = data.pop(__KEY_CAMERA_VENDOR__)
|
||||||
data[media.KEY_CAMERA] = '%s: %s' % (vendor, model)
|
data[common.KEY_CAMERA] = '%s: %s' % (vendor, model)
|
||||||
# Add time if not exists
|
# Add time if not exists
|
||||||
if media.KEY_TIME not in data:
|
if common.KEY_TIME not in data:
|
||||||
if media.KEY_YEAR in data and media.KEY_TRACK in data:
|
if common.KEY_YEAR in data and common.KEY_TRACK in data:
|
||||||
if data[media.KEY_YEAR] != 0: # ignore year 0 - must be wrong
|
if data[common.KEY_YEAR] != 0: # ignore year 0 - must be wrong
|
||||||
# Use a date where track 1 is the newest in the given year
|
# Use a date where track 1 is the newest in the given year
|
||||||
minute = int(data[media.KEY_TRACK] / 60)
|
minute = int(data[common.KEY_TRACK] / 60)
|
||||||
second = (data[media.KEY_TRACK] - 60 * minute) % 60
|
second = (data[common.KEY_TRACK] - 60 * minute) % 60
|
||||||
#
|
#
|
||||||
data[media.KEY_TIME] = int(time.mktime((data[media.KEY_YEAR], 1, 1, 0, 59 - minute, 59 - second, 0, 0, 0)))
|
data[common.KEY_TIME] = int(time.mktime((data[common.KEY_YEAR], 1, 1, 0, 59 - minute, 59 - second, 0, 0, 0)))
|
||||||
data[media.KEY_TIME_IS_SUBSTITUTION] = True
|
data[common.KEY_TIME_IS_SUBSTITUTION] = True
|
||||||
else:
|
else:
|
||||||
data[media.KEY_TIME] = int(os.path.getmtime(full_path))
|
data[common.KEY_TIME] = int(os.path.getmtime(full_path))
|
||||||
data[media.KEY_TIME_IS_SUBSTITUTION] = True
|
data[common.KEY_TIME_IS_SUBSTITUTION] = True
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
@ -114,19 +198,19 @@ def __get_exif_data__(full_path):
|
|||||||
else:
|
else:
|
||||||
conv_key_dict = {}
|
conv_key_dict = {}
|
||||||
# IMAGE
|
# IMAGE
|
||||||
conv_key_dict[0x9003] = (__datetime_conv__, media.KEY_TIME)
|
conv_key_dict[0x9003] = (__datetime_conv__, common.KEY_TIME)
|
||||||
conv_key_dict[0x8822] = (__exposure_program_conv__, media.KEY_EXPOSURE_PROGRAM)
|
conv_key_dict[0x8822] = (__exposure_program_conv__, common.KEY_EXPOSURE_PROGRAM)
|
||||||
conv_key_dict[0x829A] = (__num_denum_conv__, media.KEY_EXPOSURE_TIME)
|
conv_key_dict[0x829A] = (__num_denum_conv__, common.KEY_EXPOSURE_TIME)
|
||||||
conv_key_dict[0x9209] = (__flash_conv__, media.KEY_FLASH)
|
conv_key_dict[0x9209] = (__flash_conv__, common.KEY_FLASH)
|
||||||
conv_key_dict[0x829D] = (__num_denum_conv__, media.KEY_APERTURE)
|
conv_key_dict[0x829D] = (__num_denum_conv__, common.KEY_APERTURE)
|
||||||
conv_key_dict[0x920A] = (__num_denum_conv__, media.KEY_FOCAL_LENGTH)
|
conv_key_dict[0x920A] = (__num_denum_conv__, common.KEY_FOCAL_LENGTH)
|
||||||
conv_key_dict[0x8825] = (__gps_conv__, media.KEY_GPS)
|
conv_key_dict[0x8825] = (__gps_conv__, common.KEY_GPS)
|
||||||
conv_key_dict[0xA003] = (__int_conv__, media.KEY_HEIGHT)
|
conv_key_dict[0xA003] = (__int_conv__, common.KEY_HEIGHT)
|
||||||
conv_key_dict[0x8827] = (__int_conv__, media.KEY_ISO)
|
conv_key_dict[0x8827] = (__int_conv__, common.KEY_ISO)
|
||||||
conv_key_dict[0x010F] = (str, __KEY_CAMERA_VENDOR__)
|
conv_key_dict[0x010F] = (str, __KEY_CAMERA_VENDOR__)
|
||||||
conv_key_dict[0x0110] = (str, __KEY_CAMERA_MODEL__)
|
conv_key_dict[0x0110] = (str, __KEY_CAMERA_MODEL__)
|
||||||
conv_key_dict[0x0112] = (__int_conv__, media.KEY_ORIENTATION)
|
conv_key_dict[0x0112] = (__int_conv__, common.KEY_ORIENTATION)
|
||||||
conv_key_dict[0xA002] = (__int_conv__, media.KEY_WIDTH)
|
conv_key_dict[0xA002] = (__int_conv__, common.KEY_WIDTH)
|
||||||
for key in conv_key_dict:
|
for key in conv_key_dict:
|
||||||
if key in exif:
|
if key in exif:
|
||||||
tp, name = conv_key_dict[key]
|
tp, name = conv_key_dict[key]
|
||||||
|
2
requirements.txt
Normal file
2
requirements.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
pillow
|
||||||
|
discid
|
Loading…
x
Reference in New Issue
Block a user