def __init__(self, config, database): if type(config) != MusicDBConfig: raise TypeError("Config-class of unknown type") if type(database) != MusicDatabase: raise TypeError("Database-class of unknown type") self.db = database self.cfg = config self.fs = Filesystem() self.musicroot = Filesystem(self.cfg.music.path) self.artworkroot = Filesystem(self.cfg.artwork.path) # Define the prefix that must be used by the WebUI and server to access the artwork files # -> $PREFIX/$Artworkname.jpg self.manifestawprefix = "artwork" # Check if all paths exist that have to exist pathlist = [] pathlist.append(self.cfg.music.path) pathlist.append(self.cfg.artwork.path) pathlist.append(self.cfg.artwork.manifesttemplate) for path in pathlist: if not self.fs.Exists(path): raise ValueError("Path \"" + path + "\" does not exist.") # Instantiate dependent classes self.meta = MetaTags(self.cfg.music.path) self.awcache = ArtworkCache(self.cfg.artwork.path)
def __init__(self, config, database): if type(config) != MusicDBConfig: raise TypeError("Config-class of unknown type") if type(database) != MusicDatabase: raise TypeError("Database-class of unknown type") self.db = database self.cfg = config self.fs = Filesystem() self.musicroot = Filesystem(self.cfg.music.path) self.framesroot = Filesystem(self.cfg.videoframes.path) self.metadata = MetaTags(self.cfg.music.path) self.maxframes = self.cfg.videoframes.frames self.previewlength = self.cfg.videoframes.previewlength self.scales = self.cfg.videoframes.scales # Check if all paths exist that have to exist pathlist = [] pathlist.append(self.cfg.music.path) pathlist.append(self.cfg.videoframes.path) for path in pathlist: if not self.fs.Exists(path): raise ValueError("Path \"" + path + "\" does not exist.")
def __init__(self, config, albumpath, title, x, y, w, h): ListView.__init__(self, title, x, y, w, h) ButtonView.__init__(self, align="center") self.AddButton("↑", "Go up") self.AddButton("↓", "Go down") self.AddButton("c", "Clean name") self.AddButton("e", "Edit name") #self.AddButton("␣", "Toggle") #self.AddButton("↵", "Commit") #self.AddButton("␛", "Cancel") # elements are a tuple (original path, new path) self.cfg = config self.fs = Filesystem(self.cfg.music.path) self.albumpath = albumpath self.nameinput = FileNameInput() self.numberinput = TextInput() self.cdnuminput = TextInput() dialogh = 2 + 3 self.dialog = Dialog("Rename Song", self.x, self.y + 1, self.w, dialogh) self.dialog.AddInput("Song name:", self.nameinput, "Correct name only") self.dialog.AddInput("Song number:", self.numberinput, "Song number only") self.dialog.AddInput("CD number:", self.cdnuminput, "CD number or nothing") self.dialogmode = False
def __init__(self, config, database): if type(config) != MusicDBConfig: raise TypeError("config argument not of type MusicDBConfig") if database != None and type(database) != MusicDatabase: raise TypeError( "database argument not of type MusicDatabase or None") self.db = database self.cfg = config self.uploadfs = Filesystem(self.cfg.uploads.path) self.musicfs = Filesystem(self.cfg.music.path) self.artworkfs = Filesystem(self.cfg.artwork.path) # TODO: check write permission of all directories self.fileprocessing = Fileprocessing(self.cfg.uploads.path) self.dbmanager = MusicDBDatabase(config, database) global Tasks if Tasks == None: self.LoadTasks()
def __init__(self, config): if type(config) != MusicDBConfig: logging.critical( "FATAL ERROR: Config-class of unknown type \"%s\"!", str(type(config))) raise TypeError() self.config = config self.fs = Filesystem(self.config.music.path) self.genrelist = self.config.musicai.genrelist self.modelname = self.config.musicai.modelname self.modelfile = self.config.musicai.modelpath + "/" + self.modelname + ".DCNN.tfl"
def __init__(self, config): if type(config) != MusicDBConfig: logging.error("Config-class of unknown type!") raise TypeError("config argument not of type MusicDBConfig") logging.debug("Crawler path is %s", CRAWLERPATH) self.config = config self.lycradb = LycraDatabase(self.config.lycra.dbpath) self.fs = Filesystem(CRAWLERPATH) self.crawlers = None
def __init__(self, filename): Config.__init__(self, filename) self.fs = Filesystem("/") logging.info("Reading and checking WKServer Configuration") # [meta] self.meta = META() self.meta.version = self.Get(int, "meta", "version", 1) # [websocket] self.websocket = WEBSOCKET() self.websocket.address = self.Get(str, "websocket","address", "127.0.0.1") self.websocket.port = self.Get(int, "websocket","port", 9000) self.websocket.url = self.Get(str, "websocket","url", "wss://localhost:9000") self.websocket.apikey = self.Get(str, "websocket","apikey", None) if not self.websocket.apikey: logging.warning("Value of [websocket]->apikey is not set!") self.caldav = CALDAV() self.caldav.username = self.Get(str, "caldav","username", "user") self.caldav.password = self.Get(str, "caldav","password", "password") self.caldav.url = self.Get(str, "caldav","url", "https://localhost:443") # [TLS] self.tls = TLS() self.tls.cert = self.GetFile( "tls", "cert", "/dev/null") self.tls.key = self.GetFile( "tls", "key", "/dev/null") if self.tls.cert == "/dev/null" or self.tls.key == "/dev/null": logging.warning("You have to set a valid TLS certificate and key!") # [log] self.log = LOG() self.log.logfile = self.Get(str, "log", "logfile", "stderr") self.log.loglevel = self.Get(str, "log", "loglevel", "WARNING") if not self.log.loglevel in ["DEBUG", "INFO", "WARNING", "ERROR"]: logging.error("Invalid loglevel for [log]->loglevel. Loglevel must be one of the following: DEBUG, INFO, WARNING, ERROR") self.log.debugfile = self.Get(str, "log", "debugfile", None) if self.log.debugfile == "/dev/null": self.log.debugfile = None self.log.ignore = self.Get(str, "log", "ignore", None, islist=True) # [debug] self.debug = DEBUG() logging.info("\033[1;32mdone")
def __init__(self, config, database): if type(config) != MusicDBConfig: print( "\033[1;31mFATAL ERROR: Config-class of unknown type!\033[0m") raise TypeError("config argument not of type MusicDBConfig") if type(database) != MusicDatabase: print( "\033[1;31mFATAL ERROR: Database-class of unknown type!\033[0m" ) raise TypeError("database argument not of type MusicDatabase") self.db = database self.cfg = config self.fs = Filesystem(self.cfg.music.cache) self.fileprocessor = Fileprocessing(self.cfg.music.cache) self.artworkcache = ArtworkCache(self.cfg.artwork.path)
def Backup(self): """ Creates a backup of the database file. The path of the backup will be the same as the source file. It gets the following extension: ``.YYYY-MM-DDTHH:MM.bak`` Returns: *Nothing* """ backuppath = self.databasepath backuppath += "." backuppath += datetime.now().isoformat(timespec='minutes') backuppath += ".bak" fs = Filesystem() fs.CopyFile(self.databasepath, backuppath) return
def __init__(self, config, database): if type(config) != MusicDBConfig: print( "\033[1;31mFATAL ERROR: Config-class of unknown type!\033[0m") raise TypeError("config argument not of type MusicDBConfig") if type(database) != MusicDatabase: print( "\033[1;31mFATAL ERROR: Database-class of unknown type!\033[0m" ) raise TypeError("database argument not of type MusicDatabase") self.db = database self.cfg = config self.mp = None self.fs = Filesystem("/") self.fileprocessor = Fileprocessing("/") self.artworkcache = ArtworkCache(self.cfg.artwork.path) self.SetMountpoint( "/mnt") # initialize self.mp with the default mount point /mnt
def UpgradeMusicDB(self): self.PrintCheckFile("music.db") newversion = 3 # Check version of MusicDB version = self.GetDatabaseVersion(self.db) # Check if good if version == newversion: self.PrintGood() return True # Upgrade if too old self.PrintUpgrade(version, newversion) if version == 1: retval = self.AddMetaTableToDatabase(self.db) if not retval: return False version = 2 # Upgrade to version 3 if version == 2: print("Updating albums-table:\033[0;36m") # Add new column to Albums-Table sql = "ALTER TABLE albums ADD COLUMN added INTEGER DEFAULT 0;" self.db.Execute(sql) # Initialize new column fs = Filesystem(self.cfg.music.path) albums = self.db.GetAlbums() for album in tqdm(albums, unit="albums"): moddate = fs.GetModificationDate(album["path"]) album["added"] = moddate self.db.WriteAlbum(album) # Update version in database self.SetDatabaseVersion(self.db, 3) version = 3 self.PrintGood() return True
def Backup(self): """ Creates a backup of the configuration file. The path of the backup will be the same as the source file. It gets the following extension: ``.YYYY-MM-DDTHH:MM.bak`` Returns: *Nothing* """ backuppath = self.configpath backuppath += "." backuppath += datetime.now().isoformat(timespec='minutes') backuppath += ".bak" fs = Filesystem() try: fs.CopyFile(self.configpath, backuppath) except Exception as e: logging.warning( "creating backup of configuration failed with error %s. Writing backup to /tmp/configbackup.ini", str(e)) fs.CopyFile(self.configpath, "/tmp/configbackup.ini") return
def __init__(self, config, database): self.config = config self.musicdb = database self.fs = Filesystem(self.config.music.path) self.trackerdb = TrackerDatabase(self.config.tracker.dbpath)
def __init__(self, filename): Config.__init__(self, filename) self.fs = Filesystem("/") logging.info("Reading and checking MusicDB Configuration") # [meta] self.meta = META() self.meta.version = self.Get(int, "meta", "version", 1) if self.meta.version < 2: logging.warning("Version of musicdb.ini is too old. Please update the MusicDB Configuration!") # [server] self.server = SERVER() self.server.pidfile = self.Get(str, "server", "pidfile", "/opt/musicdb/data/musicdb.pid") self.server.statedir = self.Get(str, "server", "statedir", "/opt/musicdb/data/mdbstate") self.server.fifofile = self.Get(str, "server", "fifofile", "/opt/musicdb/data/musicdb.fifo") # [websocket] self.websocket = WEBSOCKET() self.websocket.address = self.Get(str, "websocket","address", "127.0.0.1") self.websocket.port = self.Get(int, "websocket","port", 9000) self.websocket.url = self.Get(str, "websocket","url", "wss://localhost:9000") self.websocket.opentimeout = self.Get(int, "websocket","opentimeout", 10) self.websocket.closetimeout = self.Get(int, "websocket","closetimeout", 5) self.websocket.apikey = self.Get(str, "websocket","apikey", None) if not self.websocket.apikey: logging.warning("Value of [websocket]->apikey is not set!") # [TLS] self.tls = TLS() self.tls.cert = self.GetFile( "tls", "cert", "/dev/null") self.tls.key = self.GetFile( "tls", "key", "/dev/null") if self.tls.cert == "/dev/null" or self.tls.key == "/dev/null": logging.warning("You have to set a valid TLS certificate and key!") # [database] self.database = DATABASE() self.database.path = self.GetFile( "database", "path", "/opt/musicdb/data/music.db") # [music] self.music = MUSIC() self.music.path = self.GetDirectory("music", "path", "/var/music") self.music.owner = self.Get(str, "music", "owner", "user") self.music.group = self.Get(str, "music", "group", "musicdb") try: pwd.getpwnam(self.music.owner) except KeyError: logging.warning("The group name for [music]->owner is not an existing UNIX user!") try: grp.getgrnam(self.music.group) except KeyError: logging.warning("The group name for [music]->group is not an existing UNIX group!") ignorelist = self.Get(str, "music", "ignoreartists","lost+found") ignorelist = ignorelist.split("/") self.music.ignoreartists = [item.strip() for item in ignorelist] ignorelist = self.Get(str, "music", "ignorealbums", "") ignorelist = ignorelist.split("/") self.music.ignorealbums = [item.strip() for item in ignorelist] ignorelist = self.Get(str, "music", "ignoresongs", ".directory / desktop.ini / Desktop.ini / .DS_Store / Thumbs.db") ignorelist = ignorelist.split("/") self.music.ignoresongs = [item.strip() for item in ignorelist] # [artwork] self.artwork = ARTWORK() self.artwork.path = self.GetDirectory("artwork", "path", "/opt/musicdb/data/artwork") self.artwork.scales = self.Get(int, "artwork", "scales", "50, 150, 500", islist=True) for s in [50, 150, 500]: if not s in self.artwork.scales: logging.error("Missing scale in [artwork]->scales: The web UI expects a scale of %d (res: %dx%d)", s, s, s) self.artwork.manifesttemplate=self.GetFile( "artwork", "manifesttemplate", "/opt/musicdb/server/manifest.txt", logging.warning) # a missing manifest does not affect the main functionality self.artwork.manifest = self.Get(str, "artwork", "manifest", "/opt/musicdb/server/webui/manifest.appcache") # [extern] self.extern = EXTERN() self.extern.configtemplate = self.GetFile( "extern", "configtemplate","/opt/musicdb/server/share/extconfig.ini") self.extern.statedir = self.Get(str, "extern", "statedir", ".mdbstate") self.extern.configfile = self.Get(str, "extern", "configfile", "config.ini") self.extern.songmap = self.Get(str, "extern", "songmap", "songmap.csv") # [tracker] self.tracker = TRACKER() self.tracker.dbpath = self.GetFile( "tracker", "dbpath", "/opt/musicdb/data/tracker.db") self.tracker.cuttime = self.Get(int, "tracker", "cuttime", "30") # [lycra] self.lycra = LYCRA() self.lycra.dbpath = self.GetFile( "lycra", "dbpath", "/opt/musicdb/data/lycra.db") # [Icecast] self.icecast = ICECAST() self.icecast.port = self.Get(int, "Icecast", "port", "6666") self.icecast.user = self.Get(str, "Icecast", "user", "source") self.icecast.password = self.Get(str, "Icecast", "password", "hackme") self.icecast.mountname = self.Get(str, "Icecast", "mountname","/stream") # [MusicAI] self.musicai = MUSICAI() self.musicai.modelpath = self.GetDirectory("MusicAI", "modelpath", "/opt/musicdb/data/musicai/models") self.musicai.tmppath = self.GetDirectory("MusicAI", "tmppath", "/opt/musicdb/data/musicai/tmp") self.musicai.logpath = self.GetDirectory("MusicAI", "logpath", "/opt/musicdb/data/musicai/log") self.musicai.specpath = self.GetDirectory("MusicAI", "spectrogrampath", "/opt/musicdb/data/musicai/spectrograms") self.musicai.slicesize = self.Get(int, "MusicAI", "slicesize", 128) self.musicai.epoch = self.Get(int, "MusicAI", "epoch", 20) self.musicai.batchsize = self.Get(int, "MusicAI", "batchsize", 128) self.musicai.usegpu = self.Get(bool,"MusicAI", "usegpu", True) self.musicai.modelname = self.Get(str, "MusicAI", "modelname", "MusicGenre") self.musicai.genrelist = self.Get(str, "MusicAI", "genrelist", None, islist=True) # [Randy] self.randy = RANDY() self.randy.nodisabled = self.Get(bool, "Randy", "nodisabled", True) self.randy.nohated = self.Get(bool, "Randy", "nohated", True) self.randy.minsonglen = self.Get(int, "Randy", "minsonglen", 120) self.randy.maxsonglen = self.Get(int, "Randy", "maxsonglen", 600) self.randy.songbllen = self.Get(int, "Randy", "songbllen", 50) self.randy.albumbllen = self.Get(int, "Randy", "albumbllen", 20) self.randy.artistbllen = self.Get(int, "Randy", "artistbllen", 10) self.randy.maxblage = self.Get(int, "Randy", "maxblage", 24) # [log] self.log = LOG() self.log.logfile = self.Get(str, "log", "logfile", "stderr") self.log.loglevel = self.Get(str, "log", "loglevel", "WARNING") if not self.log.loglevel in ["DEBUG", "INFO", "WARNING", "ERROR"]: logging.error("Invalid loglevel for [log]->loglevel. Loglevel must be one of the following: DEBUG, INFO, WARNING, ERROR") self.log.debugfile = self.Get(str, "log", "debugfile", None) if self.log.debugfile == "/dev/null": self.log.debugfile = None self.log.ignore = self.Get(str, "log", "ignore", None, islist=True) # [debug] self.debug = DEBUG() self.debug.disablestats = self.Get(int, "debug", "disablestats", 0) self.debug.disabletracker = self.Get(int, "debug", "disabletracker", 0) self.debug.disableai = self.Get(int, "debug", "disableai", 1) self.debug.disabletagging = self.Get(int, "debug", "disabletagging", 0) logging.info("\033[1;32mdone")
def __init__(self, config, database): MDBModule.__init__(self) self.musicdb = database self.config = config self.fs = Filesystem(self.config.music.path)
def StreamingThread(): """ This thread manages the streaming of the songs from the Song Queue to the Icecast server. The thread tracks the played song using the :doc:`/mdbapi/tracker` module. It also tracks randomly added songs assuming the user skips or removes songs that don't fit. Only completely played songs will be considered. Skipped songs will be ignored. The thread triggers the following events: * ``StatusChanged``: When the play-state * ``TimeChanged``: To update the current streaming progress of a song The ``TimeChanged`` event gets triggered approximately every second. """ from lib.stream.icecast import IcecastInterface from mdbapi.tracker import Tracker global Config global RunThread global CommandQueue global State # Create all interfaces that are needed by this Thread musicdb = MusicDatabase(Config.database.path) tracker = Tracker(Config, musicdb) filesystem = Filesystem(Config.music.path) queue = SongQueue(Config, musicdb) randy = Randy(Config, musicdb) icecast = IcecastInterface(port=Config.icecast.port, user=Config.icecast.user, password=Config.icecast.password, mountname=Config.icecast.mountname) icecast.Mute() while RunThread: # Sleep a bit to reduce the load on the CPU. If disconnected, sleep a bit longer if State["isconnected"]: time.sleep(0.1) else: time.sleep(2) # Check connection to Icecast, and connect if disconnected. isconnected = icecast.IsConnected() if State["isconnected"] != isconnected: State["isconnected"] = isconnected Event_StatusChanged() if not isconnected: # Try to connect, and check if connection succeeded in the next turn of the main loop logging.info("Trying to reconnect to Icecast…") icecast.Connect() continue # Get current song that shall be streamed. queueentry = queue.CurrentSong() if queueentry == None or queueentry["entryid"] == None: logging.info("Waiting for 5s to try to get a new song to play.") time.sleep(5) continue mdbsong = musicdb.GetSongById(queueentry["songid"]) songpath = filesystem.AbsolutePath(mdbsong["path"]) # Stream song icecast.UpdateTitle(mdbsong["path"]) logging.debug("Start streaming %s", songpath) timeplayed = 0 lasttimestamp = time.time() for frameinfo in icecast.StreamFile(songpath): # Send every second the estimated time position of the song. if not frameinfo["muted"]: timeplayed += frameinfo["header"]["frametime"] timestamp = time.time() timediff = timestamp - lasttimestamp if timediff >= 1.0: Event_TimeChanged(timeplayed / 1000) lasttimestamp = timestamp # Check if the thread shall be exit if not RunThread: break # read and handle queue commands if there is one if len(CommandQueue) == 0: continue command, argument = CommandQueue.pop(0) if command == "PlayNextSong": logging.debug("Playing next song") break # Stop streaming current song, and start the next one elif command == "Play": logging.debug("Setting Play-State to %s", str(argument)) State["isplaying"] = argument icecast.Mute( not State["isplaying"]) # Mute stream, when not playing Event_StatusChanged() else: # when the for loop streaming the current song gets not left via break, # then the whole song was streamed. So add that song to the trackers list # Also update the last time played information. # In case the loop ended because Icecast failed, update the Status if icecast.IsConnected(): if not queueentry["israndom"]: # do not track random songs tracker.AddSong(queueentry["songid"]) if not Config.debug.disablestats: musicdb.UpdateSongStatistic(queueentry["songid"], "lastplayed", int(time.time())) else: icecast.Mute() State["isplaying"] = False State["isconnected"] = False Event_StatusChanged() # Current song completely streamed. Get next one. # When the song was stopped to shutdown the server, do not skip to the next one # In case the loop stopped because of an Icecast error, stay at the last song. if RunThread and icecast.IsConnected(): queue.NextSong()
def UploadManagementThread(): """ This thread handles the uploaded files. It maintains the storage of temporary data and allows asynchronous file management and importing """ # TODO: Remove left over uploads (uploaded files without task-ID) # TODO: Continue uploads that were interrupted # TODO: Identify discontinued uploads # TODO: Handle failed uploads (clean up) global Config global Thread global RunThread global Tasks musicdb = MusicDatabase(Config.database.path) filesystem = Filesystem(Config.uploads.path) manager = UploadManager(Config, musicdb) if not Config.uploads.allow: logging.warning( "Uploads not allowed! \033[1;30m(See MusicDB Configuration: [uploads]->allow)" ) # Start streaming … while RunThread: # Sleep a bit to reduce the load on the CPU. If nothing to do, sleep a bit longer if len(Tasks) > 0: time.sleep(1) else: time.sleep(1) deletekeys = [] for key, task in Tasks.items(): state = task["state"] contenttype = task["contenttype"] logging.debug("Task with state \"%s\" found. (%s)", str(state), str(contenttype)) if state == "uploadfailed" or state == "importfailed" or state == "importcomplete": if contenttype in ["artwork"]: task["state"] = "remove" manager.SaveTask(task) elif state == "uploadcomplete": manager.PreProcessUploadedFile(task) elif state == "startimport": if contenttype == "video": success = manager.ImportVideo(task) elif contenttype == "artwork": success = manager.ImportArtwork(task) else: logging.error( "Invalid content type \"%s\". \033[1;30m(forcing state importfailed)", contenttype) success = False if success: if contenttype in ["album", "video"]: task["state"] = "importartwork" else: task["state"] = "importcomplete" manager.SaveTask(task) manager.NotifyClient("StateUpdate", task) else: manager.UpdateState(task, "importfailed") elif state == "importartwork": if contenttype == "video": success = manager.ImportVideoArtwork(task) else: logging.error( "Invalid content type \"%s\". \033[1;30m(forcing state importfailed)", contenttype) success = False if success: manager.UpdateState(task, "importcompleted") else: manager.UpdateState(task, "importfailed") elif state == "remove": manager.RemoveTask(task) deletekeys.append(task["id"]) # Remove all deleted tasks for key in deletekeys: Tasks.pop(key, None) return
def __init__(self, artworkdir): self.artworkroot = Filesystem(artworkdir)
def MDBM_Main(self, args): if args.test: from tqdm import tqdm print("\033[1;35mTranslating old table to new table …\033[0m") # # Translate old table to new table # sql = "SELECT song, successor, weight FROM graph" # results = self.trackerdb.GetFromDatabase(sql) # for result in results: # for _ in range(result[2]): # self.trackerdb.AddRelation("song", result[0], result[1]) # # Generate artistrelations out of songrelations # sql = "SELECT songida, songidb, weight FROM songrelations" # results = self.trackerdb.GetFromDatabase(sql) # for result in tqdm(results): # artista = self.musicdb.GetSongById(result[0])["artistid"] # artistb = self.musicdb.GetSongById(result[1])["artistid"] # for _ in range(result[2]): # self.trackerdb.AddRelation("artist", artista, artistb) print("\033[1;32mdone!\033[0m") return 0 # Genrate path relative to the music root directory - if possible try: path = self.fs.AbsolutePath( args.path) # Be sure the path is absolute (resolve "./") path = self.fs.RemoveRoot( path) # Now make a relative artist or song path except Exception as e: print( "\033[1;31mInvalid path. Determin relative path to the music root directory failed with error: %s", str(e)) return 1 # Identify target by path and get target ID if self.fs.IsFile(path): mdbsong = self.musicdb.GetSongByPath(path) if not mdbsong: print( "\033[1;31mPath %s is a file, but it is not a song file!\033[0m" % (path)) target = "song" targetid = mdbsong["id"] elif self.fs.IsDirectory(path): mdbartist = self.musicdb.GetArtistByPath(path) if not mdbartist: print( "\033[1;31mPath %s is a directory, but it is not an artist directory!\033[0m" % (path)) target = "artist" targetid = mdbartist["id"] else: print("\033[1;31mPath %s does not exist!\033[0m" % (path)) return 1 # Get target relation print( "\033[1;34mGetting \033[1;36m%s\033[1;34m relations from database … \033[0m" % (target)) relations = self.trackerdb.GetRelations(target, targetid) print("\033[1;36m%d\033[1;34m entries found.\033[0m" % (len(relations))) # Apply parameters if args.show: self.ShowRelations(target, targetid, relations) if args.dot: rootfs = Filesystem() dotfile = rootfs.AbsolutePath(args.dot) self.GenerateDotFile(target, targetid, relations, dotfile) return 0
def __init__(self, root="/"): self.fs = Filesystem(root) self.file = None # the mutagenx file handler/object self.ftype = None # contains the identifier of the filetype (m4a, mp3, flac) self.extension = None # contains the actual file extension self.path = None # contains the full path of the file for debugging