Merge pull request #282 from exislow/108-feature-adding-option-for-downloading-playlists-as-m3u
108 feature adding option for downloading playlists as m3u
This commit is contained in:
@@ -75,6 +75,8 @@ If you like to have the GUI version only, have a look at the
|
|||||||
- Adjustable audio and video download quality.
|
- Adjustable audio and video download quality.
|
||||||
- FLAC extraction from MP4 containers
|
- FLAC extraction from MP4 containers
|
||||||
- Lyrics and album art / cover download
|
- Lyrics and album art / cover download
|
||||||
|
- Creates playlist files
|
||||||
|
- Can symlink tracks instead of having several copies, if added to different playlist
|
||||||
|
|
||||||
## ▶️ Getting started with development
|
## ▶️ Getting started with development
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ COVER_NAME: str = "cover.jpg"
|
|||||||
BLOCK_SIZE: int = 4096
|
BLOCK_SIZE: int = 4096
|
||||||
BLOCKS: int = 1024
|
BLOCKS: int = 1024
|
||||||
CHUNK_SIZE: int = BLOCK_SIZE * BLOCKS
|
CHUNK_SIZE: int = BLOCK_SIZE * BLOCKS
|
||||||
|
PLAYLIST_EXTENSION: str = ".m3u"
|
||||||
|
PLAYLIST_PREFIX: str = "_"
|
||||||
|
|
||||||
|
|
||||||
class QualityVideo(StrEnum):
|
class QualityVideo(StrEnum):
|
||||||
|
|||||||
@@ -182,6 +182,8 @@ class DialogPreferences(QtWidgets.QDialog):
|
|||||||
"metadata_cover_embed",
|
"metadata_cover_embed",
|
||||||
"cover_album_file",
|
"cover_album_file",
|
||||||
"skip_existing",
|
"skip_existing",
|
||||||
|
"symlink_to_track",
|
||||||
|
"playlist_create",
|
||||||
]
|
]
|
||||||
|
|
||||||
def gui_populate(self):
|
def gui_populate(self):
|
||||||
|
|||||||
+132
-45
@@ -23,6 +23,8 @@ from tidal_dl_ng.constants import (
|
|||||||
CHUNK_SIZE,
|
CHUNK_SIZE,
|
||||||
COVER_NAME,
|
COVER_NAME,
|
||||||
EXTENSION_LYRICS,
|
EXTENSION_LYRICS,
|
||||||
|
PLAYLIST_EXTENSION,
|
||||||
|
PLAYLIST_PREFIX,
|
||||||
REQUESTS_TIMEOUT_SEC,
|
REQUESTS_TIMEOUT_SEC,
|
||||||
MediaType,
|
MediaType,
|
||||||
QualityVideo,
|
QualityVideo,
|
||||||
@@ -299,7 +301,7 @@ class Download:
|
|||||||
|
|
||||||
# Create file name and path
|
# Create file name and path
|
||||||
file_extension_dummy: str = AudioExtensions.FLAC
|
file_extension_dummy: str = AudioExtensions.FLAC
|
||||||
file_name_relative = format_path_media(file_template, media, self.settings.data.album_track_num_pad_min)
|
file_name_relative: str = format_path_media(file_template, media, self.settings.data.album_track_num_pad_min)
|
||||||
path_media_dst: pathlib.Path = (
|
path_media_dst: pathlib.Path = (
|
||||||
(pathlib.Path(self.path_base).expanduser() / (file_name_relative + file_extension_dummy))
|
(pathlib.Path(self.path_base).expanduser() / (file_name_relative + file_extension_dummy))
|
||||||
.resolve()
|
.resolve()
|
||||||
@@ -310,8 +312,21 @@ class Download:
|
|||||||
path_media_dst = pathlib.Path(path_file_sanitize(str(path_media_dst), adapt=True))
|
path_media_dst = pathlib.Path(path_file_sanitize(str(path_media_dst), adapt=True))
|
||||||
|
|
||||||
# Compute if and how downloads need to be skipped.
|
# Compute if and how downloads need to be skipped.
|
||||||
|
skip_download: bool = False
|
||||||
|
|
||||||
if self.skip_existing:
|
if self.skip_existing:
|
||||||
skip_file: bool = check_file_exists(path_media_dst, extension_ignore=True)
|
skip_file: bool = check_file_exists(path_media_dst, extension_ignore=True)
|
||||||
|
|
||||||
|
if self.settings.data.symlink_to_track and not isinstance(media, Video):
|
||||||
|
# Compute symlink tracks path, sanitize and check if file exists
|
||||||
|
file_name_symlink_relative: str = format_path_media(self.settings.data.format_track, media)
|
||||||
|
path_media_symlink_dst: pathlib.Path = (
|
||||||
|
(pathlib.Path(self.path_base).expanduser() / (file_name_symlink_relative + file_extension_dummy))
|
||||||
|
.resolve()
|
||||||
|
.absolute()
|
||||||
|
)
|
||||||
|
path_media_symlink_dst = pathlib.Path(path_file_sanitize(str(path_media_symlink_dst), adapt=True))
|
||||||
|
skip_download = check_file_exists(path_media_symlink_dst, extension_ignore=True)
|
||||||
else:
|
else:
|
||||||
skip_file: bool = False
|
skip_file: bool = False
|
||||||
|
|
||||||
@@ -353,56 +368,64 @@ class Download:
|
|||||||
elif isinstance(media, Video):
|
elif isinstance(media, Video):
|
||||||
file_extension = AudioExtensions.MP4 if self.settings.data.video_convert_mp4 else VideoExtensions.TS
|
file_extension = AudioExtensions.MP4 if self.settings.data.video_convert_mp4 else VideoExtensions.TS
|
||||||
|
|
||||||
# Compute file name and create destination directory
|
# Compute file name, sanitize once again and create destination directory
|
||||||
path_media_dst = path_media_dst.with_suffix(file_extension)
|
path_media_dst = path_media_dst.with_suffix(file_extension)
|
||||||
|
path_media_dst = pathlib.Path(path_file_sanitize(str(path_media_dst), adapt=True))
|
||||||
os.makedirs(path_media_dst.parent, exist_ok=True)
|
os.makedirs(path_media_dst.parent, exist_ok=True)
|
||||||
|
|
||||||
# Create a temp directory and file.
|
if not skip_download:
|
||||||
with tempfile.TemporaryDirectory(ignore_cleanup_errors=True) as tmp_path_dir:
|
# Create a temp directory and file.
|
||||||
tmp_path_file: pathlib.Path = pathlib.Path(tmp_path_dir) / str(uuid4())
|
with tempfile.TemporaryDirectory(ignore_cleanup_errors=True) as tmp_path_dir:
|
||||||
|
tmp_path_file: pathlib.Path = pathlib.Path(tmp_path_dir) / str(uuid4())
|
||||||
|
|
||||||
# Create empty file
|
# Create empty file
|
||||||
tmp_path_file.touch()
|
tmp_path_file.touch()
|
||||||
|
|
||||||
# Download media.
|
# Download media.
|
||||||
result_download, tmp_path_file = self._download(
|
result_download, tmp_path_file = self._download(
|
||||||
media=media, stream_manifest=stream_manifest, path_file=tmp_path_file
|
media=media, stream_manifest=stream_manifest, path_file=tmp_path_file
|
||||||
|
)
|
||||||
|
|
||||||
|
if result_download:
|
||||||
|
# Convert video from TS to MP4
|
||||||
|
if isinstance(media, Video) and self.settings.data.video_convert_mp4:
|
||||||
|
# Convert `*.ts` file to `*.mp4` using ffmpeg
|
||||||
|
tmp_path_file = self._video_convert(tmp_path_file)
|
||||||
|
|
||||||
|
# Extract FLAC from MP4 container using ffmpeg
|
||||||
|
if isinstance(media, Track) and self.settings.data.extract_flac and do_flac_extract:
|
||||||
|
tmp_path_file = self._extract_flac(tmp_path_file)
|
||||||
|
|
||||||
|
tmp_path_lyrics: pathlib.Path | None = None
|
||||||
|
tmp_path_cover: pathlib.Path | None = None
|
||||||
|
|
||||||
|
# Write metadata to file.
|
||||||
|
if not isinstance(media, Video):
|
||||||
|
result_metadata, tmp_path_lyrics, tmp_path_cover = self.metadata_write(
|
||||||
|
media, tmp_path_file, is_parent_album, media_stream
|
||||||
|
)
|
||||||
|
|
||||||
|
# Move lyrics file
|
||||||
|
if self.settings.data.lyrics_file and not isinstance(media, Video) and tmp_path_lyrics:
|
||||||
|
self._move_lyrics(tmp_path_lyrics, path_media_dst)
|
||||||
|
|
||||||
|
# Move cover file
|
||||||
|
# TODO: Cover is downloaded with every track of the album. Needs refactoring, so cover is only
|
||||||
|
# downloaded for an album once.
|
||||||
|
if self.settings.data.cover_album_file and tmp_path_cover:
|
||||||
|
self._move_cover(tmp_path_cover, path_media_dst)
|
||||||
|
|
||||||
|
self.fn_logger.info(f"Downloaded item '{name_builder_item(media)}'.")
|
||||||
|
|
||||||
|
# Move final file to the configured destination directory.
|
||||||
|
shutil.move(tmp_path_file, path_media_dst)
|
||||||
|
|
||||||
|
# If files needs to be symlinked, do postprocessing here.
|
||||||
|
if self.settings.data.symlink_to_track and not isinstance(media, Video):
|
||||||
|
path_media_symlink_dst: pathlib.Path = self.media_move_and_symlink(
|
||||||
|
media, path_media_dst, file_extension
|
||||||
)
|
)
|
||||||
|
|
||||||
if result_download:
|
|
||||||
# Convert video from TS to MP4
|
|
||||||
if isinstance(media, Video) and self.settings.data.video_convert_mp4:
|
|
||||||
# Convert `*.ts` file to `*.mp4` using ffmpeg
|
|
||||||
tmp_path_file = self._video_convert(tmp_path_file)
|
|
||||||
|
|
||||||
# Extract FLAC from MP4 container using ffmpeg
|
|
||||||
if isinstance(media, Track) and self.settings.data.extract_flac and do_flac_extract:
|
|
||||||
tmp_path_file = self._extract_flac(tmp_path_file)
|
|
||||||
|
|
||||||
tmp_path_lyrics: pathlib.Path | None = None
|
|
||||||
tmp_path_cover: pathlib.Path | None = None
|
|
||||||
|
|
||||||
# Write metadata to file.
|
|
||||||
if not isinstance(media, Video):
|
|
||||||
result_metadata, tmp_path_lyrics, tmp_path_cover = self.metadata_write(
|
|
||||||
media, tmp_path_file, is_parent_album, media_stream
|
|
||||||
)
|
|
||||||
|
|
||||||
# Move lyrics file
|
|
||||||
if self.settings.data.lyrics_file and not isinstance(media, Video) and tmp_path_lyrics:
|
|
||||||
self._move_lyrics(tmp_path_lyrics, path_media_dst)
|
|
||||||
|
|
||||||
# Move cover file
|
|
||||||
# TODO: Cover is downloaded with every track of the album. Needs refactoring, so cover is only
|
|
||||||
# downloaded for an album once.
|
|
||||||
if self.settings.data.cover_album_file and tmp_path_cover:
|
|
||||||
self._move_cover(tmp_path_cover, path_media_dst)
|
|
||||||
|
|
||||||
self.fn_logger.info(f"Downloaded item '{name_builder_item(media)}'.")
|
|
||||||
|
|
||||||
# Move final file to the configured destination directory.
|
|
||||||
shutil.move(tmp_path_file, path_media_dst)
|
|
||||||
|
|
||||||
if quality_audio:
|
if quality_audio:
|
||||||
# Set quality back to the global user value
|
# Set quality back to the global user value
|
||||||
self.adjust_quality_audio(quality_audio_old)
|
self.adjust_quality_audio(quality_audio_old)
|
||||||
@@ -430,6 +453,32 @@ class Download:
|
|||||||
|
|
||||||
return status_download, path_media_dst
|
return status_download, path_media_dst
|
||||||
|
|
||||||
|
def media_move_and_symlink(
|
||||||
|
self, media: Track | Video, path_media_src: pathlib.Path, file_extension: str
|
||||||
|
) -> pathlib.Path:
|
||||||
|
# Compute tracks path, sanitize and ensure path exists
|
||||||
|
file_name_relative: str = format_path_media(self.settings.data.format_track, media)
|
||||||
|
path_media_dst: pathlib.Path = (
|
||||||
|
(pathlib.Path(self.path_base).expanduser() / (file_name_relative + file_extension)).resolve().absolute()
|
||||||
|
)
|
||||||
|
path_media_dst = pathlib.Path(path_file_sanitize(str(path_media_dst), adapt=True))
|
||||||
|
|
||||||
|
os.makedirs(path_media_dst.parent, exist_ok=True)
|
||||||
|
|
||||||
|
# Move item and symlink it
|
||||||
|
if path_media_dst != path_media_src:
|
||||||
|
if self.skip_existing:
|
||||||
|
skip_file: bool = check_file_exists(path_media_dst, extension_ignore=True)
|
||||||
|
else:
|
||||||
|
skip_file: bool = False
|
||||||
|
|
||||||
|
if not skip_file:
|
||||||
|
shutil.move(path_media_src, path_media_dst)
|
||||||
|
|
||||||
|
os.symlink(path_media_dst, path_media_src)
|
||||||
|
|
||||||
|
return path_media_dst
|
||||||
|
|
||||||
def adjust_quality_audio(self, quality) -> Quality:
|
def adjust_quality_audio(self, quality) -> Quality:
|
||||||
# Save original quality settings
|
# Save original quality settings
|
||||||
quality_old: Quality = self.session.audio_quality
|
quality_old: Quality = self.session.audio_quality
|
||||||
@@ -596,7 +645,7 @@ class Download:
|
|||||||
raise MediaMissing
|
raise MediaMissing
|
||||||
|
|
||||||
# Create file name and path
|
# Create file name and path
|
||||||
file_name_relative = format_path_media(file_template, media, self.settings.data.album_track_num_pad_min)
|
file_name_relative: str = format_path_media(file_template, media, self.settings.data.album_track_num_pad_min)
|
||||||
|
|
||||||
# Get the name of the list and check, if videos should be included.
|
# Get the name of the list and check, if videos should be included.
|
||||||
list_media_name: str = name_builder_title(media)
|
list_media_name: str = name_builder_title(media)
|
||||||
@@ -618,6 +667,7 @@ class Download:
|
|||||||
)
|
)
|
||||||
|
|
||||||
is_album: bool = isinstance(media, Album)
|
is_album: bool = isinstance(media, Album)
|
||||||
|
result_dirs: [pathlib.Path] = []
|
||||||
|
|
||||||
# Iterate through list items
|
# Iterate through list items
|
||||||
while not self.progress.finished:
|
while not self.progress.finished:
|
||||||
@@ -639,6 +689,7 @@ class Download:
|
|||||||
for future in futures.as_completed(l_futures):
|
for future in futures.as_completed(l_futures):
|
||||||
# Retrieve result
|
# Retrieve result
|
||||||
status, result_path_file = future.result()
|
status, result_path_file = future.result()
|
||||||
|
result_dirs.append(result_path_file.parent)
|
||||||
|
|
||||||
# Advance progress bar.
|
# Advance progress bar.
|
||||||
self.progress.advance(p_task1)
|
self.progress.advance(p_task1)
|
||||||
@@ -646,8 +697,44 @@ class Download:
|
|||||||
if not progress_stdout:
|
if not progress_stdout:
|
||||||
self.progress_gui.list_item.emit(self.progress.tasks[p_task1].percentage)
|
self.progress_gui.list_item.emit(self.progress.tasks[p_task1].percentage)
|
||||||
|
|
||||||
|
# Create playlist file
|
||||||
|
if self.settings.data.playlist_create:
|
||||||
|
self.playlist_populate(set(result_dirs), list_media_name, is_album)
|
||||||
|
|
||||||
self.fn_logger.info(f"Finished list '{list_media_name}'.")
|
self.fn_logger.info(f"Finished list '{list_media_name}'.")
|
||||||
|
|
||||||
|
def playlist_populate(self, dirs_scoped: [pathlib.Path], name_list: str, is_album: bool) -> [pathlib.Path]:
|
||||||
|
result: [pathlib.Path] = []
|
||||||
|
|
||||||
|
# For each dir, which contains tracks
|
||||||
|
for dir_scoped in dirs_scoped:
|
||||||
|
# Sanitize final playlist name to fit into OS boundaries.
|
||||||
|
path_playlist = dir_scoped / (PLAYLIST_PREFIX + name_list + PLAYLIST_EXTENSION)
|
||||||
|
path_playlist = pathlib.Path(path_file_sanitize(path_playlist, adapt=True))
|
||||||
|
|
||||||
|
# Get all tracks in the directory
|
||||||
|
path_tracks: [pathlib.Path] = []
|
||||||
|
|
||||||
|
for extension_audio in AudioExtensions:
|
||||||
|
path_tracks = path_tracks + list(dir_scoped.glob(f"*{extension_audio!s}"))
|
||||||
|
|
||||||
|
# If it is not an album sort by modification time
|
||||||
|
if not is_album:
|
||||||
|
path_tracks.sort(key=lambda x: os.path.getmtime(x))
|
||||||
|
|
||||||
|
# Write data to m3u file
|
||||||
|
with path_playlist.open(mode="w", encoding="utf-8") as f:
|
||||||
|
for path_track in path_tracks:
|
||||||
|
# If its a symlink write the relative file path to the actual track into the playlist file
|
||||||
|
if path_track.is_symlink():
|
||||||
|
media_file_target = path_track.resolve().relative_to(path_track.parent, walk_up=True)
|
||||||
|
|
||||||
|
f.write(str(media_file_target) + os.linesep)
|
||||||
|
|
||||||
|
result.append(path_playlist)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
def _video_convert(self, path_file: pathlib.Path) -> pathlib.Path:
|
def _video_convert(self, path_file: pathlib.Path) -> pathlib.Path:
|
||||||
path_file_out: pathlib.Path = path_file.with_suffix(AudioExtensions.MP4)
|
path_file_out: pathlib.Path = path_file.with_suffix(AudioExtensions.MP4)
|
||||||
result, _ = (
|
result, _ = (
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ class Settings:
|
|||||||
download_delay_sec_max: float = 5.0
|
download_delay_sec_max: float = 5.0
|
||||||
album_track_num_pad_min: int = 1
|
album_track_num_pad_min: int = 1
|
||||||
downloads_concurrent_max: int = 3
|
downloads_concurrent_max: int = 3
|
||||||
|
symlink_to_track: bool = False
|
||||||
|
playlist_create: bool = False
|
||||||
|
|
||||||
|
|
||||||
@dataclass_json
|
@dataclass_json
|
||||||
@@ -90,6 +92,11 @@ class HelpSettings:
|
|||||||
"Minimum length of the album track count, will be padded with zeroes (0). To disable padding set this to 1."
|
"Minimum length of the album track count, will be padded with zeroes (0). To disable padding set this to 1."
|
||||||
)
|
)
|
||||||
downloads_concurrent_max: str = "Maximum concurrent number of downloads (threads)."
|
downloads_concurrent_max: str = "Maximum concurrent number of downloads (threads)."
|
||||||
|
symlink_to_track: str = (
|
||||||
|
"If enabled the tracks of albums, playlists and mixes will be downloaded to the track directory but symlinked "
|
||||||
|
"accordingly."
|
||||||
|
)
|
||||||
|
playlist_create: str = "Creates a '_playlist.m3u8' file for downloaded albums, playlists and mixes."
|
||||||
|
|
||||||
|
|
||||||
@dataclass_json
|
@dataclass_json
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ class Ui_DialogSettings:
|
|||||||
def setupUi(self, DialogSettings):
|
def setupUi(self, DialogSettings):
|
||||||
if not DialogSettings.objectName():
|
if not DialogSettings.objectName():
|
||||||
DialogSettings.setObjectName("DialogSettings")
|
DialogSettings.setObjectName("DialogSettings")
|
||||||
DialogSettings.resize(640, 767)
|
DialogSettings.resize(640, 800)
|
||||||
sizePolicy = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
sizePolicy = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
||||||
sizePolicy.setHorizontalStretch(100)
|
sizePolicy.setHorizontalStretch(100)
|
||||||
sizePolicy.setVerticalStretch(100)
|
sizePolicy.setVerticalStretch(100)
|
||||||
@@ -165,13 +165,35 @@ class Ui_DialogSettings:
|
|||||||
|
|
||||||
self.horizontalLayout.addLayout(self.lv_flag_skip_existing)
|
self.horizontalLayout.addLayout(self.lv_flag_skip_existing)
|
||||||
|
|
||||||
self.verticalLayout_3 = QVBoxLayout()
|
self.lv_symlink_to_track = QVBoxLayout()
|
||||||
self.verticalLayout_3.setObjectName("verticalLayout_3")
|
self.lv_symlink_to_track.setObjectName("lv_symlink_to_track")
|
||||||
|
self.cb_symlink_to_track = QCheckBox(self.gb_flags)
|
||||||
|
self.cb_symlink_to_track.setObjectName("cb_symlink_to_track")
|
||||||
|
|
||||||
self.horizontalLayout.addLayout(self.verticalLayout_3)
|
self.lv_symlink_to_track.addWidget(self.cb_symlink_to_track)
|
||||||
|
|
||||||
|
self.horizontalLayout.addLayout(self.lv_symlink_to_track)
|
||||||
|
|
||||||
self.lv_flags.addLayout(self.horizontalLayout)
|
self.lv_flags.addLayout(self.horizontalLayout)
|
||||||
|
|
||||||
|
self.horizontalLayout_12 = QHBoxLayout()
|
||||||
|
self.horizontalLayout_12.setObjectName("horizontalLayout_12")
|
||||||
|
self.lv_playlist_create = QVBoxLayout()
|
||||||
|
self.lv_playlist_create.setObjectName("lv_playlist_create")
|
||||||
|
self.cb_playlist_create = QCheckBox(self.gb_flags)
|
||||||
|
self.cb_playlist_create.setObjectName("cb_playlist_create")
|
||||||
|
|
||||||
|
self.lv_playlist_create.addWidget(self.cb_playlist_create)
|
||||||
|
|
||||||
|
self.horizontalLayout_12.addLayout(self.lv_playlist_create)
|
||||||
|
|
||||||
|
self.verticalLayout_4 = QVBoxLayout()
|
||||||
|
self.verticalLayout_4.setObjectName("verticalLayout_4")
|
||||||
|
|
||||||
|
self.horizontalLayout_12.addLayout(self.verticalLayout_4)
|
||||||
|
|
||||||
|
self.lv_flags.addLayout(self.horizontalLayout_12)
|
||||||
|
|
||||||
self.lv_main.addWidget(self.gb_flags)
|
self.lv_main.addWidget(self.gb_flags)
|
||||||
|
|
||||||
self.gb_choices = QGroupBox(DialogSettings)
|
self.gb_choices = QGroupBox(DialogSettings)
|
||||||
@@ -574,6 +596,8 @@ class Ui_DialogSettings:
|
|||||||
self.cb_metadata_cover_embed.setText(QCoreApplication.translate("DialogSettings", "CheckBox", None))
|
self.cb_metadata_cover_embed.setText(QCoreApplication.translate("DialogSettings", "CheckBox", None))
|
||||||
self.cb_cover_album_file.setText(QCoreApplication.translate("DialogSettings", "CheckBox", None))
|
self.cb_cover_album_file.setText(QCoreApplication.translate("DialogSettings", "CheckBox", None))
|
||||||
self.cb_skip_existing.setText(QCoreApplication.translate("DialogSettings", "CheckBox", None))
|
self.cb_skip_existing.setText(QCoreApplication.translate("DialogSettings", "CheckBox", None))
|
||||||
|
self.cb_symlink_to_track.setText(QCoreApplication.translate("DialogSettings", "CheckBox", None))
|
||||||
|
self.cb_playlist_create.setText(QCoreApplication.translate("DialogSettings", "CheckBox", None))
|
||||||
self.gb_choices.setTitle(QCoreApplication.translate("DialogSettings", "Choices", None))
|
self.gb_choices.setTitle(QCoreApplication.translate("DialogSettings", "Choices", None))
|
||||||
self.l_icon_quality_audio.setText(QCoreApplication.translate("DialogSettings", "TextLabel", None))
|
self.l_icon_quality_audio.setText(QCoreApplication.translate("DialogSettings", "TextLabel", None))
|
||||||
self.l_quality_audio.setText(QCoreApplication.translate("DialogSettings", "TextLabel", None))
|
self.l_quality_audio.setText(QCoreApplication.translate("DialogSettings", "TextLabel", None))
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>640</width>
|
<width>640</width>
|
||||||
<height>767</height>
|
<height>800</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
@@ -224,7 +224,33 @@
|
|||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_3"/>
|
<layout class="QVBoxLayout" name="lv_symlink_to_track">
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="cb_symlink_to_track">
|
||||||
|
<property name="text">
|
||||||
|
<string>CheckBox</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_12">
|
||||||
|
<item>
|
||||||
|
<layout class="QVBoxLayout" name="lv_playlist_create">
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="cb_playlist_create">
|
||||||
|
<property name="text">
|
||||||
|
<string>CheckBox</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_4"/>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
|
|||||||
Reference in New Issue
Block a user