Is_lossless plugin no longer working with m4a

Tags: #<Tag:0x00007f1c9e87b9c0>

Hello,
I start writing a full script again and I realize that the is_lossless plugin no longer correctly detects M4As.

Is_lossless
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
# -*- coding: utf-8 -*-
PLUGIN_NAME = 'Tagger script functions is_lossless() and is_lossy()'
PLUGIN_AUTHOR = 'Philipp Wolfer'
PLUGIN_DESCRIPTION = 'Tagger script functions to detect if a file is lossless or lossy'
PLUGIN_VERSION = "0.1"
PLUGIN_API_VERSIONS = ["1.3.0", "2.0", "2.1", "2.2"]
PLUGIN_LICENSE = "GPL-2.0"
PLUGIN_LICENSE_URL = "https://www.gnu.org/licenses/gpl-2.0.html"

from picard.script import register_script_function

LOSSLESS_EXTENSIONS = ['flac', 'oggflac', 'ape', 'ofr', 'tak', 'wv', 'tta', 'wav']

def is_lossless(parser):
    """
    Returns true, if the file processed is a lossless audio format.
    Note: This depends mainly on the file extension and does not look inside the
    file to detect the codec.
    """
    if parser.context['~extension'] in LOSSLESS_EXTENSIONS:
        return "1"
    elif parser.context['~extension'] == 'm4a':
        # Mutagen < 1.26 fails to detect the bitrate for Apple Lossless Audio Codec.
        if not parser.context['~bitrate'] or parser.context['~bitrate'] > 1000:
            return "1"
        else:
            return ""
    else:
        return ""

def is_lossy(parser):
    """Returns true, if the file processed is a lossy audio format."""
    if is_lossless(parser) == "1":
        return ""
    else:
        return "1"

register_script_function(is_lossless)
register_script_function(is_lossy)
Log
D: 08:04:16,896 /usr/lib/picard/picard/formats/mp4._save:249: Saving file '/media/Data0/Public/Medias/Retag3/Lossy/M4A/Arctic Monkeys/Favourite Worst Nightmare/01 Brianstorm[1411.2].m4a'
E: 08:04:16,904 /usr/lib/picard/picard/util/thread.run:64: Traceback (most recent call last):
  File "/usr/lib/picard/picard/util/thread.py", line 60, in run
    result = self.func()
  File "/usr/lib/picard/picard/file.py", line 297, in _save_and_rename
    new_filename = self._rename(old_filename, metadata)
  File "/usr/lib/picard/picard/file.py", line 453, in _rename
    self.make_filename(old_filename, metadata))
  File "/usr/lib/picard/picard/file.py", line 442, in make_filename
    new_filename = self._format_filename(new_dirname, new_filename, metadata, settings)
  File "/usr/lib/picard/picard/file.py", line 407, in _format_filename
    new_filename = self._script_to_filename(naming_format, metadata, ext, settings)
  File "/usr/lib/picard/picard/file.py", line 384, in _script_to_filename
    naming_format, metadata, file=self, settings=settings)
  File "/usr/lib/picard/picard/util/scripttofilename.py", line 59, in script_to_filename_with_metadata
    filename = ScriptParser().eval(naming_format, new_metadata, file)
  File "/usr/lib/picard/picard/script.py", line 315, in eval
    return ScriptParser._cache[key].eval(self)
  File "/usr/lib/picard/picard/script.py", line 154, in eval
    return "".join([item.eval(state) for item in self])
  File "/usr/lib/picard/picard/script.py", line 154, in <listcomp>
    return "".join([item.eval(state) for item in self])
  File "/usr/lib/picard/picard/script.py", line 145, in eval
    args = [arg.eval(parser) for arg in self.args]
  File "/usr/lib/picard/picard/script.py", line 145, in <listcomp>
    args = [arg.eval(parser) for arg in self.args]
  File "/usr/lib/picard/picard/script.py", line 154, in eval
    return "".join([item.eval(state) for item in self])
  File "/usr/lib/picard/picard/script.py", line 154, in <listcomp>
    return "".join([item.eval(state) for item in self])
  File "/usr/lib/picard/picard/script.py", line 148, in eval
    return function(parser, *args)
  File "/usr/lib/picard/picard/script.py", line 439, in func_if
    if _if.eval(parser):
  File "/usr/lib/picard/picard/script.py", line 154, in eval
    return "".join([item.eval(state) for item in self])
  File "/usr/lib/picard/picard/script.py", line 154, in <listcomp>
    return "".join([item.eval(state) for item in self])
  File "/usr/lib/picard/picard/script.py", line 148, in eval
    return function(parser, *args)
  File "/home/rickey/.config/MusicBrainz/Picard/plugins/islossless.py", line 37, in is_lossless
    if not parser.context['~bitrate'] or parser.context['~bitrate'] > 1000:
TypeError: '>' not supported between instances of 'str' and 'int'

D: 08:04:16,917 /usr/lib/picard/picard/file.update:614: Updating file <MP4File '01 Brianstorm[1411.2].m4a'>
Codec details by MediaInfo
General

Complete name : 01 Brianstorm[1411.2].m4a

