Code Monkey home page Code Monkey logo

Comments (1)

sweep-ai avatar sweep-ai commented on June 2, 2024

Here's the PR! #22.

⚡ Sweep Free Trial: I used GPT-4 to create this ticket. You have 5 GPT-4 tickets left. For more GPT-4 tickets, visit our payment portal.


Step 1: 🔍 Code Search

I found the following snippets in your repository. I will now analyze these snippets and come up with a plan.

Some code snippets I looked at (click to expand). If some file is missing from here, you can mention the path in the ticket description.

stemgen/metadata.py

Lines 1 to 390 in 1fe4ccb

#!/usr/bin/env python3
import os
import sys
import subprocess
import json
sys.path.append(os.path.abspath("ni-stem/mutagen"))
import mutagen
def get_cover(FILE_EXTENSION, FILE_PATH, OUTPUT_PATH, FILE_NAME):
print("Extracting cover...")
if FILE_EXTENSION in (".wav", ".wave", ".aif", ".aiff"):
# Open the file
file = mutagen.File(FILE_PATH)
# Check if the file contains any cover art
if "APIC:" in file:
# Extract the cover art
cover = file["APIC:"].data
# Save the cover art to a file
with open(f"{OUTPUT_PATH}/{FILE_NAME}/cover.jpg", "wb") as f:
f.write(cover)
print("Cover extracted from APIC tag.")
else:
print("Error: The file does not contain any cover art.")
else:
subprocess.run(
[
"ffmpeg",
"-i",
FILE_PATH,
"-an",
"-vcodec",
"copy",
f"{OUTPUT_PATH}/{FILE_NAME}/cover.jpg",
"-y",
]
)
print("Cover extracted with ffmpeg.")
print("Done.")
def get_metadata(DIR, FILE_PATH, OUTPUT_PATH, FILE_NAME):
print("Extracting metadata...")
# Extract metadata with mutagen
file = mutagen.File(FILE_PATH)
if file.tags is not None:
print(file.tags.pprint())
TAGS = {}
# `title`
if "TIT2" in file:
TAGS["title"] = file["TIT2"].text[0]
elif "TITLE" in file:
TAGS["title"] = file["TITLE"][0]
else:
TAGS["title"] = FILE_NAME
# `artist`
if "TPE1" in file:
TAGS["artist"] = file["TPE1"].text[0]
if "ARTIST" in file:
TAGS["artist"] = file["ARTIST"][0]
# `album`
if "TALB" in file:
TAGS["album"] = file["TALB"].text[0]
if "ALBUM" in file:
TAGS["album"] = file["ALBUM"][0]
# `label`
if "TPUB" in file:
TAGS["label"] = file["TPUB"].text[0]
if "ORGANIZATION" in file:
TAGS["label"] = file["ORGANIZATION"][0]
if "LABEL" in file:
TAGS["label"] = file["LABEL"][0]
# `genre`
if "TCON" in file:
TAGS["genre"] = file["TCON"].text[0]
if "GENRE" in file:
TAGS["genre"] = file["GENRE"][0]
# `url`
if "WXXX:" in file:
TAGS["www"] = file["WXXX:"].url
if "WWW" in file:
TAGS["www"] = file["WWW"][0]
# `year` / `date`
if "TDRC" in file:
TAGS["year"] = str(file["TDRC"].text[0])
if "DATE" in file:
TAGS["year"] = file["DATE"][0]
# `track`
if "TRCK" in file:
TAGS["track_no"] = file["TRCK"].text[0]
if "TRACKNUMBER" in file:
TAGS["track_no"] = file["TRACKNUMBER"][0]
# `track_count`
if "TOTALTRACKS" in file:
TAGS["track_count"] = file["TOTALTRACKS"][0]
# `bpm`
if "TBPM" in file:
TAGS["bpm"] = file["TBPM"].text[0]
if "BPM" in file:
TAGS["bpm"] = file["BPM"][0]
# `key`
if "TKEY" in file:
TAGS["key"] = file["TKEY"].text[0]
if "KEY" in file:
TAGS["key"] = file["KEY"][0]
# `initialkey`
if "TKEY" in file:
TAGS["initialkey"] = file["TKEY"].text[0]
if "INITIALKEY" in file:
TAGS["initialkey"] = file["INITIALKEY"][0]
# `remixer`
if "TPE4" in file:
TAGS["remixer"] = file["TPE4"].text[0]
if "REMIXER" in file:
TAGS["remixer"] = file["REMIXER"][0]
# `mix`
if "TXXX:MIX" in file:
TAGS["mix"] = file["TXXX:MIX"].text[0]
if "MIX" in file:
TAGS["mix"] = file["MIX"][0]
# `producer`
if "TXXX:PRODUCER" in file:
TAGS["producer"] = file["TXXX:PRODUCER"].text[0]
if "PRODUCER" in file:
TAGS["producer"] = file["PRODUCER"][0]
# `catalog_no`
if "TXXX:CATALOGNUMBER" in file:
TAGS["catalog_no"] = file["TXXX:CATALOGNUMBER"].text[0]
if "CATALOGNUMBER" in file:
TAGS["catalog_no"] = file["CATALOGNUMBER"][0]
# `discogs_release_id`
if "TXXX:DISCOGS_RELEASE_ID" in file:
TAGS["discogs_release_id"] = file["TXXX:DISCOGS_RELEASE_ID"].text[0]
if "DISCOGS_RELEASE_ID" in file:
TAGS["discogs_release_id"] = file["DISCOGS_RELEASE_ID"][0]
# `url_discogs_release_site`
if "WXXX:DISCOGS_RELEASE_SITE" in file:
TAGS["url_discogs_release_site"] = file["WXXX:DISCOGS_RELEASE_SITE"].text[0]
if "URL_DISCOGS_RELEASE_SITE" in file:
TAGS["url_discogs_release_site"] = file["URL_DISCOGS_RELEASE_SITE"][0]
# `url_discogs_artist_site`
if "WXXX:DISCOGS_ARTIST_SITE" in file:
TAGS["url_discogs_artist_site"] = file["WXXX:DISCOGS_ARTIST_SITE"].text[0]
if "URL_DISCOGS_ARTIST_SITE" in file:
TAGS["url_discogs_artist_site"] = file["URL_DISCOGS_ARTIST_SITE"][0]
# `youtube_id`
if "TXXX:YOUTUBE_ID" in file:
TAGS["youtube_id"] = file["TXXX:YOUTUBE_ID"].text[0]
if "YOUTUBE_ID" in file:
TAGS["youtube_id"] = file["YOUTUBE_ID"][0]
# `beatport_id`
if "TXXX:BEATPORT_ID" in file:
TAGS["beatport_id"] = file["TXXX:BEATPORT_ID"].text[0]
if "BEATPORT_ID" in file:
TAGS["beatport_id"] = file["BEATPORT_ID"][0]
# `qobuz_id`
if "TXXX:QOBUZ_ID" in file:
TAGS["qobuz_id"] = file["TXXX:QOBUZ_ID"].text[0]
if "QOBUZ_ID" in file:
TAGS["qobuz_id"] = file["QOBUZ_ID"][0]
# `lyrics`
if "USLT" in file:
TAGS["lyrics"] = file["USLT"].text[0]
if "LYRICS" in file:
TAGS["lyrics"] = file["LYRICS"][0]
# `mood`
if "TXXX:MOOD" in file:
TAGS["mood"] = file["TXXX:MOOD"].text[0]
if "MOOD" in file:
TAGS["mood"] = file["MOOD"][0]
# `comment`
if "COMM" in file:
TAGS["comment"] = file["COMM"].text[0]
if "COMMENT" in file:
TAGS["comment"] = file["COMMENT"][0]
# `description`
if "TXXX:DESCRIPTION" in file:
TAGS["description"] = file["TXXX:DESCRIPTION"].text[0]
if "DESCRIPTION" in file:
TAGS["description"] = file["DESCRIPTION"][0]
# `barcode`
if "TXXX:BARCODE" in file:
TAGS["barcode"] = file["TXXX:BARCODE"].text[0]
if "BARCODE" in file:
TAGS["barcode"] = file["BARCODE"][0]
# `upc`
if "TXXX:UPC" in file:
TAGS["upc"] = file["TXXX:UPC"].text[0]
if "UPC" in file:
TAGS["upc"] = file["UPC"][0]
# `isrc`
if "TSRC" in file:
TAGS["isrc"] = file["TSRC"].text[0]
if "ISRC" in file:
TAGS["isrc"] = file["ISRC"][0]
# `www`
if "TXXX:WWW" in file:
TAGS["www"] = file["TXXX:WWW"].text[0]
if "WWW" in file:
TAGS["www"] = file["WWW"][0]
# `album_artist`
if "TPE2" in file:
TAGS["album_artist"] = file["TPE2"].text[0]
if "ALBUMARTIST" in file:
TAGS["album_artist"] = file["ALBUMARTIST"][0]
# `style`
if "TXXX:STYLE" in file:
TAGS["style"] = file["TXXX:STYLE"].text[0]
if "STYLE" in file:
TAGS["style"] = file["STYLE"][0]
# `track`
if "TPOS" in file:
TAGS["track"] = file["TPOS"].text[0]
# `copyright`
if "TCOP" in file:
TAGS["copyright"] = file["TCOP"].text[0]
if "COPYRIGHT" in file:
TAGS["copyright"] = file["COPYRIGHT"][0]
# `media`
if "TMED" in file:
TAGS["media"] = file["TMED"].text[0]
if "MEDIATYPE" in file:
TAGS["media"] = file["MEDIATYPE"][0]
# `country`
if "TXXX:COUNTRY" in file:
TAGS["country"] = file["TXXX:COUNTRY"].text[0]
if "COUNTRY" in file:
TAGS["country"] = file["COUNTRY"][0]
# `cover`
if os.path.exists(os.path.join(OUTPUT_PATH, FILE_NAME, "cover.jpg")):
TAGS["cover"] = f"{os.path.join(DIR, OUTPUT_PATH, FILE_NAME, 'cover.jpg')}"
print(TAGS)
print("Creating tags.json...")
with open(os.path.join(OUTPUT_PATH, FILE_NAME, "tags.json"), "w") as f:
json.dump(TAGS, f)
print("Done.")
def create_metadata_json(stems, path):
print(stems)
metadata = {
"mastering_dsp": {
"compressor": {
"enabled": False,
"ratio": 3,
"output_gain": 0.5,
"release": 0.300000011920929,
"attack": 0.003000000026077032,
"input_gain": 0.5,
"threshold": 0,
"hp_cutoff": 300,
"dry_wet": 50,
},
"limiter": {
"enabled": False,
"release": 0.05000000074505806,
"threshold": 0,
"ceiling": -0.3499999940395355,
},
},
"version": 1,
"stems": stems,
}
with open(path, "w") as f:
json.dump(metadata, f)
ableton_color_index_to_hex = {
0: "#F099A7",
1: "#F2A948",
2: "#C49B40",
3: "#F7F48D",
4: "#CBF94F",
5: "#77FB58",
6: "#79FBAF",
7: "#8DFCE8",
8: "#97C3FA",
9: "#5E7FDD",
10: "#96A6F9",
11: "#CA72DE",
12: "#D45D9E",
13: "#FFFFFF",
14: "#EB4A41",
15: "#E5742E",
16: "#937451",
17: "#FCF15E",
18: "#A5FC7C",
19: "#67C039",
20: "#56BCAF",
21: "#6DE6FC",
22: "#4BA1E8",
23: "#367BBB",
24: "#836DDD",
25: "#AD7AC1",
26: "#EB4CCE",
27: "#D0D0D0",
28: "#D36E60",
29: "#F2A77C",
30: "#CDAE79",
31: "#F0FEB7",
32: "#D5E3A0",
33: "#BECF7F",
34: "#A3C392",
35: "#DCFCE3",
36: "#D4F0F7",
37: "#BBC1E0",
38: "#CABCE1",
39: "#AA99E0",
40: "#E3DCE1",
41: "#A9A9A9",
42: "#BE948D",
43: "#AF845D",
44: "#95846D",
45: "#BEBA74",
46: "#ABBD3B",
47: "#88AF5A",
48: "#94C0BA",
49: "#A0B2C2",
50: "#8BA4BF",
51: "#8693C7",
52: "#A296B3",
53: "#BAA0BC",
54: "#B27595",
55: "#7B7B7B",
56: "#A13D38",
57: "#9E5639",
58: "#6D5043",
59: "#D7C440",
60: "#889637",
61: "#669D42",
62: "#469A8E",
63: "#356281",
64: "#1F2E90",
65: "#37519D",
66: "#5E4CA7",
67: "#9850A8",
68: "#BC3D6D",
69: "#3C3C3C",
}

