Item.title is a tuple?

While working on this, I got stuck.

I have an i_main inline function and it gets tripped up on the title field, saying it’s a tuple. Is it? How can I get a string title I can concat with everything else? I’ve tried stuff like title[0], str(title), and both at once.

# some other functions above
        def i_main():
            out = ''
            print(out)
            # make a subfolder
            out += '/'
            print(out)
            disc_layer = i_disc_layer(disc, disctotal)
            if disc_layer:
                # no space
                out += disc_layer + "-"
            print(out)
            mytrack = i_track(track)
            if mytrack:
                out += mytrack
            else:
                out += "[no track number]"
            print(out)
            out += ". "
            print(out)
            if title:
                # tuple error if uncommented
                # out += str(title),
                out += 'yyy'
            else:
                out += '[unknown]'
            print(out)
            return out
        return i_main()
C:\apps\ytdl
λ beet -vv mv fiona
user configuration: C:\Users\RollingStar\AppData\Roaming\beets\config.yaml
data directory: C:\Users\RollingStar\AppData\Roaming\beets
plugin paths:
Sending event: pluginload
inline: adding item field i_cust_catalog
inline: adding item field i_main
inline: adding album field alb_main
inline: adding album field alb_main_suffix
library database: e:\Music\beetslibrary.bib
library directory: e:\Music
Sending event: library_opened

/
/
/01
/01.

/
/
/01
/01.
Traceback (most recent call last):
  File "c:\apps\cmder_mini\src\beets\beetsplug\inline.py", line 123, in _func_func
    return func()
  File "inline", line 95, in __INLINE_FUNC__
  File "inline", line 90, in i_main
TypeError: can only concatenate str (not "tuple") to str

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "c:\apps\cmder_mini\src\beets\beets\util\functemplate.py", line 598, in substitute
    res = self.compiled(values, functions)
  File "c:\apps\cmder_mini\src\beets\beets\util\functemplate.py", line 622, in wrapper_func
    args[VARIABLE_PREFIX + varname] = values[varname]
  File "c:\apps\cmder_mini\src\beets\beets\library.py", line 415, in __getitem__
    value = self._get(key)
  File "c:\apps\cmder_mini\src\beets\beets\library.py", line 406, in _get
    return self._get_formatted(self.model, key)
  File "c:\apps\cmder_mini\src\beets\beets\dbcore\db.py", line 81, in _get_formatted
    value = model._type(key).format(model.get(key))
  File "c:\apps\cmder_mini\src\beets\beets\dbcore\db.py", line 443, in get
    return self[key]
  File "c:\apps\cmder_mini\src\beets\beets\dbcore\db.py", line 354, in __getitem__
    return getters[key](self)
  File "c:\apps\cmder_mini\src\beets\beetsplug\inline.py", line 125, in _func_func
    raise InlineError(python_code, exc)
beetsplug.inline.InlineError: error in inline path field code:
def i_reissue_year(year, original_year):
    if year > original_year:
        if original_year > 0:
            return str(year)
    return ''
def i_cust_media(media):
    # see https://musicbrainz.org/doc/Release/Format
    # omit uninteresting formats
    # these formats are bit-identical to CD
    snake_oil_formats = ['Blu-spec CD', 'SHM-CD', 'HQCD']
    media_types_to_omit = snake_oil_formats + \
        ['CD', 'CD-R', 'Enhanced CD', 'CDDA', 'Digital Media', '']
    if media in media_types_to_omit:
        return ''
    # combine hybrid SACD with SACD, see https://en.wikipedia.org/wiki/Super_Audio_CD#Technology
    elif 'SACD' in media:
        return 'SACD'
    # combine all vinyl types into "Vinyl"
    elif 'Vinyl' in media:
        # https://en.wikipedia.org/wiki/VinylDisc
        if media == 'VinylDisc':
            # assume it's a CD
            return ''
        return 'Vinyl'
    elif "USB" in media:
        return 'USB'
    elif media == 'HD-DVD':
        return 'HD-DVD'
    elif 'DVD' in media or media == "DualDisc":
        return 'DVD'
    else:
        return media
