Skip to content

Commit

Permalink
new: CountAndTotalTuple named tuple for count/total values.
Browse files Browse the repository at this point in the history
  • Loading branch information
nicfit committed Feb 28, 2021
1 parent aec72de commit d2b14c6
Show file tree
Hide file tree
Showing 3 changed files with 27 additions and 17 deletions.
25 changes: 16 additions & 9 deletions eyed3/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import functools
import pathlib
import dataclasses
from collections import namedtuple
from typing import Optional

from . import LOCAL_FS_ENCODING
from .utils.log import getLogger
Expand All @@ -14,7 +16,6 @@
# Audio type selector for MPEG (mp3) audio.
AUDIO_MP3 = 1


AUDIO_TYPES = (AUDIO_NONE, AUDIO_MP3)

LP_TYPE = "lp"
Expand All @@ -27,7 +28,6 @@
SINGLE_TYPE = "single"
ALBUM_TYPE_IDS = [LP_TYPE, EP_TYPE, COMP_TYPE, LIVE_TYPE, VARIOUS_TYPE,
DEMO_TYPE, SINGLE_TYPE]

VARIOUS_ARTISTS = "Various Artists"

# A key that can be used in a TXXX frame to specify the type of collection
Expand All @@ -39,6 +39,9 @@
# The format is: city<tab>state<tab>country
TXXX_ARTIST_ORIGIN = "eyeD3#artist_origin"

# A 2-tuple for count and a total count. e.g. track 3 of 10, count of total.
CountAndTotalTuple = namedtuple("CountAndTotalTuple", "count, total")


@dataclasses.dataclass
class ArtistOrigin:
Expand All @@ -53,13 +56,17 @@ def id3Encode(self):
return "\t".join([(o if o else "") for o in dataclasses.astuple(self)])


@dataclasses.dataclass
class AudioInfo:
"""A base container for common audio details."""

# The number of seconds of audio data (i.e., the playtime)
time_secs: float = 0.0
time_secs: float
# The number of bytes of audio data.
size_bytes: int = 0
size_bytes: int

def __post_init__(self):
self.time_secs = int(self.time_secs * 100.0) / 100.0


class Tag:
Expand Down Expand Up @@ -96,7 +103,7 @@ def _getTitle(self):
def _setTrackNum(self, val):
raise NotImplementedError() # pragma: nocover

def _getTrackNum(self):
def _getTrackNum(self) -> CountAndTotalTuple:
raise NotImplementedError() # pragma: nocover

@property
Expand Down Expand Up @@ -132,7 +139,7 @@ def title(self, v):
self._setTitle(v)

@property
def track_num(self):
def track_num(self) -> CountAndTotalTuple:
"""Track number property.
Must return a 2-tuple of (track-number, total-number-of-tracks).
Either tuple value may be ``None``.
Expand Down Expand Up @@ -206,7 +213,7 @@ def path(self, path):
self._path = path

@property
def info(self):
def info(self) -> AudioInfo:
"""Returns a concrete implemenation of :class:`eyed3.core.AudioInfo`"""
return self._info

Expand Down Expand Up @@ -405,14 +412,14 @@ def __str__(self):
return s


def parseError(ex):
def parseError(ex) -> None:
"""A function that is invoked when non-fatal parse, format, etc. errors
occur. In most cases the invalid values will be ignored or possibly fixed.
This function simply logs the error."""
log.warning(ex)


def load(path, tag_version=None) -> AudioFile:
def load(path, tag_version=None) -> Optional[AudioFile]:
"""Loads the file identified by ``path`` and returns a concrete type of
:class:`eyed3.core.AudioFile`. If ``path`` is not a file an ``IOError`` is
raised. ``None`` is returned when the file type (i.e. mime-type) is not
Expand Down
14 changes: 7 additions & 7 deletions eyed3/mp3/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import os
import re
import stat

from .. import Error
from .. import id3
Expand Down Expand Up @@ -37,7 +38,6 @@ def __init__(self, file_obj, start_offset, tag):
from .headers import timePerFrame

log.debug("mp3 header search starting @ %x" % start_offset)
core.AudioInfo.__init__(self)

self.mp3_header = None
self.xing_header = None
Expand Down Expand Up @@ -90,19 +90,18 @@ def __init__(self, file_obj, start_offset, tag):
self.lame_tag = headers.LameHeader(mp3_frame)

# Set file size
import stat
self.size_bytes = os.stat(file_obj.name)[stat.ST_SIZE]
size_bytes = os.stat(file_obj.name)[stat.ST_SIZE]

# Compute track play time.
if self.xing_header and self.xing_header.vbr:
tpf = timePerFrame(self.mp3_header, True)
self.time_secs = tpf * self.xing_header.numFrames
time_secs = tpf * self.xing_header.numFrames
elif self.vbri_header and self.vbri_header.version == 1:
tpf = timePerFrame(self.mp3_header, True)
self.time_secs = tpf * self.vbri_header.num_frames
time_secs = tpf * self.vbri_header.num_frames
else:
tpf = timePerFrame(self.mp3_header, False)
length = self.size_bytes
length = size_bytes
if tag and tag.isV2():
length -= tag.header.SIZE + tag.header.tag_size
# Handle the case where there is a v2 tag and a v1 tag.
Expand All @@ -111,7 +110,7 @@ def __init__(self, file_obj, start_offset, tag):
length -= 128
elif tag and tag.isV1():
length -= 128
self.time_secs = (length / self.mp3_header.frame_length) * tpf
time_secs = (length / self.mp3_header.frame_length) * tpf

# Compute bitrate
if (self.xing_header and self.xing_header.vbr and
Expand All @@ -127,6 +126,7 @@ def __init__(self, file_obj, start_offset, tag):
self.sample_freq = self.mp3_header.sample_freq
self.mode = self.mp3_header.mode

super().__init__(time_secs, size_bytes)
##
# Helper to get the bitrate as a string. The prefix '~' is used to denote
# variable bit rates.
Expand Down
5 changes: 4 additions & 1 deletion tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,12 @@ def _read(self):

def test_AudioInfo():
from eyed3.core import AudioInfo
info = AudioInfo()
info = AudioInfo(0, 0)
assert (info.time_secs == 0)
assert (info.size_bytes == 0)
info = AudioInfo(size_bytes=1000, time_secs=3.14)
assert (info.time_secs == 3.14)
assert (info.size_bytes == 1000)


def test_Date():
Expand Down

0 comments on commit d2b14c6

Please sign in to comment.