stemgen/stemcopy.py

Lines 1 to 97 in 1fe4ccb

#!/usr/bin/env python3
# Use this script to match the metadata of your double stems by copying
# the metadata from the first part to the second part
# Install `trakor-nml-utils`:
# `pip install git+https://github.com/wolkenarchitekt/traktor-nml-utils`
# They need to follow this naming convention:
# "Artist - Title (Version) [part 1].stem.m4a"
# "Artist - Title (Version) [part 2].stem.m4a"
# where everything before "[part 1]" or "[part 2]" is the same for both files
# First, you need to make sure that the [part1] is correctly analyzed in Traktor
# Then you can run this script:
# `python3 stemcopy.py /Users/Shared/Traktor/collection.nml`
# where `collection.nml` is the path to your Traktor collection file, e.g.
# Please make sure to make a backup of your `collection.nml` file first!
import argparse
from traktor_nml_utils import TraktorCollection
from pathlib import Path
parser = argparse.ArgumentParser()
parser.add_argument(
"collection",
help="Path to Traktor `collection.nml` file",
default="collection.nml",
)
args = parser.parse_args()
print("Loading collection.nml...")
collection = TraktorCollection(path=Path(args.collection))
print("Loaded!")
print("Matching...")
for part1 in collection.nml.collection.entry:
# Check if part 1 of stem file
if (
part1.stems
and part1.lock != 1
and (
part1.location.file.endswith("[part 1].stem.m4a")
or part1.location.file.endswith("[part 1].stem.mp4")
)
):
print(part1.location.file)
# Match part 1 with part 2
for part2 in collection.nml.collection.entry:
if (
part2.stems
and (
part2.location.file.endswith("[part 2].stem.m4a")
or part2.location.file.endswith("[part 2].stem.mp4")
)
and (
part1.location.file.removesuffix("[part 1].stem.m4a")
== part2.location.file.removesuffix("[part 2].stem.m4a")
or part1.location.file.removesuffix("[part 1].stem.mp4")
== part2.location.file.removesuffix("[part 2].stem.mp4")
)
):
print(part2.location.file)
if part1.cue_v2:
part2.cue_v2 = part1.cue_v2
if part1.tempo:
part2.tempo = part1.tempo
if part1.musical_key:
part2.musical_key = part1.musical_key
if part1.info.genre:
part2.info.genre = part1.info.genre
if part1.info.label:
part2.info.label = part1.info.label
if part1.info.playcount:
part2.info.playcount = part1.info.playcount
if part1.info.last_played:
part2.info.last_played = part1.info.last_played
if part1.info.color:
part2.info.color = part1.info.color
part1.lock = 1
part2.lock = 1
print("OK")
break
print("\n")
print("Done.")
print("Saving...")
collection.save()
print("Done.")