def i_cust_catalog(media, year, original_year):
    # don't mix this inline with everything else, just call it
    # directly with inline.
    parsed_media = i_cust_media(media)
    result = i_reissue_year(year, original_year)
    # condition = custom attribute to note vinyl skips
    # and other noticable artifacts.
    result += ' ' + parsed_media  # + i_condition()
    result = result.strip(' ')
    if result != '':
        return ' (' + result + ')'
    return result
def i_track(track):
    # untested
    if not track:
        return None
    # hotfix for beets' hardcoded 2-digit track numbers
    # https://github.com/beetbox/beets/issues/3352
    str_track = str(track)
    # pad based on length of highest track number (per disc).
    # may interact with per_disc_numbering, not sure
    length = len(str(tracktotal))
    if length < 2:
        length = 2
    return str_track.zfill(length)
def i_disc_layer(disc, disctotal):
    # do nothing for single-disc releases
    if disctotal > 1:
        # todo should this be 'is not None'?
        if disc != '':
            str_disc = str(disc)
            # pad based on length of highest disc number.
            # ex. if total discs = 2 digits, pad 1 zero for discs 1-9 (01-09)
            legth_to_pad_to = len(str(disctotal))
            return str_disc.zfill(legth_to_pad_to)
    return ''
def i_main():
    out = ''
    print(out)
    # make a subfolder
    out += '/'
    print(out)
    disc_layer = i_disc_layer(disc, disctotal)
    if disc_layer:
        # no space
        out += disc_layer + "-"
    print(out)
    mytrack = i_track(track)
    if mytrack:
        out += mytrack
    else:
        out += "[no track number]"
    print(out)
    out += ". "
    print(out)
    if title:
        out += str(title),
    else:
        out += '[unknown]'
    print(out)
    return out
return i_main()
TypeError: can only concatenate str (not "tuple") to str

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "c:\apps\cmder_mini\src\beets\beetsplug\inline.py", line 123, in _func_func
    return func()
  File "inline", line 95, in __INLINE_FUNC__
  File "inline", line 90, in i_main
TypeError: can only concatenate str (not "tuple") to str

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\RollingStar\AppData\Local\Programs\Python\Python37\Scripts\beet-script.py", line 11, in <module>
    load_entry_point('beets', 'console_scripts', 'beet')()
  File "c:\apps\cmder_mini\src\beets\beets\ui\__init__.py", line 1267, in main
    _raw_main(args)
  File "c:\apps\cmder_mini\src\beets\beets\ui\__init__.py", line 1254, in _raw_main
    subcommand.func(lib, suboptions, subargs)
  File "c:\apps\cmder_mini\src\beets\beets\ui\commands.py", line 1558, in move_func
    opts.timid, opts.export)
  File "c:\apps\cmder_mini\src\beets\beets\ui\commands.py", line 1505, in move_items
    objs = [o for o in objs if (isalbummoved if album else isitemmoved)(o)]
  File "c:\apps\cmder_mini\src\beets\beets\ui\commands.py", line 1505, in <listcomp>
    objs = [o for o in objs if (isalbummoved if album else isitemmoved)(o)]
  File "c:\apps\cmder_mini\src\beets\beets\ui\commands.py", line 1503, in <lambda>
    isitemmoved = lambda item: item.path != item.destination(basedir=dest)
  File "c:\apps\cmder_mini\src\beets\beets\library.py", line 880, in destination
    subpath = self.evaluate_template(subpath_tmpl, True)
  File "c:\apps\cmder_mini\src\beets\beets\dbcore\db.py", line 602, in evaluate_template
    self._template_funcs())
  File "c:\apps\cmder_mini\src\beets\beets\util\functemplate.py", line 600, in substitute
    res = self.interpret(values, functions)
  File "c:\apps\cmder_mini\src\beets\beets\util\functemplate.py", line 592, in interpret
    return self.expr.evaluate(Environment(values, functions))
  File "c:\apps\cmder_mini\src\beets\beets\util\functemplate.py", line 282, in evaluate
    out.append(part.evaluate(env))
  File "c:\apps\cmder_mini\src\beets\beets\util\functemplate.py", line 184, in evaluate
    if self.ident in env.values:
  File "c:\users\RollingStar\appdata\local\programs\python\python37\lib\_collections_abc.py", line 666, in __contains__
    self[key]
  File "c:\apps\cmder_mini\src\beets\beets\library.py", line 415, in __getitem__
    value = self._get(key)
  File "c:\apps\cmder_mini\src\beets\beets\library.py", line 406, in _get
    return self._get_formatted(self.model, key)
  File "c:\apps\cmder_mini\src\beets\beets\dbcore\db.py", line 81, in _get_formatted
    value = model._type(key).format(model.get(key))
  File "c:\apps\cmder_mini\src\beets\beets\dbcore\db.py", line 443, in get
    return self[key]
  File "c:\apps\cmder_mini\src\beets\beets\dbcore\db.py", line 354, in __getitem__
    return getters[key](self)
  File "c:\apps\cmder_mini\src\beets\beetsplug\inline.py", line 125, in _func_func
    raise InlineError(python_code, exc)
