-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 0148cf3
Showing
19 changed files
with
835 additions
and
0 deletions.
There are no files selected for viewing
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
# TrackMagic: YouTube downloader for personal use | ||
### Why do I need this thing | ||
Idk maybe u want to download a few videos to keep them or smth | ||
### How it work?? | ||
Open the `TrackMagic` file and follow the instructions by giving a video/playlist link and the application will start downloading from youtube. | ||
The output folders are `./Video/` and `./Audio/` | ||
|
||
## Setup this thing | ||
### First of all, what do you need? | ||
- You need a working `Python interpreter` | ||
- And you need `ffmpeg` | ||
|
||
To setup literally just open `TrackMagic` and it will setup itself. | ||
### You need Python | ||
If you're met with the message \ | ||
`'py' is not recognized as an internal or external command,` \ | ||
`operable program or batch file.` \ | ||
Install a [Python Interpreter](https://www.python.org/downloads/) | ||
### If that doesn't work, then you might need be missing `pip` from the PATH | ||
Maybe you're asking questions like... wtf is pip and why are you screaming PATH. \ | ||
Bro, just don't question it.. | ||
### How to pip if has no pip | ||
1. Install python if not already installed. \ | ||
1.1 If you don't have python and you're installing, then enable the option `Add to %PATH%` and you will get the pip. | ||
2. Now if you already had python and pip is missing, head over to windows search and type `Edit the system enviroment variables`. | ||
3. Click the first result that shows up and in the next window select `Enviroment Variables...`. | ||
4. Another window should appear, now just click `Path` or `PATH` in the `User variables for X` section. | ||
5. Select the option `Edit` and another window should appear. | ||
6. Now select `New` and we will have to add the path to the directory that contains `pip.exe`. \ | ||
6.1 Head over to `C:\Users\%USERNAME%\AppData\Local\Programs\Python`. \ | ||
6.2 Find the python version that you just installed and enter the `Scripts` folder. \ | ||
6.3 Copy the path to this directory, should look something like `C:\Users\%USERNAME%\AppData\Local\Programs\Python\PythonYOURVERSION\Scripts` | ||
7. Now you should have pip added, you can exit all the windows by clicking `Ok`. | ||
### If that doesn't work, then you might need be missing `ffmpeg` from the PATH | ||
Basically download [ffmpeg for windows](https://www.gyan.dev/ffmpeg/builds/) and add it to path like in the pip tutorial above ^ ^ ^ \ | ||
It's recommended to download the `ffmpeg full` version(haven't tested the essentials version) | ||
### If it still doesn't work, it's ggs | ||
Well whatever it is you can try messaging the creator with the logs files (good luck) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
PyGetWindow | ||
pywin32 | ||
yt-dlp | ||
Pillow | ||
ffmpy |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
@echo off | ||
cls | ||
py TrackMagic.py | ||
pause |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
echo off | ||
cls | ||
pip uninstall -r ..\requirements.txt -y | ||
echo Everything has been cleaned up! | ||
pause |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
from datetime import datetime | ||
from time import time | ||
import traceback | ||
import os | ||
|
||
LOGS_DIRECTORY = '../logs/' | ||
|
||
|
||
class Logger: | ||
log_file_extension = 'txt' | ||
|
||
def __init__(self, log_file_name: str, log_to_file: bool = False): | ||
self.log_file_name: str = Logger.rename_file_if_exists(log_file_name, Logger.log_file_extension) | ||
self.log_to_file: bool = log_to_file | ||
|
||
def set_log_to_file(self, state: bool) -> None: | ||
self.log_to_file = state | ||
|
||
def run(self, func) -> None: | ||
try: | ||
func() | ||
except Exception as e: | ||
error_string = traceback.format_exc() | ||
self.log('ERROR', f'An error has occurred: {e}\nLog written to {self.log_file_name}\n{error_string}') | ||
except KeyboardInterrupt: | ||
self.log('EXIT', 'Closed via keyboard interrupt') | ||
|
||
def log(self, subsection: str = 'NORMAL', text: str = '') -> None: | ||
log_text = f'[{datetime.now().strftime("%m/%d/%Y %H:%M:%S")}][{subsection}] {text}\n' | ||
print(log_text, end='') | ||
|
||
if self.log_to_file: | ||
if not os.path.exists(LOGS_DIRECTORY): | ||
os.mkdir(LOGS_DIRECTORY) | ||
with open(f'{LOGS_DIRECTORY}{self.log_file_name}', 'a', encoding='utf-8') as log_file: | ||
log_file.write(log_text) | ||
|
||
@staticmethod | ||
def rename_file_if_exists(file_name: str, file_extension: str) -> str: | ||
file_number = 0 | ||
search_file = '.'.join([file_name, file_extension]) | ||
while os.path.exists(search_file): | ||
file_number += 1 | ||
search_file = f'{file_name} {file_number}{file_extension}' | ||
return search_file | ||
|
||
|
||
class Launcher: | ||
def __init__(self): | ||
self.startup_date_timestamp: datetime = datetime.now() | ||
self.startup_timestamp: float = time() | ||
|
||
self.startup_timestamp_int: int = int(self.startup_timestamp) | ||
self.logger = Logger(f'{datetime.now().strftime("%Y-%m-%d %H-%M-%S")}') | ||
|
||
def launch(self, launch_main) -> None: | ||
self.logger.run(launch_main) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
from classes.static.Configuration import Configuration | ||
from classes.static.FileExplorer import FileExplorer | ||
from classes.Logger import Logger | ||
import os | ||
|
||
|
||
class Records: | ||
def __init__(self, logger: Logger): | ||
self.logger = logger | ||
self.records: dict = {} | ||
|
||
def check_integrity(self) -> None: | ||
for record in self.records.values(): | ||
record.check_file_integrity() | ||
self.save_records() | ||
|
||
def load_records(self) -> None: | ||
content = FileExplorer.read_or_create_empty(Configuration.records_file) | ||
records_unparsed = [attributes for attributes in content.split(Configuration.record_seperator)] | ||
|
||
for attributes in records_unparsed: | ||
record = Record() | ||
if record.parse(attributes): | ||
self.records[record.video_id] = record | ||
|
||
self.logger.log('Records', f'Loaded {len(self.records)} record/s') | ||
|
||
def save_records(self) -> None: | ||
new_content = '' | ||
for record in self.records.values(): | ||
new_content += record.serialize() | ||
|
||
with open(Configuration.records_file, 'w', encoding='utf-8') as f: | ||
f.write(new_content) | ||
|
||
def get_record(self, video_id: str): | ||
return self.records.get(video_id, None) | ||
|
||
def update_record(self, record): | ||
video_id: str = record.video_id | ||
self.records[video_id] = record | ||
|
||
|
||
class Record: | ||
def __init__(self): | ||
self.video_id: str = None # type: ignore | ||
self.title: str = None # type: ignore | ||
self.length: int = None # type: ignore | ||
self.video: str = None # type: ignore | ||
self.video_stream: str = None # type: ignore | ||
self.audio: str = None # type: ignore | ||
self.audio_stream: str = None # type: ignore | ||
self.thumbnail: str = None # type: ignore | ||
|
||
def check_file_integrity(self) -> bool: | ||
changed_data = False | ||
if self.video is not None and not os.path.exists(self.video): | ||
self.video = None | ||
self.video_stream = None | ||
changed_data = True | ||
|
||
if self.audio is not None and not os.path.exists(self.audio): | ||
self.audio = None | ||
self.audio_stream = None | ||
changed_data = True | ||
|
||
return changed_data | ||
|
||
def serialize(self) -> str: | ||
return '\n'.join(f'{key}={item}' for key, item in self.__dict__.items()) + '\n' + Configuration.record_seperator | ||
|
||
def parse(self, unparsed_attributes: str) -> bool: | ||
attributes = [attribute.split('=') for attribute in unparsed_attributes.splitlines(keepends=False)] | ||
|
||
if not attributes: | ||
return False # Drop bad records (empty lines, corrupted, etc.) | ||
|
||
for key, value in attributes: | ||
if value == 'None': | ||
value = None | ||
elif value == 'False': | ||
value = False | ||
elif value == 'True': | ||
value = True | ||
elif key == 'length' and value is not None: | ||
value = int(value) | ||
setattr(self, key, value) | ||
return True | ||
|
||
def __repr__(self): | ||
return str(self.__dict__) |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
class Configuration: | ||
records_file: str = '../records' | ||
record_seperator: str = '-= End of record =-\n' | ||
video_dir: str = '../Videos/' | ||
audio_dir: str = '../Audio/' | ||
temp_dir: str = '../temp/' | ||
audio_ext: str = '.m4a' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
from classes.static.Configuration import Configuration | ||
import yt_dlp as youtube | ||
import os | ||
|
||
|
||
class Downloader: | ||
stream_filepath: str = None # type: ignore | ||
stream_filename: str = None # type: ignore | ||
|
||
@staticmethod | ||
def _set_stream_filepath_hook(data: dict) -> None: | ||
if data['status'] == 'finished': | ||
Downloader.stream_filepath = data['info_dict']['filename'] | ||
Downloader.stream_filename = os.path.basename(Downloader.stream_filepath) | ||
|
||
@staticmethod | ||
def download_stream(format_id: str, video_url: str) -> str: | ||
downloader_options = {'outtmpl': f'{Configuration.temp_dir}%(title)s.%(ext)s', | ||
'progress_hooks': [Downloader._set_stream_filepath_hook], | ||
'format': format_id, | ||
'noplaylist': True, | ||
'quiet': True} | ||
|
||
with youtube.YoutubeDL(downloader_options) as ydl: | ||
ydl.download(video_url) | ||
|
||
return Downloader.stream_filepath |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
from classes.static.Configuration import Configuration | ||
from classes.Logger import Logger | ||
import subprocess | ||
import ffmpy | ||
import os | ||
|
||
|
||
class FFmpeg: | ||
logger: Logger = None # type: ignore | ||
|
||
@staticmethod | ||
def init(logger: Logger) -> None: | ||
FFmpeg.logger = logger | ||
|
||
@staticmethod | ||
def check_ffmpeg(): | ||
try: | ||
ffmpeg_env = os.getenv('FFMPEG_BINARY', 'ffmpeg') | ||
exit_code = subprocess.call([ffmpeg_env, '-version', '-loglevel panic'], stdout=subprocess.DEVNULL) | ||
except FileNotFoundError: | ||
FFmpeg.logger.log('FFmpeg', 'FFmpeg was not found on your system') | ||
FFmpeg.logger.log('FFmpeg', 'Either you\'ve never downloaded FFmpeg') | ||
FFmpeg.logger.log('FFmpeg', ' or you haven\'t added it to PATH') | ||
FFmpeg.logger.log('FFmpeg', '') | ||
FFmpeg.logger.log('FFmpeg', 'Check readme.md for more information') | ||
exit(-1) | ||
|
||
# @staticmethod | ||
# def process_video_stream(video_stream, progressive: bool): | ||
# video_at = video_stream.download(Configuration.temp_video_dir) | ||
# video_file = os.path.basename(video_at) | ||
# video_filename, video_ext = os.path.splitext(video_file) | ||
# if not progressive: | ||
# return video_at | ||
# | ||
# video_path = f'{Configuration.video_dir}{video_filename}.mp4' | ||
# ffmpeg = ffmpy.FFmpeg(inputs={video_at: None}, | ||
# outputs={video_path: None}, | ||
# global_options='-y -loglevel warning') | ||
# ffmpeg.run() | ||
# os.remove(video_at) | ||
# return video_path | ||
|
||
@staticmethod | ||
def process_audio_from_video(video_path: str): | ||
video_directory = os.path.abspath(video_path) | ||
video_file = os.path.basename(video_path) | ||
video_filename, _ = os.path.splitext(video_file) | ||
|
||
audio_path = os.path.join(video_directory, f'{video_filename}.{Configuration.audio_ext}') | ||
|
||
ffmpeg = ffmpy.FFmpeg(inputs={video_path: None}, | ||
outputs={audio_path: None}, | ||
global_options='-y -loglevel warning') | ||
ffmpeg.run() | ||
return audio_path | ||
|
||
@staticmethod | ||
def convert_audio(audio_path: str): | ||
audio_directory = os.path.dirname(audio_path) | ||
audio_file = os.path.basename(audio_path) | ||
audio_filename, audio_extension = os.path.splitext(audio_file) | ||
|
||
if audio_extension == Configuration.audio_ext: | ||
return audio_path | ||
|
||
new_audio_path = os.path.join(audio_directory, f'{audio_filename}{Configuration.audio_ext}') | ||
|
||
ffmpeg = ffmpy.FFmpeg(inputs={audio_path: None}, | ||
outputs={new_audio_path: None}, | ||
global_options='-y -loglevel warning') | ||
ffmpeg.run() | ||
return new_audio_path | ||
|
||
@staticmethod | ||
def merge_video_audio(video_path: str, audio_path: str): | ||
video_directory = os.path.dirname(video_path) | ||
video_file = os.path.basename(video_path) | ||
video_filename, _ = os.path.splitext(video_file) | ||
new_video_path = os.path.join(video_directory, f'{video_filename}.merge.mp4') | ||
|
||
ffmpeg = ffmpy.FFmpeg(inputs={video_path: None, audio_path: None}, | ||
outputs={new_video_path: '-c copy -map 0:v:0 -map 1:a:0'}, | ||
global_options='-y -loglevel warning') | ||
ffmpeg.run() | ||
return new_video_path | ||
|
||
@staticmethod | ||
def add_thumbnail_to_media_file(media_path: str, thumbnail_path: str): # Either video or audio (apparently) | ||
if thumbnail_path is None: | ||
return media_path | ||
|
||
media_directory = os.path.dirname(media_path) | ||
media_file = os.path.basename(media_path) | ||
media_filename, media_extension = os.path.splitext(media_file) | ||
new_media_path = os.path.join(media_directory, f'{media_filename}.thumbnail{media_extension}') | ||
|
||
try: | ||
ffmpeg = ffmpy.FFmpeg(inputs={media_path: None, thumbnail_path: None}, | ||
outputs={new_media_path: '-map 1 -map 0 -c copy -disposition:0 attached_pic'}, | ||
global_options='-y -loglevel warning') | ||
ffmpeg.run() | ||
except ffmpy.FFRuntimeError: | ||
return media_path | ||
|
||
return new_media_path |
Oops, something went wrong.