Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
SpotifyBackup
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Model registry
Operate
Environments
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
GitLab community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
Robert Goldmann
SpotifyBackup
Compare revisions
7effe35e622b2ec10633f1e32418efa2022a62df to 06a719be77e98ca75d1edf0adc26e282f9dca2e2
Compare revisions
Changes are shown as if the
source
revision was being merged into the
target
revision.
Learn more about comparing revisions.
Source
deadlocker8/SpotifyBackup
Select target project
No results found
06a719be77e98ca75d1edf0adc26e282f9dca2e2
Select Git revision
Loading items
Swap
Target
deadlocker8/SpotifyBackup
Select target project
deadlocker8/SpotifyBackup
1 result
7effe35e622b2ec10633f1e32418efa2022a62df
Select Git revision
Loading items
Show changes
Only incoming changes from source
Include changes to target since source was created
Compare
Commits on Source (4)
refactor: renamed class
· 8ce97024
Robert Goldmann
authored
1 year ago
8ce97024
added new class to convert wav to mp3, maximize volume and set mp3 tags
· c2883a4d
Robert Goldmann
authored
1 year ago
c2883a4d
SpotifyRecorder: handle no current playback
· e55040e8
Robert Goldmann
authored
1 year ago
e55040e8
SpotifyRecorder: call wav to mp3 conversion, normalize volume and set mp3 tags
· 06a719be
Robert Goldmann
authored
1 year ago
06a719be
Show whitespace changes
Inline
Side-by-side
Showing
5 changed files
AudioRecorder.py
+3
-3
3 additions, 3 deletions
AudioRecorder.py
Mp3Converter.py
+68
-0
68 additions, 0 deletions
Mp3Converter.py
SpotifyRecorder.py
+34
-13
34 additions, 13 deletions
SpotifyRecorder.py
poetry.lock
+12
-1
12 additions, 1 deletion
poetry.lock
pyproject.toml
+1
-0
1 addition, 0 deletions
pyproject.toml
with
118 additions
and
17 deletions
Spotify
AudioRecorder.py
→
AudioRecorder.py
View file @
06a719be
...
...
@@ -6,10 +6,10 @@ import pyaudiowpatch as pyaudio
from
TheCodeLabs_BaseUtils.DefaultLogger
import
DefaultLogger
LOG_FORMAT
=
'
[%(levelname)-7s] - %(asctime)s - %(message)s
'
LOGGER
=
DefaultLogger
().
create_logger_if_not_exists
(
'
Spotify
AudioRecorder
'
,
logFormat
=
LOG_FORMAT
)
LOGGER
=
DefaultLogger
().
create_logger_if_not_exists
(
'
AudioRecorder
'
,
logFormat
=
LOG_FORMAT
)
class
Spotify
AudioRecorder
:
class
AudioRecorder
:
_CHUNK_SIZE
=
512
def
__init__
(
self
,
deviceName
:
str
,
destinationFilePath
:
str
):
...
...
@@ -59,7 +59,7 @@ class SpotifyAudioRecorder:
if
__name__
==
'
__main__
'
:
recorder
=
Spotify
AudioRecorder
(
recorder
=
AudioRecorder
(
deviceName
=
'
3/4 - Musik (2- GIGAPort HD Audio driver) [Loopback]
'
,
destinationFilePath
=
'
C:/Users/RobertG/Desktop/output.wav
'
)
...
...
This diff is collapsed.
Click to expand it.
Mp3Converter.py
0 → 100644
View file @
06a719be
import
re
import
subprocess
from
TheCodeLabs_BaseUtils.DefaultLogger
import
DefaultLogger
from
mutagen.easyid3
import
EasyID3
LOG_FORMAT
=
'
[%(levelname)-7s] - %(asctime)s - %(message)s
'
LOGGER
=
DefaultLogger
().
create_logger_if_not_exists
(
'
Mp3Converter
'
,
logFormat
=
LOG_FORMAT
)
class
Mp3Converter
:
_PATTERN_MAX_VOLUME
=
re
.
compile
(
r
'
max_volume: ([-.\d+]*)\s
'
)
@staticmethod
def
convert_to_mp3
(
inputFilePath
:
str
,
outputFilePath
:
str
)
->
None
:
output
=
subprocess
.
check_output
([
'
ffmpeg
'
,
'
-i
'
,
inputFilePath
,
'
-af
'
,
'
volumedetect
'
,
'
-vn
'
,
# ignore video streams
'
-sn
'
,
# ignore subtitle streams
'
-sn
'
,
# ignore data streams
'
-hide_banner
'
,
# suppress unnecessary log messages
'
-f
'
,
'
null
'
,
'
NULL
'
],
stderr
=
subprocess
.
STDOUT
)
output
=
output
.
decode
(
'
utf-8
'
)
matches
=
re
.
findall
(
Mp3Converter
.
_PATTERN_MAX_VOLUME
,
output
)
detectedMaxVolume
=
None
if
matches
:
detectedMaxVolume
=
float
(
matches
[
0
])
LOGGER
.
debug
(
f
'
\t\t
Detected max volume:
{
detectedMaxVolume
}
dB
'
)
arguments
=
[
'
ffmpeg
'
,
'
-i
'
,
inputFilePath
,
'
-y
'
,
# overwrite if already exists
'
-hide_banner
'
,
# suppress unnecessary log messages
'
-loglevel
'
,
'
error
'
,
'
-vn
'
,
# ignore video streams
'
-sn
'
,
# ignore subtitle streams
'
-sn
'
,
# ignore data streams
'
-ac
'
,
'
2
'
,
# 2 channels
'
-b:a
'
,
'
320k
'
]
# 320k bitrate
if
detectedMaxVolume
is
not
None
and
detectedMaxVolume
<
0
:
arguments
.
extend
([
'
-filter:a
'
,
f
'
volume=
{
abs
(
detectedMaxVolume
)
}
dB
'
])
# normalizes volume
arguments
.
append
(
outputFilePath
)
LOGGER
.
info
(
f
'
\t\t
Running
{
"
"
.
join
(
arguments
)
}
'
)
subprocess
.
check_call
(
arguments
)
@staticmethod
def
set_mp3_tags
(
filePath
:
str
,
title
:
str
,
artist
:
str
,
album
:
str
)
->
None
:
LOGGER
.
info
(
f
'
\t\t
Setting mp3 tags for
"
{
filePath
}
"'
)
audio
=
EasyID3
(
filePath
)
audio
[
'
title
'
]
=
title
audio
[
'
artist
'
]
=
artist
audio
[
'
album
'
]
=
album
audio
.
save
()
if
__name__
==
'
__main__
'
:
Mp3Converter
.
convert_to_mp3
(
'
C:/Programmierung/SpotifyBackup/recorder/1 - Bignic - tetrapod.wav
'
,
'
C:/Programmierung/SpotifyBackup/recorder/a.mp3
'
)
Mp3Converter
.
set_mp3_tags
(
'
C:/Programmierung/SpotifyBackup/recorder/a.mp3
'
,
title
=
'
Fancy Song
'
,
artist
=
'
Various Artists
'
,
album
=
'
Album #1
'
)
This diff is collapsed.
Click to expand it.
SpotifyRecorder.py
View file @
06a719be
...
...
@@ -7,7 +7,8 @@ import spotipy
from
TheCodeLabs_BaseUtils.DefaultLogger
import
DefaultLogger
from
spotipy
import
SpotifyOAuth
,
CacheFileHandler
from
SpotifyAudioRecorder
import
SpotifyAudioRecorder
from
AudioRecorder
import
AudioRecorder
from
Mp3Converter
import
Mp3Converter
LOG_FORMAT
=
'
[%(levelname)-7s] - %(asctime)s - %(message)s
'
LOGGER
=
DefaultLogger
().
create_logger_if_not_exists
(
'
SpotifyRecorder
'
,
logFormat
=
LOG_FORMAT
)
...
...
@@ -106,23 +107,39 @@ class SpotifyRecorder:
skippedTrackNumbers
.
append
(
indexInPlaylist
)
continue
LOGGER
.
info
(
f
'
>>> Recording track
{
index
+
1
}
/
{
len
(
tracks
)
}
:
#
{
indexInPlaylist
}
"
{
track
[
"
track
"
][
"
name
"
]
}
"
...
'
)
LOGGER
.
info
(
f
'
>>> Recording track
{
index
+
1
}
/
{
len
(
tracks
)
}
:
'
f
'
#
{
indexInPlaylist
}
"
{
track
[
"
track
"
][
"
name
"
]
}
"
...
'
)
try
:
self
.
__stop_playback_if_playing
(
deviceId
)
filePath
=
self
.
__determine_file_path
(
indexInPlaylist
+
1
,
track
)
recorder
=
SpotifyAudioRecorder
(
self
.
_audioDeviceName
,
filePath
)
fileName
=
self
.
__determine_file_name
(
indexInPlaylist
,
track
)
filePathWav
=
f
'
{
fileName
}
.wav
'
filePathMp3
=
f
'
{
fileName
}
.mp3
'
recorder
=
AudioRecorder
(
self
.
_audioDeviceName
,
filePathWav
)
with
recorder
.
record
():
self
.
__play_track
(
deviceId
,
track
[
'
track
'
][
'
uri
'
])
timeWaitedForPlaying
=
self
.
__wait_for_track_playing
(
track
[
'
track
'
][
'
id
'
])
self
.
__wait_for_track_end
(
track
,
timeWaitedForPlaying
)
Mp3Converter
.
convert_to_mp3
(
filePathWav
,
filePathMp3
)
Mp3Converter
.
set_mp3_tags
(
filePath
=
filePathMp3
,
title
=
track
[
'
track
'
][
'
name
'
],
artist
=
self
.
__join_artists
(
track
),
album
=
track
[
'
track
'
][
'
album
'
][
'
name
'
])
recordedTrackNumbers
.
append
(
indexInPlaylist
)
except
Exception
as
e
:
LOGGER
.
error
(
f
'
An error occurred while recording track
"
{
track
[
"
track
"
][
"
name
"
]
}
"'
,
exc_info
=
e
)
errorTrackNumbers
.
append
(
indexInPlaylist
)
self
.
__print_end_statistics
(
errorTrackNumbers
,
recordedTrackNumbers
,
skippedTrackNumbers
,
len
(
tracks
))
def
__join_artists
(
self
,
track
)
->
str
:
artists
=
track
[
'
track
'
][
'
artists
'
]
return
'
&
'
.
join
(
artist
[
'
name
'
]
for
artist
in
artists
)
def
__print_end_statistics
(
self
,
errorTrackNumbers
:
list
[
int
],
recordedTrackNumbers
:
list
[
int
],
skippedTrackNumbers
:
list
[
int
],
numberOfTracks
:
int
):
LOGGER
.
info
(
'
### DONE ###
'
)
LOGGER
.
info
(
'
>>> Skipped <<<
'
)
...
...
@@ -133,13 +150,12 @@ class SpotifyRecorder:
for
number
in
errorTrackNumbers
:
LOGGER
.
info
(
f
'
Error #
{
number
}
'
)
LOGGER
.
info
(
f
'
###
{
len
(
tracks
)
}
tracks,
{
len
(
recordedTrackNumbers
)
}
recorded,
{
len
(
skippedTrackNumbers
)
}
skipped,
{
len
(
errorTrackNumbers
)
}
errors ###
'
)
LOGGER
.
info
(
f
'
###
{
numberOfTracks
}
tracks,
{
len
(
recordedTrackNumbers
)
}
recorded,
'
f
'
{
len
(
skippedTrackNumbers
)
}
skipped,
{
len
(
errorTrackNumbers
)
}
errors ###
'
)
def
__determine_file_path
(
self
,
index
:
int
,
track
)
->
str
:
artists
=
track
[
'
track
'
][
'
artists
'
]
artists
=
'
&
'
.
join
(
artist
[
'
name
'
]
for
artist
in
artists
)
fileName
=
f
'
{
index
}
-
{
artists
}
-
{
track
[
"
track
"
][
"
name
"
]
}
.wav
'
def
__determine_file_name
(
self
,
index
:
int
,
track
)
->
str
:
artists
=
self
.
__join_artists
(
track
)
fileName
=
f
'
{
index
}
-
{
artists
}
-
{
track
[
"
track
"
][
"
name
"
]
}
'
return
os
.
path
.
join
(
self
.
_destinationFolder
,
fileName
)
def
__wait_for_track_end
(
self
,
track
,
timeWaitedForPlaying
:
float
)
->
None
:
...
...
@@ -197,7 +213,11 @@ class SpotifyRecorder:
self
.
_spotify
.
start_playback
(
device_id
=
deviceId
,
uris
=
[
trackUri
])
def
__stop_playback_if_playing
(
self
,
deviceId
:
str
)
->
None
:
if
self
.
_spotify
.
current_playback
()[
'
is_playing
'
]:
currentPlayback
=
self
.
_spotify
.
current_playback
()
if
currentPlayback
is
None
:
return
if
currentPlayback
[
'
is_playing
'
]:
self
.
_spotify
.
pause_playback
(
device_id
=
deviceId
)
def
__wait_for_track_playing
(
self
,
expectedTrackId
:
str
)
->
float
:
...
...
@@ -212,7 +232,8 @@ class SpotifyRecorder:
LOGGER
.
debug
(
f
'
\t\t
Track started playing after
{
duration
:
.
1
f
}
s
'
)
break
else
:
raise
RuntimeError
(
f
'
Wrong track started playing (actual:
{
currentPlayback
[
"
item
"
][
"
id
"
]
}
, expected:
{
expectedTrackId
}
)
'
)
raise
RuntimeError
(
f
'
Wrong track started playing (actual:
{
currentPlayback
[
"
item
"
][
"
id
"
]
}
, expected:
{
expectedTrackId
}
)
'
)
time
.
sleep
(
1
)
...
...
This diff is collapsed.
Click to expand it.
poetry.lock
View file @
06a719be
...
...
@@ -132,6 +132,17 @@ files = [
{file
=
"idna-3.6.tar.gz"
,
hash
=
"sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"
}
,
]
[[package]]
name
=
"mutagen"
version
=
"1.47.0"
description
=
"read and write audio tags for many formats"
optional
=
false
python-versions
=
">
=
3.7
"
files
=
[
{file
=
"mutagen-1.47.0-py3-none-any.whl"
,
hash
=
"sha256:edd96f50c5907a9539d8e5bba7245f62c9f520aef333d13392a79a4f70aca719"
}
,
{file
=
"mutagen-1.47.0.tar.gz"
,
hash
=
"sha256:719fadef0a978c31b4cf3c956261b3c58b6948b32023078a2117b1de09f0fc99"
}
,
]
[[package]]
name
=
"psutil"
version
=
"5.9.7"
...
...
@@ -341,4 +352,4 @@ zstd = ["zstandard (>=0.18.0)"]
[
metadata
]
lock-version
=
"2.0"
python-versions
=
"^3.11"
content-hash
=
"
a10c10003642c988271028a8a2a44227cae6123d1e05e4a66f3cb2dbab5a655a
"
content-hash
=
"
bb77de02b25207bf10249bf1370a69682b27ad88452311d42abc7d9830eb5494
"
This diff is collapsed.
Click to expand it.
pyproject.toml
View file @
06a719be
...
...
@@ -14,6 +14,7 @@ python = "^3.11"
thecodelabs-baseutils
=
{
version
=
"*"
,
source
=
"TheCodeLabs"
}
spotipy
=
"2.22.0"
PyAudioWPatch
=
"0.2.12.6"
mutagen
=
"1.47.0"
[tool.poetry.dev-dependencies]
...
...
This diff is collapsed.
Click to expand it.