Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

enhancements/enhance-code #2

Merged
merged 4 commits into from
Sep 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -161,5 +161,10 @@ cython_debug/
.idea/


removing_music.log
./removing_music.log

/input/*
!/input/.gitkeep

/output/*
!/output/.gitkeep
File renamed without changes.
141 changes: 72 additions & 69 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,69 +4,78 @@
from itertools import chain
from pathlib import Path

logging.basicConfig(
encoding='utf-8',
format='%(asctime)s - %(levelname)s - %(message)s',
level=logging.INFO,
handlers=[
logging.FileHandler("removing_music.log", encoding='utf-8'),
logging.StreamHandler(sys.stdout)
]
)

def process_file(original_video: Path):

def cleanup(no_music_sound: Path, original_video: Path) -> None:
"""
removes music from videos in input folder
:return: True if there's no file to process, which marks the end of the mass process
delete Intermediate audio files and original video
"""
try:
logging.info(f'Processing file "{original_video.name}"')
logging.info(f'"{original_video.name}": deleting original video...')
original_video.unlink()
logging.info(f'"{original_video.name}": original video deleted successfully')
logging.info(f'"{original_video.name}": deleting vocal sound...')
for file in [file for file in no_music_sound.parent.iterdir()]:
file.unlink()
no_music_sound.parent.rmdir()
logging.info(f'"{original_video.name}": vocal sound deleted successfully')

no_music_sound: Path = Path(f'separated/htdemucs/{original_video.stem}/vocals.mp3')
if no_music_sound.exists():
logging.info(f'"{original_video.name}": vocal already separated, skipping separating vocal')
else:
logging.info(f'"{original_video.name}": start separating vocal...')
# TODO: export vocal sound as `wav` extension
remove_music_command: list[str] = ['pipenv', 'run', 'demucs', '--mp3', '--two-stems=vocals',
original_video.absolute()]
completed_process = subprocess.run(remove_music_command, encoding='utf-8', text=True,
capture_output=True,
check=True)
# raise exception if vocal sound is not created, exception raised manually
# because demucs command doesn't return error code
if not no_music_sound.exists():
raise subprocess.CalledProcessError(returncode=1,
cmd=completed_process.args,
output=completed_process.stdout,
stderr=completed_process.stderr)
logging.info(f'"{original_video.name}": vocal seperated successfully')
# 3- replace the sound of the video with the no music version,
# and save the new video in folder 'output'
# if not already done
no_music_video: Path = Path(f'output/{original_video.name}')
if no_music_video.exists():
logging.info(
f'"{original_video.name}": a video with no music already exists, skipping creating a new video')
else:
logging.info(f'"{original_video.name}": creating a new video with no music...')
create_no_music_video_command: list[str] = ['ffmpeg', '-i', original_video.absolute(),
'-i', no_music_sound.absolute(),
'-c:v', 'copy', '-map', '0:v:0', '-map', '1:a:0',
no_music_video.absolute()]
subprocess.run(create_no_music_video_command, encoding='utf-8', text=True, capture_output=True, check=True)
logging.info(f'"{original_video.name}": a new video with no music has been created')
# clean up
logging.info(f'"{original_video.name}": deleting original video...')
original_video.unlink()
logging.info(f'"{original_video.name}": original video deleted successfully')
logging.info(f'"{original_video.name}": deleting vocal sound...')
for file in [file for file in no_music_sound.parent.iterdir()]:
file.unlink()
no_music_sound.parent.rmdir()
logging.info(f'"{original_video.name}": vocal sound deleted successfully')
except subprocess.CalledProcessError as error:
logging.exception(error.stderr)
raise error
except KeyboardInterrupt as error:
logging.exception('the process was stopped by user')
raise error
except BaseException as error:
logging.exception('Unexpected error happened')
raise error

def create_video_without_music(no_music_sound: Path, original_video: Path) -> Path:
"""
replace the sound of the video with the no music version,
and save the new video in folder 'output'
if not already done
"""
no_music_video: Path = Path(f'output/{original_video.name}')
if no_music_video.exists():
logging.info(
f'"{original_video.name}": a video with no music already exists, skipping creating a new video')
else:
logging.info(f'"{original_video.name}": Processing finished')
logging.info(f'"{original_video.name}": creating a new video with no music...')
create_no_music_video_command: list[str] = ['ffmpeg', '-i', original_video.absolute(),
'-i', no_music_sound.absolute(),
'-c:v', 'copy', '-map', '0:v:0', '-map', '1:a:0',
no_music_video.absolute()]
subprocess.run(create_no_music_video_command, encoding='utf-8', text=True, capture_output=True, check=True)
logging.info(f'"{original_video.name}": a new video with no music has been created')
return no_music_video


def separate_vocal(original_video: Path) -> Path:
"""
separate vocal from music using demucs machine learning algorithm

:exception subprocess.CalledProcessError
"""
no_music_sound: Path = Path(f'separated/htdemucs/{original_video.stem}/vocals.mp3')
if no_music_sound.exists():
logging.info(f'"{original_video.name}": vocal already separated, skipping separating vocal')
else:
logging.info(f'"{original_video.name}": start separating vocal...')
# TODO: export vocal sound as `wav` extension
remove_music_command: list[str] = ['pipenv', 'run', 'demucs', '--mp3', '--two-stems=vocals',
original_video.absolute()]
completed_process = subprocess.run(remove_music_command, encoding='utf-8', text=True,
capture_output=True,
check=True)
# raise exception if vocal sound is not created, exception raised manually
# because demucs command doesn't return error code
if not no_music_sound.exists():
raise subprocess.CalledProcessError(returncode=1,
cmd=completed_process.args,
output=completed_process.stdout,
stderr=completed_process.stderr)
logging.info(f'"{original_video.name}": vocal seperated successfully')
return no_music_sound


def get_original_video() -> Path | None:
Expand All @@ -79,20 +88,14 @@ def get_original_video() -> Path | None:


def main() -> None:
logging.basicConfig(
encoding='utf-8',
format='%(asctime)s - %(levelname)s - %(message)s',
level=logging.INFO,
handlers=[
logging.FileHandler("removing_music.log", encoding='utf-8'),
logging.StreamHandler(sys.stdout)
]
)

logging.info('Mass processing started')

while original_video := get_original_video():
process_file(original_video)
logging.info(f'Processing file "{original_video.name}"')
no_music_sound: Path = separate_vocal(original_video)
create_video_without_music(no_music_sound, original_video)
cleanup(no_music_sound, original_video)
logging.info(f'"{original_video.name}": Processing finished')
else:
logging.info("There's no file to process")

Expand Down
Empty file added output/.gitkeep
Empty file.