Added som features for audio handling (cddb, rip, encode)

This commit is contained in:
Dirk Alders 2024-09-12 20:19:38 +02:00
parent abcf63d02e
commit 402e837551
7 changed files with 603 additions and 244 deletions

138
CDDB.py Normal file
View 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

View File

@ -22,203 +22,52 @@ media (Media Tools)
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__ = []
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.
For more Information read the documentation.""" % __name__.replace('_', '\_')
"""The Module Description"""
__INTERPRETER__ = (3, )
"""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

View File

@ -1,12 +1,42 @@
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_IMAGE = 'image'
FILETYPE_VIDEO = 'video'
FILETYPE_DISC = 'disc'
CALLBACK_CDDB_CHOICE = 0
CALLBACK_MAN_INPUT = 1
EXTENTIONS_AUDIO = ['.mp3', ]
EXTENTIONS_IMAGE = ['.jpg', '.jpeg', '.jpe', '.png', '.tif', '.tiff', '.gif', ]
EXTENTIONS_VIDEO = ['.avi', '.mpg', '.mpeg', '.mpe', '.mov', '.qt', '.mp4', '.webm', '.ogv', '.flv', '.3gp', ]
PREFIX_DISC = '/dev/'
def get_filetype(full_path):
@ -17,3 +47,9 @@ def get_filetype(full_path):
return FILETYPE_IMAGE
elif ext in EXTENTIONS_VIDEO:
return FILETYPE_VIDEO
elif full_path.startswith(PREFIX_DISC):
return FILETYPE_DISC
def get_disc_device():
return discid.get_default_device()

View File

@ -1,8 +1,17 @@
import io
from media import common, logger
from media import common
from PIL import Image
import subprocess
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):
@ -33,3 +42,94 @@ def get_pil_image(media_instance):
return media_instance.copy()
else:
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
View 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

View File

