Skip to content

Commit

Permalink
Add option to auto embed coverart image (bug 1742)
Browse files Browse the repository at this point in the history
Refactored the code to be able to add filetype specific logic.
Until now we used only the mutagen abstraction to implement one
code for all file types, but this don't work for special needs
(e.g: coverart manipulation)
  • Loading branch information
brot committed Apr 20, 2013
1 parent 63d6df5 commit f1f218d
Showing 1 changed file with 166 additions and 29 deletions.
195 changes: 166 additions & 29 deletions share/gpodder/extensions/tagging.py
Expand Up @@ -23,15 +23,22 @@
# The episode title is written into the title tag
# The podcast title is written into the album tag

import base64
import datetime
import mimetypes
import os

import gpodder
from gpodder import coverart

import logging
logger = logging.getLogger(__name__)

from mutagen import File
from mutagen.flac import Picture
from mutagen.mp3 import MP3
from mutagen.id3 import ID3, APIC
from mutagen.mp4 import MP4Cover

_ = gpodder.gettext

Expand All @@ -47,24 +54,165 @@
'strip_album_from_title': True,
'genre_tag': 'Podcast',
'always_remove_tags': False,
'auto_embed_coverart': False,
}


class AudioFile(object):
def __init__(self, filename, album, title, genre, pubDate, cover):
self.filename = filename
self.album = album
self.title = title
self.genre = genre
self.pubDate = pubDate
self.cover = cover

def remove_tags(self):
audio = File(self.filename, easy=True)
if audio.tags is not None:
audio.delete()
audio.save()

def write_basic_tags(self):
audio = File(self.filename, easy=True)

if audio.tags is None:
audio.add_tags()

if self.album is not None:
audio.tags['album'] = self.album

if self.title is not None:
audio.tags['title'] = self.title

if self.genre is not None:
audio.tags['genre'] = self.genre

if self.pubDate is not None:
audio.tags['date'] = self.pubDate

audio.save()

def insert_coverart(self):
""" implement the cover art logic in the subclass
"""
None

def get_cover_picture(self, cover):
""" Returns mutage Picture class for the cover image
Usefull for OGG and FLAC format
Picture type = cover image
see http://flac.sourceforge.net/documentation_tools_flac.html#encoding_options
"""
f = file(cover)
p = Picture()
p.type = 3
p.data = f.read()
p.mime = mimetypes.guess_type(cover)[0]
f.close()

return p


class OggFile(AudioFile):
def __init__(self, filename, album, title, genre, pubDate, cover):
super(OggFile, self).__init__(filename, album, title, genre, pubDate, cover)

def insert_coverart(self):
audio = File(self.filename, easy=True)
p = self.get_cover_picture(self.cover)
audio['METADATA_BLOCK_PICTURE'] = base64.b64encode(p.write())
audio.save()


class Mp4File(AudioFile):
def __init__(self, filename, album, title, genre, pubDate, cover):
super(Mp4File, self).__init__(filename, album, title, genre, pubDate, cover)

def insert_coverart(self):
audio = File(self.filename)

if self.cover.endswith('png'):
cover_format = MP4Cover.FORMAT_PNG
else:
cover_format = MP4Cover.FORMAT_JPEG

data = open(self.cover, 'rb').read()
audio.tags['covr'] = [MP4Cover(data, cover_format)]
audio.save()


class Mp3File(AudioFile):
def __init__(self, filename, album, title, genre, pubDate, cover):
super(Mp3File, self).__init__(filename, album, title, genre, pubDate, cover)

def insert_coverart(self):
audio = MP3(self.filename, ID3=ID3)

if audio.tags is None:
audio.add_tags()

audio.tags.add(
APIC(
encoding = 3, # 3 is for utf-8
mime = mimetypes.guess_type(self.cover)[0],
type = 3,
desc = u'Cover',
data = open(self.cover).read()
)
)
audio.save()


class gPodderExtension:
def __init__(self, container):
self.container = container

def on_episode_downloaded(self, episode):
info = self.read_episode_info(episode)
self.write_info2file(info)
if info['filename'] is None:
return

self.write_info2file(info, episode)

def get_audio(self, info, episode):
audio = None
cover = None

if self.container.config.auto_embed_coverart:
cover = self.get_cover(episode.channel)

if info['filename'].endswith('.mp3'):
audio = Mp3File(info['filename'],
info['album'],
info['title'],
info['genre'],
info['pubDate'],
cover)
elif info['filename'].endswith('.ogg'):
audio = OggFile(info['filename'],
info['album'],
info['title'],
info['genre'],
info['pubDate'],
cover)
elif info['filename'].endswith('.m4a') or info['filename'].endswith('.mp4'):
audio = Mp4File(info['filename'],
info['album'],
info['title'],
info['genre'],
info['pubDate'],
cover)

logger.info(u'tagging.on_episode_downloaded(%s/%s)' % (episode.channel.title, episode.title))
return audio

def read_episode_info(self, episode):
info = {
'filename': None,
'album': None,
'title': None,
'genre': None,
'pubDate': None
}

Expand All @@ -81,6 +229,9 @@ def read_episode_info(self, episode):
else:
info['title'] = title

if self.container.config.genre_tag is not None:
info['genre'] = self.container.config.genre_tag

# convert pubDate to string
try:
pubDate = datetime.datetime.fromtimestamp(episode.pubDate)
Expand All @@ -95,34 +246,20 @@ def read_episode_info(self, episode):

return info

def write_info2file(self, info):
# open file with mutagen
audio = File(info['filename'], easy=True)
if audio is None:
return
def write_info2file(self, info, episode):
audio = self.get_audio(info, episode)

if self.container.config.always_remove_tags:
if audio.tags is not None:
audio.delete()
audio.remove_tags()
else:
# write title+album information into audio files
if audio.tags is None:
audio.add_tags()

# write album+title
if info['album'] is not None:
audio.tags['album'] = info['album']
if info['title'] is not None:
audio.tags['title'] = info['title']

# write genre tag
if self.container.config.genre_tag is not None:
audio.tags['genre'] = self.container.config.genre_tag
else:
audio.tags['genre'] = ''

# write pubDate
if info['pubDate'] is not None:
audio.tags['date'] = info['pubDate']
audio.write_basic_tags()

audio.save()
if self.container.config.auto_embed_coverart:
audio.insert_coverart()

logger.info(u'tagging.on_episode_downloaded(%s/%s)', episode.channel.title, episode.title)

def get_cover(self, podcast):
downloader = coverart.CoverDownloader()
return downloader.get_cover(podcast.cover_file, podcast.cover_url,
podcast.url, podcast.title, None, None, True)

0 comments on commit f1f218d

Please sign in to comment.