stemgen/stemgen.py

Lines 1 to 405 in 1fe4ccb

#!/usr/bin/env python3
import argparse
import os
import platform
import shutil
import sys
import subprocess
from pathlib import Path
import time
import unicodedata
from metadata import get_cover, get_metadata
LOGO = r"""
_____ _____ _____ _____ _____ _____ _____
| __|_ _| __| | __| __| | |
|__ | | | | __| | | | | | __| | | |
|_____| |_| |_____|_|_|_|_____|_____|_|___|
"""
SUPPORTED_FILES = [".wave", ".wav", ".aiff", ".aif", ".flac"]
REQUIRED_PACKAGES = ["ffmpeg", "sox"]
USAGE = f"""{LOGO}
Stemgen is a Stem file generator. Convert any track into a stem and have fun with Traktor.
Usage: python3 stemgen.py -i [INPUT_PATH] -o [OUTPUT_PATH]
Supported input file format: {SUPPORTED_FILES}
"""
VERSION = "5.0.0"
parser = argparse.ArgumentParser(
description=USAGE, formatter_class=argparse.RawTextHelpFormatter
)
parser.add_argument(
"-i", dest="INPUT_PATH", required=True, help="the path to the input file"
)
parser.add_argument(
"-o", dest="OUTPUT_PATH", default="output", help="the path to the output folder"
)
parser.add_argument("-f", dest="FORMAT", default="alac", help="aac or alac")
parser.add_argument("-v", "--version", action="version", version=VERSION)
args = parser.parse_args()
INPUT_PATH = args.INPUT_PATH
OUTPUT_PATH = args.OUTPUT_PATH
FORMAT = args.FORMAT
DIR = Path(__file__).parent.absolute()
PYTHON_EXEC = sys.executable if not None else "python3"
# CONVERSION AND GENERATION
def convert():
print("Converting to wav and/or downsampling...")
# We downsample to 44.1kHz to avoid problems with the separation software
# because the models are trained on 44.1kHz audio files
# QUALITY WIDTH REJ dB TYPICAL USE
# -v very high 95% 175 24-bit mastering
# -M/-I/-L Phase response = minimum/intermediate/linear(default)
# -s Steep filter (band-width = 99%)
# -a Allow aliasing above the pass-band
global BIT_DEPTH
global SAMPLE_RATE
converted_file_path = os.path.join(OUTPUT_PATH, FILE_NAME, FILE_NAME + ".wav")
if BIT_DEPTH == 32:
# Downconvert to 24-bit
if FILE_PATH == converted_file_path:
subprocess.run(
[
"sox",
FILE_PATH,
"--show-progress",
"-b",
"24",
os.path.join(OUTPUT_PATH, FILE_NAME, FILE_NAME + ".24bit.wav"),
"rate",
"-v",
"-a",
"-I",
"-s",
"44100",
],
check=True,
)
os.remove(converted_file_path)
os.rename(
os.path.join(OUTPUT_PATH, FILE_NAME, FILE_NAME + ".24bit.wav"),
converted_file_path,
)
else:
subprocess.run(
[
"sox",
FILE_PATH,
"--show-progress",
"-b",
"24",
converted_file_path,
"rate",
"-v",
"-a",
"-I",
"-s",
"44100",
],
check=True,
)
BIT_DEPTH = 24
else:
if (
FILE_EXTENSION == ".wav" or FILE_EXTENSION == ".wave"
) and SAMPLE_RATE == 44100:
print("No conversion needed.")
else:
if FILE_PATH == converted_file_path:
subprocess.run(
[
"sox",
FILE_PATH,
"--show-progress",
"--no-dither",
os.path.join(
OUTPUT_PATH, FILE_NAME, FILE_NAME + ".44100Hz.wav"
),
"rate",
"-v",
"-a",
"-I",
"-s",
"44100",
],
check=True,
)
os.remove(converted_file_path)
os.rename(
os.path.join(OUTPUT_PATH, FILE_NAME, FILE_NAME + ".44100Hz.wav"),
converted_file_path,
)
else:
subprocess.run(
[
"sox",
FILE_PATH,
"--show-progress",
"--no-dither",
converted_file_path,
"rate",
"-v",
"-a",
"-I",
"-s",
"44100",
],
check=True,
)
print("Done.")
def split_stems():
print("Splitting stems...")
if BIT_DEPTH == 24:
print("Using 24-bit model...")
subprocess.run(
[
PYTHON_EXEC,
"-m",
"demucs",
"--int24",
"-n",
"htdemucs",
"-d",
"cpu",
FILE_PATH,
"-o",
f"{OUTPUT_PATH}/{FILE_NAME}",
]
)
else:
print("Using 16-bit model...")
subprocess.run(
[
PYTHON_EXEC,
"-m",
"demucs",
"-n",
"htdemucs",
"-d",
"cpu",
FILE_PATH,
"-o",
f"{OUTPUT_PATH}/{FILE_NAME}",
]
)
print("Done.")
def create_stem():
print("Creating stem...")
cd_root()
stem_args = [PYTHON_EXEC, "ni-stem/ni-stem", "create", "-s"]
stem_args += [
f"{OUTPUT_PATH}/{FILE_NAME}/htdemucs/{FILE_NAME}/drums.wav",
f"{OUTPUT_PATH}/{FILE_NAME}/htdemucs/{FILE_NAME}/bass.wav",
f"{OUTPUT_PATH}/{FILE_NAME}/htdemucs/{FILE_NAME}/other.wav",
f"{OUTPUT_PATH}/{FILE_NAME}/htdemucs/{FILE_NAME}/vocals.wav",
]
stem_args += [
"-x",
f"{OUTPUT_PATH}/{FILE_NAME}/{FILE_NAME}.wav",
"-t",
f"{OUTPUT_PATH}/{FILE_NAME}/tags.json",
"-m",
"metadata.json",
"-f",
FORMAT,
]
subprocess.run(stem_args)
print("Done.")
# SETUP
def cd_root():
os.chdir(DIR)
def setup():
for package in REQUIRED_PACKAGES:
if not shutil.which(package):
print(f"Please install {package} before running Stemgen.")
sys.exit(2)
if (
subprocess.run(
[PYTHON_EXEC, "-m", "demucs", "-h"], capture_output=True, text=True
).stdout.strip()
== ""
):
print("Please install demucs before running Stemgen.")
sys.exit(2)
if not os.path.exists(OUTPUT_PATH):
os.mkdir(OUTPUT_PATH)
print("Output dir created.")
else:
print("Output dir already exists.")
global BASE_PATH, FILE_EXTENSION
BASE_PATH = os.path.basename(INPUT_PATH)
FILE_EXTENSION = os.path.splitext(BASE_PATH)[1]
if FILE_EXTENSION not in SUPPORTED_FILES:
print("Invalid input file format. File should be one of:", SUPPORTED_FILES)
sys.exit(1)
setup_file()
get_bit_depth()
get_sample_rate()
get_cover(FILE_EXTENSION, FILE_PATH, OUTPUT_PATH, FILE_NAME)
get_metadata(DIR, FILE_PATH, OUTPUT_PATH, FILE_NAME)
convert()
print("Ready!")
def run():
print(f"Creating a Stem file for {FILE_NAME}...")
split_stems()
create_stem()
clean_dir()
print("Success! Have fun :)")
def get_bit_depth():
print("Extracting bit depth...")
global BIT_DEPTH
if FILE_EXTENSION == ".flac":
BIT_DEPTH = int(
subprocess.check_output(
[
"ffprobe",
"-v",
"error",
"-select_streams",
"a",
"-show_entries",
"stream=bits_per_raw_sample",
"-of",
"default=noprint_wrappers=1:nokey=1",
FILE_PATH,
]
)
)
else:
BIT_DEPTH = int(
subprocess.check_output(
[
"ffprobe",
"-v",
"error",
"-select_streams",
"a",
"-show_entries",
"stream=bits_per_sample",
"-of",
"default=noprint_wrappers=1:nokey=1",
FILE_PATH,
]
)
)
print(f"bits_per_sample={BIT_DEPTH}")
print("Done.")
def get_sample_rate():
print("Extracting sample rate...")
global SAMPLE_RATE
SAMPLE_RATE = int(
subprocess.check_output(
[
"ffprobe",
"-v",
"error",
"-select_streams",
"a",
"-show_entries",
"stream=sample_rate",
"-of",
"default=noprint_wrappers=1:nokey=1",
FILE_PATH,
]
)
)
print(f"sample_rate={SAMPLE_RATE}")
print("Done.")
def strip_accents(text):
text = unicodedata.normalize("NFKD", text)
text = text.encode("ascii", "ignore")
text = text.decode("utf-8")
return str(text)
def setup_file():
global FILE_NAME, INPUT_FOLDER, FILE_PATH
FILE_NAME = strip_accents(BASE_PATH.removesuffix(FILE_EXTENSION))
INPUT_FOLDER = os.path.dirname(INPUT_PATH)
if os.path.exists(f"{OUTPUT_PATH}/{FILE_NAME}"):
print("Working dir already exists.")
else:
os.mkdir(f"{OUTPUT_PATH}/{FILE_NAME}")
print("Working dir created.")
shutil.copy(INPUT_PATH, f"{OUTPUT_PATH}/{FILE_NAME}/{FILE_NAME}{FILE_EXTENSION}")
FILE_PATH = f"{OUTPUT_PATH}/{FILE_NAME}/{FILE_NAME}{FILE_EXTENSION}"
print("Done.")
def clean_dir():
print("Cleaning...")
if platform.system() == "Windows":
time.sleep(5)
os.chdir(os.path.join(OUTPUT_PATH, FILE_NAME))
if os.path.isfile(f"{FILE_NAME}.stem.m4a"):
os.rename(f"{FILE_NAME}.stem.m4a", os.path.join("..", f"{FILE_NAME}.stem.m4a"))
shutil.rmtree(os.path.join(DIR, OUTPUT_PATH + "/" + FILE_NAME))
input_dir = os.path.join(DIR, INPUT_FOLDER)
for file in os.listdir(input_dir):
if file.endswith(".m4a"):
os.remove(os.path.join(input_dir, file))
print("Done.")
cd_root()
setup()
run()

