From fd35c3fbaaab7b6b1563af2b01bac35cb6837f99 Mon Sep 17 00:00:00 2001 From: shin Date: Thu, 26 Dec 2019 12:57:48 +0530 Subject: [PATCH] MP4/M4A files use 'mutagen' for thumbnail embedding Previously, AtomicParsley was used to embed thumbnails into moov.udta.meta.ilst atom which contains iTunes style metadata for audio files. Using AtomicParsley for only embedding thumbnails was quite infiuriating.Mutagen can manipulate audio metadata and the module itself is quite lightweight and doesn't require any deps. I (shin-ts) favoured to have a package as addon instead of an executable, because this method is quite convinient in my opinion. --- youtube_dl/postprocessor/embedthumbnail.py | 82 +++++++++++----------- 1 file changed, 42 insertions(+), 40 deletions(-) diff --git a/youtube_dl/postprocessor/embedthumbnail.py b/youtube_dl/postprocessor/embedthumbnail.py index 56be914b8..405ea2dbd 100644 --- a/youtube_dl/postprocessor/embedthumbnail.py +++ b/youtube_dl/postprocessor/embedthumbnail.py @@ -5,6 +5,10 @@ from __future__ import unicode_literals import os import subprocess +import imghdr +from mutagen.id3 import PictureType, ID3, APIC +from mutagen.mp4 import MP4, MP4Cover + from .ffmpeg import FFmpegPostProcessor from ..utils import ( @@ -28,7 +32,6 @@ class EmbedThumbnailPP(FFmpegPostProcessor): def run(self, info): filename = info['filepath'] - temp_filename = prepend_extension(filename, 'temp') if not info.get('thumbnails'): self._downloader.to_screen('[embedthumbnail] There aren\'t any thumbnails to embed') @@ -42,52 +45,51 @@ class EmbedThumbnailPP(FFmpegPostProcessor): return [], info if info['ext'] == 'mp3': - options = [ - '-c', 'copy', '-map', '0', '-map', '1', - '-metadata:s:v', 'title="Album cover"', '-metadata:s:v', 'comment="Cover (Front)"'] - - self._downloader.to_screen('[ffmpeg] Adding thumbnail to "%s"' % filename) - - self.run_ffmpeg_multiple_files([filename, thumbnail_filename], temp_filename, options) - + try: + meta = ID3(filename) + except: + raise EmbedThumbnailPPError("MP3 file doesn't have a existing ID3v2 tag.") + + # Update older tags (eg. ID3v1) to a newer version, + # which supports embedded-thumbnails (e.g ID3v2.3). + # NOTE: ID3v2.4 might not be supported by programs. + meta.update_to_v23() + + # Appends a Cover-front thumbnail, it's the most common + # type of thumbnail distributed with. + meta.add(APIC( + data= open(thumbnail_filename, 'rb').read(), + mime= 'image/'+imghdr.what(thumbnail_filename), + type= PictureType.COVER_FRONT + )) + + meta.save() # Save the changes to file, does in-place replacement. + self._downloader.to_screen('[mutagen.id3] Merged Thumbnail into "%s"' % filename) + if not self._already_have_thumbnail: - os.remove(encodeFilename(thumbnail_filename)) - os.remove(encodeFilename(filename)) - os.rename(encodeFilename(temp_filename), encodeFilename(filename)) + os.remove(encodeFilename(thumbnail_filename)) elif info['ext'] in ['m4a', 'mp4']: - if not check_executable('AtomicParsley', ['-v']): - raise EmbedThumbnailPPError('AtomicParsley was not found. Please install.') + try: + meta = MP4(filename) + except: + raise EmbedThumbnailPPError("MPEG-4 file's atomic structure for embedding isn't correct!") - cmd = [encodeFilename('AtomicParsley', True), - encodeFilename(filename, True), - encodeArgument('--artwork'), - encodeFilename(thumbnail_filename, True), - encodeArgument('-o'), - encodeFilename(temp_filename, True)] + # NOTE: the 'covr' atom is a non-standard MPEG-4 atom, + # Apple iTunes 'M4A' files include the 'moov.udta.meta.ilst' atom. + meta.tags['covr'] = [MP4Cover( + data= open(thumbnail_filename, 'rb').read(), + imageformat= MP4Cover.FORMAT_JPEG if \ + imghdr.what(thumbnail_filename) == 'jpeg' \ + else MP4Cover.FORMAT_PNG + )] - self._downloader.to_screen('[atomicparsley] Adding thumbnail to "%s"' % filename) - - if self._downloader.params.get('verbose', False): - self._downloader.to_screen('[debug] AtomicParsley command line: %s' % shell_quote(cmd)) - - p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - stdout, stderr = p.communicate() - - if p.returncode != 0: - msg = stderr.decode('utf-8', 'replace').strip() - raise EmbedThumbnailPPError(msg) + meta.save() + self._downloader.to_screen('[mutagen.mp4] Merged thumbnail to "%s"' % filename) if not self._already_have_thumbnail: os.remove(encodeFilename(thumbnail_filename)) - # for formats that don't support thumbnails (like 3gp) AtomicParsley - # won't create to the temporary file - if b'No changes' in stdout: - self._downloader.report_warning('The file format doesn\'t support embedding a thumbnail') - else: - os.remove(encodeFilename(filename)) - os.rename(encodeFilename(temp_filename), encodeFilename(filename)) else: - raise EmbedThumbnailPPError('Only mp3 and m4a/mp4 are supported for thumbnail embedding for now.') + raise EmbedThumbnailPPError('Only mp3, m4a/mp4 are supported for thumbnail embedding for now.') - return [], info + return [], info \ No newline at end of file