├── .gitignore ├── Chunkify.py ├── LICENSE ├── README.md └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # PyCharm 7 | .idea/ 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | pip-wheel-metadata/ 27 | share/python-wheels/ 28 | *.egg-info/ 29 | .installed.cfg 30 | *.egg 31 | MANIFEST 32 | 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .nox/ 47 | .coverage 48 | .coverage.* 49 | .cache 50 | nosetests.xml 51 | coverage.xml 52 | *.cover 53 | .hypothesis/ 54 | .pytest_cache/ 55 | 56 | # Translations 57 | *.mo 58 | *.pot 59 | 60 | # Django stuff: 61 | *.log 62 | local_settings.py 63 | db.sqlite3 64 | db.sqlite3-journal 65 | 66 | # Flask stuff: 67 | instance/ 68 | .webassets-cache 69 | 70 | # Scrapy stuff: 71 | .scrapy 72 | 73 | # Sphinx documentation 74 | docs/_build/ 75 | 76 | # PyBuilder 77 | target/ 78 | 79 | # Jupyter Notebook 80 | .ipynb_checkpoints 81 | 82 | # IPython 83 | profile_default/ 84 | ipython_config.py 85 | 86 | # pyenv 87 | .python-version 88 | 89 | # pipenv 90 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 91 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 92 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 93 | # install all needed dependencies. 94 | #Pipfile.lock 95 | 96 | # celery beat schedule file 97 | celerybeat-schedule 98 | 99 | # SageMath parsed files 100 | *.sage.py 101 | 102 | # Environments 103 | .env 104 | .venv 105 | env/ 106 | venv/ 107 | ENV/ 108 | env.bak/ 109 | venv.bak/ 110 | 111 | # Spyder project settings 112 | .spyderproject 113 | .spyproject 114 | 115 | # Rope project settings 116 | .ropeproject 117 | 118 | # mkdocs documentation 119 | /site 120 | 121 | # mypy 122 | .mypy_cache/ 123 | .dmypy.json 124 | dmypy.json 125 | 126 | # Pyre type checker 127 | .pyre/ 128 | -------------------------------------------------------------------------------- /Chunkify.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Teitoku42 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in all 11 | # copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | 21 | import cv2 22 | import os 23 | from concurrent.futures import ThreadPoolExecutor 24 | import sys 25 | import getopt 26 | import subprocess 27 | from numpy import arange 28 | from itertools import repeat 29 | import moviepy.editor as editor 30 | 31 | def Worker(a_InputFile, a_OutputFileName, a_Scale, a_FrameChunkCount, a_CurrID, a_TotalFrameCount): 32 | print("Starting processing on chunk", a_CurrID) 33 | BeginFrame = a_CurrID * a_FrameChunkCount + 1 34 | if a_CurrID == 0: 35 | BeginFrame = 0 36 | 37 | EndFrame = a_FrameChunkCount + (a_FrameChunkCount * a_CurrID) 38 | if EndFrame + 1 == a_TotalFrameCount: 39 | EndFrame += 1 40 | 41 | if EndFrame > a_TotalFrameCount: 42 | EndFrame = a_TotalFrameCount 43 | 44 | Cmd = ('veai.exe -i "' + a_InputFile + '" ' 45 | '-o "' + os.path.dirname(os.path.abspath(a_InputFile)) + '\\' + a_OutputFileName + '_' + str(a_CurrID) + '.mp4" ' 46 | '-f mp4 ' 47 | '-m ghq-1.0.1 ' 48 | '-s ' + str(a_Scale) + ' ' 49 | '-c ' + str(a_CurrID) + ' ' 50 | '-b ' + str(int(BeginFrame)) + ' ' 51 | '-e ' + str(int(EndFrame))) 52 | 53 | Status = subprocess.call(Cmd, shell=True) 54 | print("Processing finished on chunk", a_CurrID) 55 | return Status 56 | 57 | def Process(a_InputFile, a_OutputFileName, a_Scale, a_GPUCount, a_ConcatenateVids): 58 | if not os.path.exists('C:\\Program Files\\Topaz Labs LLC\\Topaz Video Enhance AI'): 59 | print("Topaz Video Enhance AI was not found on your system.") 60 | return 61 | 62 | os.chdir('C:\\Program Files\\Topaz Labs LLC\\Topaz Video Enhance AI') 63 | Capture = cv2.VideoCapture(a_InputFile) 64 | if Capture.isOpened() is False: 65 | print("Video stream could not be opened.") 66 | return 67 | 68 | FrameCount = int(Capture.get(cv2.CAP_PROP_FRAME_COUNT)) - 1 69 | print("Video frame count:", FrameCount) 70 | 71 | ChunkSize = float(FrameCount) / a_GPUCount 72 | ChunkSize = int(abs(ChunkSize)) 73 | print("Chunk processing size:", ChunkSize) 74 | 75 | IDs = arange(0, a_GPUCount) 76 | print("Executing upscaling, this may take a while...") 77 | with ThreadPoolExecutor(max_workers=a_GPUCount) as Exec: 78 | Results = Exec.map(Worker, repeat(a_InputFile), repeat(a_OutputFileName), 79 | repeat(a_Scale), repeat(ChunkSize), IDs, repeat(FrameCount)) 80 | 81 | Success = True 82 | for Result in Results: 83 | if Result != 0: 84 | Success = False 85 | break 86 | 87 | if not Success: 88 | print("Upscaling failed.") 89 | return 90 | else: 91 | print("Upscaling successful") 92 | 93 | if a_ConcatenateVids: 94 | print("Concatenating chunks into single video file...") 95 | os.chdir(os.path.dirname(os.path.abspath(a_InputFile)) + '\\') 96 | Videos = [] 97 | for Index in range(0, a_GPUCount): 98 | Videos.append(editor.VideoFileClip(a_OutputFileName + '_' + str(Index) + '.mp4')) 99 | 100 | FinalVideo = editor.concatenate_videoclips(Videos) 101 | FinalVideo.write_videofile(a_OutputFileName + '_' + 'Final.mp4') 102 | print("Done writing concatenated video file.") 103 | Input = input("Do you want to delete the chunk clips? (Y/N)") 104 | if Input == 'Y': 105 | for Index in range(0, a_GPUCount): 106 | os.remove(a_OutputFileName + '_' + str(Index) + '.mp4') 107 | 108 | print("Finished.") 109 | os.system('pause') 110 | 111 | def Main(a_Args): 112 | try: 113 | Options, Arguments = getopt.getopt(a_Args, "hi:o:g:s:c") 114 | except getopt.GetoptError as err: 115 | print("Chunkify.py -h for instructions") 116 | return 117 | 118 | if len(Options) == 0: 119 | print("Chunkify.py -h for instructions") 120 | return 121 | 122 | ReqOptionCheck = False 123 | OutName = "video" 124 | GPUCount = 1 125 | Scale = 2.0 126 | InPath = "" 127 | ConcatenateVid = False 128 | for Option, Argument in Options: 129 | if Option == "-i": 130 | ReqOptionCheck = True 131 | InPath = Argument 132 | elif Option == "-o": 133 | OutName = Argument 134 | elif Option == "-g": 135 | GPUCount = int(Argument) 136 | elif Option == "-s": 137 | Scale = float(Argument) / 100 138 | elif Option == "-c": 139 | if Argument == "Y": 140 | ConcatenateVid = True 141 | else: 142 | print("Chunkify - Evenly divide upscaling workload over multiple GPUs\n" 143 | "\n" 144 | "-i (Required)