stemgen/ableton.py

Lines 1 to 240 in 1fe4ccb

#!/usr/bin/env python3
# Stemgen for Ableton Live
# Installation:
# `pip install opencv-python`
# Only on macOS: `pip install pyobjc-core`
# Only on macOS: `pip install pyobjc`
# `pip install pyautogui`
# `pip install pylive`
# Also install https://github.com/ideoforms/AbletonOSC as a Remote Script
# Only on Windows: you can install https://github.com/p-groarke/wsay/releases to get audio feedback
# Usage:
# Open Ableton Live
# Open the project you want to export
# Check your export settings and make sure that the export folder is set to "stemgen/input"
# Solo the tracks you want to export as stems
# Run `python3 ableton.py`
# Enter the name of the file
# Don't touch your computer until it's done
# Enjoy your stems!
import os
import platform
import sys
import subprocess
import live
import pyautogui
import time
import logging
from metadata import create_metadata_json, ableton_color_index_to_hex
# Settings
NAME = "track"
IS_RETINA = False
OS = "windows" if platform.system() == "Windows" else "macos"
PYTHON_EXEC = sys.executable if not None else "python3"
STEMS = []
# https://github.com/asweigart/pyautogui/issues/790
if OS == "macos":
import pyscreeze
import PIL
__PIL_TUPLE_VERSION = tuple(int(x) for x in PIL.__version__.split("."))
pyscreeze.PIL__version__ = __PIL_TUPLE_VERSION
def say(text):
if OS == "windows":
os.system("wsay " + text)
else:
os.system("say " + text)
return
# Switch to Ableton Live
def switch_to_ableton():
print("Looking for Ableton Live...")
if OS == "windows":
ableton = pyautogui.getWindowsWithTitle("Ableton Live")[0]
if ableton != None:
print("Found it!")
ableton.activate()
ableton.maximize()
return
pyautogui.keyDown("command")
pyautogui.press("tab")
time.sleep(1)
x, y = pyautogui.locateCenterOnScreen(
"screenshots/" + OS + "/logo.png", confidence=0.9
)
print("Found it!")
if IS_RETINA == True:
x = x / 2
y = y / 2
pyautogui.moveTo(x, y)
pyautogui.keyUp("command")
return
# Export a track based on a solo location
def export(track, position):
# Solo the track (if not exporting master)
if position != 0:
track.solo = True
# Get the track name and color
print(track.name)
name = track.name
color = ableton_color_index_to_hex[track.color_index]
STEMS.append({"color": color, "name": name})
# Export the track
if OS == "windows":
pyautogui.hotkey("ctrl", "shift", "r")
else:
pyautogui.hotkey("command", "shift", "r")
pyautogui.press("enter")
time.sleep(1)
pyautogui.typewrite(NAME + "." + str(position) + ".aif")
pyautogui.press("enter")
print("Exporting: " + NAME + "." + str(position) + ".aif")
# Wait for the export to finish
time.sleep(1)
while True:
location = pyautogui.locateOnScreen(
"screenshots/" + OS + "/export.png", confidence=0.9
)
if location != None:
print("Exporting...")
else:
print("Exported: " + NAME + "." + str(position) + ".aif")
break
# Unsolo the track (if not exporting master)
if position != 0:
track.solo = False
return
def main():
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s.%(msecs)03d: %(message)s",
datefmt="%H:%M:%S",
)
print("Welcome to Stemgen for Ableton Live!")
# Get file name
global NAME
NAME = pyautogui.prompt(
text="Enter the name of the file", title="Stemgen for Ableton Live", default=""
)
print("File name: " + NAME)
# Check Retina Display
if OS == "macos":
global IS_RETINA
if (
subprocess.call(
"system_profiler SPDisplaysDataType | grep 'Retina'", shell=True
)
== 0
):
IS_RETINA = True
else:
IS_RETINA = False
print("Retina Display: " + str(IS_RETINA))
# Get Ableton Live set
set = live.Set()
set.scan(scan_clip_names=True, scan_device=True)
switch_to_ableton()
time.sleep(1)
# Get the solo-ed tracks locations
soloed_tracks = []
for track in set.tracks:
if track.solo:
soloed_tracks.append(track)
if len(soloed_tracks) == 0:
print("You need to solo the tracks or groups you want to export as stems.")
say("Oops")
exit()
if len(soloed_tracks) < 4:
print("You need to solo at least 4 tracks or groups.")
say("Oops")
exit()
if len(soloed_tracks) > 8:
print("You can't create stems with more than 8 tracks or groups.")
say("Oops")
exit()
print("Found " + str(len(soloed_tracks)) + " solo-ed tracks.")
# Unsolo the tracks
for track in set.tracks:
if track.solo:
track.solo = False
# Export master
export(soloed_tracks, 0)
# Export stems
i = 1
for soloed_track in soloed_tracks:
export(soloed_track, i)
i += 1
# Switch to Terminal
if OS == "windows":
cmd = pyautogui.getWindowsWithTitle("Command Prompt") or pyautogui.getWindowsWithTitle("Windows PowerShell")
if cmd[0] != None:
cmd[0].activate()
else:
pyautogui.keyDown("command")
pyautogui.press("tab")
pyautogui.keyUp("command")
# Create metadata.part1.json and metadata.part2.json if double stems
if len(soloed_tracks) == 8:
create_metadata_json(STEMS[:4], "metadata.part1.json")
create_metadata_json(STEMS[4:], "metadata.part2.json")
# Create the stem file(s)
if OS == "windows":
subprocess.run([
PYTHON_EXEC,
"stem.py",
"-i",
"input/" + NAME + ".0.aif",
"-f",
"aac",
])
else:
subprocess.run([
PYTHON_EXEC,
"stem.py",
"-i",
"input/" + NAME + ".0.aif"
])
print("Done! Enjoy :)")
say("Done")
return
main()