beetsplug.inline.InlineError: error in inline path field code:
def i_reissue_year(year, original_year):
    if year > original_year:
        if original_year > 0:
            return str(year)
    return ''
def i_cust_media(media):
    # see https://musicbrainz.org/doc/Release/Format
    # omit uninteresting formats
    # these formats are bit-identical to CD
    snake_oil_formats = ['Blu-spec CD', 'SHM-CD', 'HQCD']
    media_types_to_omit = snake_oil_formats + \
        ['CD', 'CD-R', 'Enhanced CD', 'CDDA', 'Digital Media', '']
    if media in media_types_to_omit:
        return ''
    # combine hybrid SACD with SACD, see https://en.wikipedia.org/wiki/Super_Audio_CD#Technology
    elif 'SACD' in media:
        return 'SACD'
    # combine all vinyl types into "Vinyl"
    elif 'Vinyl' in media:
        # https://en.wikipedia.org/wiki/VinylDisc
        if media == 'VinylDisc':
            # assume it's a CD
            return ''
        return 'Vinyl'
    elif "USB" in media:
        return 'USB'
    elif media == 'HD-DVD':
        return 'HD-DVD'
    elif 'DVD' in media or media == "DualDisc":
        return 'DVD'
    else:
        return media
def i_cust_catalog(media, year, original_year):
    # don't mix this inline with everything else, just call it
    # directly with inline.
    parsed_media = i_cust_media(media)
    result = i_reissue_year(year, original_year)
    # condition = custom attribute to note vinyl skips
    # and other noticable artifacts.
    result += ' ' + parsed_media  # + i_condition()
    result = result.strip(' ')
    if result != '':
        return ' (' + result + ')'
    return result
def i_track(track):
    # untested
    if not track:
        return None
    # hotfix for beets' hardcoded 2-digit track numbers
    # https://github.com/beetbox/beets/issues/3352
    str_track = str(track)
    # pad based on length of highest track number (per disc).
    # may interact with per_disc_numbering, not sure
    length = len(str(tracktotal))
    if length < 2:
        length = 2
    return str_track.zfill(length)
def i_disc_layer(disc, disctotal):
    # do nothing for single-disc releases
    if disctotal > 1:
        # todo should this be 'is not None'?
        if disc != '':
            str_disc = str(disc)
            # pad based on length of highest disc number.
            # ex. if total discs = 2 digits, pad 1 zero for discs 1-9 (01-09)
            legth_to_pad_to = len(str(disctotal))
            return str_disc.zfill(legth_to_pad_to)
    return ''
def i_main():
    out = ''
    print(out)
    # make a subfolder
    out += '/'
    print(out)
    disc_layer = i_disc_layer(disc, disctotal)
    if disc_layer:
        # no space
        out += disc_layer + "-"
    print(out)
    mytrack = i_track(track)
    if mytrack:
        out += mytrack
    else:
        out += "[no track number]"
    print(out)
    out += ". "
    print(out)
    if title:
        out += str(title[0]),
    else:
        out += '[unknown]'
    print(out)
    return out
return i_main()
TypeError: can only concatenate str (not "tuple") to str

It’s this: out += str(title),

The extra comma there constructs a one-element tuple.