├── video-remixer
├── requirements.txt
├── README.md
└── main.py
├── README.md
├── LICENSE
└── .gitignore
/video-remixer/requirements.txt:
--------------------------------------------------------------------------------
1 | yt-dlp>=2023.11.16
2 | moviepy==1.0.3
3 | nendo>=0.1.1
4 | nendo-plugin-classify-core>=0.1.1
5 | nendo-plugin-musicgen>=0.1.1
6 | nendo-plugin-fx-core>=0.1.0
7 | nendo-plugin-stemify-demucs>=0.1.0
8 | audiocraft @ git+https://github.com/okio-ai/audiocraft
9 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Nendo Example Apps
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | ---
10 |
11 | 
12 | [](https://twitter.com/okio_ai) [](https://discord.gg/XpkUsjwXTp)
13 |
14 |
15 | This repository contains example apps built with the nendo core.
16 |
17 |
18 | ## Apps
19 |
20 | - [Video Remixer](video-remixer/README.md)
21 |
22 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Okio
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | share/python-wheels/
24 | *.egg-info/
25 | .installed.cfg
26 | *.egg
27 | MANIFEST
28 |
29 | # PyInstaller
30 | # Usually these files are written by a python script from a template
31 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
32 | *.manifest
33 | *.spec
34 |
35 | # Installer logs
36 | pip-log.txt
37 | pip-delete-this-directory.txt
38 |
39 | # Unit test / coverage reports
40 | htmlcov/
41 | .tox/
42 | .nox/
43 | .coverage
44 | .coverage.*
45 | .cache
46 | nosetests.xml
47 | coverage.xml
48 | *.cover
49 | *.py,cover
50 | .hypothesis/
51 | .pytest_cache/
52 | cover/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | .pybuilder/
76 | target/
77 |
78 | # Jupyter Notebook
79 | .ipynb_checkpoints
80 |
81 | # IPython
82 | profile_default/
83 | ipython_config.py
84 |
85 | # pyenv
86 | # For a library or package, you might want to ignore these files since the code is
87 | # intended to run in multiple environments; otherwise, check them in:
88 | # .python-version
89 |
90 | # pipenv
91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
94 | # install all needed dependencies.
95 | #Pipfile.lock
96 |
97 | # poetry
98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
99 | # This is especially recommended for binary packages to ensure reproducibility, and is more
100 | # commonly ignored for libraries.
101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
102 | #poetry.lock
103 |
104 | # pdm
105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
106 | #pdm.lock
107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
108 | # in version control.
109 | # https://pdm.fming.dev/#use-with-ide
110 | .pdm.toml
111 |
112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
113 | __pypackages__/
114 |
115 | # Celery stuff
116 | celerybeat-schedule
117 | celerybeat.pid
118 |
119 | # SageMath parsed files
120 | *.sage.py
121 |
122 | # Environments
123 | .env
124 | .venv
125 | env/
126 | venv/
127 | ENV/
128 | env.bak/
129 | venv.bak/
130 |
131 | # Spyder project settings
132 | .spyderproject
133 | .spyproject
134 |
135 | # Rope project settings
136 | .ropeproject
137 |
138 | # mkdocs documentation
139 | /site
140 |
141 | # mypy
142 | .mypy_cache/
143 | .dmypy.json
144 | dmypy.json
145 |
146 | # Pyre type checker
147 | .pyre/
148 |
149 | # pytype static type analyzer
150 | .pytype/
151 |
152 | # Cython debug symbols
153 | cython_debug/
154 |
155 | # PyCharm
156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
158 | # and can be added to the global gitignore or merged into this file. For a more nuclear
159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder.
160 | .idea/
161 | library/
162 | *.mp4
163 | *.mp3
--------------------------------------------------------------------------------
/video-remixer/README.md:
--------------------------------------------------------------------------------
1 | # Nendo Video Remixer
2 |
3 | The video remixer is an App based on a chain of different nendo plugins.
4 | It allows you to generate music videos remixed with AI audio from youtube links.
5 |
6 | **⚠ Important Disclaimer:**
7 | The video/audio content created using Remix are intended solely for research and educational purposes.
8 | We assume no responsibility for their use or misuse.
9 | We urge users to employ this tool responsibly and ethically.
10 |
11 | Try it in colab:
12 |
13 |
14 |
15 |
16 | Check out an example:
17 |
18 | https://github.com/okio-ai/nendo-example-apps/assets/17432850/061a07a2-64ac-4647-872a-8b6f1370fab8
19 |
20 |
21 | ## Features
22 |
23 | - Generate music videos of remixed audio from youtube links
24 | - Smells Like Teen Spirit (Dub Version)? Shake It Off (Trash Metal Remix)? Yes please!
25 | - Endless creativity, inspiration and memes
26 |
27 | ## Requirements
28 |
29 | As this app uses the [Nendo musicgen plugin](https://github.com/okio-ai/nendo_plugin_musicgen), you have to install Pytorch 2.0.0 or higher first:
30 |
31 | `pip install "torch>=2.0"`
32 |
33 | > Note: On Mac OSX, the instructions for installing pytorch differ. Please refer to the [pytorch installation instructions](https://pytorch.org/get-started/locally/).
34 |
35 | ## Installation
36 |
37 | Install nendo and the required plugins via `requirements.txt`:
38 |
39 | ```bash
40 | git clone https://github.com/okio-ai/nendo-example-apps.git
41 | cd nendo-example-apps/video-remixer
42 | pip install -r requirements.txt
43 | ```
44 |
45 | ## Usage
46 |
47 | Create a video remix from a youtube link:
48 |
49 | ```bash
50 | python main.py -l https://www.youtube.com/watch\?v\=OPf0YbXqDm0 -p "jazz bebop" -o "uptown_jazz.mp4"
51 | ```
52 |
53 | Create an audio-only remix from a file:
54 |
55 | ```bash
56 | python main.py -f /path/to/file.mp3 -p "jazz bebop" -oa "jazz_remix.mp3"
57 | ```
58 |
59 | ## Nendo Functionality
60 |
61 | ### Plugins in use:
62 |
63 | - [nendo-plugin-classify-core](https://github.com/okio-ai/nendo_plugin_classify_core)
64 | - [nendo-plugin-stemify-demucs](https://github.com/okio-ai/nendo_plugin_stemify_demucs)
65 | - [nendo-plugin-musicgen](https://github.com/okio-ai/nendo_plugin_musicgen)
66 | - [nendo-plugin-fx-core](https://github.com/okio-ai/nendo_plugin_fx_core)
67 |
68 |
69 | The full audio remixing code in this example takes up about 30 lines of code,
70 | to do MIR, stemification, audio generation and effects processing.
71 | Here is the full example from [main.py](main.py):
72 |
73 | ```python
74 | def run_nendo_plugin_chain(
75 | path_to_audio: str, prompt: str, vocal_gain: float, model: str,
76 | ) -> str:
77 | nd = Nendo(
78 | config=NendoConfig(
79 | plugins=[
80 | "nendo_plugin_stemify_demucs",
81 | "nendo_plugin_musicgen",
82 | "nendo_plugin_classify_core",
83 | "nendo_plugin_fx_core",
84 | ],
85 | )
86 | )
87 |
88 | track = nd.library.add_track(file_path=path_to_audio)
89 |
90 | track = nd.plugins.classify_core(track=track)
91 | bpm = int(float(track.get_plugin_data("nendo_plugin_classify_core", "tempo")))
92 | key = track.get_plugin_data("nendo_plugin_classify_core", "key")
93 | scale = track.get_plugin_data("nendo_plugin_classify_core", "scale")
94 |
95 | stems = nd.plugins.stemify_demucs(
96 | track=track, model="mdx_extra", stem_types=["vocals", "no_vocals"]
97 | )
98 | vocals, background = stems[0], stems[1]
99 |
100 | remixed_bg = nd.plugins.musicgen(
101 | track=background,
102 | prompt=prompt,
103 | bpm=bpm,
104 | key=key,
105 | scale=scale,
106 | conditioning_length=2,
107 | n_samples=1,
108 | model=model,
109 | )[0]
110 |
111 | vocals = nd.plugins.fx_core.highpass(track=vocals, cutoff_frequency_hz=100)
112 | vocals = nd.plugins.fx_core.reverb(track=vocals, wet_level=0.2, dry_level=0.8)
113 |
114 | remix = remixed_bg.overlay(vocals, gain_db=vocal_gain)
115 | return remix.resource.src
116 | ```
117 |
118 |
--------------------------------------------------------------------------------
/video-remixer/main.py:
--------------------------------------------------------------------------------
1 | import yt_dlp
2 | import argparse
3 | import subprocess
4 | from moviepy.editor import VideoFileClip, AudioFileClip
5 | import os
6 | from nendo import NendoConfig, Nendo
7 |
8 |
9 | def yt_download(
10 | link: str,
11 | output_path: str,
12 | start_time: str,
13 | end_time: str,
14 | ) -> str:
15 | start_time = f"00:{start_time}.00"
16 | end_time = f"00:{end_time}.00"
17 | ydl_opts = {
18 | "format": "bestvideo+bestaudio/best",
19 | "outtmpl": output_path,
20 | "nocheckcertificate": True,
21 | "ignoreerrors": True,
22 | "no_warnings": True,
23 | "quiet": True,
24 | "postprocessor_args": [
25 | "-ss",
26 | start_time,
27 | "-to",
28 | end_time,
29 | ],
30 | }
31 | with yt_dlp.YoutubeDL(ydl_opts) as ydl:
32 | ydl.download([link])
33 |
34 | return f"{output_path}.webm"
35 |
36 |
37 | def remix_video(video_path: str, audio_path: str, output_path: str) -> str:
38 | video = VideoFileClip(video_path)
39 | audio = AudioFileClip(audio_path)
40 |
41 | video_with_audio = video.set_audio(audio)
42 | video_with_audio.write_videofile(
43 | output_path,
44 | codec="libx264",
45 | audio_codec="aac",
46 | temp_audiofile="temp-audio.m4a",
47 | remove_temp=True,
48 | )
49 | return output_path
50 |
51 |
52 | def extract_audio(video_path: str, output_path: str) -> str:
53 | cmd = [
54 | "ffmpeg",
55 | "-y",
56 | "-i",
57 | video_path, # Input video file
58 | "-q:a",
59 | "0", # Best audio quality
60 | "-map",
61 | "a", # Map to audio stream
62 | "-vn", # No video
63 | output_path, # Output audio file
64 | ]
65 | subprocess.check_call(cmd)
66 | return output_path
67 |
68 |
69 | def run_nendo_plugin_chain(
70 | path_to_audio: str,
71 | prompt: str,
72 | vocal_gain: float,
73 | model: str,
74 | conditioning_length: float,
75 | prompt_strength: float,
76 | output_audio_path: str,
77 | ) -> str:
78 | nd = Nendo(
79 | config=NendoConfig(
80 | log_level="warning",
81 | plugins=[
82 | "nendo_plugin_stemify_demucs",
83 | "nendo_plugin_musicgen",
84 | "nendo_plugin_classify_core",
85 | "nendo_plugin_fx_core",
86 | ],
87 | )
88 | )
89 |
90 | track = nd.library.add_track(file_path=path_to_audio)
91 |
92 | track = nd.plugins.classify_core(track=track)
93 | bpm = int(float(track.get_plugin_value("tempo")))
94 | key = track.get_plugin_value("key")
95 | scale = track.get_plugin_value("scale")
96 |
97 | stems = nd.plugins.stemify_demucs(
98 | track=track, model="mdx_extra", stem_types=["vocals", "no_vocals"],
99 | )
100 | vocals, background = stems[0], stems[1]
101 |
102 | remixed_bg = nd.plugins.musicgen(
103 | track=background,
104 | prompt=prompt,
105 | bpm=bpm,
106 | key=key,
107 | scale=scale,
108 | conditioning_length=conditioning_length,
109 | cfg_coef=prompt_strength,
110 | n_samples=1,
111 | model=model,
112 | )[0]
113 |
114 | vocals = nd.plugins.fx_core.highpass(track=vocals, cutoff_frequency_hz=100)
115 | vocals = nd.plugins.fx_core.reverb(track=vocals, wet_level=0.2, dry_level=0.8)
116 |
117 | remix = remixed_bg.overlay(vocals, gain_db=vocal_gain)
118 | remix.export(output_audio_path)
119 | return output_audio_path
120 |
121 |
122 | def parse_args() -> argparse.Namespace:
123 | parser = argparse.ArgumentParser()
124 | parser.add_argument(
125 | "-l",
126 | "--link",
127 | type=str,
128 | help="Youtube link to download or audio file path for the remix.",
129 | )
130 | parser.add_argument(
131 | "-p", "--prompt", type=str, required=True, help="Prompt to use for the remix."
132 | )
133 | parser.add_argument("-s", "--start-time", type=str, default="00:00")
134 | parser.add_argument("-e", "--end-time", type=str, default="00:30")
135 | parser.add_argument("-v", "--vocal-gain", type=float, default=0.5)
136 | parser.add_argument(
137 | "-m", "--model", type=str, default="facebook/musicgen-stereo-medium"
138 | )
139 | parser.add_argument("-c", "--conditioning-length", type=float, default=5)
140 | parser.add_argument("-ps", "--prompt-strength", type=float, default=3.5)
141 | parser.add_argument(
142 | "-o",
143 | "--output-video-path",
144 | type=str,
145 | default="output.mp4",
146 | help="Output path for the remix.",
147 | )
148 | parser.add_argument(
149 | "-oa",
150 | "--output-audio-path",
151 | type=str,
152 | default="remixed_audio.mp3",
153 | help="Output path for the audio of the remix.",
154 | )
155 | return parser.parse_args()
156 |
157 |
158 | def main(
159 | link_or_path: str,
160 | start_time: str,
161 | end_time: str,
162 | prompt: str,
163 | output_video_path: str,
164 | output_audio_path: str,
165 | vocal_gain: float,
166 | conditioning_length: float,
167 | prompt_strength: float,
168 | model: str,
169 | ):
170 | is_link = link_or_path.startswith("https://")
171 | if is_link:
172 | print("Downloading video...")
173 | video_path = yt_download(
174 | link=link_or_path, start_time=start_time, end_time=end_time, output_path="video"
175 | )
176 | audio_path = extract_audio(video_path, output_path="audio.mp3")
177 | else:
178 | print("Using local audio file...")
179 | audio_path = link_or_path
180 |
181 | print("Running nendo plugin chain...")
182 | run_nendo_plugin_chain(
183 | audio_path, prompt,
184 | vocal_gain=vocal_gain,
185 | model=model,
186 | conditioning_length=conditioning_length,
187 | prompt_strength=prompt_strength,
188 | output_audio_path=output_audio_path
189 | )
190 |
191 | if is_link:
192 | print("Remixing audio and video...")
193 | remix_video(video_path, output_audio_path, output_path=output_video_path)
194 | os.remove(video_path)
195 | os.remove(audio_path)
196 |
197 | print("Done!")
198 |
199 |
200 | if __name__ == "__main__":
201 | args = parse_args()
202 | main(
203 | link_or_path=args.link,
204 | prompt=args.prompt,
205 | start_time=args.start_time,
206 | end_time=args.end_time,
207 | output_video_path=args.output_video_path,
208 | output_audio_path=args.output_audio_path,
209 | conditioning_length=args.conditioning_length,
210 | prompt_strength=args.prompt_strength,
211 | vocal_gain=args.vocal_gain,
212 | model=args.model,
213 | )
214 |
--------------------------------------------------------------------------------