stemgen/stem.py

Lines 1 to 263 in 1fe4ccb

#!/usr/bin/env python3
import argparse
import os
import platform
import shutil
import sys
import subprocess
from pathlib import Path
import time
import json
import unicodedata
from metadata import get_cover, get_metadata
LOGO = r"""
_____ _____ _____ _____
| __|_ _| __| |
|__ | | | | __| | | |
|_____| |_| |_____|_|_|_|
"""
SUPPORTED_FILES = [".wave", ".wav", ".aiff", ".aif", ".flac"]
REQUIRED_PACKAGES = ["ffmpeg"]
USAGE = f"""{LOGO}
Stem is a Stem file creator. Convert your multitrack into a stem (or two) and have fun with Traktor.
Usage: python3 stem.py -i [INPUT_PATH] -o [OUTPUT_PATH]
To create a stem, simply pass the master track in input.
Example: python3 stem.py -i track.0.wav
Supported input file format: {SUPPORTED_FILES}
Naming convention for input files: [TRACK_NAME].[TRACK_NUMBER].[FILE_EXTENSION]
TRACK_NAME should be identical for all files.
Please use 0 as the TRACK_NUMBER for the master file.
Example: 'track.0.wav' for the master file then 'track.1.wav' for the first stem, etc...
You can also use ableton.py to automatically create the stems from Ableton Live.
"""
VERSION = "1.0.0"
parser = argparse.ArgumentParser(
description=USAGE, formatter_class=argparse.RawTextHelpFormatter
)
parser.add_argument(
"-i", dest="INPUT_PATH", required=True, help="the path to the input file"
)
parser.add_argument(
"-o", dest="OUTPUT_PATH", default="output", help="the path to the output folder"
)
parser.add_argument("-f", dest="FORMAT", default="alac", help="aac or alac")
parser.add_argument("-v", "--version", action="version", version=VERSION)
args = parser.parse_args()
INPUT_PATH = args.INPUT_PATH
OUTPUT_PATH = args.OUTPUT_PATH
FORMAT = args.FORMAT
DIR = Path(__file__).parent.absolute()
PYTHON_EXEC = sys.executable if not None else "python3"
# CREATION
def create_stem():
print("Creating stem...")
cd_root()
is_double_stem = False
# Create another stem if we have more than 4 stems in the folder
if os.path.exists(f"{INPUT_FOLDER}/{FILE_NAME}.5{FILE_EXTENSION}"):
is_double_stem = True
if is_double_stem:
# Open tags.json and edit the title
with open(f"{OUTPUT_PATH}/{FILE_NAME}/tags.json", "r+") as f:
tags = json.load(f)
tags["title"] = f"{tags['title']} [part 1]"
f.seek(0)
json.dump(tags, f)
f.truncate()
stem_args = [PYTHON_EXEC, "ni-stem/ni-stem", "create", "-s"]
stem_args += [
f"{INPUT_FOLDER}/{FILE_NAME}.1{FILE_EXTENSION}",
f"{INPUT_FOLDER}/{FILE_NAME}.2{FILE_EXTENSION}",
f"{INPUT_FOLDER}/{FILE_NAME}.3{FILE_EXTENSION}",
f"{INPUT_FOLDER}/{FILE_NAME}.4{FILE_EXTENSION}",
]
stem_args += [
"-x",
INPUT_PATH,
"-t",
f"{OUTPUT_PATH}/{FILE_NAME}/tags.json",
"-m",
"metadata.part1.json",
"-f",
FORMAT,
"-o",
f"{OUTPUT_PATH}/{FILE_NAME}/{FILE_NAME} [part 1].stem.m4a",
]
subprocess.run(stem_args)
# Open tags.json and edit the title (again)
with open(f"{OUTPUT_PATH}/{FILE_NAME}/tags.json", "r+") as f:
tags = json.load(f)
tags["title"] = tags["title"].replace(" [part 1]", " [part 2]")
f.seek(0)
json.dump(tags, f)
f.truncate()
stem_args = [PYTHON_EXEC, "ni-stem/ni-stem", "create", "-s"]
stem_args += [
f"{INPUT_FOLDER}/{FILE_NAME}.5{FILE_EXTENSION}",
f"{INPUT_FOLDER}/{FILE_NAME}.6{FILE_EXTENSION}",
f"{INPUT_FOLDER}/{FILE_NAME}.7{FILE_EXTENSION}",
f"{INPUT_FOLDER}/{FILE_NAME}.8{FILE_EXTENSION}",
]
stem_args += [
"-x",
INPUT_PATH,
"-t",
f"{OUTPUT_PATH}/{FILE_NAME}/tags.json",
"-m",
"metadata.part2.json",
"-f",
FORMAT,
"-o",
f"{OUTPUT_PATH}/{FILE_NAME}/{FILE_NAME} [part 2].stem.m4a",
]
subprocess.run(stem_args)
else:
stem_args = [PYTHON_EXEC, "ni-stem/ni-stem", "create", "-s"]
stem_args += [
f"{INPUT_FOLDER}/{FILE_NAME}.1{FILE_EXTENSION}",
f"{INPUT_FOLDER}/{FILE_NAME}.2{FILE_EXTENSION}",
f"{INPUT_FOLDER}/{FILE_NAME}.3{FILE_EXTENSION}",
f"{INPUT_FOLDER}/{FILE_NAME}.4{FILE_EXTENSION}",
]
stem_args += [
"-x",
INPUT_PATH,
"-t",
f"{OUTPUT_PATH}/{FILE_NAME}/tags.json",
"-m",
"metadata.json",
"-f",
FORMAT,
"-o",
f"{OUTPUT_PATH}/{FILE_NAME}/{FILE_NAME}.stem.m4a",
]
subprocess.run(stem_args)
print("Done.")
# SETUP
def cd_root():
os.chdir(DIR)
def setup():
for package in REQUIRED_PACKAGES:
if not shutil.which(package):
print(f"Please install {package} before running Stem.")
sys.exit(2)
if not os.path.exists("ni-stem/ni-stem"):
print("Please install ni-stem before running Stem.")
sys.exit(2)
if not os.path.exists(OUTPUT_PATH):
os.mkdir(OUTPUT_PATH)
print("Output dir created.")
else:
print("Output dir already exists.")
global BASE_PATH, FILE_EXTENSION
BASE_PATH = os.path.basename(INPUT_PATH)
FILE_EXTENSION = os.path.splitext(BASE_PATH)[1]
if FILE_EXTENSION not in SUPPORTED_FILES:
print("Invalid input file format. File should be one of:", SUPPORTED_FILES)
sys.exit(1)
setup_file()
get_cover(FILE_EXTENSION, FILE_PATH, OUTPUT_PATH, FILE_NAME)
get_metadata(DIR, FILE_PATH, OUTPUT_PATH, FILE_NAME)
print("Ready!")
def run():
print(f"Creating a Stem file for {FILE_NAME}...")
create_stem()
clean_dir()
print("Success! Have fun :)")
def strip_accents(text):
text = unicodedata.normalize("NFKD", text)
text = text.encode("ascii", "ignore")
text = text.decode("utf-8")
return str(text)
def setup_file():
global FILE_NAME, INPUT_FOLDER, FILE_PATH
FILE_NAME = BASE_PATH.removesuffix(FILE_EXTENSION).removesuffix(".0")
INPUT_FOLDER = os.path.dirname(INPUT_PATH)
if os.path.exists(f"{OUTPUT_PATH}/{FILE_NAME}"):
print("Working dir already exists.")
else:
os.mkdir(f"{OUTPUT_PATH}/{FILE_NAME}")
print("Working dir created.")
shutil.copy(INPUT_PATH, f"{OUTPUT_PATH}/{FILE_NAME}/{FILE_NAME}{FILE_EXTENSION}")
FILE_PATH = f"{OUTPUT_PATH}/{FILE_NAME}/{FILE_NAME}{FILE_EXTENSION}"
print("Done.")
def clean_dir():
print("Cleaning...")
if platform.system() == "Windows":
time.sleep(5)
os.chdir(os.path.join(OUTPUT_PATH, FILE_NAME))
if os.path.isfile(f"{FILE_NAME}.stem.m4a"):
os.rename(f"{FILE_NAME}.stem.m4a", os.path.join("..", f"{FILE_NAME}.stem.m4a"))
if os.path.isfile(f"{FILE_NAME} [part 1].stem.m4a"):
os.rename(
f"{FILE_NAME} [part 1].stem.m4a",
os.path.join("..", f"{FILE_NAME} [part 1].stem.m4a"),
)
if os.path.isfile(f"{FILE_NAME} [part 2].stem.m4a"):
os.rename(
f"{FILE_NAME} [part 2].stem.m4a",
os.path.join("..", f"{FILE_NAME} [part 2].stem.m4a"),
)
shutil.rmtree(os.path.join(DIR, OUTPUT_PATH + "/" + FILE_NAME))
input_dir = os.path.join(DIR, INPUT_FOLDER)
for file in os.listdir(input_dir):
if file.endswith(".m4a"):
os.remove(os.path.join(input_dir, file))
print("Done.")
cd_root()
setup()
run()

