mirror of
https://github.com/fergalmoran/picard.git
synced 2026-02-04 14:54:02 +00:00
Merge
This commit is contained in:
@@ -38,9 +38,6 @@ class IFileOpener(Interface):
|
||||
def get_supported_formats(self):
|
||||
pass
|
||||
|
||||
def can_open_file(self, filename):
|
||||
pass
|
||||
|
||||
def open_file(self, filename):
|
||||
pass
|
||||
|
||||
|
||||
@@ -27,25 +27,6 @@ from picard.util import LockableObject, needs_write_lock, needs_read_lock, encod
|
||||
|
||||
class File(LockableObject):
|
||||
|
||||
NEW = 0
|
||||
CHANGED = 1
|
||||
TO_BE_SAVED = 2
|
||||
SAVED = 3
|
||||
|
||||
def __init__(self, filename):
|
||||
LockableObject.__init__(self)
|
||||
self.id = self.new_id()
|
||||
self.filename = filename
|
||||
self.base_filename = os.path.basename(filename)
|
||||
self.state = File.NEW
|
||||
self.orig_metadata = Metadata()
|
||||
self.metadata = Metadata()
|
||||
self.similarity = 1.0
|
||||
self.parent = None
|
||||
|
||||
def __str__(self):
|
||||
return '<File #%d "%s">' % (self.id, self.base_filename)
|
||||
|
||||
__id_counter = 0
|
||||
|
||||
@staticmethod
|
||||
@@ -53,8 +34,43 @@ class File(LockableObject):
|
||||
File.__id_counter += 1
|
||||
return File.__id_counter
|
||||
|
||||
PENDING = 0
|
||||
NORMAL = 1
|
||||
CHANGED = 2
|
||||
ERROR = 3
|
||||
SAVED = 4
|
||||
|
||||
def __init__(self, filename):
|
||||
LockableObject.__init__(self)
|
||||
self.id = self.new_id()
|
||||
self.filename = filename
|
||||
self.base_filename = os.path.basename(filename)
|
||||
self.state = File.PENDING
|
||||
|
||||
self.orig_metadata = Metadata()
|
||||
self.user_metadata = Metadata()
|
||||
self.server_metadata = Metadata()
|
||||
self.metadata = self.user_metadata
|
||||
|
||||
self.orig_metadata["~#length"] = 0
|
||||
self.orig_metadata["title"] = os.path.basename(self.filename)
|
||||
|
||||
self.user_metadata.copy(self.orig_metadata)
|
||||
self.server_metadata.copy(self.orig_metadata)
|
||||
|
||||
self.similarity = 1.0
|
||||
self.parent = None
|
||||
|
||||
def __str__(self):
|
||||
return '<File #%d "%s">' % (self.id, self.base_filename)
|
||||
|
||||
def load(self):
|
||||
"""Save the metadata."""
|
||||
self.read()
|
||||
self.state = File.NORMAL
|
||||
|
||||
def save(self):
|
||||
"""Save the file."""
|
||||
"""Save the metadata."""
|
||||
raise NotImplementedError
|
||||
|
||||
def save_images(self):
|
||||
@@ -127,7 +143,6 @@ class File(LockableObject):
|
||||
similarity = metadata1.compare(metadata2)
|
||||
self.lock_for_write()
|
||||
self.similarity = similarity
|
||||
self.state = self.CHANGED
|
||||
self.unlock()
|
||||
if signal:
|
||||
self.log.debug(u"Updating file %s", self)
|
||||
|
||||
@@ -79,19 +79,19 @@ mutagen._util.delete_bytes = _delete_bytes
|
||||
|
||||
from picard.api import IFileOpener
|
||||
from picard.component import Component, implements
|
||||
from picard.plugins.picardmutagen.asf import MutagenASFFile
|
||||
from picard.plugins.picardmutagen.mp4 import MP4File
|
||||
from picard.plugins.picardmutagen.id3 import (
|
||||
from picard.formats.asf import MutagenASFFile
|
||||
from picard.formats.mp4 import MP4File
|
||||
from picard.formats.id3 import (
|
||||
MP3File,
|
||||
TrueAudioFile,
|
||||
)
|
||||
from picard.plugins.picardmutagen.apev2 import (
|
||||
from picard.formats.apev2 import (
|
||||
MonkeysAudioFile,
|
||||
MusepackFile,
|
||||
OptimFROGFile,
|
||||
WavPackFile,
|
||||
)
|
||||
from picard.plugins.picardmutagen.vorbis import (
|
||||
from picard.formats.vorbis import (
|
||||
FLACFile,
|
||||
OggFLACFile,
|
||||
OggSpeexFile,
|
||||
@@ -128,16 +128,8 @@ class MutagenComponent(Component):
|
||||
return [(key, value[1]) for key, value in
|
||||
self.__supported_formats.items()]
|
||||
|
||||
def can_open_file(self, filename):
|
||||
for ext in self.__supported_formats.keys():
|
||||
if filename.lower().endswith(ext):
|
||||
return True
|
||||
return False
|
||||
|
||||
def open_file(self, filename):
|
||||
for ext in self.__supported_formats.keys():
|
||||
if filename.lower().endswith(ext):
|
||||
file = self.__supported_formats[ext][0](filename)
|
||||
file.read()
|
||||
return (file,)
|
||||
return self.__supported_formats[ext][0](filename)
|
||||
return None
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
from picard.file import File
|
||||
from picard.util import encode_filename
|
||||
from picard.plugins.picardmutagen.mutagenext.asf import ASF
|
||||
from picard.formats.mutagenext.asf import ASF
|
||||
|
||||
class MutagenASFFile(File):
|
||||
|
||||
@@ -22,7 +22,7 @@ import mutagen.mp3
|
||||
import mutagen.trueaudio
|
||||
from mutagen import id3
|
||||
from picard.file import File
|
||||
from picard.plugins.picardmutagen.mutagenext import compatid3
|
||||
from picard.formats.mutagenext import compatid3
|
||||
from picard.util import encode_filename
|
||||
|
||||
class ID3File(File):
|
||||
@@ -19,6 +19,5 @@
|
||||
|
||||
"""Built-in plugins."""
|
||||
|
||||
import picard.plugins.cuesheet
|
||||
import picard.plugins.csv_opener
|
||||
import picard.plugins.picardmutagen
|
||||
#import picard.plugins.cuesheet
|
||||
#import picard.plugins.csv_opener
|
||||
|
||||
109
picard/tagger.py
109
picard/tagger.py
@@ -31,6 +31,7 @@ import imp
|
||||
|
||||
import picard.resources
|
||||
import picard.plugins
|
||||
import picard.formats
|
||||
import picard.tagz
|
||||
|
||||
from picard import musicdns
|
||||
@@ -248,53 +249,35 @@ class Tagger(QtGui.QApplication, ComponentManager, Component):
|
||||
formats.extend(opener.get_supported_formats())
|
||||
return formats
|
||||
|
||||
def add_files(self, files):
|
||||
"""Load and add files."""
|
||||
files = map(os.path.normpath, files)
|
||||
self.log.debug(u"Adding files %r", files)
|
||||
filenames = []
|
||||
for filename in files:
|
||||
not_found = True
|
||||
for file in self.files:
|
||||
if file.filename == filename:
|
||||
not_found = False
|
||||
break
|
||||
if not_found:
|
||||
for opener in self.file_openers:
|
||||
if opener.can_open_file(filename):
|
||||
filenames.append((filename, opener.open_file))
|
||||
if filenames:
|
||||
self.thread_assist.spawn(self.__add_files_thread, filenames,
|
||||
thread=self.load_thread)
|
||||
|
||||
def __add_files_thread(self, filenames):
|
||||
"""Load the files."""
|
||||
files = []
|
||||
for filename, opener in filenames:
|
||||
try:
|
||||
files.extend(opener(filename))
|
||||
except:
|
||||
import traceback; traceback.print_exc()
|
||||
while files:
|
||||
self.thread_assist.proxy_to_main(self.__add_files_finished,
|
||||
files[:100])
|
||||
files = files[100:]
|
||||
|
||||
def __add_files_finished(self, files):
|
||||
"""Add loaded files to the tagger."""
|
||||
for file in files:
|
||||
self.files.append(file)
|
||||
album_id = file.metadata["musicbrainz_albumid"]
|
||||
if album_id:
|
||||
album = self.get_album_by_id(album_id)
|
||||
if not album:
|
||||
album = self.load_album(album_id)
|
||||
if album.loaded:
|
||||
self.match_files_to_album([file], album)
|
||||
else:
|
||||
self._move_to_album.append((file, album))
|
||||
if not file.parent:
|
||||
def add_files(self, filenames):
|
||||
"""Add files to the tagger."""
|
||||
self.log.debug(u"Adding files %r", filenames)
|
||||
for filename in filenames:
|
||||
filename = os.path.normpath(filename)
|
||||
if self.get_file_by_filename(filename):
|
||||
continue
|
||||
for opener in self.file_openers:
|
||||
file = opener.open_file(filename)
|
||||
if not file:
|
||||
continue
|
||||
file.move(self.unmatched_files)
|
||||
self.files.append(file)
|
||||
self.thread_assist.spawn(
|
||||
self.__load_file_thread, file, thread=self.load_thread)
|
||||
|
||||
def __load_file_thread(self, file):
|
||||
"""Load metadata from the file."""
|
||||
self.log.debug(u"Loading file %r", file.filename)
|
||||
file.load()
|
||||
self.thread_assist.proxy_to_main(self.__load_file_finished, file)
|
||||
|
||||
def __load_file_finished(self, file):
|
||||
"""Move loaded file to right album/cluster."""
|
||||
file.update()
|
||||
album_id = file.metadata["musicbrainz_albumid"]
|
||||
if album_id:
|
||||
album = self.load_album(album_id)
|
||||
self.move_files_to_album([file], album)
|
||||
|
||||
def add_directory(self, directory):
|
||||
"""Add all files from the directory ``directory`` to the tagger."""
|
||||
@@ -305,23 +288,21 @@ class Tagger(QtGui.QApplication, ComponentManager, Component):
|
||||
def __read_directory_thread(self, directory):
|
||||
directories = [encode_filename(directory)]
|
||||
while directories:
|
||||
files = []
|
||||
directory = directories.pop()
|
||||
self.log.debug(u"Reading directory %r", directory)
|
||||
self.thread_assist.proxy_to_main(self.__set_status_bar_message,
|
||||
N_("Reading directory %s ..."),
|
||||
directory)
|
||||
self.thread_assist.proxy_to_main(
|
||||
self.__set_status_bar_message,
|
||||
N_("Reading directory %s ..."), decode_filename(directory))
|
||||
filenames = []
|
||||
for name in os.listdir(directory):
|
||||
name = os.path.join(directory, name)
|
||||
if os.path.isdir(name):
|
||||
directories.append(name)
|
||||
else:
|
||||
files.append(decode_filename(name))
|
||||
while files:
|
||||
self.thread_assist.proxy_to_main(self.add_files, files[:100])
|
||||
files = files[100:]
|
||||
self.thread_assist.proxy_to_main(self.__set_status_bar_message,
|
||||
N_("Done"))
|
||||
filenames.append(decode_filename(name))
|
||||
if filenames:
|
||||
self.thread_assist.proxy_to_main(self.add_files, filenames)
|
||||
self.thread_assist.proxy_to_main(self.__clear_status_bar_message)
|
||||
|
||||
def get_file_by_id(self, id):
|
||||
"""Get file by a file ID."""
|
||||
@@ -384,12 +365,9 @@ class Tagger(QtGui.QApplication, ComponentManager, Component):
|
||||
|
||||
def save(self, objects):
|
||||
"""Save the specified objects."""
|
||||
files = []
|
||||
for file in self.get_files_from_objects(objects):
|
||||
file.state = File.TO_BE_SAVED
|
||||
files.append(file)
|
||||
self.set_wait_cursor()
|
||||
self.thread_assist.spawn(self.__save_thread, files)
|
||||
self.thread_assist.spawn(self.__save_thread,
|
||||
self.get_files_from_objects(objects))
|
||||
|
||||
def __rename_file(self, file):
|
||||
file.lock_for_read()
|
||||
@@ -464,6 +442,7 @@ class Tagger(QtGui.QApplication, ComponentManager, Component):
|
||||
failed = False
|
||||
try:
|
||||
file.save()
|
||||
file.state = File.SAVED
|
||||
old_filename = self.__rename_file(file)
|
||||
if (self.config.setting["move_files"] and
|
||||
self.config.setting["move_additional_files"]):
|
||||
@@ -712,6 +691,9 @@ class Tagger(QtGui.QApplication, ComponentManager, Component):
|
||||
def analyze(self, objs):
|
||||
"""Analyze the selected files."""
|
||||
files = self.get_files_from_objects(objs)
|
||||
for file in files:
|
||||
file.state = File.PENDING
|
||||
file.update()
|
||||
self.thread_assist.spawn(self.__analyze_thread, files,
|
||||
thread=self._analyze_thread)
|
||||
|
||||
@@ -734,6 +716,8 @@ class Tagger(QtGui.QApplication, ComponentManager, Component):
|
||||
from picard.musicdns.webservice import TrackFilter, Query
|
||||
ws = self.get_web_service(host="ofa.musicdns.org", pathPrefix="/ofa")
|
||||
for file in files:
|
||||
if file.state != File.PENDING:
|
||||
continue
|
||||
file.lock_for_read()
|
||||
try:
|
||||
filename = file.filename
|
||||
@@ -776,6 +760,9 @@ class Tagger(QtGui.QApplication, ComponentManager, Component):
|
||||
self.__lookup_puid(file)
|
||||
else:
|
||||
self.log.debug("Fingerprint looked up, no PUID found.")
|
||||
if file.state == File.PENDING:
|
||||
file.state = File.NORMAL
|
||||
file.update()
|
||||
|
||||
def cluster(self, objs):
|
||||
"""Group files with similar metadata to 'clusters'."""
|
||||
|
||||
@@ -28,15 +28,6 @@ from picard.config import TextOption
|
||||
|
||||
__all__ = ["FileTreeView", "AlbumTreeView"]
|
||||
|
||||
def matchColor(similarity):
|
||||
colors = ((255, 255, 255), (223, 125, 125))
|
||||
res = [0, 0, 0]
|
||||
#similarity = (1 - similarity) * (1 - similarity)
|
||||
similarity = 1 - similarity
|
||||
for i in range(3):
|
||||
res[i] = colors[0][i] + (colors[1][i] - colors[0][i]) * similarity
|
||||
return QtGui.QColor(res[0], res[1], res[2])
|
||||
|
||||
class BaseTreeView(QtGui.QTreeWidget):
|
||||
|
||||
options = [
|
||||
@@ -76,6 +67,8 @@ class BaseTreeView(QtGui.QTreeWidget):
|
||||
self.contextMenu.addAction(self.main_window.save_action)
|
||||
self.contextMenu.addAction(self.main_window.remove_action)
|
||||
|
||||
self.__file_state_colors[File.NORMAL] = self.tagger.palette().text().color()
|
||||
|
||||
self.objectToItem = {}
|
||||
self.itemToObject = {}
|
||||
|
||||
@@ -115,6 +108,25 @@ class BaseTreeView(QtGui.QTreeWidget):
|
||||
def get_item_from_object(self, obj):
|
||||
return self.objectToItem[obj]
|
||||
|
||||
__file_state_colors = {
|
||||
File.PENDING: QtGui.QColor(128, 128, 128),
|
||||
File.NORMAL: QtGui.QColor(0, 0, 0),
|
||||
File.CHANGED: QtGui.QColor(0, 0, 64),
|
||||
File.ERROR: QtGui.QColor(128, 0, 0),
|
||||
File.SAVED: QtGui.QColor(0, 128, 0),
|
||||
}
|
||||
|
||||
def get_file_state_color(self, state):
|
||||
return self.__file_state_colors[state]
|
||||
|
||||
def get_file_match_color(self, similarity):
|
||||
c1 = (255, 255, 255)
|
||||
c2 = (223, 125, 125)
|
||||
return QtGui.QColor(
|
||||
c2[0] + (c1[0] - c2[0]) * similarity,
|
||||
c2[1] + (c1[1] - c2[1]) * similarity,
|
||||
c2[2] + (c1[2] - c2[2]) * similarity)
|
||||
|
||||
def supportedDropActions(self):
|
||||
return QtCore.Qt.MoveAction | QtCore.Qt.CopyAction
|
||||
|
||||
@@ -275,9 +287,11 @@ class FileTreeView(BaseTreeView):
|
||||
item.setText(0, metadata[u"title"])
|
||||
item.setText(1, format_time(metadata.get(u"~#length", 0)))
|
||||
item.setText(2, metadata[u"artist"])
|
||||
color = matchColor(file.similarity)
|
||||
fg_color = self.get_file_state_color(file.state)
|
||||
bg_color = self.get_file_match_color(file.similarity)
|
||||
for i in range(3):
|
||||
item.setBackgroundColor(i, color)
|
||||
item.setTextColor(i, fg_color)
|
||||
item.setBackgroundColor(i, bg_color)
|
||||
|
||||
def update_file(self, file):
|
||||
try:
|
||||
@@ -385,6 +399,7 @@ class AlbumTreeView(BaseTreeView):
|
||||
item = self.get_item_from_object(track)
|
||||
if track.is_linked():
|
||||
file = track.linked_file
|
||||
state = file.state
|
||||
if file.state == File.SAVED:
|
||||
similarity = 1.0
|
||||
icon = self.icon_saved
|
||||
@@ -393,13 +408,16 @@ class AlbumTreeView(BaseTreeView):
|
||||
icon = self.matchIcons[int(similarity * 5 + 0.5)]
|
||||
else:
|
||||
similarity = 1
|
||||
state = File.NORMAL
|
||||
icon = self.noteIcon
|
||||
|
||||
color = matchColor(similarity)
|
||||
# Colors
|
||||
fg_color = self.get_file_state_color(state)
|
||||
bg_color = self.get_file_match_color(similarity)
|
||||
for i in range(3):
|
||||
item.setBackgroundColor(i, color)
|
||||
item.setTextColor(i, fg_color)
|
||||
item.setBackgroundColor(i, bg_color)
|
||||
# Icon
|
||||
item.setIcon(0, icon)
|
||||
|
||||
self._set_album_metadata(track.album)
|
||||
|
||||
def add_album(self, album):
|
||||
|
||||
Reference in New Issue
Block a user