Comments (1)
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.
- Install Sweep Configs: Pull Request
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.
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", | |
} |
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.") |
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() |
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() |
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() |
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)
- ni-stem errors on linux HOT 1
- [Feature Request] GUI HOT 3
- [Feature Request] Windows Support HOT 10
- [Feature Request] MP3 support HOT 8
- [Note] ALAC stems can't be read on Windows within Traktor HOT 3
- Sweep(slow): Review the codebase and make suggestions to make the code more readable and Pythonic HOT 1
- Sweep: Refactor ableton.py to be cleaner, extracting any methods you can into separate files if that makes sense HOT 1
- Sweep: Add comments and docstrings to ableton.py. Clarify each method and global HOT 1
- Mutagen missing in installation instructions HOT 1
- Converting multiple songs at once? HOT 3
- [Feature request] Extract only vocals and instrumental HOT 2
- Error opening input: No such file or directory HOT 8
- How to know if qaac encoder is used on Windows? HOT 2
- [Feature request] Custom name & color for stem HOT 2
- [Bug] Windows cleaning step failing HOT 5
- [Feature Request] Following the Stem File Specification HOT 1
- [Bug] ALAC stems can't be read on Windows within Traktor HOT 4
- Google Colab version? HOT 1
- Extract audio files from STEM file? HOT 6
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from stemgen.