Module musar.folder
Wrapper for handling sets of tracks.
Expand source code
"""Wrapper for handling sets of tracks.
"""
import os
import glob
import logging
import subprocess
import eyed3
import eyed3.mp3
import slugify
from .accessors import Manager as AccessorManager
from .misc import most_common_key_value
class Folder:
"""Represent an actual folder containing audio tracks.
Parameters
----------
path : str
Path to the actual folder.
Attributes
----------
tracks : List[eyed3.mp3.Mp3AudioFile]
Tracks contained in the folder.
path : str
"""
def __init__(self, path):
self.path = path
self.tracks = None
def __iter__(self):
if self.tracks is None:
pass
else:
for track in self.tracks.values():
yield track
def load(self):
"""List and load MP3 files from the folder.
"""
logging.info("Loading tracks from %s", self.path)
self.tracks = dict()
for filename in glob.glob(os.path.join(self.path, "*.mp3")):
logging.debug("Loading track at %s", os.path.realpath(filename))
self.tracks[filename] = eyed3.load(filename)
def create_hierarchy(self, mkdir):
"""Compute and maybe create the folder structure for albums and
artists hierarchies.
Parameters
----------
mkdir : bool
If `True`, the folder structure is created.
Returns
-------
str
Path of the hierarchy folder structure.
"""
artist, album = None, None
for track in self:
if track.tag.album_artist is not None:
artist = track.tag.album_artist
if track.tag.album is not None:
album = track.tag.album
if artist is not None and album is not None:
break
base_folder = os.path.join(
self.path,
slugify.slugify(artist),
slugify.slugify(album)
)
if mkdir:
logging.info("Creating folder structure %s", base_folder)
os.makedirs(base_folder, exist_ok=True)
return base_folder
def convert(self, config, remove_original):
"""Convert non MP3 files within the folder into MP3s.
Parameters
----------
config : musar.config.Config
Convert configuration.
remove_original : bool
If `True`, original files will be deleted once converted.
"""
logging.info("Convert tracks from folder %s", self.path)
for extension in config.extensions:
for filename in glob.glob(os.path.join(self.path, "*." + extension)):
output_filename = os.path.splitext(filename)[0] + ".mp3"
command = [
config.options.ffmpeg_path,
"-i",
filename,
output_filename,
"-y"
]
process = subprocess.Popen(command)
process.wait()
if remove_original\
and os.path.isfile(output_filename)\
and os.path.getsize(output_filename) > 0:
try:
os.remove(filename)
except PermissionError:
logging.error(
"Could not delete %s",
os.path.realpath(filename)
)
def index(self):
"""Make a dictionnary with album data.
Returns
-------
Dict
JSON representation of the album.
"""
logging.info("Creating JSON data of album %s", self.path)
index = {
"path": self.path,
"tracks": list(),
"info": dict(),
}
mgr = AccessorManager(None)
for path, track in self.tracks.items():
item = {
"path": path,
"duration": track.info.time_secs
}
for name in ["title",
"album_artist",
"artist",
"album",
"track_num",
"disc_num",
"genre",
"year"]:
item[name] = mgr[name].get(track)
index["tracks"].append(item)
for name in ["album_artist", "album", "genre", "year"]:
index["info"][name] = most_common_key_value(name, index["tracks"])
index["info"]["duration"] = sum(map(
lambda item: item["duration"],
index["tracks"]
))
return index
Classes
class Folder (path)
-
Represent an actual folder containing audio tracks.
Parameters
path
:str
- Path to the actual folder.
Attributes
tracks
:List[eyed3.mp3.Mp3AudioFile]
- Tracks contained in the folder.
path
:str
Expand source code
class Folder: """Represent an actual folder containing audio tracks. Parameters ---------- path : str Path to the actual folder. Attributes ---------- tracks : List[eyed3.mp3.Mp3AudioFile] Tracks contained in the folder. path : str """ def __init__(self, path): self.path = path self.tracks = None def __iter__(self): if self.tracks is None: pass else: for track in self.tracks.values(): yield track def load(self): """List and load MP3 files from the folder. """ logging.info("Loading tracks from %s", self.path) self.tracks = dict() for filename in glob.glob(os.path.join(self.path, "*.mp3")): logging.debug("Loading track at %s", os.path.realpath(filename)) self.tracks[filename] = eyed3.load(filename) def create_hierarchy(self, mkdir): """Compute and maybe create the folder structure for albums and artists hierarchies. Parameters ---------- mkdir : bool If `True`, the folder structure is created. Returns ------- str Path of the hierarchy folder structure. """ artist, album = None, None for track in self: if track.tag.album_artist is not None: artist = track.tag.album_artist if track.tag.album is not None: album = track.tag.album if artist is not None and album is not None: break base_folder = os.path.join( self.path, slugify.slugify(artist), slugify.slugify(album) ) if mkdir: logging.info("Creating folder structure %s", base_folder) os.makedirs(base_folder, exist_ok=True) return base_folder def convert(self, config, remove_original): """Convert non MP3 files within the folder into MP3s. Parameters ---------- config : musar.config.Config Convert configuration. remove_original : bool If `True`, original files will be deleted once converted. """ logging.info("Convert tracks from folder %s", self.path) for extension in config.extensions: for filename in glob.glob(os.path.join(self.path, "*." + extension)): output_filename = os.path.splitext(filename)[0] + ".mp3" command = [ config.options.ffmpeg_path, "-i", filename, output_filename, "-y" ] process = subprocess.Popen(command) process.wait() if remove_original\ and os.path.isfile(output_filename)\ and os.path.getsize(output_filename) > 0: try: os.remove(filename) except PermissionError: logging.error( "Could not delete %s", os.path.realpath(filename) ) def index(self): """Make a dictionnary with album data. Returns ------- Dict JSON representation of the album. """ logging.info("Creating JSON data of album %s", self.path) index = { "path": self.path, "tracks": list(), "info": dict(), } mgr = AccessorManager(None) for path, track in self.tracks.items(): item = { "path": path, "duration": track.info.time_secs } for name in ["title", "album_artist", "artist", "album", "track_num", "disc_num", "genre", "year"]: item[name] = mgr[name].get(track) index["tracks"].append(item) for name in ["album_artist", "album", "genre", "year"]: index["info"][name] = most_common_key_value(name, index["tracks"]) index["info"]["duration"] = sum(map( lambda item: item["duration"], index["tracks"] )) return index
Methods
def convert(self, config, remove_original)
-
Convert non MP3 files within the folder into MP3s.
Parameters
config
:Config
- Convert configuration.
remove_original
:bool
- If
True
, original files will be deleted once converted.
Expand source code
def convert(self, config, remove_original): """Convert non MP3 files within the folder into MP3s. Parameters ---------- config : musar.config.Config Convert configuration. remove_original : bool If `True`, original files will be deleted once converted. """ logging.info("Convert tracks from folder %s", self.path) for extension in config.extensions: for filename in glob.glob(os.path.join(self.path, "*." + extension)): output_filename = os.path.splitext(filename)[0] + ".mp3" command = [ config.options.ffmpeg_path, "-i", filename, output_filename, "-y" ] process = subprocess.Popen(command) process.wait() if remove_original\ and os.path.isfile(output_filename)\ and os.path.getsize(output_filename) > 0: try: os.remove(filename) except PermissionError: logging.error( "Could not delete %s", os.path.realpath(filename) )
def create_hierarchy(self, mkdir)
-
Compute and maybe create the folder structure for albums and artists hierarchies.
Parameters
mkdir
:bool
- If
True
, the folder structure is created.
Returns
str
- Path of the hierarchy folder structure.
Expand source code
def create_hierarchy(self, mkdir): """Compute and maybe create the folder structure for albums and artists hierarchies. Parameters ---------- mkdir : bool If `True`, the folder structure is created. Returns ------- str Path of the hierarchy folder structure. """ artist, album = None, None for track in self: if track.tag.album_artist is not None: artist = track.tag.album_artist if track.tag.album is not None: album = track.tag.album if artist is not None and album is not None: break base_folder = os.path.join( self.path, slugify.slugify(artist), slugify.slugify(album) ) if mkdir: logging.info("Creating folder structure %s", base_folder) os.makedirs(base_folder, exist_ok=True) return base_folder
def index(self)
-
Make a dictionnary with album data.
Returns
Dict
- JSON representation of the album.
Expand source code
def index(self): """Make a dictionnary with album data. Returns ------- Dict JSON representation of the album. """ logging.info("Creating JSON data of album %s", self.path) index = { "path": self.path, "tracks": list(), "info": dict(), } mgr = AccessorManager(None) for path, track in self.tracks.items(): item = { "path": path, "duration": track.info.time_secs } for name in ["title", "album_artist", "artist", "album", "track_num", "disc_num", "genre", "year"]: item[name] = mgr[name].get(track) index["tracks"].append(item) for name in ["album_artist", "album", "genre", "year"]: index["info"][name] = most_common_key_value(name, index["tracks"]) index["info"]["duration"] = sum(map( lambda item: item["duration"], index["tracks"] )) return index
def load(self)
-
List and load MP3 files from the folder.
Expand source code
def load(self): """List and load MP3 files from the folder. """ logging.info("Loading tracks from %s", self.path) self.tracks = dict() for filename in glob.glob(os.path.join(self.path, "*.mp3")): logging.debug("Loading track at %s", os.path.realpath(filename)) self.tracks[filename] = eyed3.load(filename)