commit 8bf94aaff150db02052591ff3ef7794ef59b3e04
parent d6eeb385e65ea2a8a552b6620bc5450fc7d12b27
Author: gearsix <gearsix@tuta.io>
Date: Fri, 25 Nov 2022 16:20:18 +0000
v1.0.0 release; Squashed commit of the following:
commit e132740e7229762c457312bc37b206fa46496e73
Author: gearsix <gearsix@tuta.io>
Date: Thu Nov 24 12:24:41 2022 +0000
minor adjustments in pyqt
- options window is moved on show
- added spacer in center of options
- removed sizing from options
commit c189192efb5777efe5824f7146f62110ba8bae8e
Author: gearsix <gearsix@tuta.io>
Date: Sat Nov 19 18:27:33 2022 +0000
sya.py: added weight system to handle multiple timestamps on a line
Now the correct timestamp should be picked if there are multiple in a line.
- Only a timestamp at the start or end of a line will be chosen
- If there is a timestamp at both the start AND the end, the weight is checked.
Each timestamp found will add a weight to the left or right (depending where it's found).
p.s. All subprocess calls use shell=False;
commit b0d0d528647c4cf4b74107924d70a6db97b268db
Author: gearsix <gearsix@tuta.io>
Date: Fri Nov 18 23:04:18 2022 +0000
refactor: updated README.md
commit a8e5b5f63010020699b9a0f9c3e692ca4ea0945e
Author: gearsix <gearsix@tuta.io>
Date: Fri Nov 18 22:19:35 2022 +0000
feature: added HELP in place of the (redundant) 'Quit' button
commit 2a4237212bf3c91dc727d86f2353010940dbf2f8
Author: gearsix <gearsix@tuta.io>
Date: Fri Nov 18 21:56:10 2022 +0000
bugfix: realised shell=False is required on windows to avoid consoles popping up
commit 026f57a94251301c1f44afc99c5a23f4b93b0785
Author: gearsix <gearsix@tuta.io>
Date: Fri Nov 18 21:55:37 2022 +0000
bugfix: _init_options_output was using the wrong label
commit a778fa5d41f46c3bfbed9a706cdca64b25c43a5b
Author: gearsix <gearsix@tuta.io>
Date: Fri Nov 18 21:40:03 2022 +0000
bugfix: removed 'flv' format option
commit 9af98328f7729a502e9f57c304e87eb64d5863bb
Author: gearsix <gearsix@tuta.io>
Date: Fri Nov 18 15:01:07 2022 +0000
bugfix: ffmpeg binary assigned to wrong arg in sya-pyqt __main__
refactor: Removed centerWidget calls, annoyingly spawns on 2nd monitor
refactor: added NoWrap to logger, output is easier to read
commit db33381e083fbba6dba7e237c55c37ffa1dd41f4
Author: gearsix <gearsix@tuta.io>
Date: Fri Nov 18 15:00:43 2022 +0000
minor amendment in README
commit 5e49cf578c213d48ec2dfccc213eb4b466fef63d
Author: gearsix <gearsix@tuta.io>
Date: Fri Nov 18 14:58:41 2022 +0000
bugfix: subprocess calls on posix work properly now.
refactor: created read_tracklen(), from split_tracks.
- shell=True had to be removed
- Also added 'Success' print before clean exit
commit 09760175daa8d39d79a265230e9a17b1538b26cd
Author: gearsix <gearsix@tuta.io>
Date: Thu Nov 17 00:13:58 2022 +0000
Bunch of improvements to sya-pyqt
- The options window is now a fixed size (400,169)
- Removed the `del self.logger` and `del self.options` from quit (unnecessary)
- Fixed the application exiting
- Hooked `quit` into `options.closeEvent`
- Removed the `shutil.rmtree(self.fnSyaArgs.output)`, seemed too dangerous.
- Removed the stretch from `sya_gui_combobox` return
commit a957bb0bdea14e1aa3be4a5c8fd57629a82106a0
Author: gearsix <gearsix@tuta.io>
Date: Sat Nov 12 20:13:04 2022 +0000
docs: migrated a lot of doc files to *README.md*.
commit 8119c02c7aa3702da3833381dce1ac2037402ce4
Author: gearsix <gearsix@tuta.io>
Date: Mon Nov 7 22:51:09 2022 +0000
minor fixes & adjustments to argument defaults
- default yt-dlp & ffmpeg argument have '.exe' on default for windows (if not provided)
- sya-pyqt accepts yt-dlp & ffmpeg args (without overriding for bundled)
- bugfix to logger text cursor, now it *always* appends (even if you click to move it)
- check_options_ok called on *any* options change
commit 0ccde1a9477b53b04d33c07b1f0f481e8d77706a
Author: gearsix <gearsix@tuta.io>
Date: Mon Nov 7 15:25:27 2022 +0000
bugfix: optionsOk is enabled in done, not postMain
commit 69e98b910b09eef590df3ec1470725607418395e
Author: gearsix <gearsix@tuta.io>
Date: Mon Nov 7 15:00:59 2022 +0000
fix: sya-smol.png was still being used as the logo icon
commit 8fbaf50552dc3144f50fb20c919921677f7efa9e
Author: gearsix <gearsix@tuta.io>
Date: Mon Nov 7 13:29:20 2022 +0000
updated docs
commit 28665441c9cd73f9ee0a91491a2be77d1646bb7f
Author: gearsix <gearsix@tuta.io>
Date: Mon Nov 7 13:05:29 2022 +0000
added sya.png logo; tracklist & output reset on 'done'
commit e19eb69125455f5dd5a1cb5ac1a081855781b086
Author: gearsix <gearsix@tuta.io>
Date: Fri Nov 4 22:02:31 2022 +0000
sya-pyqt: massive refactor to tidyup and make it more manageable.
Not much changed architecturally, aside from a few renames and that `_init_combobox` and `_init_filepicker` are now their own functions . Naming was more standardised and PyQt signals are effectively used.
Also 'log()' was removed from *sya.py* (was unnecessary) and `shell=True` was added to all the `Popen` calls to avoid a console window popping up in the sya-pyqt binary. Also (finally) fixed the string decoding.
Diffstat:
A | BUGS.md | | | 29 | +++++++++++++++++++++++++++++ |
D | BUGS.txt | | | 23 | ----------------------- |
A | HELP.md | | | 65 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | README.md | | | 150 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
D | README.txt | | | 126 | ------------------------------------------------------------------------------- |
A | TODO.txt | | | 3 | +++ |
D | requirements.txt | | | 41 | ----------------------------------------- |
A | screenshot.PNG | | | 0 | |
M | sya-pyqt.py | | | 477 | ++++++++++++++++++++++++++++++++++++++++++++++++------------------------------- |
A | sya.png | | | 0 | |
M | sya.py | | | 110 | +++++++++++++++++++++++++++++++++++++++++++++++++------------------------------ |
11 files changed, 607 insertions(+), 417 deletions(-)
diff --git a/BUGS.md b/BUGS.md
@@ -0,0 +1,29 @@
+# BUGS
+
+## Legend
+
+[ ] = Not started
+[x] = Won't do
+[~] = Doing
+[*] = Done
+- ... = Note about the above item
+
+## List
+
+---
+
+**2021-07-31** [ ] don't apply file numbering when song names include file number already
+
+ #regex-improvement
+
+---
+
+**2021-06-22** [*] handle multiple timestamp regex matches
+ When there are multiple matches for the timestamp regex in a line, the last match is used as the timestamp.
+
+ Discovered that the following line causes an issue because 2019 is counted as a timestamp
+ `58:18 C4C - Melted w_ Hazy Year (Chillhop Winter Essentials 2019).`
+
+ #regex-improvement
+
+---
diff --git a/BUGS.txt b/BUGS.txt
@@ -1,23 +0,0 @@
-# BUGS
-
-## Legend
-
-[ ] = Not started
-[x] = Won't do
-[~] = Doing
-[*] = Done
-- ... = Note about the above item
-
-## List
-
-**2021-07-31** [ ] don't apply file numbering when song names include file number already
-
- #regex-improvement
-
-**2021-06-22** [*] handle multiple timestamp regex matches
- When there are multiple matches for the timestamp regex in a line, the last match is used as the timestamp.
-
- Discovered that the following line causes an issue because 2019 is counted as a timestamp
- `58:18 C4C - Melted w_ Hazy Year (Chillhop Winter Essentials 2019).`
-
- #regex-improvemnet
diff --git a/HELP.md b/HELP.md
@@ -0,0 +1,65 @@
+
+# Help
+
+**sya - split youtube audio**, downloads, converts & splits audio from youtube videos into multiple audio tracks.
+
+## Overview
+
+To work sya requires some manual work: **tracklist** information. For more details on this, see **Tracklists** below.<br/>
+The rest of the options can be configured but are provided with defaults.
+
+Here's an overview of the options:
+
+- **Tracklist** - the text file containing tracklist information
+- **Format** - set the format to convert the audio to
+- **Quality** - set the audio quality to download in, for reference 5 is equal to *128k*
+- **Keep unsplit file** - keep the downloaded audio file (before it gets split up)
+- **Output** - the directory to download the audio track to and split it into multiple tracks
+
+The resulting files can be found at the *Output:* filepath on your system.
+
+If you've found a bug or want to suggest improvements, email: `gearsix@tuta.io`
+
+## Tracklists
+
+A tracklist is just a text file some where on your system.
+It should contains:
+
+- A *youtube URL* to download the audio from, this should be on the first line.
+- Timestamps and titles for each track to split, there should be one timestamp and one title per-track (this can usually be found in the youtube video description or a top comment).
+
+**Example**
+
+Below you can see the contents of an example playlist.<br/>
+Try saving it to a text file on your computer and as a test if you like.
+
+ https://www.youtube.com/watch?v=LbjcaMAhJRQ
+ Sneaky Snitch (0:00)
+ Fluffing a Duck (2:16)
+ Cipher (3:24)
+ Scheming Weasel (7:15)
+ Carefree (8:44)
+ Thatched Villagers (12:09)
+ Monkeys Spinning Monkeys (16:15)
+ Wallpaper (18:20)
+ Pixel Peeker Polka (21:59)
+ Killing Time (25:21)
+ Hitman (28:46)
+ The Cannery (32:07)
+ Cut and Run (35:09)
+ Life of Riley (38:44)
+ Quirky Dog (42:39)
+ The Complex (45:08)
+ Hyperfun (49:35)
+ Black Vortex (53:29)
+ Rock on Chicago (56:19)
+ Volatile Reaction (57:58)
+ On the Ground (1:00:44)
+ Wagon Wheel (electronic) (1:03:23)
+ Call to Adventure (1:08:26)
+ Hustle (1:12:33)
+ Cupids Revenge (1:14:34)
+ Dirt Rhodes (1:16:20)
+ Rhinoceros (1:18:20)
+ Who Likes to Party (1:21:43)
+ Spazzmatica Polka (1:26:01)
diff --git a/README.md b/README.md
@@ -0,0 +1,150 @@
+
+# sya
+
+**sya - split youtube audio**, download, convert & split audio from youtube videos into multiple audio tracks using 'yt-dlp' and 'ffmpeg'.
+
+---
+
+## sya.py
+
+This is the base Python script, which contains all the functionality:
+
+ sya.py [OPTIONS] TRACKLIST
+
+### OPTIONS
+
+```
+ -h, --help
+ show this help message and exit
+
+ -o [PATH], --output [PATH]
+ specify the directory to write output files to (default: ./out)
+
+ -f [FORMAT], --format [FORMAT]
+ specify the --audio-format argument to pass to yt-dlp (default: mp3)
+
+ -q [QUALITY], --quality [QUALITY]
+ specify the --audio-quality argument to pass to yt-dlp (default: 320K)
+
+ --yt-dlp [PATH]
+ path of the "yt-dlp" binary to use
+
+ --ffmpeg [PATH]
+ path of the "ffmpeg" binary to use
+
+ -k, --keep
+ keep any files removed during processing (full video/audio file)
+```
+
+### TRACKLIST
+
+**TRACKLIST** should be the filepath of a UTF-8 text file that has the URL of the video to download to audio track of on line 1.
+Every line after this should be the track name and timestamp of each track, note that the timestamp is *where the track starts*.
+
+Regex is used to parse the tracks (timestamp and name), it tries to be fairly accomodating but isn't perfect - so here are a few rules:
+
+- Timestamps can be before or after the track name.
+- Timestamps can be wrapped in `[]` or `()`.
+- Timestamps can be `MM:SS` or `HH:MM:SS`.
+- Timestamps can be split using `:` or `.`.
+
+<details>
+ <summary>Example Tracklist</summary>
+ <pre>
+ https://www.youtube.com/watch?v=LbjcaMAhJRQ
+ Sneaky Snitch (0:00)
+ Fluffing a Duck (2:16)
+ Cipher (3:24)
+ Scheming Weasel (7:15)
+ Carefree (8:44)
+ Thatched Villagers (12:09)
+ Monkeys Spinning Monkeys (16:15)
+ Wallpaper (18:20)
+ Pixel Peeker Polka (21:59)
+ Killing Time (25:21)
+ Hitman (28:46)
+ The Cannery (32:07)
+ Cut and Run (35:09)
+ Life of Riley (38:44)
+ Quirky Dog (42:39)
+ The Complex (45:08)
+ Hyperfun (49:35)
+ Black Vortex (53:29)
+ Rock on Chicago (56:19)
+ Volatile Reaction (57:58)
+ On the Ground (1:00:44)
+ Wagon Wheel (electronic) (1:03:23)
+ Call to Adventure (1:08:26)
+ Hustle (1:12:33)
+ Cupids Revenge (1:14:34)
+ Dirt Rhodes (1:16:20)
+ Rhinoceros (1:18:20)
+ Who Likes to Party (1:21:43)
+ Spazzmatica Polka (1:26:01)
+ </pre>
+</details>
+
+### Install
+
+It's a Python script, so there are many ways to install it.
+
+Here's one that should work on *any* system:
+
+ python3 ./setup.py install --user --record install.txt
+
+To **uninstall**, just remove all files recorded to *./install.txt*.
+
+---
+
+## sya-pyqt
+
+Some people don't like the cli and I wanted to play with PyQt, so sya-pyqt wraps a nice GUI around the *sya.py* runtime.
+
+![screenshot](screenshot.png "sya-pyqt on Windows)
+
+### Development
+
+Working on or building requires *PyQt5*, this can be installed with `pip install PyQt5`.
+
+To run *sya-pyqt*, you don't need to build a binary everytime, you can just run `python ./sya-pyqt.py`.
+
+### Building
+
+[pyinstaller](https://pyinstaller.org) is used to build a portable binary for sya-pyqt for easy distribution.
+
+**POSIX (Linux, MacOS, BSD)**
+
+ pyinstaller ./sya-pyqt.py -F --windowed --add-data ".\HELP.md;." --add-data "./folder.png:." --add-data "./file.png:." --add-data "./sya.png:." --add-data "$FFMPEG:." --add-data "$YT-DLP:."
+
+**Windows**
+
+ pyinstaller ./sya-pyqt.py -F --windowed --add-data ".\HELP.md;." --add-data ".\folder.png;." --add-data ".\file.png;." --add-data ".\sya.png;." --add-data "$FFMPEG;." --add-data "$YT-DLP;."
+
+- Make sure you have a *yt-dlp* binary available, the filepath of this is referred to as *$YT-DLP*.
+- Make sure you have a *ffmpeg* binary available, the filepath of this is referred to as *$FFMPEG*.
+- In some cases, I've found the path of PyQt5 has had to be explicitly given to *pyinstaller*:
+`--path <site-packages filepath>\PyQt5`
+
+Optionally, you can add an icon to the binary, I'd recommend installing [Pillow](https://python-pillow.org/) so you don't need to manually convert `sya.png` to an icon file. Just add `--icon "sya.png"` to the build command.
+
+
+---
+
+## Thanks
+
+These two tools do all the heavy lifting:
+- youtube-dl (https://ytdl-org.github.io/youtube-dl/)
+- ffmpeg (https://ffmpeg.org)
+
+And the cool folder & file icons used in 'sya-pyqt' are from the Palemoon MicroMoon theme:
+https://repo.palemoon.org/Lootyhoof/micromoon
+
+
+## Disclaimer
+
+It should go without saying, don't use this for pirating music. Get nice high-quality, properly tagged audio tracks from official services (shoutout to Bandcamp).
+The tool was originally written to download radio mixes and ambient soundtracks for a DnD group.
+
+## Authors
+
+- gearsix
diff --git a/README.txt b/README.txt
@@ -1,126 +0,0 @@
-NAME
- sya - split youtube audio
-
-SYNOPSIS
- sya.py [OPTIONS] TRACKLIST
-
-DESCRIPTION
- sya downloads, converts and splits youtube videos into multiple audio
- tracks using `youtube-dl` and `ffmpeg`.
-
-OPTIONS
- -h --help displays help message
- -k, --keep
- youtube-dl option, keep the full track on disk after post-processing,
- the video is erased by default
- -o, --output [PATH]
- specify the directory to write output files to (defaults to a directory
- named after the tracklist filename)
- -f, --format [EXT]
- specify the --audio-format argument to pass to youtube-dl (default: mp3)
- -q, --quality [QUALITY]
- specify the --audio-quality argument to pass (default: 320K)
- --youtube-dl [PATH]
- path of the youtube-dl binary to use
- --ffmpeg [PATH]
- path of the ffmpeg binary to use
-
-TRACKLIST
- TRACKLIST files should be text file that has the URL/v=code of the youtube video to
- download on the first line and the starting timestamp of each section to split, followed
- by the title of that section section for every line after.
-
- Of course, you don't have to put the timestamp first, sya will try and accomocodate
- whatever syntax is used, just beware that this might cause problems.
-
- Here's an example:
-
- https://www.youtube.com/watch?v=LbjcaMAhJRQ
- Sneaky Snitch (0:00)
- Fluffing a Duck (2:16)
- Cipher (3:24)
- Scheming Weasel (7:15)
- Carefree (8:44)
- Thatched Villagers (12:09)
- Monkeys Spinning Monkeys (16:15)
- Wallpaper (18:20)
- Pixel Peeker Polka (21:59)
- Killing Time (25:21)
- Hitman (28:46)
- The Cannery (32:07)
- Cut and Run (35:09)
- Life of Riley (38:44)
- Quirky Dog (42:39)
- The Complex (45:08)
- Hyperfun (49:35)
- Black Vortex (53:29)
- Rock on Chicago (56:19)
- Volatile Reaction (57:58)
- On the Ground (1:00:44)
- Wagon Wheel (electronic) (1:03:23)
- Call to Adventure (1:08:26)
- Hustle (1:12:33)
- Cupids Revenge (1:14:34)
- Dirt Rhodes (1:16:20)
- Rhinoceros (1:18:20)
- Who Likes to Party (1:21:43)
- Spazzmatica Polka (1:26:01)
-
-
-BUILDING
- It's more convenient to build sya-pyqt into a single binary (bundled
- with assets & libraries).
- To do this 'pyinstaller' seems most convenient (installed via "pip
- install pyinstaller"):
-
- pyinstaller sya-pyqt.py -F --windowed --add-data "folder.png:." --add-data "file.png:."
-
- On Windows, you'll need to use the following:
-
- pyinstaller sya-pyqt.py -F --windowed --add-data "folder.png;." --add-data "file.png;."
-
- This process also works to create binaries for the terminal version
- of the tool (sya.py). When building that version, the "--windowed"
- and "--add-data "folder.png:."" arguments don't need to be included.
-
-INSTALL
- There are a lot of ways to install this tool.
-
- For most cases, you can just download and run the distribution binaries.
-
- FROM SOURCE
- This method should work on all platforms to install it for the current
- user.
- Note that there are a lot of ways to install a python package from
- source, this is just one.
-
- python3 ./setup.py install --user --record install.txt
-
- The "--user" argument will only install sya for the current user. Remove
- it to install the tool to the system, although note that this will require
- admin permissions.
-
- This will create a 'install.txt' file in the current directory. This file
- contains all the files installed to your system. To uninstall using just
- remove all of these files.
-
-THANKS
- These two tools do all the heavy lifting:
- - youtube-dl (https://ytdl-org.github.io/youtube-dl/)
- - ffmpeg (https://ffmpeg.org)
-
- And the cool icons used are from the MicroMoon theme for Palemoon:
- https://repo.palemoon.org/Lootyhoof/micromoon
-
-DISCLAIMER
- It should go without saying, don't use this for pirating music.
- If you do, you're a dick and you're working against whatever band/label you
- love enough to download their product.
-
- Service like Bandcamp (https://bandcamp.com) are great and allow you download
- audio files of the albums you've bought, properly tagged and available for
- re-download whenever you need.
-
-AUTHORS
- - gearsix (gearsix@tuta.io)
-
diff --git a/TODO.txt b/TODO.txt
@@ -0,0 +1,2 @@
+- QSpacerItem should be dynamic, not static size
+- On exit, SyaGuiLogStream gets delted (runtime error?)
+\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
@@ -1,41 +0,0 @@
-altgraph==0.17.3
-Brotli==1.0.9
-build==0.9.0
-certifi==2022.9.24
-dbus-python==1.2.18
-distlib==0.3.6
-filelock==3.8.0
-meson==0.60.3
-MouseInfo==0.1.3
-mugshot==0.4.3
-mutagen==1.46.0
-packaging==21.3
-pep517==0.13.0
-pexpect==4.8.0
-platformdirs==2.5.2
-psutil==5.9.3
-ptyprocess==0.7.0
-PyAutoGUI==0.9.53
-pycairo==1.20.1
-pycryptodomex==3.15.0
-PyGetWindow==0.0.9
-PyGObject==3.42.2
-pyinstaller==5.6.2
-pyinstaller-hooks-contrib==2022.11
-PyMsgBox==1.0.9
-pyparsing==3.0.9
-pyperclip==1.8.2
-PyQt5==5.15.7
-PyQt5-Qt5==5.15.2
-PyQt5-sip==12.11.0
-PyRect==0.2.0
-PyScreeze==0.1.28
-python3-xlib==0.15
-pytweening==1.0.4
-tomli==2.0.1
-trash-cli==0.22.8.27
-ufw==0.36.1
-virtualenv==20.16.6
-virtualenv-clone==0.5.7
-websockets==10.4
-yt-dlp==2022.10.4
diff --git a/screenshot.PNG b/screenshot.PNG
Binary files differ.
diff --git a/sya-pyqt.py b/sya-pyqt.py
@@ -20,225 +20,330 @@ def resource_path(relative_path):
base_path = os.path.abspath(".")
return os.path.join(base_path, relative_path)
-def centerWidget(widget):
+
+def center_widget(widget):
sg = qtwidg.QDesktopWidget().screenGeometry()
wg = widget.geometry()
return qtcore.QPoint(
round(sg.width() / 2) - round(wg.width() / 2),
round(sg.height() / 2) - round(wg.height() / 2))
-class LogStream(qtcore.QObject):
- txt = qtcore.pyqtSignal(str)
- def write(self, txt):
- self.txt.emit(str(txt))
-
-class SyaGuiMain(qtcore.QThread):
+class SyaGuiThread(qtcore.QThread):
def __init__(self, fn, args=None):
super().__init__()
self.fn = fn
self.args = args
def run(self):
- if self.args != None:
- self.fn(self.args)
- else:
+ if self.args is None:
self.fn()
+ else:
+ self.fn(self.args)
+
+
+class SyaGuiLogStream(qtcore.QObject):
+ txt = qtcore.pyqtSignal(str)
+
+ def write(self, txt):
+ self.txt.emit(str(txt))
+
+
+def sya_gui_combobox(parent, label, items, default_item, fn_update):
+ label = qtwidg.QLabel(label, parent)
+
+ combobox = qtwidg.QComboBox(parent)
+ for i in items:
+ combobox.addItem(i)
+ if default_item in items:
+ combobox.setCurrentIndex(items.index(default_item))
+ combobox.activated[str].connect(fn_update)
+
+ layout = qtwidg.QHBoxLayout()
+ layout.addWidget(label)
+ layout.addWidget(combobox)
+
+ return layout
+
+
+def sya_gui_filepicker(parent, label, fn_select, fn_update, default_value='', icon=''):
+ label = qtwidg.QLabel(label, parent)
+
+ lineEdit = qtwidg.QLineEdit(parent)
+ lineEdit.setText(default_value)
+ lineEdit.textChanged.connect(fn_update)
+
+ btnIcon = qtgui.QIcon(resource_path('{}.png'.format(icon)))
+ btn = qtwidg.QPushButton(btnIcon, '', parent)
+ btn.clicked.connect(fn_select)
+
+ layout = qtwidg.QHBoxLayout()
+ layout.addWidget(label)
+ layout.addWidget(lineEdit)
+ layout.addWidget(btn)
+
+ return layout, lineEdit
+
class SyaGui(qtwidg.QMainWindow):
- def __init__(self, fnSya, args):
+ def __init__(self, fn_sya, fn_sya_args):
super().__init__()
- self.args = args
- self.fnSya = fnSya
+ self.fnSya = fn_sya
+ self.fnSyaArgs = fn_sya_args
+
+ self.availableFormats = ['mp3', 'wav', 'ogg', 'aac']
+ self.availableQualities = ['0 (better)', '1', '2', '3', '4', '5', '6', '7', '8', '9 (worse)']
+
+ self._init_options_value()
+ self._init_options()
+ self._init_help()
+ self._init_logger()
- self._edits = {}
- options = qtwidg.QWidget()
- options.setWindowTitle('sya')
- options = self._init_options(options)
- #options.setWindowIcon(pyqt_options.QIcon(''))
- options.move(centerWidget(options))
- self._options = options
-
- logs = qtwidg.QWidget()
- logs.resize(800, 400)
- logs = self._init_logs(logs)
- logs.move(centerWidget(logs))
- self._logs = logs
-
- sys.stdout = LogStream(txt=self.log)
- self._options.show()
-
- def _init_options(self, options):
- layout = qtwidg.QGridLayout()
- # tracklist
- self._tracklistLabel = 'Tracklist:'
- layout.addLayout(self._init_filepicker(options, self._tracklistLabel,
- self._filepicker_tracklist, self.args.tracklist, 'file'), 0, 0, 1, 3)
- # formats
- formats = ['mp3', 'flv', 'wav', 'ogg', 'aac']
- layout.addLayout(self._init_combobox(options, 'Format:', self._set_format, formats,
- self.args.format), 1, 0)
- # quality
- qualities = ['0 (better)', '1', '2', '3', '4', '5', '6', '7', '8', '9 (worse)']
- layout.addLayout(self._init_combobox(options, 'Quality:', self._set_quality, qualities,
- self.args.quality), 2, 0)
- # keep
- keep = qtwidg.QCheckBox('keep original', options)
- if self.args.keep == True:
- keep.setChecked(True)
- keep.toggled.connect(self._keep_toggle, self.args.keep)
- layout.addWidget(keep, 1, 2, 2, 1)
- # output
- self._outputLabel = 'Output:'
- layout.addLayout(self._init_filepicker(options, self._outputLabel, self._filepicker_output,
- self.args.output), 3, 0, 1, 3)
- # quit
- quit_btn = qtwidg.QPushButton('Quit')
- quit_btn.clicked.connect(sys.exit)
- layout.addWidget(quit_btn, 4, 1)
- # ok
- self._ok_btn = qtwidg.QPushButton('OK')
- self._ok_btn.clicked.connect(self._ok)
- layout.addWidget(self._ok_btn, 4, 2)
- self._check_ok()
-
- options.setLayout(layout)
- return options
-
- def _init_logs(self, logs):
+ self.options.closeEvent = self.quit
+ self.optionsHelp.clicked.connect(self.show_help)
+ self.optionsOk.clicked.connect(self.main)
+ self.help.closeEvent = self.hide_help
+ self.loggerCancel.clicked.connect(self.cancel)
+ self.loggerDone.clicked.connect(self.done)
+
+ sys.stdout = SyaGuiLogStream(txt=self.log)
+ self.running = 0
+
+ # Runtime Methods
+ def log(self, msg):
+ self.loggerTextbox.moveCursor(qtgui.QTextCursor.End)
+ self.loggerTextbox.textCursor().insertText(msg)
+ self.loggerTextbox.ensureCursorVisible()
+
+ def cancel(self):
+ if self.running > 0:
+ self.main_t.terminate()
+ self.main_t.wait()
+ self.running -= 1
+ self.logger.hide()
+ self.loggerTextbox.clear()
+
+ def quit(self, event):
+ sys.stdout = sys.__stdout__
+ if self.running > 0:
+ self.cancel()
+ self.options.close()
+ self.logger.close()
+ self.close()
+
+ def done(self):
+ self.set_tracklist('')
+ self.set_output('')
+ self.optionsOk.setEnabled(True)
+ self.logger.hide()
+ self.loggerTextbox.clear()
+
+ def show_help(self):
+ x = self.options.x() - self.options.width() - 50
+ y = self.options.y() - self.options.height()
+ self.help.move(x, y)
+ self.help.show()
+ self.optionsHelp.setEnabled(False)
+
+ def hide_help(self, signal):
+ self.help.hide()
+ self.optionsHelp.setEnabled(True)
+
+ def preMain(self):
+ self.optionsOk.setEnabled(False)
+ self.loggerDone.setEnabled(False)
+
+ def postMain(self):
+ self.loggerDone.setEnabled(True)
+
+ def main(self):
+ self.fnSyaArgs.tracklist = self.optionsValue[self.tracklistLabel]
+ self.fnSyaArgs.format = self.optionsValue[self.formatLabel]
+ self.fnSyaArgs.quality = self.optionsValue[self.qualityLabel]
+ self.fnSyaArgs.keep = self.optionsValue[self.keepLabel]
+ self.fnSyaArgs.output = self.optionsValue[self.outputLabel]
+
+ self.main_t = SyaGuiThread(self.fnSya, self.fnSyaArgs)
+ self.main_t.started.connect(self.preMain)
+ self.main_t.finished.connect(self.postMain)
+
+ self.logger.setWindowTitle(self.fnSyaArgs.output)
+ self.logger.show()
+ self.running += 1
+ self.main_t.start()
+
+ # optionsValue
+ def _init_options_value(self):
+ self.tracklistLabel = 'Tracklist:'
+ self.formatLabel = 'Format:'
+ self.qualityLabel = 'Quality:'
+ self.keepLabel = 'Keep unsplit file'
+ self.outputLabel = 'Output:'
+ self.optionsValue = {
+ self.tracklistLabel: self.fnSyaArgs.tracklist,
+ self.formatLabel: self.fnSyaArgs.format,
+ self.qualityLabel: self.fnSyaArgs.quality,
+ self.keepLabel: self.fnSyaArgs.keep,
+ self.outputLabel: self.fnSyaArgs.output
+ }
+
+ # options
+ def _init_options(self):
+ self.options = qtwidg.QWidget()
+ self.optionsOk = qtwidg.QPushButton('OK')
+ self.optionsHelp = qtwidg.QPushButton('Help')
+
layout = qtwidg.QGridLayout()
- # textbox
- logbox = qtwidg.QPlainTextEdit()
- logbox.setReadOnly(True)
- self._logbox = logbox
- layout.addWidget(logbox, 1, 0, 1, 5)
- # cancel
- cancel_btn = qtwidg.QPushButton('Cancel')
- cancel_btn.clicked.connect(self._cancel)
- layout.addWidget(cancel_btn, 2, 0)
- # warning
- warning = qtwidg.QLabel('Be patient, this might take a while. Click "Done" when finished.')
- layout.addWidget(warning, 2, 1, 1, 2)
- # done
- self._done_btn = qtwidg.QPushButton('Done')
- self._done_btn.clicked.connect(sys.exit)
- self._done_btn.setEnabled(False)
- layout.addWidget(self._done_btn, 2, 4)
-
- logs.setLayout(layout)
- return logs
-
- def _init_filepicker(self, widget, labelText, filepickerFn, default=None, icon='folder'):
- layout = qtwidg.QHBoxLayout()
- # label
- label = qtwidg.QLabel(labelText, widget)
- layout.addWidget(label)
- # line edit
- self._edits[labelText] = qtwidg.QLineEdit(widget)
- if default != None:
- self._edits[labelText].setText(default)
- layout.addWidget(self._edits[labelText])
- # filepicker btn
- button_logo = qtgui.QIcon(resource_path('{}.png'.format(icon)))
- button = qtwidg.QPushButton(button_logo, '', widget)
- button.clicked.connect(filepickerFn)
- layout.addWidget(button)
+ layout.addLayout(self._init_options_tracklist(), 0, 0, 1, 3)
+ layout.addLayout(self._init_options_format(), 1, 0)
+ layout.addLayout(self._init_options_quality(), 2, 0)
+ layout.addItem(qtwidg.QSpacerItem(int(self.options.width()/4), 0, qtwidg.QSizePolicy.Expanding, qtwidg.QSizePolicy.Expanding))
+ layout.addWidget(self._init_options_keep(), 1, 2, 2, 1)
+ layout.addLayout(self._init_options_output(), 3, 0, 1, 3)
+ layout.addWidget(self.optionsHelp, 4, 0)
+ layout.addWidget(self.optionsOk, 4, 2)
+
+ self.options.setLayout(layout)
+ self.options.setWindowTitle('sya (split youtube audio)')
+ self.options.setWindowIcon(qtgui.QIcon(resource_path('sya.png')))
- return layout
+ self.update_options_ok()
+ self.options.show()
- def _init_combobox(self, widget, label, setFn, options, default):
- layout = qtwidg.QHBoxLayout()
- # label
- label = qtwidg.QLabel(label, widget)
- layout.addWidget(label)
- # combobox
- combo = qtwidg.QComboBox(widget)
- for opt in options:
- combo.addItem(opt)
- if default in options:
- combo.setCurrentIndex(options.index(default))
- combo.activated[str].connect(setFn)
- layout.addWidget(combo)
-
- layout.setStretch(0, 2)
+ def _init_options_tracklist(self):
+ label = self.tracklistLabel
+ layout, self.optionsTracklist = sya_gui_filepicker(self.options, label, self.select_tracklist, self.set_tracklist, self.optionsValue[label], 'file')
return layout
-
- def _filepicker_tracklist(self, signal):
- file = qtwidg.QFileDialog.getOpenFileName(self._options,
- 'Select a tracklist', os.path.expanduser('~'), "Text file (*.txt)",
- None, qtwidg.QFileDialog.DontUseNativeDialog)
- if len(file) > 0:
- self.args.tracklist = file[0]
- self._edits[self._tracklistLabel].setText(self.args.tracklist)
- if len(self._edits[self._outputLabel].text()) == 0:
- self.args.output = os.path.splitext(self.args.tracklist)[0]
- self._edits[self._outputLabel].setText(self.args.output)
- self._check_ok()
-
- def _filepicker_output(self, signal):
- file = qtwidg.QFileDialog.getExistingDirectory(self._options,
- 'Select directory', os.path.expanduser('~'),
- qtwidg.QFileDialog.DontUseNativeDialog)
- if len(file) > 0:
- self.args.output = file
- self._edits[self._outputLabel].setText(file)
- self._check_ok()
- def _set_format(self, format):
- self.args.format = format
+ def _init_options_format(self):
+ label = self.formatLabel
+ self.optionsFormat = sya_gui_combobox(self.options, label, self.availableFormats, self.optionsValue[label], self.set_format)
+ return self.optionsFormat
- def _set_quality(self, quality):
- self.args.quality = quality[0]
+ def _init_options_quality(self):
+ label = self.qualityLabel
+ self.optionsQuality = sya_gui_combobox(self.options, label, self.availableQualities, self.optionsValue[label], self.set_quality)
+ return self.optionsQuality
- def _keep_toggle(self):
- self.args.keep = not self.args.keep
+ def _init_options_output(self):
+ label = self.outputLabel
+ layout, self.optionsOutput = sya_gui_filepicker(self.options, label, self.select_output, self.set_output, self.optionsValue[label], 'folder')
+ return layout
- def _check_ok(self):
- if self.args.tracklist != None and self.args.output != None and \
- os.path.exists(self.args.tracklist) and len(self.args.output) > 0:
- self._ok_btn.setEnabled(True)
+ def _init_options_keep(self):
+ label = self.keepLabel
+ self.optionsKeep = qtwidg.QCheckBox(label, self.options)
+ if self.optionsValue[label]:
+ self.optionsKeep.setChecked(True)
+ self.optionsKeep.toggled.connect(self.toggle_keep)
+ return self.optionsKeep
+
+ # Options Callbacks
+ def select_tracklist(self):
+ dialog = qtwidg.QFileDialog()
+ dialog.setWindowIcon(qtgui.QIcon(resource_path('sya.png')))
+ file = dialog.getOpenFileName(self.options, 'Select a tracklist', os.path.expanduser('~'), "Text file (*.txt)", None, qtwidg.QFileDialog.DontUseNativeDialog)
+ if len(file) > 0:
+ self.set_tracklist(file[0])
+
+ def set_tracklist(self, text):
+ self.optionsValue[self.tracklistLabel] = text
+ self.optionsTracklist.setText(text)
+ self.set_output(os.path.splitext(text)[0])
+ self.update_options_ok()
+
+ def select_output(self):
+ dialog = qtwidg.QFileDialog()
+ dialog.setWindowIcon(qtgui.QIcon(resource_path('sya.png')))
+ file = dialog.getExistingDirectory(self.options, 'Select directory', os.path.expanduser('~'), qtwidg.QFileDialog.DontUseNativeDialog)
+ if len(file) > 0:
+ self.set_output(file[0])
+
+ def set_output(self, text):
+ self.optionsValue[self.outputLabel] = text
+ self.optionsOutput.setText(text)
+ self.update_options_ok()
+
+ def set_format(self, option):
+ if option not in self.availableFormats:
+ return
+ self.optionsValue[self.formatLabel] = option
+ self.update_options_ok()
+
+ def set_quality(self, option):
+ if option not in self.availableQualities:
+ return
+ self.optionsValue[self.qualityLabel] = option
+ self.update_options_ok()
+
+ def toggle_keep(self):
+ self.optionsValue[self.keepLabel] = not self.optionsValue[self.keepLabel]
+ self.update_options_ok()
+
+ def update_options_ok(self):
+ tracklist = self.optionsValue[self.tracklistLabel]
+ output = self.optionsValue[self.outputLabel]
+ if os.path.exists(tracklist) and len(output) > 0:
+ self.optionsOk.setEnabled(True)
else:
- self._ok_btn.setEnabled(False)
+ self.optionsOk.setEnabled(False)
+
+ # Help Widget
+ def _init_help(self):
+ self.help = qtwidg.QTextEdit()
+ with open(resource_path("HELP.md")) as f:
+ self.help.setMarkdown(f.read())
+ self.help.resize(500, 500)
+ self.help.setReadOnly(True)
+ return
+
+ # Logger Widget
+ def _init_logger(self):
+ layout = qtwidg.QGridLayout()
+ layout.addWidget(self._init_logger_textbox(), 1, 0, 1, 5)
+ layout.addWidget(self._init_logger_cancel(), 2, 0)
+ layout.addWidget(self._init_logger_warning(), 2, 1, 1, 2)
+ layout.addWidget(self._init_logger_done(), 2, 4)
- def _ok(self):
- self._logs.show()
- self.start_main()
+ self.logger = qtwidg.QWidget()
+ self.logger.setLayout(layout)
+ self.logger.setWindowIcon(qtgui.QIcon(resource_path('sya.png')))
+ self.logger.resize(800, 400)
- def log(self, msg):
- cursor = self._logbox.textCursor()
- cursor.insertText(msg)
- self._logbox.setTextCursor(cursor)
- self._logbox.ensureCursorVisible()
-
- def start_main(self):
- self.main_t = SyaGuiMain(self.fnSya, args=self.args)
- self.check_t = SyaGuiMain(self._check_done)
- self.main_t.start()
- self.check_t.start()
+ def _init_logger_textbox(self):
+ self.loggerTextbox = qtwidg.QPlainTextEdit()
+ self.loggerTextbox.setReadOnly(True)
+ self.loggerTextbox.setLineWrapMode(qtwidg.QPlainTextEdit.NoWrap)
+ return self.loggerTextbox
- def _check_done(self):
- while self.main_t.isFinished() != True:
- continue
- self._ok_btn.setEnabled(True)
- self._options.setEnabled(False)
+ def _init_logger_cancel(self):
+ self.loggerCancel = qtwidg.QPushButton('Cancel')
+ return self.loggerCancel
- def _cancel(self):
- self.main_t.exit()
- self.check_t.exit()
- shutil.rmtree(self.args.output)
- del(self._logs)
+ @staticmethod
+ def _init_logger_warning():
+ return qtwidg.QLabel('This might take a while. You can click "Done" when it\'s finished.')
+ def _init_logger_done(self):
+ self.loggerDone = qtwidg.QPushButton('Done')
+ self.loggerDone.setEnabled(False)
+ return self.loggerDone
+
+
+# Main
if __name__ == '__main__':
app = qtwidg.QApplication(sys.argv)
- gui = SyaGui(sya.sya, sya.parse_args())
-
- if sys.platform == 'win32':
- gui.args.youtubedl = resource_path('yt-dlp.exe')
- gui.args.ffmpeg = resource_path('ffmpeg.exe')
- else:
- gui.args.youtubedl = resource_path('yt-dlp')
- gui.args.ffmpeg = resource_path('ffmpeg')
-
- sys.exit(app.exec_())
+ args = sya.parse_args()
+ if args.tracklist is None:
+ args.tracklist = ''
+ if args.output is None:
+ args.output = ''
+ if args.youtubedl is None:
+ args.youtubedl = resource_path('yt-dlp') if sys.platform != 'win32' else resource_path('yt-dlp.exe')
+ if args.ffmpeg is None:
+ args.ffmpeg = resource_path('ffmpeg') if sys.platform != 'win32' else resource_path('ffmpeg.exe')
+ gui = SyaGui(sya.sya, args)
+
+ sys.exit(app.exec_())
diff --git a/sya.png b/sya.png
Binary files differ.
diff --git a/sya.py b/sya.py
@@ -15,34 +15,30 @@ class TracklistItem:
self.title = title
# utilities
-def log(msg):
- print(msg)
-
def error_exit(msg):
- log('exit failure "{}"'.format(msg))
+ print('exit failure "{}"'.format(msg))
sys.exit()
def check_bin(*binaries):
for b in binaries:
try:
- subprocess.call([b], stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL)
+ subprocess.call([b], stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL, shell=False)
except:
error_exit('failed to execute {}'.format(b))
# functions
def get_audio(youtubedl, url, outdir, format='mp3', quality='320K', keep=True, ffmpeg='ffmpeg'):
- log('{} getting {}, {} ({})'.format(youtubedl, format, quality, url))
+ print('Downloading {} ({}, {})...'.format(url, format, quality))
fname = '{}/{}'.format(outdir, os.path.basename(outdir), format)
- cmd = [youtubedl, url, '--newline', '--extract-audio', '--audio-format', format,
- '--audio-quality', quality, '--prefer-ffmpeg', '-o', fname + '.%(ext)s']
+ cmd = [youtubedl, '--newline', '--extract-audio', '--audio-format', format,
+ '--audio-quality', quality, '--prefer-ffmpeg', '--ffmpeg-location', ffmpeg,
+ '-o', fname + '.%(ext)s']
if keep == True:
cmd.append('-k')
- if ffmpeg != 'ffmpeg':
- cmd.append('--ffmpeg-location')
- cmd.append(ffmpeg)
- p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
- for line in iter(p.stdout.readline, b''):
- log(line.decode('utf-8').rstrip('\n'))
+ cmd.append(url)
+ p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False)
+ for line in p.stdout.readlines():
+ print(' {}'.format(line.decode('utf-8', errors='ignore').strip()))
return '{}.{}'.format(fname, format)
def load_tracklist(path):
@@ -57,16 +53,25 @@ def load_tracklist(path):
def parse_tracks(tracklist):
tracks = []
+ weightR = 0 # num. timestamps on right-side
+ weightL = 0 # num. timestamps on left-side
for lcount, line in enumerate(tracklist):
sline = line.split(' ')
timestamp = None
- for l in sline:
- if Timestamp.match(l):
- timestamp = l.strip('[()]')
+ for i, l in enumerate(sline):
+ if i != 0 and i != len(sline)-1:
+ continue
+ elif Timestamp.match(l):
+ if timestamp == None or weightR > weightL:
+ timestamp = l.strip('[()]')
+ if i == 0:
+ weightL += 1
+ else:
+ weightR += 1
sline.remove(l)
if timestamp == None:
- log('line {}, missing timestamp: "{}"'.format(lcount, line))
+ print('line {}, missing timestamp: "{}"'.format(lcount, line))
title = ' '.join(sline).strip(' ')
title = re.sub(r"[/\\?%*:|\"<>\x7F\x00-\x1F]", '', title)
@@ -81,30 +86,36 @@ def missing_times(tracks):
missing.append(i)
return missing
-def split_tracks(ffmpeg, audio_fpath, tracks, format='mp3', outpath='out'):
- log('splitting tracks...')
- cmd = ['ffmpeg', '-v', 'quiet', '-stats', '-i', audio_fpath, '-f', 'null', '-']
- ret = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
- # some nasty string manip. to extract length (printed to stderr)
+def read_tracklen(ffmpeg, track_fpath):
+ cmd = [ffmpeg, '-v', 'quiet', '-stats', '-i', track_fpath, '-f', 'null', '-']
+ length = '00:00'
try:
+ ret = subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=False)
length = str(ret).split('\\r')
- length = length[len(length)-1].split(' ')[1].split('=')[1][:-3]
+ # some nasty string manip. to extract length (printed to stderr)
+ if sys.platform == 'win32':
+ length = length[len(length)-2].split(' ')[1].split('=')[1][:-3]
+ else:
+ length = length[len(length)-1].split(' ')[1].split('=')[1][:-3]
+ print('Track length: {}'.format(length))
except:
- log('Failed to find track length, {}'.format(length))
- return
-
+ error_exit('Failed to find track length, aborting.')
+ return length
+
+def split_tracks(ffmpeg, audio_fpath, audio_len, tracks, format='mp3', outpath='out'):
+ print('Splitting...')
for i, t in enumerate(tracks):
outfile = '{}/{} - {}.{}'.format(outpath, str(i+1).zfill(2), t.title.strip(' - '), format)
- end = length
+ end = audio_len
if i < len(tracks)-1:
end = tracks[i+1].timestamp
- log('\t{} ({} - {})'.format(outfile, t.timestamp, end))
+ print(' {} ({} - {})'.format(outfile, t.timestamp, end))
cmd = ['ffmpeg', '-nostdin', '-y', '-loglevel', 'error',
'-i', audio_fpath, '-ss', t.timestamp, '-to', end,
'-acodec', 'copy', outfile]
- p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
- for line in iter(p.stdout.readline, b''):
- log(line.decode('utf-8').rstrip('\n'))
+ p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False)
+ for line in p.stdout.readlines():
+ print(' {}'.format(line.decode('utf-8', errors='ignore').strip()))
return
# runtime
@@ -115,21 +126,32 @@ def parse_args():
parser.add_argument('tracklist', metavar='TRACKLIST', nargs='?',
help='tracklist to split audio by')
# options
- parser.add_argument('-o', '--output', metavar='PATH', type=str, nargs='?', dest='output',
+ parser.add_argument('-o', '--output',
+ metavar='PATH', type=str, nargs='?', dest='output',
help='specify the directory to write output files to (default: ./out)')
- parser.add_argument('-f', '--format', type=str, nargs='?', default='mp3', dest='format',
+ parser.add_argument('-f', '--format',
+ type=str, nargs='?', default='mp3', dest='format',
help='specify the --audio-format argument to pass to yt-dlp (default: mp3)')
- parser.add_argument('-q', '--quality', type=str, nargs='?', default='320K', dest='quality',
+ parser.add_argument('-q', '--quality',
+ type=str, nargs='?', default='320K', dest='quality',
help='specify the --audio-quality argument to pass to yt-dlp (default: 320K)')
- parser.add_argument('--yt-dlp', metavar='PATH', type=str, nargs='?', default='yt-dlp', dest='youtubedl',
+ parser.add_argument('--yt-dlp',
+ metavar='PATH', type=str, nargs='?', dest='youtubedl',
help='path of the "yt-dlp" binary to use')
- parser.add_argument('--ffmpeg', metavar='PATH', type=str, nargs='?', default='ffmpeg', dest='ffmpeg',
+ parser.add_argument('--ffmpeg',
+ metavar='PATH', type=str, nargs='?', dest='ffmpeg',
help='path of the "ffmpeg" binary to use')
- parser.add_argument('-k', '--keep', action='store_true', default=False, dest='keep',
+ parser.add_argument('-k', '--keep',
+ action='store_true', default=False, dest='keep',
help='keep any files removed during processing (full video/audio file)')
return parser.parse_args()
def sya(args):
+ if args.youtubedl == None:
+ args.youtubedl = 'yt-dlp.exe' if sys.platform == 'win32' else 'yt-dlp'
+ if args.ffmpeg == None:
+ args.ffmpeg = 'ffmpeg.exe' if sys.platform == 'win32' else 'ffmpeg'
+
if check_bin(args.youtubedl, args.ffmpeg) == False:
error_exit('required binaries are missing')
if args.tracklist == None or os.path.exists(args.tracklist) == False:
@@ -141,18 +163,24 @@ def sya(args):
audio_fpath = get_audio(args.youtubedl, tracklist[0], args.output,
args.format, args.quality, args.keep, args.ffmpeg)
-
- tracks = parse_tracks(tracklist[1:])
+ if os.path.exists(audio_fpath) == False:
+ error_exit('download failed, aborting')
+
+ tracks = parse_tracks(tracklist[1:])
+
missing = missing_times(tracks)
if len(missing) > 0:
error_exit('some tracks are missing timestamps')
+ length = read_tracklen(args.ffmpeg, audio_fpath)
os.makedirs(args.output, exist_ok=True)
- split_tracks(args.ffmpeg, audio_fpath, tracks, args.format, args.output)
+ split_tracks(args.ffmpeg, audio_fpath, length, tracks, args.format, args.output)
if args.keep is False:
os.remove(audio_fpath)
+ print('Success')
+
if __name__ == '__main__':
sya(parse_args())