コード例 #1
0
class Lycra(object):
    """
    This class does the main lyrics management.

    Args:
        config: MusicDB Configuration object.

    Raises:
        TypeError: when *config* is not of type :class:`~lib.cfg.musicdb.MusicDBConfig`
    """
    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 LoadCrawlers(self):
        """
        This method loads all crawlers inside the crawler directory.

        .. warning::

            Changes at crawler may not be recognized until the whole application gets restarted.
            Only new added crawler gets loaded.
            Already loaded crawler are stuck at Pythons module cache.

        Returns:
            ``None``
        """
        # Get a list of all modules
        crawlerfiles = self.fs.GetFiles(".")
        modulenames = [
            self.fs.GetFileName(x) for x in crawlerfiles
            if self.fs.GetFileExtension(x) == "py"
        ]
        if len(modulenames) == 0:
            logging.warning(
                "No modules found in \"%s\"! \033[1;30m(… but crawler cache is still usable.)",
                self.fs.AbsolutePath(CRAWLERPATH))
            self.crawlers = None
            return None

        # load all modules
        self.crawlers = []
        for modulename in modulenames:
            modfp, modpath, moddesc = imp.find_module(modulename,
                                                      [CRAWLERPATH])

            try:
                logging.debug("Loading %s …", str(modpath))
                module = imp.load_module(modulename, modfp, modpath, moddesc)
            except Exception as e:
                logging.error(
                    "Loading Crawler %s failed with error: %s! \033[1;30m(Ignoring this specific Crawler)",
                    str(e), str(modpath))
            finally:
                # Since we may exit via an exception, close fp explicitly.
                if modfp:
                    modfp.close()

            crawler = {}
            crawler["module"] = module
            crawler["modulename"] = modulename
            self.crawlers.append(crawler)

        if len(self.crawlers) == 0:
            logging.warning(
                "No crawler loaded from \"%s\"! \033[1;30m(… but crawler cache is still usable.)",
                self.fs.AbsolutePath(CRAWLERPATH))
            self.crawlers = None
        return None

    def RunCrawler(self, crawler, artistname, albumname, songname, songid):
        """
        This method runs a specific crawler.
        This crawler gets all information available to search for a specific songs lyric.

        This method is for class internal use.
        When using this class, call :meth:`~mdbapi.lycra.Lycra.CrawlForLyrics` instead of calling this method directly.
        Before calling this method, :meth:`~mdbapi.lycra.Lycra.LoadCrawlers` must be called.

        The crawler base class :class:`lib.crawlerapi.LycraCrawler` catches all exceptions so that they do not net to be executed in an try-except environment.

        Args:
            crawler (str): Name of the crawler. If it addresses the file ``lib/crawler/example.py`` the name is ``example``
            artistname (str): The name of the artist as stored in the MusicDatabase
            albumname (str): The name of the album as stored in the MusicDatabase
            songname (str): The name of the song as stored in the MusicDatabase
            songid (int): The ID of the song to associate the lyrics with the song

        Returns:
            ``None``
        """
        crawlerclass = getattr(crawler["module"], crawler["modulename"])
        crawlerentity = crawlerclass(self.lycradb)
        crawlerentity.Crawl(artistname, albumname, songname, songid)
        return None

    def CrawlForLyrics(self, artistname, albumname, songname, songid):
        """
        Loads all crawler from the crawler directory via :meth:`~mdbapi.lycra.Lycra.LoadCrawlers` 
        and runs them via :meth:`~mdbapi.lycra.Lycra.RunCrawler`.

        Args:
            artistname (str): The name of the artist as stored in the music database
            albumname (str): The name of the album as stored in the music database
            songname (str): The name of the song as stored in the music database
            songid (int): The ID of the song to associate the lyrics with the song

        Returns:
            ``False`` if something went wrong. Otherwise ``True``. (This is *no* indication that there were lyrics found!)
        """
        # Load / Reload crawlers
        try:
            self.LoadCrawlers()
        except Exception as e:
            logging.error(
                "Loading Crawlers failed with error \"%s\"! \033[1;30m(… but crawler cache is still usable.)",
                str(e))
            return False

        if not self.crawlers:
            return False

        for crawler in self.crawlers:
            self.RunCrawler(crawler, artistname, albumname, songname, songid)

        return True

    def GetLyrics(self, songid):
        """
        This method returns the lyrics of a song.
        See :meth:`lib.db.lycradb.LycraDatabase.GetLyricsFromCache`
        """
        return self.lycradb.GetLyricsFromCache(songid)
