Merge pull request #1227 from phw/PICARD-1559-move-files-only

PICARD-1559: Create directories from script when moving files without renaming
This commit is contained in:
Philipp Wolfer
2019-08-12 14:03:51 +02:00
committed by GitHub
5 changed files with 233 additions and 160 deletions

View File

@@ -374,7 +374,7 @@ class File(QtCore.QObject, Item):
return new_filename, ext
def _format_filename(self, new_dirname, new_filename, metadata, settings):
# TODO: tests !!
old_filename = new_filename
new_filename, ext = self._fixed_splitext(new_filename)
ext = ext.lower()
new_filename = new_filename + ext
@@ -385,6 +385,8 @@ class File(QtCore.QObject, Item):
new_filename = self._script_to_filename(naming_format, metadata, settings)
# NOTE: the _script_to_filename strips the extension away
new_filename = new_filename + ext
if not settings['rename_files']:
new_filename = os.path.join(os.path.dirname(new_filename), old_filename)
if not settings['move_files']:
new_filename = os.path.basename(new_filename)
win_compat = IS_WIN or settings['windows_compatibility']
@@ -404,7 +406,7 @@ class File(QtCore.QObject, Item):
new_filename = unicodedata.normalize("NFD", new_filename)
return new_filename
def _make_filename(self, filename, metadata, settings=None):
def make_filename(self, filename, metadata, settings=None):
"""Constructs file name based on metadata and file naming formats."""
if settings is None:
settings = config.setting
@@ -416,7 +418,7 @@ class File(QtCore.QObject, Item):
new_dirname = os.path.dirname(filename)
new_filename = os.path.basename(filename)
if settings["rename_files"]:
if settings["rename_files"] or settings["move_files"]:
new_filename = self._format_filename(new_dirname, new_filename, metadata, settings)
new_path = os.path.join(new_dirname, new_filename)
@@ -428,7 +430,7 @@ class File(QtCore.QObject, Item):
def _rename(self, old_filename, metadata):
new_filename, ext = os.path.splitext(
self._make_filename(old_filename, metadata))
self.make_filename(old_filename, metadata))
if old_filename == new_filename + ext:
return old_filename

View File

@@ -150,7 +150,7 @@ class RenamingOptionsPage(OptionsPage):
if s_enabled and s_text:
parser = ScriptParser()
parser.eval(s_text, file.metadata)
filename = file._make_filename(file.filename, file.metadata, settings)
filename = file.make_filename(file.filename, file.metadata, settings)
if not settings["move_files"]:
return os.path.basename(filename)
return filename

View File