@ -1,47 +1,131 @@
import logging
import os
from PIL import Image
import math
import media
import subprocess
import media.CDDB
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_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):
conv_key_dict = {}
conv_key_dict['album'] = (str, media.KEY_ALBUM)
conv_key_dict['TAG:album'] = (str, media.KEY_ALBUM)
conv_key_dict['TAG:artist'] = (str, media.KEY_ARTIST)
conv_key_dict['artist'] = (str, media.KEY_ARTIST)
conv_key_dict['bit_rate'] = (__int_conv__, media.KEY_BITRATE)
conv_key_dict['duration'] = (float, media.KEY_DURATION)
conv_key_dict['TAG:genre'] = (str, media.KEY_GENRE)
conv_key_dict['genre'] = (str, media.KEY_GENRE)
conv_key_dict['TAG:title'] = (str, media.KEY_TITLE)
conv_key_dict['title'] = (str, media.KEY_TITLE)
conv_key_dict['TAG:track'] = (__int_conv__, media.KEY_TRACK)
conv_key_dict['track'] = (__int_conv__, media.KEY_TRACK)
conv_key_dict['TAG:date'] = (__int_conv__, media.KEY_YEAR)
conv_key_dict['date'] = (__int_conv__, media.KEY_YEAR)
conv_key_dict['album'] = (str, common.KEY_ALBUM)
conv_key_dict['TAG:album'] = (str, common.KEY_ALBUM)
conv_key_dict['TAG:artist'] = (str, common.KEY_ARTIST)
conv_key_dict['artist'] = (str, common.KEY_ARTIST)
conv_key_dict['bit_rate'] = (__int_conv__, common.KEY_BITRATE)
conv_key_dict['duration'] = (float, common.KEY_DURATION)
conv_key_dict['TAG:genre'] = (str, common.KEY_GENRE)
conv_key_dict['genre'] = (str, common.KEY_GENRE)
conv_key_dict['TAG:title'] = (str, common.KEY_TITLE)
conv_key_dict['title'] = (str, common.KEY_TITLE)
conv_key_dict['TAG:track'] = (__int_conv__, common.KEY_TRACK)
conv_key_dict['track'] = (__int_conv__, common.KEY_TRACK)
conv_key_dict['TAG:date'] = (__int_conv__, common.KEY_YEAR)
conv_key_dict['date'] = (__int_conv__, common.KEY_YEAR)
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):
conv_key_dict = {}
conv_key_dict['creation_time'] = (__vid_datetime_conv__, media.KEY_TIME)
conv_key_dict['TAG:creation_time'] = (__vid_datetime_conv__, media.KEY_TIME)
conv_key_dict['bit_rate'] = (__int_conv__, media.KEY_BITRATE)
conv_key_dict['duration'] = (float, media.KEY_DURATION)
conv_key_dict['height'] = (__int_conv__, media.KEY_HEIGHT)
conv_key_dict['width'] = (__int_conv__, media.KEY_WIDTH)
conv_key_dict['display_aspect_ratio'] = (__ratio_conv__, media.KEY_RATIO)
conv_key_dict['creation_time'] = (__vid_datetime_conv__, common.KEY_TIME)
conv_key_dict['TAG:creation_time'] = (__vid_datetime_conv__, common.KEY_TIME)
conv_key_dict['bit_rate'] = (__int_conv__, common.KEY_BITRATE)
conv_key_dict['duration'] = (float, common.KEY_DURATION)
conv_key_dict['height'] = (__int_conv__, common.KEY_HEIGHT)
conv_key_dict['width'] = (__int_conv__, common.KEY_WIDTH)
conv_key_dict['display_aspect_ratio'] = (__ratio_conv__, common.KEY_RATIO)
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):
data[media.KEY_SIZE] = os.path.getsize(full_path)
data[common.KEY_SIZE] = os.path.getsize(full_path)
# Join Camera Vendor and Camera Model
if __KEY_CAMERA_MODEL__ in data and __KEY_CAMERA_VENDOR__ in data:
model = data.pop(__KEY_CAMERA_MODEL__)
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
if media.KEY_TIME not in data:
if media.KEY_YEAR in data and media.KEY_TRACK in data:
if data[media.KEY_YEAR] != 0: # ignore year 0 - must be wrong
if common.KEY_TIME not in data:
if common.KEY_YEAR in data and common.KEY_TRACK in data:
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
minute = int(data[media.KEY_TRACK] / 60)
second = (data[media.KEY_TRACK] - 60 * minute) % 60
minute = int(data[common.KEY_TRACK] / 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[media.KEY_TIME_IS_SUBSTITUTION] = True
data[common.KEY_TIME] = int(time.mktime((data[common.KEY_YEAR], 1, 1, 0, 59 - minute, 59 - second, 0, 0, 0)))
data[common.KEY_TIME_IS_SUBSTITUTION] = True
else:
data[media.KEY_TIME] = int(os.path.getmtime(full_path))
data[media.KEY_TIME_IS_SUBSTITUTION] = True
data[common.KEY_TIME] = int(os.path.getmtime(full_path))
data[common.KEY_TIME_IS_SUBSTITUTION] = True
return data
@ -114,19 +198,19 @@ def __get_exif_data__(full_path):
else:
conv_key_dict = {}
# IMAGE
conv_key_dict[0x9003] = (__datetime_conv__, media.KEY_TIME)
conv_key_dict[0x8822] = (__exposure_program_conv__, media.KEY_EXPOSURE_PROGRAM)
conv_key_dict[0x829A] = (__num_denum_conv__, media.KEY_EXPOSURE_TIME)
conv_key_dict[0x9209] = (__flash_conv__, media.KEY_FLASH)
conv_key_dict[0x829D] = (__num_denum_conv__, media.KEY_APERTURE)
conv_key_dict[0x920A] = (__num_denum_conv__, media.KEY_FOCAL_LENGTH)
conv_key_dict[0x8825] = (__gps_conv__, media.KEY_GPS)
conv_key_dict[0xA003] = (__int_conv__, media.KEY_HEIGHT)
conv_key_dict[0x8827] = (__int_conv__, media.KEY_ISO)
conv_key_dict[0x9003] = (__datetime_conv__, common.KEY_TIME)
conv_key_dict[0x8822] = (__exposure_program_conv__, common.KEY_EXPOSURE_PROGRAM)
conv_key_dict[0x829A] = (__num_denum_conv__, common.KEY_EXPOSURE_TIME)
conv_key_dict[0x9209] = (__flash_conv__, common.KEY_FLASH)
conv_key_dict[0x829D] = (__num_denum_conv__, common.KEY_APERTURE)
conv_key_dict[0x920A] = (__num_denum_conv__, common.KEY_FOCAL_LENGTH)
conv_key_dict[0x8825] = (__gps_conv__, common.KEY_GPS)
conv_key_dict[0xA003] = (__int_conv__, common.KEY_HEIGHT)
conv_key_dict[0x8827] = (__int_conv__, common.KEY_ISO)
conv_key_dict[0x010F] = (str, __KEY_CAMERA_VENDOR__)
conv_key_dict[0x0110] = (str, __KEY_CAMERA_MODEL__)
conv_key_dict[0x0112] = (__int_conv__, media.KEY_ORIENTATION)
conv_key_dict[0xA002] = (__int_conv__, media.KEY_WIDTH)
conv_key_dict[0x0112] = (__int_conv__, common.KEY_ORIENTATION)
conv_key_dict[0xA002] = (__int_conv__, common.KEY_WIDTH)
for key in conv_key_dict:
if key in exif:
tp, name = conv_key_dict[key]

2
requirements.txt Normal file
View File

@ -0,0 +1,2 @@
pillow
discid