コード例 #2
0
ファイル: add.py プロジェクト: whiteblue3/musicdb
class SongView(ListView, ButtonView):
    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 FindSongs(self):
        files = self.fs.GetFiles(self.albumpath, self.cfg.music.ignoresongs)
        songs = []
        # Only take audio files into account - ignore images and booklets
        for f in files:
            extension = self.fs.GetFileExtension(f)
            if extension in ["mp3", "flac", "m4a", "aac"]:
                songs.append((f, f))
        return songs

    def CleanFileNames(self):
        for index, element in enumerate(self.elements):
            origpath = element[0]
            path = element[1]
            directory = self.fs.GetDirectory(path)
            filename = self.fs.GetFileName(path)
            extension = self.fs.GetFileExtension(path)
            seg = self.FileNameSegments(filename)

            newfilename = filename[seg["number"]:seg["gap"]]
            newfilename += filename[seg["name"]:]
            newfilename = unicodedata.normalize("NFC", newfilename)

            newpath = os.path.join(directory, newfilename + "." + extension)
            self.elements[index] = (origpath, newpath)

    # no path, no file extension!
    # returns indices of name segments
    def FileNameSegments(self, filename):
        seg = {}

        # Start of song number
        m = re.search("\d", filename)
        if m:
            seg["number"] = m.start()
        else:
            seg["number"] = 0

        # End of song number (1 space is necessary)
        m = re.search("\s", filename[seg["number"]:])
        if m:
            seg["gap"] = seg["number"] + 1 + m.start()
        else:
            seg["gap"] = seg["number"] + 1

        # Find start of song name
        m = re.search("\w", filename[seg["gap"]:])
        if m:
            seg["name"] = seg["gap"] + m.start()
        else:
            seg["name"] = seg["gap"]

        return seg

    def UpdateUI(self):
        newsongs = self.FindSongs()
        self.SetData(newsongs)

    def onDrawElement(self, element, number, maxwidth):
        oldpath = element[0]
        path = element[1]
        width = maxwidth
        filename = self.fs.GetFileName(path)
        extension = self.fs.GetFileExtension(path)
        analresult = self.fs.AnalyseSongFileName(filename + "." + extension)

        # Render validation
        if not analresult:
            validation = "\033[1;31m ✘ "
        else:
            validation = "\033[1;32m ✔ "
        width -= 3

        # Render file name
        renderedname = ""
        width -= len(filename)
        seg = self.FileNameSegments(filename)
        renderedname += "\033[1;31m\033[4m" + filename[
            0:seg["number"]] + "\033[24m"
        renderedname += "\033[1;34m" + filename[seg["number"]:seg["gap"]]
        renderedname += "\033[1;31m\033[4m" + filename[
            seg["gap"]:seg["name"]] + "\033[24m"
        renderedname += "\033[1;34m" + filename[seg["name"]:]

        # Render file extension
        fileextension = "." + extension
        fileextension = fileextension[:width]
        fileextension = fileextension.ljust(width)
        return validation + "\033[1;34m" + renderedname + "\033[1;30m" + fileextension

    def Draw(self):
        if self.dialogmode == True:
            pass
        else:
            ListView.Draw(self)
            x = self.x + 1
            y = self.y + self.h - 1
            w = self.w - 2
            ButtonView.Draw(self, x, y, w)

    def HandleKey(self, key):
        if self.dialogmode == True:
            if key == "enter":  # Commit dialog inputs
                songname = self.nameinput.GetData()
                songnumber = self.numberinput.GetData()
                cdnumber = self.cdnuminput.GetData()

                element = self.dialog.oldelement
                path = element[
                    1]  # the editable path is 1, 0 is the original path
                directory = self.fs.GetDirectory(path)
                extension = self.fs.GetFileExtension(path)

                if len(songnumber) == 1:
                    songnumber = "0" + songnumber
                if cdnumber:
                    songnumber = cdnumber + "-" + songnumber

                newpath = os.path.join(
                    directory, songnumber + " " + songname + "." + extension)
                self.SetSelectedData((element[0], newpath))

                self.dialogmode = False
                self.Draw()  # show list view instead of dialog

            elif key == "escape":
                self.dialogmode = False
                self.dialog.oldname = None  # prevent errors by leaving a clean state
                self.Draw()  # show list view instead of dialog
                # reject changes

            else:
                self.dialog.HandleKey(key)

        else:
            if key == "up" or key == "down":
                ListView.HandleKey(self, key)

            elif key == "c":
                self.CleanFileNames()

            elif key == "e":  # edit name
                element = self.GetSelectedData()
                editpath = element[1]
                filename = self.fs.GetFileName(editpath)
                seg = self.FileNameSegments(filename)
                songnumber = filename[seg["number"]:seg["gap"]].strip()
                songname = filename[seg["name"]:].strip()

                if "-" in songnumber:
                    cdnumber = songnumber.split("-")[0].strip()
                    songnumber = songnumber.split("-")[1].strip()
                else:
                    cdnumber = ""

                self.nameinput.SetData(songname)
                self.numberinput.SetData(songnumber)
                self.cdnuminput.SetData(cdnumber)

                self.dialog.oldelement = element
                self.dialog.Draw()
                self.dialogmode = True