I also found the following external resources that might be helpful:

Summaries of links found in the content:


Step 2: 🧐 Snippet Analysis

From looking through the relevant snippets, I decided to make the following modifications:

File Path Proposed Changes
metadata.py Refactor the code to follow PEP 8 style guide, simplify complex code, optimize any identified bottlenecks, and add appropriate error handling.
stemcopy.py Refactor the code to follow PEP 8 style guide, simplify complex code, optimize any identified bottlenecks, and add appropriate error handling.
stemgen.py Refactor the code to follow PEP 8 style guide, simplify complex code, optimize any identified bottlenecks, and add appropriate error handling.
ableton.py Refactor the code to follow PEP 8 style guide, simplify complex code, optimize any identified bottlenecks, and add appropriate error handling.
stem.py Refactor the code to follow PEP 8 style guide, simplify complex code, optimize any identified bottlenecks, and add appropriate error handling.

Step 3: 📝 Planning

I have created a plan for writing the pull request. I am now working my plan and coding the required changes to address this issue. Here is the planned pull request:

Refactor codebase to improve readability and Pythonic style
sweep/refactor-codebase

Description

This PR aims to improve the readability and Pythonic style of the codebase in the stemgen repository. The following files have been reviewed and refactored:

  • metadata.py
  • stemcopy.py
  • stemgen.py
  • ableton.py
  • stem.py

The changes made include:

  • Refactoring the code to follow the PEP 8 style guide consistently across all files.
  • Simplifying complex code and replacing it with more Pythonic alternatives where appropriate.
  • Optimizing identified bottlenecks to improve performance.
  • Adding appropriate error handling to enhance the robustness and reliability of the code.

Summary of Changes

  • In metadata.py, the code has been refactored to use context managers for file handling and to simplify complex if-else structures.
  • In stemcopy.py, the code has been refactored to simplify nested for loops and to use more descriptive variable names.
  • In stemgen.py, the code has been refactored to simplify complex if-else structures and to use more descriptive variable names.
  • In ableton.py, the code has been refactored to simplify complex if-else structures and to use more descriptive variable names.
  • In stem.py, the code has been refactored to simplify complex if-else structures and to use more descriptive variable names.

These changes aim to improve the readability, maintainability, and performance of the codebase, while also ensuring a consistent coding style across all files.


Step 4: ⌨️ Coding

I have finished coding the issue. I am now reviewing it for completeness.


Step 5: 🔁 Code Review

Success! 🚀


I'm a bot that handles simple bugs and feature requests but I might make mistakes. Please be kind!
Join Our Discord

from stemgen.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.