Format : MPEG-4

Codec ID : M4V (isom/iso2/avc1)

File size : 21.7 MiB
Duration : 2 min 50 s
Overall bit rate mode : Variable
Overall bit rate : 1 071 kb/s
Movie name : Brianstorm

Video
ID : 1
Format : AVC
Format/Info : Advanced Video Codec
Format profile : High@L6.2
Format settings : CABAC / 4 Ref Frames
Format settings, CABAC : Yes
Format settings, ReFrames : 4 frames
Codec ID : avc1
Codec ID/Info : Advanced Video Coding
Width : 500 pixels
Height : 448 pixels
Display aspect ratio : 1.116
Frame rate mode : Constant
Frame rate : 90 000.000 FPS
Color space : YUV
Chroma subsampling : 4:2:0
Bit depth : 8 bits
Scan type : Progressive
Stream size : 0.00 Byte (0%)
Source stream size : 3.95 KiB (0%)
Writing library : x264 core 152 r2854 e9a5903
Encoding settings : cabac=1 / ref=3 / deblock=1:0:0 / analyse=0x3:0x113 / me=hex / subme=7 / psy=1 / psy_rd=1.00:0.00 / mixed_ref=1 / me_range=16 / chroma_me=1 / trellis=1 / 8x8dct=1 / cqm=0 / deadzone=21,11 / fast_pskip=1 / chroma_qp_offset=-2 / threads=6 / lookahead_threads=1 / sliced_threads=0 / nr=0 / decimate=1 / interlaced=0 / bluray_compat=0 / constrained_intra=0 / bframes=3 / b_pyramid=2 / b_adapt=1 / b_bias=0 / direct=1 / weightb=1 / open_gop=0 / weightp=2 / keyint=250 / keyint_min=25 / scenecut=40 / intra_refresh=0 / rc_lookahead=40 / rc=crf / mbtree=1 / crf=23.0 / qcomp=0.60 / qpmin=0 / qpmax=69 / qpstep=4 / ip_ratio=1.40 / aq=1:1.00
Color range : Full


Audio
ID : 2
Format : ALAC
Codec ID : alac
Codec ID/Info : Apple Lossless Audio Codec
Duration : 2 min 50 s
Duration_LastFrame : -88 ms
Bit rate mode : Variable
Bit rate : 1 068 kb/s
Nominal bit rate : 1 411 kb/s
Channel(s) : 2 channels
Sampling rate : 44.1 kHz
Bit depth : 16 bits
Stream size : 21.7 MiB (100%)
Default : Yes
Alternate group : 1

When I comment on this part of the code, the error disappears but no M4A is detected correctly of course.

elif parser.context['~extension'] == 'm4a':
        # Mutagen < 1.26 fails to detect the bitrate for Apple Lossless Audio Codec.
        if not parser.context['~bitrate'] or parser.context['~bitrate'] > 1000:
            return "1"
        else:
            return ""

what’s wrong since version 2.0?

I didn’t even remember I shared this script in the past :slight_smile:

But the version I currently use is at https://github.com/phw/picard-plugins/blob/formathelpers/plugins/formathelpers/init.py and that takes care of the bug.

Keep in mind that these functions are a bit unreliable in any case. It mainly just detects the lossless status based on file extension with some special handling for m4a files which is more a heuristic than a reliable detection of lossless codec. This works for my use case, but is also the reason why I never submitted this plugin for inclusion in the official plugin list. Basically I don’t want to support this code beyond what it is already doing and which fits my needs.

3 Likes

Thank you so much.

I know it’s not reliable, but it avoids a lot of lines of code.
Besides, I have a script that allows us to refine the results, and I plan to improve it to complement the islossless.py plugin.

Birate
$noop(################ BITRATE TYPE DETECTION ################)
$set(BRType,$if($eq_any(%_bitrate%,320.0,256.0,224.0,192.0,160.0,128.0,112.0,96.0,80.0,64.0,56.0,48.0,40.0,32.0,24.0,16.0,8.0),CBR,VBR))
$if($eq(%BRType%,VBR),$set(BrVBR,$left(%_bitrate%,3)))
$if($eq($upper(%_extension%),WMA), 
	$if($eq(%BRType%,VBR),
			$if($gte(%BrVBR%,256),$set(wmatype,VBR),
			$if($lt(%BrVBR%,192),$set(wmatype,VBR),
			$if($lt(%BrVBR%,128),$set(wmatype,VBR),
			$if($lt(%BrVBR%,96),$set(wmatype,VBR),
			$if($lt(%BrVBR%,80),$set(wmatype,VBR),
			))))))
	$if($not(%wmatype%),$set(wmatype,CBR)))
$if($eq($upper(%_extension%),WMA),$set(_BRType,%wmatype%),)
$if($not(%_BRType%),$set(_BRType,%BRType%))
$noop(################ END OF BITRATE TYPE DETECTION ################)

I noticed with MediaInfo, that some files have the “compression mode:” tag:
is it possible to search for this tag with a plugin? same thing with different tags like “Bit rate mode:” / “Overall bit rate mode:”; “Codec ID:”; etc …