class _TagsView(_Widget): ''' A view of some `Tag`s. ''' def __init__(self, parent, *, get_tag_widget=None, **kw): super().__init__(parent, **kw) # the working TagSet, distinct from those supplied self.tags = TagSet() # a function to get Tag suggestions from a Tag name self.get_suggested_tag_values = lambda tag_name: () # a reference TagSet of background Tags self.bg_tags = TagSet() if get_tag_widget is None: get_tag_widget = TagWidget self.get_tag_widget = get_tag_widget def set_tags(self, tags, get_suggested_tag_values=None, bg_tags=None): ''' Update `self.tags` to match `tags`. Optionally set `self.get_suggested_tag_values` if `get_suggested_tag_values` is not `None`. Optionally set `self.bg_tags` if `bg_tags` is not `None`. ''' self.tags.clear() self.tags.update(tags) if get_suggested_tag_values is not None: self.get_suggested_tag_values = get_suggested_tag_values if bg_tags is not None: self.bg_tags = bg_tags
def __init__(self, parent, *, get_tag_widget=None, **kw): super().__init__(parent, **kw) # the working TagSet, distinct from those supplied self.tags = TagSet() # a function to get Tag suggestions from a Tag name self.get_suggested_tag_values = lambda tag_name: () # a reference TagSet of background Tags self.bg_tags = TagSet() if get_tag_widget is None: get_tag_widget = TagWidget self.get_tag_widget = get_tag_widget
def cmd_ls(self, argv): ''' Usage: {cmd} [-l] List the contents of the Calibre library. ''' long = False if argv and argv[0] == '-l': long = True argv.pop(0) if argv: raise GetoptError("extra arguments: %r" % (argv, )) options = self.options calibre = options.calibre for book in calibre: with Pfx("%d:%s", book.id, book.title): print(f"{book.title} ({book.dbid})") if long: print(" ", book.path) identifiers = book.identifiers_as_dict() if identifiers: print(" ", TagSet(identifiers)) for fmt, subpath in book.formats_as_dict().items(): with Pfx(fmt): fspath = calibre.pathto(subpath) size = pfx_call(os.stat, fspath).st_size print(" ", fmt, transcribe_bytes_geek(size), subpath)
def __init__(self, *, get_tag_widget=None, **kw): if get_tag_widget is None: get_tag_widget = lambda tag: TagWidget(tag) self._get_tag_widget = get_tag_widget super().__init__( size=(800, 400), layout=self.layout_tags(TagSet(a=1, b=2)), **kw )
def apply_defaults(self): ''' Set default options: * empty `.categories`, a `set` * empty `.tags`, a `TagSet` * `.when` = `time.time()` ''' self.options.categories = set() self.options.dbpath = expanduser(DEFAULT_DBPATH) self.options.logpath = expanduser(DEFAULT_LOGPATH) self.options.tags = TagSet() self.options.when = time.time()
class _TagsView(_Widget): @typechecked def __init__(self, *a, **kw): self.tags = TagSet() super().__init__(*a, **kw) def set_tags(self, tags): ''' Set the tags. ''' self.tags.clear() self.tags.update(tags) def _row_of_tags(self): ''' Make a row of tag widgets. ''' X("TagsView_Frame: TAGS=%s", self.tags) ##row = [self._get_tag_widget(tag) for tag in self.tags] row = [sg.Text(str(tag)) for tag in self.tags] X("ROW OF TAGS: %r", row) return row
def auto_name(self, srcpath, dstdirpath, tags): ''' Generate a filename computed from `srcpath`, `dstdirpath` and `tags`. ''' tagged = self.fstags[dstdirpath] formats = self.conf_tag(tagged.merged_tags(), 'auto_name', ()) if isinstance(formats, str): formats = [formats] if formats: if not isinstance(tags, TagSet): tags = TagSet() for tag in tags: tags.add(tag) for fmt in formats: with Pfx(repr(fmt)): try: formatted = pfx_call(tags.format_as, fmt, strict=True) if formatted.endswith('/'): formatted += basename(srcpath) return formatted except FormatAsError: ##warning("%s", e) ##print("auto_name(%r): %r: %s", srcpath, fmt, e) continue return basename(srcpath)
def __init__(self, tags: TagSet, tag_name: str, alt_values=None): if alt_values is None: alt_values = set() self.tags = tags self.tag_name = tag_name self.alt_values = alt_values layout = [ [ sg.Text(tag_name + ("" if tag_name in tags else " (missing)")), sg.Combo( list(self.alt_values), default_value=tags.get(tag_name), ) ] ] super().__init__(layout=layout)
def fspath(self, new_fspath): ''' Switch the preview to look at a new filesystem path. ''' print("SET fspath =", repr(new_fspath)) self._fspath = new_fspath self._tag_widgets = {} self.config(text=shortpath(new_fspath) or "NONE") self.preview.fspath = new_fspath tagged = self.tagged all_tags = TagSet(tagged.merged_tags()) suggested_tags = self.suggested_tags for sg_name in suggested_tags.keys(): if sg_name not in all_tags: all_tags[sg_name] = None self.tagsview.set_tags( tagged, lambda tag: suggested_tags.get(tag.name), bg_tags=all_tags ) print("tag suggestions =", repr(self.suggested_tags))
def tagset(self): ''' Return a `TagSet` with the ID3 tag information. ''' return TagSet( { k: v for k, v in dict( title=self.title, artist=self.artist, album=self.album, year=self.year, comment=self.comment, track=self.track, genre_id=self.genre_id, ).items() if v is not None and not (isinstance(v, int) and v == 0) }, _ontology=self.ONTOLOGY, )
def tagset(self): ''' Return a `TagSet` with the ID3 tag information. ''' tags = TagSet( _ontology=self.ONTOLOGY, v1=self.v1, v2=self.v2, version=f'{self.v1}.{self.v2}', ) for tag_frame in self.tag_frames: tag_id = tag_frame.tag_id.decode('ascii').lower() tags.set(tag_id, tag_frame.datafrome_body.value) if tag_frame.flags != 0: tags.set(f"{tag_id}.flags", tag_frame.flags) return tags
def set_tags(self, tags, get_suggested_tag_values=None, bg_tags=None): old_tags = list(self.tags) super().set_tags( tags, get_suggested_tag_values=get_suggested_tag_values, bg_tags=bg_tags ) display_tags = TagSet(self.tags) if bg_tags: # fill in background tags if not present for tag_name, tag_value in bg_tags.items(): if tag_name not in tags: display_tags[tag_name] = tag_value # remove tags no longer named for tag in old_tags: if tag.name not in display_tags: self._del_tag(tag.name) # redo the displayed tags # TODO: update the widgets directly instead for tag in display_tags: alt_values = self.get_suggested_tag_values(tag) w = self.tag_widget(tag, alt_values=alt_values) self._add_tag(tag.name, w) w.grid(sticky=tk.W)
def tags_of(bfr): ''' Scan `bfr` containing MP3 data. Return a `TagSet` containing the tags found in an mp3 buffer. The returned `Tag`s have the prefix `'id3v1.'` for ID3v1 tags and `'id3v2'` for ID3v2 tags. ''' tags = TagSet() for frame in MP3Frame.scan(bfr): frame_type = type(frame) if issubclass(frame_type, (ID3V1Frame, ID3V2Frame)): tags.update(frame.tagset(), prefix={ ID3V1Frame: 'id3v1', ID3V2Frame: 'id3v2' }[frame_type]) elif issubclass(frame_type, MP3AudioFrame): tags.update(dict(bitrate_kbps=frame.bitrate_kbps), prefix='audio') else: warning("unhandled tag type %s", frame_type.__name__) return tags
def rip( device, mbdb, *, output_dirpath, disc_id=None, fstags=None, no_action=False ): ''' Pull audio from `device` and save in `output_dirpath`. ''' if disc_id is None: dev_info = discid.read(device=device) disc_id = dev_info.id if fstags is None: fstags = FSTags() with Pfx("MB: discid %s", disc_id, print=True): disc = mbdb.discs[disc_id] level1 = ", ".join(disc.artist_names).replace(os.sep, '_') or "NO_ARTISTS" level2 = disc.title or "UNTITLED" if disc.medium_count > 1: level2 += f" ({disc.medium_position} of {disc.medium_count})" subdir = joinpath(output_dirpath, level1, level2) if not isdirpath(subdir): with Pfx("makedirs(%r)", subdir, print=True): os.makedirs(subdir) fstags[subdir].update( TagSet(discid=disc.id, title=disc.title, artists=disc.artist_names) ) for tracknum, recording_id in enumerate(disc.recordings, 1): recording = disc.ontology.metadata('recording', recording_id) track_fstags = TagSet( discid=disc.mbkey, artists=recording.artist_names, title=recording.title, track=tracknum ) track_artists = ", ".join(recording.artist_names) track_base = f"{tracknum:02} - {recording.title} -- {track_artists}".replace( os.sep, '-' ) wav_filename = joinpath(subdir, track_base + '.wav') mp3_filename = joinpath(subdir, track_base + '.mp3') if existspath(mp3_filename): warning("MP3 file already exists, skipping track: %r", mp3_filename) else: with NamedTemporaryFile(dir=subdir, prefix=f"cdparanoia--track{tracknum}--", suffix='.wav') as T: if existspath(wav_filename): info("using existing WAV file: %r", wav_filename) else: argv = ['cdparanoia', '-d', '1', '-w', str(tracknum), T.name] if no_action: print(*argv) else: with Pfx("+ %r", argv, print=True): subprocess.run(argv, stdin=subprocess.DEVNULL, check=True) with Pfx("%r => %r", T.name, wav_filename, print=True): os.link(T.name, wav_filename) if no_action: print("fstags[%r].update(%s)" % (wav_filename, track_fstags)) else: fstags[wav_filename].update(track_fstags) fstags[wav_filename].rip_command = argv argv = [ 'lame', '-q', '7', '-V', '0', '--tt', recording.title or "UNTITLED", '--ta', track_artists or "NO ARTISTS", '--tl', level2, ## '--ty',recording year '--tn', str(tracknum), ## '--tg', recording genre ## '--ti', album cover filename wav_filename, mp3_filename ] if no_action: print(*argv) else: with Pfx("+ %r", argv, print=True): subprocess.run(argv, stdin=subprocess.DEVNULL, check=True) fstags[mp3_filename].conversion_command = argv if no_action: print("fstags[%r].update(%s)" % (mp3_filename, track_fstags)) else: fstags[mp3_filename].update(track_fstags) if not no_action: subprocess.run(['ls', '-la', subdir]) os.system("eject")
def __init__(self, *a, **kw): self.tags = TagSet() super().__init__(*a, **kw)
def conf_tags(cls, tags: TagSet): ''' The `Tagger` related subtags from `tags`, a `TagSet`. ''' return tags.subtags(cls.TAG_PREFIX)
def dlog( headline: str, *, logpath: Optional[str] = None, sqltags: Optional[SQLTags] = None, tags=None, categories: Optional[Iterable] = None, when: Union[None, int, float, datetime] = None, ): ''' Log `headline` to the dlog. Parameters: * `headline`: the log line message * `logpath`: optional text log pathname, default `{DEFAULT_LOGPATH}` from DEFAULT_LOGPATH * `sqltags`: optional `SQLTags` instance, default uses `{DEFAULT_DBPATH}` from DEFAULT_DBPATH * `tags`: optional iterable of `Tag`s to associate with the log entry * `categories`: optional iterable of category strings * `when`: optional UNIX time or `datetime`, default now ''' if sqltags is None: # pylint: disable=redefined-argument-from-local with SQLTags(expanduser(DEFAULT_DBPATH)) as sqltags: dlog(headline, logpath=logpath, sqltags=sqltags, tags=tags, categories=categories, when=when) if logpath is None: logpath = expanduser(DEFAULT_LOGPATH) logtags = TagSet() if tags: for tag in tags: logtags.add(tag) categories = sorted(( ) if categories is None else set(map(str.lower, categories))) if when is None: when = time.time() elif isinstance(when, (int, float)): pass elif isinstance(when, datetime): dt = when if dt.tzinfo is None: # create a nonnaive datetime in the local zone dt = dt.astimezone() when = datetime2unixtime(dt) else: raise TypeError("when=%s:%r: unhandled type" % (type(when).__name__, when)) tt = time.localtime(when) print_args = [ '{:4d}-{:02d}-{:02d} {:02d}:{:02d}:{:02d}'.format( tt.tm_year, tt.tm_mon, tt.tm_mday, tt.tm_hour, tt.tm_min, tt.tm_sec, ) ] if categories: print_args.append(','.join(categories).upper() + ':') print_args.append(headline) if logtags: print_args.append('[' + ' '.join(map(str, logtags)) + ']') with pfx_call(open, logpath, 'a') as logf: print(*print_args, file=logf) # add the headline and categories to the tags logtags.add('headline', headline) if categories: logtags.add('categories', sorted(categories)) sqltags.default_factory(None, unixtime=when, tags=logtags)