Option to set preferred release types for improved album matching.

This commit is contained in:
Philipp Wolfer
2011-05-13 00:27:47 +02:00
7 changed files with 485 additions and 13 deletions

View File

@@ -2,6 +2,7 @@ Version 0.14 - IN DEVELOPMENT
* Fixed a problem with network operations hanging after a network error
(#5794, #5884)
* ID3v2.3 with UTF-16 is now the default ID3 version
* Option to set preferred release types for improved album matching
Version 0.13 - 2011-03-06
* Changed Picard icon license to CC by-sa

View File

@@ -43,6 +43,7 @@ from picard.util import (
LockableObject,
pathcmp,
mimetype,
load_release_type_scores,
)
@@ -433,10 +434,11 @@ class File(LockableObject, Item):
Weigths:
* title = 13
* artist name = 3
* artist name = 4
* release name = 5
* length = 10
* number of tracks = 3
* number of tracks = 4
* album type = 20
"""
total = 0.0
@@ -467,7 +469,8 @@ class File(LockableObject, Item):
parts.append((score, 10))
total += 10
track_list = track.release_list[0].release[0].track_list[0]
first_release = track.release_list[0].release[0]
track_list = first_release.track_list[0]
if 'totaltracks' in self.metadata and 'count' in track_list.attribs:
try:
a = int(self.metadata['totaltracks'])
@@ -483,6 +486,16 @@ class File(LockableObject, Item):
except ValueError:
pass
type_scores = load_release_type_scores(self.config.setting["release_type_scores"])
if 'type' in first_release.attribs:
release_type = first_release.type
score = type_scores.get(release_type, type_scores.get('Other', 0.5))
print release_type, score
else:
score = 0.0
parts.append((score, 20))
total += 20
return reduce(lambda x, y: x + y[0] * y[1] / total, parts, 0.0)
def _lookup_finished(self, lookuptype, document, http, error):

View File

@@ -17,8 +17,9 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
from PyQt4 import QtGui
from picard.config import FloatOption
from PyQt4 import QtCore, QtGui
from picard.config import FloatOption, TextOption
from picard.util import load_release_type_scores, save_release_type_scores
from picard.ui.options import OptionsPage, OptionsCheckError, register_options_page
from picard.ui.ui_options_matching import Ui_MatchingOptionsPage
@@ -35,22 +36,48 @@ class MatchingOptionsPage(OptionsPage):
FloatOption("setting", "file_lookup_threshold", 0.7),
FloatOption("setting", "cluster_lookup_threshold", 0.8),
FloatOption("setting", "track_matching_threshold", 0.4),
TextOption("setting", "release_type_scores", "Album 0.2 Single 0.2 EP 0.2 Compilation 0.2 Soundtrack 0.2 Spokenword 0.2 Interview 0.2 Audiobook 0.2 Live 0.2 Remix 0.2 Other 0.5"),
]
_release_type_sliders = {}
def __init__(self, parent=None):
super(MatchingOptionsPage, self).__init__(parent)
self.ui = Ui_MatchingOptionsPage()
self.ui.setupUi(self)
self.connect(self.ui.reset_preferred_types_btn, QtCore.SIGNAL("clicked()"), self.reset_preferred_types)
self._release_type_sliders["Album"] = self.ui.prefer_album_score
self._release_type_sliders["Single"] = self.ui.prefer_single_score
self._release_type_sliders["EP"] = self.ui.prefer_ep_score
self._release_type_sliders["Compilation"] = self.ui.prefer_compilation_score
self._release_type_sliders["Soundtrack"] = self.ui.prefer_soundtrack_score
self._release_type_sliders["Spokenword"] = self.ui.prefer_spokenword_score
self._release_type_sliders["Interview"] = self.ui.prefer_interview_score
self._release_type_sliders["Audiobook"] = self.ui.prefer_audiobook_score
self._release_type_sliders["Live"] = self.ui.prefer_live_score
self._release_type_sliders["Remix"] = self.ui.prefer_remix_score
self._release_type_sliders["Other"] = self.ui.prefer_other_score
def load(self):
self.ui.file_lookup_threshold.setValue(int(self.config.setting["file_lookup_threshold"] * 100))
self.ui.cluster_lookup_threshold.setValue(int(self.config.setting["cluster_lookup_threshold"] * 100))
self.ui.track_matching_threshold.setValue(int(self.config.setting["track_matching_threshold"] * 100))
scores = load_release_type_scores(self.config.setting["release_type_scores"])
for (release_type, release_type_slider) in self._release_type_sliders.iteritems():
release_type_slider.setValue(int(scores.get(release_type, 0.5) * 100))
def save(self):
self.config.setting["file_lookup_threshold"] = float(self.ui.file_lookup_threshold.value()) / 100.0
self.config.setting["cluster_lookup_threshold"] = float(self.ui.cluster_lookup_threshold.value()) / 100.0
self.config.setting["track_matching_threshold"] = float(self.ui.track_matching_threshold.value()) / 100.0
scores = {}
for (release_type, release_type_slider) in self._release_type_sliders.iteritems():
scores[release_type] = float(release_type_slider.value()) / 100.0
self.config.setting["release_type_scores"] = save_release_type_scores(scores)
def reset_preferred_types(self):
for release_type_slider in self._release_type_sliders.values():
release_type_slider.setValue(50)
register_options_page(MatchingOptionsPage)

View File

@@ -2,8 +2,8 @@
# Form implementation generated from reading ui file 'ui/options_matching.ui'
#
# Created: Thu Sep 3 00:39:04 2009
# by: PyQt4 UI code generator 4.4.4
# Created: Thu May 12 23:52:13 2011
# by: PyQt4 UI code generator 4.7.2
#
# WARNING! All changes made in this file will be lost!
@@ -12,7 +12,7 @@ from PyQt4 import QtCore, QtGui
class Ui_MatchingOptionsPage(object):
def setupUi(self, MatchingOptionsPage):
MatchingOptionsPage.setObjectName("MatchingOptionsPage")
MatchingOptionsPage.resize(383, 313)
MatchingOptionsPage.resize(382, 498)
self.vboxlayout = QtGui.QVBoxLayout(MatchingOptionsPage)
self.vboxlayout.setSpacing(6)
self.vboxlayout.setMargin(9)
@@ -60,8 +60,125 @@ class Ui_MatchingOptionsPage(object):
self.label_5.setObjectName("label_5")
self.gridlayout.addWidget(self.label_5, 1, 0, 1, 1)
self.vboxlayout.addWidget(self.rename_files)
spacerItem = QtGui.QSpacerItem(20, 41, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
self.vboxlayout.addItem(spacerItem)
self.groupBox = QtGui.QGroupBox(MatchingOptionsPage)
self.groupBox.setObjectName("groupBox")
self.gridLayout = QtGui.QGridLayout(self.groupBox)
self.gridLayout.setObjectName("gridLayout")
self.prefer_album_score = QtGui.QSlider(self.groupBox)
self.prefer_album_score.setMaximum(100)
self.prefer_album_score.setProperty("value", 50)
self.prefer_album_score.setOrientation(QtCore.Qt.Horizontal)
self.prefer_album_score.setObjectName("prefer_album_score")
self.gridLayout.addWidget(self.prefer_album_score, 0, 2, 1, 1)
self.prefer_single_score = QtGui.QSlider(self.groupBox)
self.prefer_single_score.setMaximum(100)
self.prefer_single_score.setProperty("value", 50)
self.prefer_single_score.setOrientation(QtCore.Qt.Horizontal)
self.prefer_single_score.setObjectName("prefer_single_score")
self.gridLayout.addWidget(self.prefer_single_score, 1, 2, 1, 1)
self.label = QtGui.QLabel(self.groupBox)
self.label.setObjectName("label")
self.gridLayout.addWidget(self.label, 0, 0, 1, 1)
self.label_2 = QtGui.QLabel(self.groupBox)
self.label_2.setObjectName("label_2")
self.gridLayout.addWidget(self.label_2, 1, 0, 1, 1)
self.prefer_ep_score = QtGui.QSlider(self.groupBox)
self.prefer_ep_score.setMaximum(100)
self.prefer_ep_score.setProperty("value", 50)
self.prefer_ep_score.setOrientation(QtCore.Qt.Horizontal)
self.prefer_ep_score.setObjectName("prefer_ep_score")
self.gridLayout.addWidget(self.prefer_ep_score, 2, 2, 1, 1)
self.prefer_compilation_score = QtGui.QSlider(self.groupBox)
self.prefer_compilation_score.setMaximum(100)
self.prefer_compilation_score.setProperty("value", 50)
self.prefer_compilation_score.setOrientation(QtCore.Qt.Horizontal)
self.prefer_compilation_score.setObjectName("prefer_compilation_score")
self.gridLayout.addWidget(self.prefer_compilation_score, 3, 2, 1, 1)
self.prefer_soundtrack_score = QtGui.QSlider(self.groupBox)
self.prefer_soundtrack_score.setMaximum(100)
self.prefer_soundtrack_score.setProperty("value", 50)
self.prefer_soundtrack_score.setOrientation(QtCore.Qt.Horizontal)
self.prefer_soundtrack_score.setObjectName("prefer_soundtrack_score")
self.gridLayout.addWidget(self.prefer_soundtrack_score, 4, 2, 1, 1)
self.prefer_spokenword_score = QtGui.QSlider(self.groupBox)
self.prefer_spokenword_score.setMaximum(100)
self.prefer_spokenword_score.setProperty("value", 50)
self.prefer_spokenword_score.setOrientation(QtCore.Qt.Horizontal)
self.prefer_spokenword_score.setObjectName("prefer_spokenword_score")
self.gridLayout.addWidget(self.prefer_spokenword_score, 5, 2, 1, 1)
self.label_3 = QtGui.QLabel(self.groupBox)
self.label_3.setObjectName("label_3")
self.gridLayout.addWidget(self.label_3, 2, 0, 1, 1)
self.label_7 = QtGui.QLabel(self.groupBox)
self.label_7.setObjectName("label_7")
self.gridLayout.addWidget(self.label_7, 3, 0, 1, 1)
self.label_8 = QtGui.QLabel(self.groupBox)
self.label_8.setObjectName("label_8")
self.gridLayout.addWidget(self.label_8, 4, 0, 1, 1)
self.label_9 = QtGui.QLabel(self.groupBox)
self.label_9.setObjectName("label_9")
self.gridLayout.addWidget(self.label_9, 5, 0, 1, 1)
self.prefer_interview_score = QtGui.QSlider(self.groupBox)
self.prefer_interview_score.setMaximum(100)
self.prefer_interview_score.setProperty("value", 50)
self.prefer_interview_score.setOrientation(QtCore.Qt.Horizontal)
self.prefer_interview_score.setObjectName("prefer_interview_score")
self.gridLayout.addWidget(self.prefer_interview_score, 6, 2, 1, 1)
self.prefer_audiobook_score = QtGui.QSlider(self.groupBox)
self.prefer_audiobook_score.setMaximum(100)
self.prefer_audiobook_score.setProperty("value", 50)
self.prefer_audiobook_score.setOrientation(QtCore.Qt.Horizontal)
self.prefer_audiobook_score.setObjectName("prefer_audiobook_score")
self.gridLayout.addWidget(self.prefer_audiobook_score, 7, 2, 1, 1)
self.prefer_live_score = QtGui.QSlider(self.groupBox)
self.prefer_live_score.setMaximum(100)
self.prefer_live_score.setProperty("value", 50)
self.prefer_live_score.setOrientation(QtCore.Qt.Horizontal)
self.prefer_live_score.setObjectName("prefer_live_score")
self.gridLayout.addWidget(self.prefer_live_score, 8, 2, 1, 1)
self.label_10 = QtGui.QLabel(self.groupBox)
self.label_10.setObjectName("label_10")
self.gridLayout.addWidget(self.label_10, 6, 0, 1, 1)
self.label_11 = QtGui.QLabel(self.groupBox)
self.label_11.setObjectName("label_11")
self.gridLayout.addWidget(self.label_11, 7, 0, 1, 1)
self.label_12 = QtGui.QLabel(self.groupBox)
self.label_12.setObjectName("label_12")
self.gridLayout.addWidget(self.label_12, 8, 0, 1, 1)
self.prefer_remix_score = QtGui.QSlider(self.groupBox)
self.prefer_remix_score.setMaximum(100)
self.prefer_remix_score.setProperty("value", 50)
self.prefer_remix_score.setOrientation(QtCore.Qt.Horizontal)
self.prefer_remix_score.setObjectName("prefer_remix_score")
self.gridLayout.addWidget(self.prefer_remix_score, 9, 2, 1, 1)
self.label_13 = QtGui.QLabel(self.groupBox)
self.label_13.setObjectName("label_13")
self.gridLayout.addWidget(self.label_13, 9, 0, 1, 1)
self.prefer_other_score = QtGui.QSlider(self.groupBox)
self.prefer_other_score.setMaximum(100)
self.prefer_other_score.setSliderPosition(50)
self.prefer_other_score.setOrientation(QtCore.Qt.Horizontal)
self.prefer_other_score.setObjectName("prefer_other_score")
self.gridLayout.addWidget(self.prefer_other_score, 10, 2, 1, 1)
self.label_14 = QtGui.QLabel(self.groupBox)
self.label_14.setObjectName("label_14")
self.gridLayout.addWidget(self.label_14, 10, 0, 1, 1)
self.horizontalLayout = QtGui.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
self.horizontalLayout.addItem(spacerItem)
self.reset_preferred_types_btn = QtGui.QPushButton(self.groupBox)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.reset_preferred_types_btn.sizePolicy().hasHeightForWidth())
self.reset_preferred_types_btn.setSizePolicy(sizePolicy)
self.reset_preferred_types_btn.setObjectName("reset_preferred_types_btn")
self.horizontalLayout.addWidget(self.reset_preferred_types_btn)
self.gridLayout.addLayout(self.horizontalLayout, 12, 2, 1, 1)
self.vboxlayout.addWidget(self.groupBox)
spacerItem1 = QtGui.QSpacerItem(20, 41, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
self.vboxlayout.addItem(spacerItem1)
self.label_6.setBuddy(self.file_lookup_threshold)
self.label_4.setBuddy(self.file_lookup_threshold)
self.label_5.setBuddy(self.file_lookup_threshold)
@@ -79,4 +196,17 @@ class Ui_MatchingOptionsPage(object):
self.file_lookup_threshold.setSuffix(_(" %"))
self.label_4.setText(_("Minimal similarity for file lookups:"))
self.label_5.setText(_("Minimal similarity for cluster lookups:"))
self.groupBox.setTitle(_("Preferred release types"))
self.label.setText(_("Album"))
self.label_2.setText(_("Single"))
self.label_3.setText(_("EP"))
self.label_7.setText(_("Compilation"))
self.label_8.setText(_("Soundtrack"))
self.label_9.setText(_("Spokenword"))
self.label_10.setText(_("Interview"))
self.label_11.setText(_("Audiobook"))
self.label_12.setText(_("Live"))
self.label_13.setText(_("Remix"))
self.label_14.setText(_("Other"))
self.reset_preferred_types_btn.setText(_("Reset all"))

View File

@@ -352,3 +352,15 @@ def mbid_validate(string):
def rot13(input):
return u''.join(unichr(rot_13.encoding_map.get(ord(c), ord(c))) for c in input)
def load_release_type_scores(setting):
scores = {}
values = setting.split()
for i in range(0, len(values), 2):
scores[values[i]] = float(values[i+1]) if i+1 < len(values) else 0.0
return scores
def save_release_type_scores(scores):
return " ".join(["%s %.2f" % v for v in scores.iteritems()])

View File

@@ -99,7 +99,7 @@ class TranslateArtistTest(unittest.TestCase):
self.failIfEqual(u"Hamasaki, Ayumi & Keiko", util.translate_artist(u"浜崎あゆみ & KEIKO", u"Hamasaki, Ayumi & Keiko"))
def test_cyrillic(self):
self.failUnlessEqual(u"Pyotr Ilyich Tchaikovsky", util.translate_artist(u"Пётр Ильич Чайковский", u"Tchaikovsky, Pyotr Ilyich"))
self.failUnlessEqual(U"Pyotr Ilyich Tchaikovsky", util.translate_artist(u"Пётр Ильич Чайковский", u"Tchaikovsky, Pyotr Ilyich"))
self.failIfEqual(u"Tchaikovsky, Pyotr Ilyich", util.translate_artist(u"Пётр Ильич Чайковский", u"Tchaikovsky, Pyotr Ilyich"))
self.failIfEqual(u"Пётр Ильич Чайковский", util.translate_artist(u"Пётр Ильич Чайковский", u"Tchaikovsky, Pyotr Ilyich"))
@@ -112,3 +112,33 @@ class FormatTimeTest(unittest.TestCase):
self.failUnlessEqual("3:00", util.format_time(179500))
self.failUnlessEqual("2:59", util.format_time(179499))
class LoadReleaseTypeScoresTest(unittest.TestCase):
def test_valid(self):
release_type_score_config = "Album 1.0 Single 0.5 EP 0.5 Compilation 0.5 Soundtrack 0.5 Spokenword 0.5 Interview 0.2 Audiobook 0.0 Live 0.5 Remix 0.4 Other 0.0"
release_type_scores = util.load_release_type_scores(release_type_score_config)
self.assertEqual(1.0, release_type_scores["Album"])
self.assertEqual(0.5, release_type_scores["Single"])
self.assertEqual(0.2, release_type_scores["Interview"])
self.assertEqual(0.0, release_type_scores["Audiobook"])
self.assertEqual(0.4, release_type_scores["Remix"])
def test_invalid(self):
release_type_score_config = "Album 1.0 Other"
release_type_scores = util.load_release_type_scores(release_type_score_config)
self.assertEqual(1.0, release_type_scores["Album"])
self.assertEqual(0.0, release_type_scores["Other"])
class SaveReleaseTypeScoresTest(unittest.TestCase):
def test(self):
expected = "Album 1.00 Single 0.50 Other 0.00"
scores = {"Album": 1.0, "Single": 0.5, "Other": 0.0}
saved_scores = util.save_release_type_scores(scores)
self.assertTrue("Album 1.00" in saved_scores)
self.assertTrue("Single 0.50" in saved_scores)
self.assertTrue("Other 0.00" in saved_scores)
self.assertEqual(6, len(saved_scores.split()))

View File

@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>383</width>
<height>313</height>
<width>382</width>
<height>498</height>
</rect>
</property>
<layout class="QVBoxLayout">
@@ -110,6 +110,265 @@
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Preferred release types</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="2">
<widget class="QSlider" name="prefer_album_score">
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>50</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QSlider" name="prefer_single_score">
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>50</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Album</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Single</string>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QSlider" name="prefer_ep_score">
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>50</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="3" column="2">
<widget class="QSlider" name="prefer_compilation_score">
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>50</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="4" column="2">
<widget class="QSlider" name="prefer_soundtrack_score">
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>50</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="5" column="2">
<widget class="QSlider" name="prefer_spokenword_score">
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>50</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>EP</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Compilation</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Soundtrack</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
<string>Spokenword</string>
</property>
</widget>
</item>
<item row="6" column="2">
<widget class="QSlider" name="prefer_interview_score">
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>50</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="7" column="2">
<widget class="QSlider" name="prefer_audiobook_score">
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>50</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="8" column="2">
<widget class="QSlider" name="prefer_live_score">
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>50</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_10">
<property name="text">
<string>Interview</string>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="label_11">
<property name="text">
<string>Audiobook</string>
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QLabel" name="label_12">
<property name="text">
<string>Live</string>
</property>
</widget>
</item>
<item row="9" column="2">
<widget class="QSlider" name="prefer_remix_score">
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>50</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QLabel" name="label_13">
<property name="text">
<string>Remix</string>
</property>
</widget>
</item>
<item row="10" column="2">
<widget class="QSlider" name="prefer_other_score">
<property name="maximum">
<number>100</number>
</property>
<property name="sliderPosition">
<number>50</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="10" column="0">
<widget class="QLabel" name="label_14">
<property name="text">
<string>Other</string>
</property>
</widget>
</item>
<item row="12" column="2">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="reset_preferred_types_btn">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Reset all</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<spacer>
<property name="orientation">