@@ -3,8 +3,10 @@
# Automatically generated - don't edit.
# Use `python setup.py build_ui` to update it.
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_RenamingOptionsPage(object):
def setupUi(self, RenamingOptionsPage):
RenamingOptionsPage.setObjectName("RenamingOptionsPage")
@@ -52,24 +54,16 @@ class Ui_RenamingOptionsPage(object):
self.delete_empty_dirs.setObjectName("delete_empty_dirs")
self.verticalLayout_4.addWidget(self.delete_empty_dirs)
self.verticalLayout_5.addWidget(self.move_files)
self.rename_files = QtWidgets.QGroupBox(RenamingOptionsPage)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.rename_files.sizePolicy().hasHeightForWidth())
self.rename_files.setSizePolicy(sizePolicy)
self.rename_files.setCheckable(True)
self.rename_files.setChecked(False)
self.rename_files = QtWidgets.QCheckBox(RenamingOptionsPage)
self.rename_files.setObjectName("rename_files")
self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.rename_files)
self.verticalLayout_3.setObjectName("verticalLayout_3")
self.ascii_filenames = QtWidgets.QCheckBox(self.rename_files)
self.verticalLayout_5.addWidget(self.rename_files)
self.ascii_filenames = QtWidgets.QCheckBox(RenamingOptionsPage)
self.ascii_filenames.setObjectName("ascii_filenames")
self.verticalLayout_3.addWidget(self.ascii_filenames)
self.windows_compatibility = QtWidgets.QCheckBox(self.rename_files)
self.verticalLayout_5.addWidget(self.ascii_filenames)
self.windows_compatibility = QtWidgets.QCheckBox(RenamingOptionsPage)
self.windows_compatibility.setObjectName("windows_compatibility")
self.verticalLayout_3.addWidget(self.windows_compatibility)
self.file_naming_format_group = QtWidgets.QGroupBox(self.rename_files)
self.verticalLayout_5.addWidget(self.windows_compatibility)
self.file_naming_format_group = QtWidgets.QGroupBox(RenamingOptionsPage)
self.file_naming_format_group.setObjectName("file_naming_format_group")
self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.file_naming_format_group)
self.verticalLayout_2.setObjectName("verticalLayout_2")
@@ -110,15 +104,14 @@ class Ui_RenamingOptionsPage(object):
self.file_naming_format_default.setObjectName("file_naming_format_default")
self.horizontalLayout.addWidget(self.file_naming_format_default)
self.verticalLayout_2.addLayout(self.horizontalLayout)
self.verticalLayout_3.addWidget(self.file_naming_format_group)
self.file_naming_format_documentation = QtWidgets.QLabel(self.rename_files)
self.verticalLayout_5.addWidget(self.file_naming_format_group)
self.file_naming_format_documentation = QtWidgets.QLabel(RenamingOptionsPage)
self.file_naming_format_documentation.setText("")
self.file_naming_format_documentation.setTextFormat(QtCore.Qt.RichText)
self.file_naming_format_documentation.setWordWrap(True)
self.file_naming_format_documentation.setOpenExternalLinks(True)
self.file_naming_format_documentation.setObjectName("file_naming_format_documentation")
self.verticalLayout_3.addWidget(self.file_naming_format_documentation)
self.verticalLayout_5.addWidget(self.rename_files)
self.verticalLayout_5.addWidget(self.file_naming_format_documentation)
self.groupBox = QtWidgets.QGroupBox(RenamingOptionsPage)
self.groupBox.setObjectName("groupBox")
self.verticalLayout = QtWidgets.QVBoxLayout(self.groupBox)
@@ -149,10 +142,7 @@ class Ui_RenamingOptionsPage(object):
RenamingOptionsPage.setTabOrder(self.move_files_to_browse, self.move_additional_files)
RenamingOptionsPage.setTabOrder(self.move_additional_files, self.move_additional_files_pattern)
RenamingOptionsPage.setTabOrder(self.move_additional_files_pattern, self.delete_empty_dirs)
RenamingOptionsPage.setTabOrder(self.delete_empty_dirs, self.rename_files)
RenamingOptionsPage.setTabOrder(self.rename_files, self.ascii_filenames)
RenamingOptionsPage.setTabOrder(self.ascii_filenames, self.windows_compatibility)
RenamingOptionsPage.setTabOrder(self.windows_compatibility, self.file_naming_format)
RenamingOptionsPage.setTabOrder(self.delete_empty_dirs, self.file_naming_format)
RenamingOptionsPage.setTabOrder(self.file_naming_format, self.file_naming_format_default)
def retranslateUi(self, RenamingOptionsPage):
@@ -162,7 +152,7 @@ class Ui_RenamingOptionsPage(object):
self.move_files_to_browse.setText(_("Browse..."))
self.move_additional_files.setText(_("Move additional files (case insensitive):"))
self.delete_empty_dirs.setText(_("Delete empty directories"))
self.rename_files.setTitle(_("Rename files when saving"))
self.rename_files.setText(_("Rename files when saving"))
self.ascii_filenames.setText(_("Replace non-ASCII characters"))
self.windows_compatibility.setText(_("Windows compatibility"))
self.file_naming_format_group.setTitle(_("Name files like this"))
@@ -173,4 +163,3 @@ class Ui_RenamingOptionsPage(object):
"<p style=\"-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><br /></p></body></html>"))
self.file_naming_format_default.setText(_("Default"))
self.groupBox.setTitle(_("Examples"))

View File

@@ -1,11 +1,17 @@
import os
import shutil
from tempfile import mkdtemp
import unittest
from test.picardtestcase import PicardTestCase
from picard.const.sys import IS_MACOS
from picard import config
from picard.const.sys import (
IS_MACOS,
IS_WIN,
)
from picard.file import File
from picard.metadata import Metadata
def is_macos_10_14():
@@ -117,4 +123,97 @@ class TestPreserveTimes(PicardTestCase):
os.remove(self.file.filename)
with self.assertRaises(self.file.PreserveTimesUtimeError):
result = self.file._preserve_times(self.file.filename, save)
self.file._preserve_times(self.file.filename, save)
class FileNamingTest(PicardTestCase):
def setUp(self):
super().setUp()
self.file = File('/somepath/somefile.mp3')
config.setting = {
'ascii_filenames': False,
'clear_existing_tags': False,
'enabled_plugins': [],
'file_naming_format': '%album%/%title%',
'move_files_to': '/media/music',
'move_files': False,
'rename_files': False,
'windows_compatibility': True,
}
self.metadata = Metadata({
'album': 'somealbum',
'title': 'sometitle',
})
def test_make_filename_no_move_and_rename(self):
filename = self.file.make_filename(self.file.filename, self.metadata)
self.assertEqual(os.path.realpath(self.file.filename), filename)
def test_make_filename_rename_only(self):
config.setting['rename_files'] = True
filename = self.file.make_filename(self.file.filename, self.metadata)
self.assertEqual(os.path.realpath('/somepath/sometitle.mp3'), filename)
def test_make_filename_move_only(self):
config.setting['move_files'] = True
filename = self.file.make_filename(self.file.filename, self.metadata)
self.assertEqual(
os.path.realpath('/media/music/somealbum/somefile.mp3'),
filename)
def test_make_filename_move_and_rename(self):
config.setting['rename_files'] = True
config.setting['move_files'] = True
filename = self.file.make_filename(self.file.filename, self.metadata)
self.assertEqual(
os.path.realpath('/media/music/somealbum/sometitle.mp3'),
filename)
def test_make_filename_move_relative_path(self):
config.setting['move_files'] = True
config.setting['move_files_to'] = 'subdir'
filename = self.file.make_filename(self.file.filename, self.metadata)
self.assertEqual(
os.path.realpath('/somepath/subdir/somealbum/somefile.mp3'),
filename)
def test_make_filename_replace_trailing_dots(self):
config.setting['rename_files'] = True
config.setting['move_files'] = True
config.setting['windows_compatibility'] = True
metadata = Metadata({
'album': 'somealbum.',
'title': 'sometitle',
})
filename = self.file.make_filename(self.file.filename, metadata)
self.assertEqual(
os.path.realpath('/media/music/somealbum_/sometitle.mp3'),
filename)
@unittest.skipUnless(not IS_WIN, "non-windows test")
def test_make_filename_keep_trailing_dots(self):
config.setting['rename_files'] = True
config.setting['move_files'] = True
config.setting['windows_compatibility'] = False
metadata = Metadata({
'album': 'somealbum.',
'title': 'sometitle',
})
filename = self.file.make_filename(self.file.filename, metadata)
self.assertEqual(
os.path.realpath('/media/music/somealbum./sometitle.mp3'),
filename)
def test_make_filename_replace_leading_dots(self):
config.setting['rename_files'] = True
config.setting['move_files'] = True
config.setting['windows_compatibility'] = True
metadata = Metadata({
'album': '.somealbum',
'title': '.sometitle',
})
filename = self.file.make_filename(self.file.filename, metadata)
self.assertEqual(
os.path.realpath('/media/music/_somealbum/_sometitle.mp3'),
filename)

View File

@@ -19,7 +19,7 @@
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5" stretch="0,0,0">
<layout class="QVBoxLayout" name="verticalLayout_5" stretch="0,0,0,0,0,0,0">
<item>
<widget class="QGroupBox" name="move_files">
<property name="title">
@@ -97,150 +97,136 @@
</widget>
</item>
<item>
<widget class="QGroupBox" name="rename_files">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<widget class="QCheckBox" name="rename_files">
<property name="text">
<string>Rename files when saving</string>
</property>
<property name="checkable">
<bool>true</bool>
</widget>
</item>
<item>
<widget class="QCheckBox" name="ascii_filenames">
<property name="text">
<string>Replace non-ASCII characters</string>
</property>
<property name="checked">
<bool>false</bool>
</widget>
</item>
<item>
<widget class="QCheckBox" name="windows_compatibility">
<property name="text">
<string>Windows compatibility</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
</widget>
</item>
<item>
<widget class="QGroupBox" name="file_naming_format_group">
<property name="title">
<string>Name files like this</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QCheckBox" name="ascii_filenames">
<property name="text">
<string>Replace non-ASCII characters</string>
<widget class="QTextEdit" name="file_naming_format">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="windows_compatibility">
<property name="text">
<string>Windows compatibility</string>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="file_naming_format_group">
<property name="title">
<string>Name files like this</string>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QTextEdit" name="file_naming_format">
<property name="enabled">
<bool>false</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="font">
<font>
<family>Monospace</family>
</font>
</property>
<property name="cursor" stdset="0">
<cursorShape>IBeamCursor</cursorShape>
</property>
<property name="tabChangesFocus">
<bool>false</bool>
</property>
<property name="lineWrapMode">
<enum>QTextEdit::NoWrap</enum>
</property>
<property name="html">
<string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
<property name="font">
<font>
<family>Monospace</family>
</font>
</property>
<property name="cursor" stdset="0">
<cursorShape>IBeamCursor</cursorShape>
</property>
<property name="tabChangesFocus">
<bool>false</bool>
</property>
<property name="lineWrapMode">
<enum>QTextEdit::NoWrap</enum>
</property>
<property name="html">
<string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Monospace'; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="tabStopWidth">
<number>20</number>
</property>
<property name="acceptRichText">
<bool>false</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::TextEditorInteraction</set>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>2</number>
</property>
<item>
<widget class="QLabel" name="renaming_error">
<property name="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="file_naming_format_default">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Default</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</property>
<property name="tabStopWidth">
<number>20</number>
</property>
<property name="acceptRichText">
<bool>false</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::TextEditorInteraction</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="file_naming_format_documentation">
<property name="text">
<string/>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>2</number>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
<item>
<widget class="QLabel" name="renaming_error">
<property name="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="file_naming_format_default">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Default</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QLabel" name="file_naming_format_documentation">
<property name="text">
<string/>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
@@ -300,9 +286,6 @@ p, li { white-space: pre-wrap; }
<tabstop>move_additional_files</tabstop>
<tabstop>move_additional_files_pattern</tabstop>
<tabstop>delete_empty_dirs</tabstop>
<tabstop>rename_files</tabstop>
<tabstop>ascii_filenames</tabstop>
<tabstop>windows_compatibility</tabstop>
<tabstop>file_naming_format</tabstop>
<tabstop>file_naming_format_default</tabstop>
</tabstops>