def fastVideo(ffmpeg, videoFile, outFile, silentT, frameMargin, SAMPLE_RATE, AUD_BITRATE, verbose, videoSpeed, silentSpeed, cutByThisTrack, keepTracksSep): print('Running from fastVideo.py') import cv2 conwrite('Reading audio.') if(not os.path.isfile(videoFile)): print('Could not find file:', videoFile) sys.exit(1) TEMP = tempfile.mkdtemp() speeds = [silentSpeed, videoSpeed] cap = cv2.VideoCapture(videoFile) width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) fourcc = cv2.VideoWriter_fourcc(*'mp4v') fps = cap.get(cv2.CAP_PROP_FPS) tracks = vidTracks(videoFile, ffmpeg) if(cutByThisTrack >= tracks): print("Error! You choose a track that doesn't exist.") print(f'There are only {tracks-1} tracks. (starting from 0)') sys.exit(1) for trackNumber in range(tracks): cmd = [ffmpeg, '-i', videoFile, '-ab', AUD_BITRATE, '-ac', '2', '-ar', str(SAMPLE_RATE),'-map', f'0:a:{trackNumber}', f'{TEMP}/{trackNumber}.wav'] if(verbose): cmd.extend(['-hide_banner']) else: cmd.extend(['-nostats', '-loglevel', '0']) subprocess.call(cmd) sampleRate, audioData = read(f'{TEMP}/{cutByThisTrack}.wav') chunks = getAudioChunks(audioData, sampleRate, fps, silentT, 2, frameMargin) # Handle the Audio for trackNumber in range(tracks): fastAudio(ffmpeg, f'{TEMP}/{trackNumber}.wav', f'{TEMP}/new{trackNumber}.wav', silentT, frameMargin, SAMPLE_RATE, AUD_BITRATE, verbose, silentSpeed, videoSpeed, False, chunks=chunks, fps=fps) if(not os.path.isfile(f'{TEMP}/new{trackNumber}.wav')): print('Error! Audio file not created.') sys.exit(1) out = cv2.VideoWriter(f'{TEMP}/spedup.mp4', fourcc, fps, (width, height)) totalFrames = chunks[len(chunks) - 1][1] beginTime = time.time() remander = 0 framesWritten = 0 while cap.isOpened(): ret, frame = cap.read() if(not ret): break cframe = int(cap.get(cv2.CAP_PROP_POS_FRAMES)) # current frame state = None for chunk in chunks: if(cframe >= chunk[0] and cframe <= chunk[1]): state = chunk[2] break if(state is not None): mySpeed = speeds[state] if(mySpeed != 99999): doIt = (1 / mySpeed) + remander for __ in range(int(doIt)): out.write(frame) framesWritten += 1 remander = doIt % 1 progressBar(cframe, totalFrames, beginTime, title='Creating new video') conwrite('Writing the output file.') cap.release() out.release() cv2.destroyAllWindows() if(verbose): print('Frames written', framesWritten) first = videoFile[:videoFile.rfind('.')] extension = videoFile[videoFile.rfind('.'):] if(outFile == ''): outFile = f'{first}_ALTERED{extension}' # Now mix new audio(s) and the new video. if(keepTracksSep): cmd = [ffmpeg, '-y'] for i in range(tracks): cmd.extend(['-i', f'{TEMP}/new{i}.wav']) cmd.extend(['-i', f'{TEMP}/spedup.mp4']) for i in range(tracks): cmd.extend(['-map', f'{i}:a:0']) cmd.extend(['-map', f'{tracks}:v:0','-c:v', 'copy', '-movflags', '+faststart', outFile]) if(verbose): cmd.extend(['-hide_banner']) else: cmd.extend(['-nostats', '-loglevel', '0']) else: # Merge all the audio tracks into one. if(tracks > 1): cmd = [ffmpeg] for i in range(tracks): cmd.extend(['-i', f'{TEMP}/new{i}.wav']) cmd.extend(['-filter_complex', f'amerge=inputs={tracks}', '-ac', '2', f'{TEMP}/newAudioFile.wav']) if(verbose): cmd.extend(['-hide_banner']) else: cmd.extend(['-nostats', '-loglevel', '0']) subprocess.call(cmd) else: os.rename(f'{TEMP}/new0.wav', f'{TEMP}/newAudioFile.wav') cmd = [ffmpeg, '-y', '-i', f'{TEMP}/newAudioFile.wav', '-i', f'{TEMP}/spedup.mp4', '-c:v', 'copy', '-movflags', '+faststart', outFile] if(verbose): cmd.extend(['-hide_banner']) else: cmd.extend(['-nostats', '-loglevel', '0']) subprocess.call(cmd) rmtree(TEMP) conwrite('') return outFile
def main(): parser = argparse.ArgumentParser(prog='Auto-Editor', usage='auto-editor [input] [options]') basic = parser.add_argument_group('Basic Options') basic.add_argument('input', nargs='*', help='the path to the file(s), folder, or url you want edited.') basic.add_argument('--frame_margin', '-m', type=int, default=6, metavar='6', help='set how many "silent" frames of on either side of "loud" sections be included.') basic.add_argument('--silent_threshold', '-t', type=float_type, default=0.04, metavar='0.04', help='set the volume that frames audio needs to surpass to be "loud". (0-1)') basic.add_argument('--video_speed', '--sounded_speed', '-v', type=float_type, default=1.00, metavar='1', help='set the speed that "loud" sections should be played at.') basic.add_argument('--silent_speed', '-s', type=float_type, default=99999, metavar='99999', help='set the speed that "silent" sections should be played at.') basic.add_argument('--output_file', '-o', nargs='*', metavar='', help='set the name(s) of the new output.') advance = parser.add_argument_group('Advanced Options') advance.add_argument('--no_open', action='store_true', help='do not open the file after editing is done.') advance.add_argument('--min_clip_length', '-mclip', type=int, default=3, metavar='3', help='set the minimum length a clip can be. If a clip is too short, cut it.') advance.add_argument('--min_cut_length', '-mcut', type=int, default=6, metavar='6', help="set the minimum length a cut can be. If a cut is too short, don't cut") advance.add_argument('--combine_files', action='store_true', help='combine all input files into one before editing.') advance.add_argument('--preview', action='store_true', help='show stats on how the input will be cut.') cutting = parser.add_argument_group('Cutting Options') cutting.add_argument('--cut_by_this_audio', '-ca', type=file_type, metavar='', help="base cuts by this audio file instead of the video's audio.") cutting.add_argument('--cut_by_this_track', '-ct', type=int, default=0, metavar='0', help='base cuts by a different audio track in the video.') cutting.add_argument('--cut_by_all_tracks', '-cat', action='store_true', help='combine all audio tracks into one before basing cuts.') cutting.add_argument('--keep_tracks_seperate', action='store_true', help="don't combine audio tracks when exporting.") debug = parser.add_argument_group('Developer/Debugging Options') debug.add_argument('--my_ffmpeg', action='store_true', help='use your ffmpeg and other binaries instead of the ones packaged.') debug.add_argument('--version', action='store_true', help='show which auto-editor you have.') debug.add_argument('--debug', '--verbose', action='store_true', help='show helpful debugging values.') misc = parser.add_argument_group('Export Options') misc.add_argument('--export_as_audio', '-exa', action='store_true', help='export as a WAV audio file.') misc.add_argument('--export_to_premiere', '-exp', action='store_true', help='export as an XML file for Adobe Premiere Pro instead of outputting a media file.') misc.add_argument('--export_to_resolve', '-exr', action='store_true', help='export as an XML file for DaVinci Resolve instead of outputting a media file.') size = parser.add_argument_group('Size Options') size.add_argument('--video_bitrate', '-vb', metavar='', help='set the number of bits per second for video.') size.add_argument('--audio_bitrate', '-ab', metavar='', help='set the number of bits per second for audio.') size.add_argument('--sample_rate', '-r', type=sample_rate_type, metavar='', help='set the sample rate of the input and output videos.') size.add_argument('--video_codec', '-vcodec', metavar='', help='set the video codec for the output file.') args = parser.parse_args() dirPath = os.path.dirname(os.path.realpath(__file__)) # fixes pip not able to find other included modules. sys.path.append(os.path.abspath(dirPath)) if(args.version): print('Auto-Editor version', version) sys.exit() if(args.export_to_premiere): print('Exporting to Adobe Premiere Pro XML file.') if(args.export_to_resolve): print('Exporting to DaVinci Resolve XML file.') if(args.export_as_audio): print('Exporting as audio.') newF = None newP = None if(platform.system() == 'Windows' and not args.my_ffmpeg): newF = os.path.join(dirPath, 'win-ffmpeg/bin/ffmpeg.exe') newP = os.path.join(dirPath, 'win-ffmpeg/bin/ffprobe.exe') if(platform.system() == 'Darwin' and not args.my_ffmpeg): newF = os.path.join(dirPath, 'mac-ffmpeg/bin/ffmpeg') newP = os.path.join(dirPath, 'mac-ffmpeg/bin/ffprobe') if(newF is not None and os.path.isfile(newF)): ffmpeg = newF ffprobe = newP else: ffmpeg = 'ffmpeg' ffprobe = 'ffprobe' makingDataFile = args.export_to_premiere or args.export_to_resolve is64bit = '64-bit' if sys.maxsize > 2**32 else '32-bit' if(args.debug): print('Python Version:', platform.python_version(), is64bit) print('Platform:', platform.system()) # Platform can be 'Linux', 'Darwin' (macOS), 'Java', 'Windows' print('FFmpeg path:', ffmpeg) print('Auto-Editor version', version) if(args.input == []): sys.exit() from usefulFunctions import Log log = Log(3 if args.debug else 2) if(is64bit == '32-bit'): # I should have put this warning a long time ago. log.warning("You have the 32-bit version of Python, which means you won't be " \ 'able to handle long videos.') if(args.frame_margin < 0): log.error('Frame margin cannot be negative.') if(args.input == []): log.error('The following arguments are required: input\n' \ 'In other words, you need the path to a video or an audio file ' \ 'so that auto-editor can do the work for you.') if(args.silent_speed <= 0 or args.silent_speed > 99999): args.silent_speed = 99999 if(args.video_speed <= 0 or args.video_speed > 99999): args.video_speed = 99999 inputList = [] for myInput in args.input: if(os.path.isdir(myInput)): def validFiles(path): for f in os.listdir(path): if(not f.startswith('.') and not f.endswith('.xml') and not f.endswith('.png') and not f.endswith('.md') and not os.path.isdir(f)): yield os.path.join(path, f) inputList += sorted(validFiles(myInput)) elif(os.path.isfile(myInput)): inputList.append(myInput) elif(myInput.startswith('http://') or myInput.startswith('https://')): print('URL detected, using youtube-dl to download from webpage.') basename = re.sub(r'\W+', '-', myInput) cmd = ['youtube-dl', '-f', 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/mp4', myInput, '--output', basename, '--no-check-certificate'] if(ffmpeg != 'ffmpeg'): cmd.extend(['--ffmpeg-location', ffmpeg]) subprocess.call(cmd) inputList.append(basename + '.mp4') else: log.error('Could not find file: ' + myInput) if(args.output_file is None): args.output_file = [] if(len(args.output_file) < len(inputList)): for i in range(len(inputList) - len(args.output_file)): oldFile = inputList[i] dotIndex = oldFile.rfind('.') if(args.export_to_premiere or args.export_to_resolve): args.output_file.append(oldFile[:dotIndex] + '.xml') else: ext = oldFile[dotIndex:] if(args.export_as_audio): ext = '.wav' end = '_ALTERED' + ext args.output_file.append(oldFile[:dotIndex] + end) TEMP = tempfile.mkdtemp() if(args.combine_files): with open(f'{TEMP}/combines.txt', 'w') as outfile: for fileref in inputList: outfile.write(f"file '{fileref}'\n") cmd = [ffmpeg, '-f', 'concat', '-safe', '0', '-i', f'{TEMP}/combines.txt', '-c', 'copy', 'combined.mp4'] subprocess.call(cmd) inputList = ['combined.mp4'] speeds = [args.silent_speed, args.video_speed] startTime = time.time() from usefulFunctions import isAudioFile, vidTracks, conwrite, getAudioChunks from wavfile import read, write numCuts = 0 for i, INPUT_FILE in enumerate(inputList): newOutput = args.output_file[i] fileFormat = INPUT_FILE[INPUT_FILE.rfind('.'):] # Grab the sample rate from the input. sr = args.sample_rate if(sr is None): output = pipeToConsole([ffmpeg, '-i', INPUT_FILE, '-hide_banner']) try: matchDict = re.search(r'\s(?P<grp>\w+?)\sHz', output).groupdict() sr = matchDict['grp'] except AttributeError: sr = 48000 args.sample_rate = sr # Grab the audio bitrate from the input. abit = args.audio_bitrate if(abit is None): output = pipeToConsole([ffprobe, '-v', 'error', '-select_streams', 'a:0', '-show_entries', 'stream=bit_rate', '-of', 'compact=p=0:nk=1', INPUT_FILE]) try: abit = int(output) except: log.warning("Couldn't automatically detect audio bitrate.") abit = '500k' log.debug('Setting audio bitrate to ' + abit) else: abit = str(round(abit / 1000)) + 'k' else: abit = str(abit) args.audio_bitrate = abit if(isAudioFile(INPUT_FILE)): fps = 30 tracks = 1 cmd = [ffmpeg, '-y', '-i', INPUT_FILE, '-b:a', args.audio_bitrate, '-ac', '2', '-ar', str(args.sample_rate), '-vn', f'{TEMP}/fastAud.wav'] if(args.debug): cmd.extend(['-hide_banner']) else: cmd.extend(['-nostats', '-loglevel', '0']) subprocess.call(cmd) sampleRate, audioData = read(f'{TEMP}/fastAud.wav') else: if(args.export_to_premiere): fps = 29.97 else: fps = ffmpegFPS(ffmpeg, INPUT_FILE, log) tracks = vidTracks(INPUT_FILE, ffprobe, log) if(args.cut_by_this_track >= tracks): log.error("You choose a track that doesn't exist.\n" \ f'There are only {tracks-1} tracks. (starting from 0)') vcodec = args.video_codec if(vcodec is None): output = pipeToConsole([ffmpeg, '-i', INPUT_FILE, '-hide_banner']) try: matchDict = re.search(r'Video:\s(?P<video>\w+?)\s', output).groupdict() vcodec = matchDict['video'] log.debug(vcodec) except AttributeError: vcodec = 'copy' log.warning("Couldn't automatically detect the video codec.") vbit = args.video_bitrate if(vbit is None): output = pipeToConsole([ffprobe, '-v', 'error', '-select_streams', 'v:0', '-show_entries', 'stream=bit_rate', '-of', 'compact=p=0:nk=1', INPUT_FILE]) try: vbit = int(output) except: log.warning("Couldn't automatically detect video bitrate.") vbit = '500k' log.debug('Setting vbit to ' + vbit) else: vbit += 300 * 1000 # Add more for better quality. vbit = str(round(vbit / 1000)) + 'k' else: vbit = str(vbit) if(vcodec == 'copy'): log.warning('Your bitrate will not be applied because' \ ' the video codec is "copy".') args.video_bitrate = vbit for trackNum in range(tracks): cmd = [ffmpeg, '-y', '-i', INPUT_FILE, '-ab', args.audio_bitrate, '-ac', '2', '-ar', str(args.sample_rate), '-map', f'0:a:{trackNum}', f'{TEMP}/{trackNum}.wav'] if(args.debug): cmd.extend(['-hide_banner']) else: cmd.extend(['-nostats', '-loglevel', '0']) subprocess.call(cmd) if(args.cut_by_all_tracks): cmd = [ffmpeg, '-y', '-i', INPUT_FILE, '-filter_complex', f'[0:a]amerge=inputs={tracks}', '-map', 'a', '-ar', str(args.sample_rate), '-ac', '2', '-f', 'wav', f'{TEMP}/combined.wav'] if(args.debug): cmd.extend(['-hide_banner']) else: cmd.extend(['-nostats', '-loglevel', '0']) subprocess.call(cmd) sampleRate, audioData = read(f'{TEMP}/combined.wav') else: if(os.path.isfile(f'{TEMP}/{args.cut_by_this_track}.wav')): sampleRate, audioData = read(f'{TEMP}/{args.cut_by_this_track}.wav') else: log.error('Audio track not found!') chunks = getAudioChunks(audioData, sampleRate, fps, args.silent_threshold, args.frame_margin, args.min_clip_length, args.min_cut_length, log) clips = [] for chunk in chunks: if(speeds[chunk[2]] == 99999): numCuts += 1 else: clips.append([chunk[0], chunk[1], speeds[chunk[2]] * 100]) if(fps is None and not isAudioFile(INPUT_FILE)): if(makingDataFile): dotIndex = INPUT_FILE.rfind('.') end = '_constantFPS' + oldFile[dotIndex:] constantLoc = oldFile[:dotIndex] + end else: constantLoc = f'{TEMP}/constantVid{fileFormat}' cmd = [ffmpeg, '-y', '-i', INPUT_FILE, '-filter:v', f'fps=fps=30', constantLoc] if(args.debug): cmd.extend(['-hide_banner']) else: cmd.extend(['-nostats', '-loglevel', '0']) subprocess.call(cmd) INPUT_FILE = constancLoc if(args.preview): args.no_open = True from preview import preview preview(INPUT_FILE, chunks, speeds, args.debug) continue if(args.export_to_premiere): args.no_open = True from premiere import exportToPremiere exportToPremiere(INPUT_FILE, TEMP, newOutput, clips, tracks, sampleRate, log) continue if(args.export_to_resolve): args.no_open = True duration = chunks[len(chunks) - 1][1] from resolve import exportToResolve exportToResolve(INPUT_FILE, newOutput, clips, duration, sampleRate, log) continue if(isAudioFile(INPUT_FILE) and not makingDataFile): from fastAudio import fastAudio fastAudio(ffmpeg, INPUT_FILE, newOutput, chunks, speeds, args.audio_bitrate, sampleRate, args.debug, True, log) continue from fastVideo import fastVideo fastVideo(ffmpeg, INPUT_FILE, newOutput, chunks, speeds, tracks, args.audio_bitrate, sampleRate, args.debug, TEMP, args.keep_tracks_seperate, vcodec, fps, args.export_as_audio, args.video_bitrate, log) if(not os.path.isfile(newOutput)): log.error(f'The file {newOutput} was not created.') if(not args.preview and not makingDataFile): timeLength = round(time.time() - startTime, 2) minutes = timedelta(seconds=round(timeLength)) print(f'Finished. took {timeLength} seconds ({minutes})') if(not args.preview and makingDataFile): timeSave = numCuts * 2 # assuming making each cut takes about 2 seconds. units = 'seconds' if(timeSave >= 3600): timeSave = round(timeSave / 3600, 1) if(timeSave % 1 == 0): timeSave = round(timeSave) units = 'hours' if(timeSave >= 60): timeSave = round(timeSave / 60, 1) if(timeSave >= 10 or timeSave % 1 == 0): timeSave = round(timeSave) units = 'minutes' print(f'Auto-Editor made {numCuts} cuts', end='') # Don't add a newline. if(numCuts > 4): print(f', which would have taken about {timeSave} {units} if edited manually.') else: print('.') if(not args.no_open): try: # should work on Windows os.startfile(newOutput) except AttributeError: try: # should work on MacOS and most Linux versions subprocess.call(['open', newOutput]) except: try: # should work on WSL2 subprocess.call(['cmd.exe', '/C', 'start', newOutput]) except: log.warning('Could not open output file.') rmtree(TEMP)
def main(): options = [] option_names = [] def add_argument(*names, nargs=1, type=str, default=None, action='default', range=None, choices=None, help='', extra=''): nonlocal options nonlocal option_names newDic = {} newDic['names'] = names newDic['nargs'] = nargs newDic['type'] = type newDic['default'] = default newDic['action'] = action newDic['help'] = help newDic['extra'] = extra newDic['range'] = range newDic['choices'] = choices options.append(newDic) option_names = option_names + list(names) add_argument('(input)', nargs='*', help='the path to a file, folder, or url you want edited.') add_argument('--help', '-h', action='store_true', help='print this message and exit.') add_argument( '--frame_margin', '-m', type=int, default=6, range='0 to Infinity', help= 'set how many "silent" frames of on either side of "loud" sections be included.' ) add_argument( '--silent_threshold', '-t', type=float_type, default=0.04, range='0 to 1', help='set the volume that frames audio needs to surpass to be "loud".') add_argument( '--video_speed', '--sounded_speed', '-v', type=float_type, default=1.00, range='0 to 999999', help='set the speed that "loud" sections should be played at.') add_argument( '--silent_speed', '-s', type=float_type, default=99999, range='0 to 99999', help='set the speed that "silent" sections should be played at.') add_argument('--output_file', '-o', nargs='*', help='set the name(s) of the new output.') add_argument('--no_open', action='store_true', help='do not open the file after editing is done.') add_argument( '--min_clip_length', '-mclip', type=int, default=3, range='0 to Infinity', help= 'set the minimum length a clip can be. If a clip is too short, cut it.' ) add_argument( '--min_cut_length', '-mcut', type=int, default=6, range='0 to Infinity', help= "set the minimum length a cut can be. If a cut is too short, don't cut" ) add_argument('--combine_files', action='store_true', help='combine all input files into one before editing.') add_argument('--preview', action='store_true', help='show stats on how the input will be cut.') add_argument( '--cut_by_this_audio', '-ca', type=file_type, help="base cuts by this audio file instead of the video's audio.") add_argument('--cut_by_this_track', '-ct', type=int, default=0, range='0 to the number of audio tracks', help='base cuts by a different audio track in the video.') add_argument('--cut_by_all_tracks', '-cat', action='store_true', help='combine all audio tracks into one before basing cuts.') add_argument('--keep_tracks_seperate', action='store_true', help="don't combine audio tracks when exporting.") add_argument( '--my_ffmpeg', action='store_true', help='use your ffmpeg and other binaries instead of the ones packaged.' ) add_argument('--version', action='store_true', help='show which auto-editor you have.') add_argument('--debug', '--verbose', action='store_true', help='show helpful debugging values.') # TODO: add export_as_video add_argument('--export_as_audio', '-exa', action='store_true', help='export as a WAV audio file.') add_argument( '--export_to_premiere', '-exp', action='store_true', help= 'export as an XML file for Adobe Premiere Pro instead of outputting a media file.' ) add_argument( '--export_to_resolve', '-exr', action='store_true', help= 'export as an XML file for DaVinci Resolve instead of outputting a media file.' ) add_argument('--video_bitrate', '-vb', help='set the number of bits per second for video.') add_argument('--audio_bitrate', '-ab', help='set the number of bits per second for audio.') add_argument('--sample_rate', '-r', type=sample_rate_type, help='set the sample rate of the input and output videos.') add_argument('--video_codec', '-vcodec', help='set the video codec for the output file.') add_argument( '--preset', '-p', default='medium', choices=[ 'ultrafast', 'superfast', 'veryfast', 'faster', 'fast', 'medium', 'slow', 'slower', 'veryslow' ], help= 'set the preset for ffmpeg to help save file size or increase quality.' ) add_argument('--tune', default='none', choices=[ 'film', 'animation', 'grain', 'stillimage', 'fastdecode', 'zerolatency', 'none' ], help='set the tune for ffmpeg to help compress video better.') add_argument( '--ignore', nargs='*', help= "the range (in seconds) that shouldn't be edited at all. (uses range syntax)" ) add_argument('--cut_out', nargs='*', help='the range (in seconds) that should be cut out completely, '\ 'regardless of anything else. (uses range syntax)') dirPath = os.path.dirname(os.path.realpath(__file__)) # Fixes pip not able to find other included modules. sys.path.append(os.path.abspath(dirPath)) from usefulFunctions import Log class parse_options(): def __init__(self, userArgs, log, *args): # Set the default options. for options in args: for option in options: key = option['names'][0].replace('-', '') if (option['action'] == 'store_true'): value = False elif (option['nargs'] != 1): value = [] else: value = option['default'] setattr(self, key, value) def get_option(item, the_args): for options in the_args: for option in options: if (item in option['names']): return option return None # Figure out attributes changed by user. myList = [] settingInputs = True optionList = 'input' i = 0 while i < len(userArgs): item = userArgs[i] if (i == len(userArgs) - 1): nextItem = None else: nextItem = userArgs[i + 1] option = get_option(item, args) if (option is not None): if (optionList is not None): setattr(self, optionList, myList) settingInputs = False optionList = None myList = [] key = option['names'][0].replace('-', '') # show help for specific option. if (nextItem == '-h' or nextItem == '--help'): print(' ', ', '.join(option['names'])) print(' ', option['help']) print(' ', option['extra']) if (option['action'] == 'default'): print(' type:', option['type'].__name__) print(' default:', option['default']) if (option['range'] is not None): print(' range:', option['range']) if (option['choices'] is not None): print(' choices:', ', '.join(option['choices'])) else: print(f' type: flag') sys.exit() if (option['nargs'] != 1): settingInputs = True optionList = key elif (option['action'] == 'store_true'): value = True else: try: # Convert to correct type. value = option['type'](nextItem) except: typeName = option['type'].__name__ log.error( f'Couldn\'t convert "{nextItem}" to {typeName}' ) if (option['choices'] is not None): if (value not in option['choices']): log.error( f'{value} is not a choice for {option}') i += 1 setattr(self, key, value) else: if (settingInputs and not item.startswith('-')): # Input file names myList.append(item) else: # Unknown Option! hmm = difflib.get_close_matches(item, option_names) potential_options = ', '.join(hmm) append = '' if (hmm != []): append = f'\n\n Did you mean:\n {potential_options}' log.error(f'Unknown option: {item}{append}') i += 1 if (settingInputs): setattr(self, optionList, myList) args = parse_options(sys.argv[1:], Log(3), options) # Print help screen for entire program. if (args.help): for option in options: print(' ', ', '.join(option['names']) + ':', option['help']) print('\nHave an issue? Make an issue. '\ 'Visit https://github.com/wyattblue/auto-editor/issues') sys.exit() if (args.version): print('Auto-Editor version', version) sys.exit() from usefulFunctions import isAudioFile, vidTracks, conwrite, getAudioChunks from wavfile import read, write if (not args.preview): if (args.export_to_premiere): conwrite('Exporting to Adobe Premiere Pro XML file.') elif (args.export_to_resolve): conwrite('Exporting to DaVinci Resolve XML file.') elif (args.export_as_audio): conwrite('Exporting as audio.') else: conwrite('Starting.') newF = None newP = None if (platform.system() == 'Windows' and not args.my_ffmpeg): newF = os.path.join(dirPath, 'win-ffmpeg/bin/ffmpeg.exe') newP = os.path.join(dirPath, 'win-ffmpeg/bin/ffprobe.exe') if (platform.system() == 'Darwin' and not args.my_ffmpeg): newF = os.path.join(dirPath, 'mac-ffmpeg/bin/ffmpeg') newP = os.path.join(dirPath, 'mac-ffmpeg/bin/ffprobe') if (newF is not None and os.path.isfile(newF)): ffmpeg = newF ffprobe = newP else: ffmpeg = 'ffmpeg' ffprobe = 'ffprobe' makingDataFile = args.export_to_premiere or args.export_to_resolve is64bit = '64-bit' if sys.maxsize > 2**32 else '32-bit' if (args.debug): print('Python Version:', platform.python_version(), is64bit) print('Platform:', platform.system()) # Platform can be 'Linux', 'Darwin' (macOS), 'Java', 'Windows' print('FFmpeg path:', ffmpeg) print('Auto-Editor version', version) if (args.input == []): sys.exit() log = Log(3 if args.debug else 2) if (is64bit == '32-bit'): # I should have put this warning a long time ago. log.warning("You have the 32-bit version of Python, which means you won't be " \ 'able to handle long videos.') if (args.frame_margin < 0): log.error('Frame margin cannot be negative.') if (args.input == []): log.error( 'You need the (input) argument so that auto-editor can do the work for you.' ) if (args.silent_speed <= 0 or args.silent_speed > 99999): args.silent_speed = 99999 if (args.video_speed <= 0 or args.video_speed > 99999): args.video_speed = 99999 inputList = [] for myInput in args.input: if (os.path.isdir(myInput)): def validFiles(path): for f in os.listdir(path): if (not f.startswith('.') and not f.endswith('.xml') and not f.endswith('.png') and not f.endswith('.md') and not os.path.isdir(f)): yield os.path.join(path, f) inputList += sorted(validFiles(myInput)) elif (os.path.isfile(myInput)): inputList.append(myInput) elif (myInput.startswith('http://') or myInput.startswith('https://')): print('URL detected, using youtube-dl to download from webpage.') basename = re.sub(r'\W+', '-', myInput) cmd = [ 'youtube-dl', '-f', 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/mp4', myInput, '--output', basename, '--no-check-certificate' ] if (ffmpeg != 'ffmpeg'): cmd.extend(['--ffmpeg-location', ffmpeg]) subprocess.call(cmd) inputList.append(basename + '.mp4') else: log.error('Could not find file: ' + myInput) if (args.output_file is None): args.output_file = [] if (len(args.output_file) < len(inputList)): for i in range(len(inputList) - len(args.output_file)): oldFile = inputList[i] dotIndex = oldFile.rfind('.') if (args.export_to_premiere or args.export_to_resolve): args.output_file.append(oldFile[:dotIndex] + '.xml') else: ext = oldFile[dotIndex:] if (args.export_as_audio): ext = '.wav' end = '_ALTERED' + ext args.output_file.append(oldFile[:dotIndex] + end) TEMP = tempfile.mkdtemp() if (args.combine_files): with open(f'{TEMP}/combines.txt', 'w') as outfile: for fileref in inputList: outfile.write(f"file '{fileref}'\n") cmd = [ ffmpeg, '-f', 'concat', '-safe', '0', '-i', f'{TEMP}/combines.txt', '-c', 'copy', 'combined.mp4' ] subprocess.call(cmd) inputList = ['combined.mp4'] speeds = [args.silent_speed, args.video_speed] startTime = time.time() numCuts = 0 for i, INPUT_FILE in enumerate(inputList): newOutput = args.output_file[i] fileFormat = INPUT_FILE[INPUT_FILE.rfind('.'):] # Grab the sample rate from the input. sr = args.sample_rate if (sr is None): output = pipeToConsole([ffmpeg, '-i', INPUT_FILE, '-hide_banner']) try: matchDict = re.search(r'\s(?P<grp>\w+?)\sHz', output).groupdict() sr = matchDict['grp'] except AttributeError: sr = 48000 args.sample_rate = sr # Grab the audio bitrate from the input. abit = args.audio_bitrate if (abit is None): output = pipeToConsole([ ffprobe, '-v', 'error', '-select_streams', 'a:0', '-show_entries', 'stream=bit_rate', '-of', 'compact=p=0:nk=1', INPUT_FILE ]) try: abit = int(output) except: log.warning("Couldn't automatically detect audio bitrate.") abit = '500k' log.debug('Setting audio bitrate to ' + abit) else: abit = str(round(abit / 1000)) + 'k' else: abit = str(abit) args.audio_bitrate = abit if (isAudioFile(INPUT_FILE)): fps = 30 tracks = 1 cmd = [ ffmpeg, '-y', '-i', INPUT_FILE, '-b:a', args.audio_bitrate, '-ac', '2', '-ar', str(args.sample_rate), '-vn', f'{TEMP}/fastAud.wav' ] if (args.debug): cmd.extend(['-hide_banner']) else: cmd.extend(['-nostats', '-loglevel', '0']) subprocess.call(cmd) sampleRate, audioData = read(f'{TEMP}/fastAud.wav') else: if (args.export_to_premiere): fps = 29.97 else: fps = ffmpegFPS(ffmpeg, INPUT_FILE, log) tracks = vidTracks(INPUT_FILE, ffprobe, log) if (args.cut_by_this_track >= tracks): log.error("You choose a track that doesn't exist.\n" \ f'There are only {tracks-1} tracks. (starting from 0)') vcodec = args.video_codec if (vcodec is None): output = pipeToConsole( [ffmpeg, '-i', INPUT_FILE, '-hide_banner']) try: matchDict = re.search(r'Video:\s(?P<video>\w+?)\s', output).groupdict() vcodec = matchDict['video'] log.debug(vcodec) except AttributeError: vcodec = 'copy' log.warning( "Couldn't automatically detect the video codec.") if (args.video_bitrate is not None and vcodec == 'copy'): log.warning('Your bitrate will not be applied because' \ ' the video codec is "copy".') for trackNum in range(tracks): cmd = [ ffmpeg, '-y', '-i', INPUT_FILE, '-ab', args.audio_bitrate, '-ac', '2', '-ar', str(args.sample_rate), '-map', f'0:a:{trackNum}', f'{TEMP}/{trackNum}.wav' ] if (args.debug): cmd.extend(['-hide_banner']) else: cmd.extend(['-nostats', '-loglevel', '0']) subprocess.call(cmd) if (args.cut_by_all_tracks): cmd = [ ffmpeg, '-y', '-i', INPUT_FILE, '-filter_complex', f'[0:a]amerge=inputs={tracks}', '-map', 'a', '-ar', str(args.sample_rate), '-ac', '2', '-f', 'wav', f'{TEMP}/combined.wav' ] if (args.debug): cmd.extend(['-hide_banner']) else: cmd.extend(['-nostats', '-loglevel', '0']) subprocess.call(cmd) sampleRate, audioData = read(f'{TEMP}/combined.wav') else: if (os.path.isfile(f'{TEMP}/{args.cut_by_this_track}.wav')): sampleRate, audioData = read( f'{TEMP}/{args.cut_by_this_track}.wav') else: log.error('Audio track not found!') chunks = getAudioChunks(audioData, sampleRate, fps, args.silent_threshold, args.frame_margin, args.min_clip_length, args.min_cut_length, args.ignore, args.cut_out, log) clips = [] for chunk in chunks: if (speeds[chunk[2]] == 99999): numCuts += 1 else: clips.append([chunk[0], chunk[1], speeds[chunk[2]] * 100]) if (fps is None and not isAudioFile(INPUT_FILE)): if (makingDataFile): dotIndex = INPUT_FILE.rfind('.') end = '_constantFPS' + oldFile[dotIndex:] constantLoc = oldFile[:dotIndex] + end else: constantLoc = f'{TEMP}/constantVid{fileFormat}' cmd = [ ffmpeg, '-y', '-i', INPUT_FILE, '-filter:v', f'fps=fps=30', constantLoc ] if (args.debug): cmd.extend(['-hide_banner']) else: cmd.extend(['-nostats', '-loglevel', '0']) subprocess.call(cmd) INPUT_FILE = constancLoc if (args.preview): args.no_open = True from preview import preview preview(INPUT_FILE, chunks, speeds, args.debug) continue if (args.export_to_premiere): args.no_open = True from premiere import exportToPremiere exportToPremiere(INPUT_FILE, TEMP, newOutput, clips, tracks, sampleRate, log) continue if (args.export_to_resolve): args.no_open = True duration = chunks[len(chunks) - 1][1] from resolve import exportToResolve exportToResolve(INPUT_FILE, newOutput, clips, duration, sampleRate, log) continue if (isAudioFile(INPUT_FILE) and not makingDataFile): from fastAudio import fastAudio fastAudio(ffmpeg, INPUT_FILE, newOutput, chunks, speeds, args.audio_bitrate, sampleRate, args.debug, True, log) continue from fastVideo import fastVideo fastVideo(ffmpeg, INPUT_FILE, newOutput, chunks, speeds, tracks, args.audio_bitrate, sampleRate, args.debug, TEMP, args.keep_tracks_seperate, vcodec, fps, args.export_as_audio, args.video_bitrate, args.preset, args.tune, log) if (not os.path.isfile(newOutput)): log.error(f'The file {newOutput} was not created.') if (not args.preview and not makingDataFile): timeLength = round(time.time() - startTime, 2) minutes = timedelta(seconds=round(timeLength)) print(f'Finished. took {timeLength} seconds ({minutes})') if (not args.preview and makingDataFile): timeSave = numCuts * 2 # assuming making each cut takes about 2 seconds. units = 'seconds' if (timeSave >= 3600): timeSave = round(timeSave / 3600, 1) if (timeSave % 1 == 0): timeSave = round(timeSave) units = 'hours' if (timeSave >= 60): timeSave = round(timeSave / 60, 1) if (timeSave >= 10 or timeSave % 1 == 0): timeSave = round(timeSave) units = 'minutes' print(f'Auto-Editor made {numCuts} cuts', end='') # Don't add a newline. if (numCuts > 4): print( f', which would have taken about {timeSave} {units} if edited manually.' ) else: print('.') if (not args.no_open): try: # should work on Windows os.startfile(newOutput) except AttributeError: try: # should work on MacOS and most Linux versions subprocess.call(['open', newOutput]) except: try: # should work on WSL2 subprocess.call(['cmd.exe', '/C', 'start', newOutput]) except: log.warning('Could not open output file.') rmtree(TEMP)
def fastAudio(ffmpeg, theFile, outFile, silentT, frameMargin, SAMPLE_RATE, audioBit, verbose, silentSpeed, soundedSpeed, needConvert, chunks=[], fps=30): if (not os.path.isfile(theFile)): print('Could not find file:', theFile) sys.exit(1) if (outFile == ''): fileName = theFile[:theFile.rfind('.')] outFile = f'{fileName}_ALTERED.wav' if (needConvert): # Only print this here so other scripts can use this function. print('Running from fastAudio.py') import tempfile from shutil import rmtree TEMP = tempfile.mkdtemp() cmd = [ ffmpeg, '-i', theFile, '-b:a', audioBit, '-ac', '2', '-ar', str(SAMPLE_RATE), '-vn', f'{TEMP}/fastAud.wav' ] if (not verbose): cmd.extend(['-nostats', '-loglevel', '0']) subprocess.call(cmd) theFile = f'{TEMP}/fastAud.wav' speeds = [silentSpeed, soundedSpeed] sampleRate, audioData = read(theFile) if (chunks == []): print('Creating chunks') chunks = getAudioChunks(audioData, sampleRate, fps, silentT, 2, frameMargin) newL = getNewLength(chunks, speeds, fps) # Get the new length in samples with some extra leeway. estLeng = int((newL * sampleRate) * 1.5) + int(sampleRate * 2) # Create an empty array for the new audio. newAudio = np.zeros((estLeng, 2), dtype=np.int16) channels = 2 yPointer = 0 totalChunks = len(chunks) beginTime = time.time() for chunkNum, chunk in enumerate(chunks): audioSampleStart = int(chunk[0] / fps * sampleRate) audioSampleEnd = int(audioSampleStart + (sampleRate / fps) * (chunk[1] - chunk[0])) theSpeed = speeds[chunk[2]] if (theSpeed != 99999): spedChunk = audioData[audioSampleStart:audioSampleEnd] if (theSpeed == 1): yPointerEnd = yPointer + spedChunk.shape[0] newAudio[yPointer:yPointerEnd] = spedChunk else: spedupAudio = np.zeros((0, 2), dtype=np.int16) with ArrReader(spedChunk, channels, sampleRate, 2) as reader: with ArrWriter(spedupAudio, channels, sampleRate, 2) as writer: phasevocoder(reader.channels, speed=theSpeed).run(reader, writer) spedupAudio = writer.output yPointerEnd = yPointer + spedupAudio.shape[0] newAudio[yPointer:yPointerEnd] = spedupAudio myL = chunk[1] - chunk[0] mySamples = (myL / fps) * sampleRate newSamples = int(mySamples / theSpeed) yPointer = yPointer + newSamples else: # Speed is too high so skip this section. yPointerEnd = yPointer progressBar(chunkNum, totalChunks, beginTime, title='Creating new audio') if (verbose): print('yPointer', yPointer) print('samples per frame', sampleRate / fps) print('Expected video length', yPointer / (sampleRate / fps)) newAudio = newAudio[:yPointer] write(outFile, sampleRate, newAudio) if ('TEMP' in locals()): rmtree(TEMP) if (needConvert): return outFile
def preview(ffmpeg, myInput, silentT, zoomT, frameMargin, sampleRate, videoSpeed, silentSpeed, cutByThisTrack, bitrate): TEMP = tempfile.mkdtemp() extension = myInput[myInput.rfind('.'):] audioFile = extension in ['.wav', '.mp3', '.m4a'] if(audioFile): fps = 30 cmd = [ffmpeg, '-i', myInput, '-b:a', bitrate, '-ac', '2', '-ar', str(sampleRate), '-vn', f'{TEMP}/fastAud.wav', '-nostats', '-loglevel', '0'] subprocess.call(cmd) sampleRate, audioData = read(f'{TEMP}/fastAud.wav') chunks = getAudioChunks(audioData, sampleRate, fps, silentT, 2, frameMargin) else: import cv2 cap = cv2.VideoCapture(myInput) fps = cap.get(cv2.CAP_PROP_FPS) tracks = vidTracks(myInput, ffmpeg) if(cutByThisTrack >= tracks): print("Error! You choose a track that doesn't exist.") print(f'There are only {tracks-1} tracks. (starting from 0)') sys.exit(1) for trackNumber in range(tracks): cmd = [ffmpeg, '-i', myInput, '-ab', bitrate, '-ac', '2', '-ar', str(sampleRate),'-map', f'0:a:{trackNumber}', f'{TEMP}/{trackNumber}.wav', '-nostats', '-loglevel', '0'] subprocess.call(cmd) sampleRate, audioData = read(f'{TEMP}/{cutByThisTrack}.wav') chunks = getAudioChunks(audioData, sampleRate, fps, silentT, 2, frameMargin) rmtree(TEMP) def printTimeFrame(title, frames, fps): inSec = round(frames / fps, 1) if(fps % 1 == 0): fps = round(fps) if(inSec < 1): minutes = f'{int(frames)}/{fps} frames' else: minutes = timedelta(seconds=round(inSec)) print(f'{title}: {inSec} secs ({minutes})') oldTime = chunks[len(chunks)-1][1] printTimeFrame('Old length', oldTime, fps) speeds = [silentSpeed, videoSpeed] newL = getNewLength(chunks, speeds, fps) printTimeFrame('New length', newL * fps, fps) clips = 0 cuts = 0 clipLengths = [] for chunk in chunks: state = chunk[2] if(speeds[state] != 99999): clips += 1 leng = (chunk[1] - chunk[0]) / speeds[state] clipLengths.append(leng) else: cuts += 1 print('Number of clips:', clips) #print('Number of cuts:', cuts) printTimeFrame('Smallest clip length', min(clipLengths), fps) printTimeFrame('Largest clip length', max(clipLengths), fps) printTimeFrame('Average clip length', sum(clipLengths) / len(clipLengths), fps)
def exportToPremiere(ffmpeg, myInput, newOutput, silentT, zoomT, frameMargin, sampleRate, videoSpeed, silentSpeed): print('Running from premiere.py') TEMP = tempfile.mkdtemp() fps = 29.97 cmd = [ ffmpeg, '-i', myInput, '-ab', '160k', '-ac', '2', '-ar', str(sampleRate), '-vn', f'{TEMP}/output.wav', '-nostats', '-loglevel', '0' ] subprocess.call(cmd) sampleRate, audioData = read(f'{TEMP}/output.wav') chunks = getAudioChunks(audioData, sampleRate, fps, silentT, zoomT, frameMargin) rmtree(TEMP) clips = [] newSpeed = [silentSpeed, videoSpeed] for chunk in chunks: if (newSpeed[chunk[2]] != 99999): clips.append([chunk[0], chunk[1], newSpeed[chunk[2]] * 100]) if (len(clips) < 1): print('Error! Less than 1 clip.') sys.exit(1) pathurl = 'file://localhost' + os.path.abspath(myInput) name = os.path.basename(myInput) extension = myInput[myInput.rfind('.'):] audioFile = extension in ['.wav', '.mp3', '.m4a'] first = myInput[:myInput.rfind('.')] newFile = f'{first}.xml' ntsc = 'FALSE' ana = 'FALSE' # anamorphic alphatype = 'none' depth = '16' if (not audioFile): try: import cv2 conwrite('Grabbing video dimensions.') cap = cv2.VideoCapture(myInput) width = str(int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))) height = str(int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))) cap.release() cv2.destroyAllWindows() except ImportError: width = '1280' height = '720' pixelar = 'square' # pixel aspect ratio colordepth = '24' sr = sampleRate if (audioFile): with open(newFile, 'w', encoding='utf-8') as outfile: outfile.write('<!-- Generated by Auto-Editor -->\n') outfile.write( '<!-- https://github.com/WyattBlue/auto-editor -->\n') outfile.write('\n') outfile.write( '<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE xmeml>\n') outfile.write('<xmeml version="4">\n') outfile.write('\t<sequence>\n') outfile.write('\t<rate>\n') outfile.write('\t\t<timebase>30</timebase>\n') outfile.write('\t\t<ntsc>TRUE</ntsc>\n') outfile.write('\t</rate>\n') outfile.write('\t\t<name>Auto-Editor Audio Group</name>\n') outfile.write('\t\t<media>\n') outfile.write('\t\t\t<audio>\n') outfile.write('\t\t\t\t<numOutputChannels>2</numOutputChannels>\n') outfile.write('\t\t\t\t<format>\n') outfile.write('\t\t\t\t\t<samplecharacteristics>\n') outfile.write(f'\t\t\t\t\t\t<depth>{depth}</depth>\n') outfile.write(f'\t\t\t\t\t\t<samplerate>{sr}</samplerate>\n') outfile.write('\t\t\t\t\t</samplecharacteristics>\n') outfile.write('\t\t\t\t</format>\n') outfile.write( '\t\t\t\t<track currentExplodedTrackIndex="0" totalExplodedTrackCount="2" premiereTrackType="Stereo">\n' ) total = 0 for j, clip in enumerate(clips): myStart = int(total) total += (clip[1] - clip[0]) / (clip[2] / 100) myEnd = int(total) outfile.write(f'\t\t\t\t\t<clipitem id="clipitem-{j+1}">\n') outfile.write( '\t\t\t\t\t\t<masterclipid>masterclip-1</masterclipid>\n') outfile.write(f'\t\t\t\t\t\t<name>{name}</name>\n') outfile.write(f'\t\t\t\t\t\t<start>{myStart}</start>\n') outfile.write(f'\t\t\t\t\t\t<end>{myEnd}</end>\n') outfile.write( f'\t\t\t\t\t\t<in>{int(clip[0] / (clip[2] / 100))}</in>\n') outfile.write( f'\t\t\t\t\t\t<out>{int(clip[1] / (clip[2] / 100))}</out>\n' ) if (j == 0): outfile.write('\t\t\t\t\t\t<file id="file-1">\n') outfile.write(f'\t\t\t\t\t\t\t<name>{name}</name>\n') outfile.write( f'\t\t\t\t\t\t\t<pathurl>{pathurl}</pathurl>\n') outfile.write('\t\t\t\t\t\t\t<rate>\n') outfile.write('\t\t\t\t\t\t\t\t<timebase>30</timebase>\n') outfile.write(f'\t\t\t\t\t\t\t\t<ntsc>{ntsc}</ntsc>\n') outfile.write('\t\t\t\t\t\t\t</rate>\n') outfile.write('\t\t\t\t\t\t\t<media>\n') outfile.write('\t\t\t\t\t\t\t\t<audio>\n') outfile.write( '\t\t\t\t\t\t\t\t\t<samplecharacteristics>\n') outfile.write( f'\t\t\t\t\t\t\t\t\t\t<depth>{depth}</depth>\n') outfile.write( f'\t\t\t\t\t\t\t\t\t\t<samplerate>{sr}</samplerate>\n') outfile.write( '\t\t\t\t\t\t\t\t\t</samplecharacteristics>\n') outfile.write( '\t\t\t\t\t\t\t\t\t<channelcount>2</channelcount>\n') outfile.write('\t\t\t\t\t\t\t\t</audio>\n') outfile.write('\t\t\t\t\t\t\t</media>\n') outfile.write('\t\t\t\t\t\t</file>\n') else: outfile.write(f'\t\t\t\t\t\t<file id="file-1"/>\n') outfile.write('\t\t\t\t\t</clipitem>\n') outfile.write('\t\t\t\t</track>\n') outfile.write('\t\t\t</audio>\n') outfile.write('\t\t</media>\n') outfile.write('\t</sequence>\n') outfile.write('</xmeml>') return newFile # End of audio file code. with open(newFile, 'w', encoding='utf-8') as outfile: outfile.write('<!-- Generated by Auto-Editor -->\n') outfile.write('<!-- https://github.com/WyattBlue/auto-editor -->\n') outfile.write('\n') outfile.write( '<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE xmeml>\n') outfile.write('<xmeml version="4">\n') outfile.write('\t<sequence>\n') outfile.write('\t\t<name>Auto-Editor Video Group</name>\n') outfile.write('\t\t<media>\n') outfile.write('\t\t\t<video>\n') outfile.write('\t\t\t\t<format>\n') outfile.write('\t\t\t\t\t<samplecharacteristics>\n') outfile.write('\t\t\t\t\t\t<rate>\n') outfile.write('\t\t\t\t\t\t\t<timebase>30</timebase>\n') outfile.write(f'\t\t\t\t\t\t\t<ntsc>{ntsc}</ntsc>\n') outfile.write('\t\t\t\t\t\t</rate>\n') outfile.write(f'\t\t\t\t\t\t<width>{width}</width>\n') outfile.write(f'\t\t\t\t\t\t<height>{height}</height>\n') outfile.write(f'\t\t\t\t\t\t<anamorphic>{ana}</anamorphic>\n') outfile.write( f'\t\t\t\t\t\t<pixelaspectratio>{pixelar}</pixelaspectratio>\n') outfile.write('\t\t\t\t\t\t<fielddominance>none</fielddominance>\n') outfile.write(f'\t\t\t\t\t\t<colordepth>{colordepth}</colordepth>\n') outfile.write('\t\t\t\t\t</samplecharacteristics>\n') outfile.write('\t\t\t\t</format>\n') outfile.write('\t\t\t\t<track>\n') # Handle clips. total = 0 for j, clip in enumerate(clips): myStart = int(total) total += (clip[1] - clip[0]) / (clip[2] / 100) myEnd = int(total) outfile.write(f'\t\t\t\t\t<clipitem id="clipitem-{j+7}">\n') outfile.write( '\t\t\t\t\t\t<masterclipid>masterclip-2</masterclipid>\n') outfile.write(f'\t\t\t\t\t\t<name>{name}</name>\n') outfile.write(f'\t\t\t\t\t\t<start>{myStart}</start>\n') outfile.write(f'\t\t\t\t\t\t<end>{myEnd}</end>\n') outfile.write( f'\t\t\t\t\t\t<in>{int(clip[0] / (clip[2] / 100))}</in>\n') outfile.write( f'\t\t\t\t\t\t<out>{int(clip[1] / (clip[2] / 100))}</out>\n') if (j == 0): outfile.write('\t\t\t\t\t\t<file id="file-2">\n') outfile.write(f'\t\t\t\t\t\t\t<name>{name}</name>\n') outfile.write(f'\t\t\t\t\t\t\t<pathurl>{pathurl}</pathurl>\n') outfile.write('\t\t\t\t\t\t\t<rate>\n') outfile.write('\t\t\t\t\t\t\t\t<timebase>30</timebase>\n') outfile.write(f'\t\t\t\t\t\t\t\t<ntsc>{ntsc}</ntsc>\n') outfile.write('\t\t\t\t\t\t\t</rate>\n') outfile.write('\t\t\t\t\t\t\t<media>\n') outfile.write('\t\t\t\t\t\t\t\t<video>\n') outfile.write('\t\t\t\t\t\t\t\t\t<samplecharacteristics>\n') outfile.write('\t\t\t\t\t\t\t\t\t\t<rate>\n') outfile.write( '\t\t\t\t\t\t\t\t\t\t\t<timebase>30</timebase>\n') outfile.write(f'\t\t\t\t\t\t\t\t\t\t\t<ntsc>{ntsc}</ntsc>\n') outfile.write('\t\t\t\t\t\t\t\t\t\t</rate>\n') outfile.write(f'\t\t\t\t\t\t\t\t\t\t<width>{width}</width>\n') outfile.write( f'\t\t\t\t\t\t\t\t\t\t<height>{height}</height>\n') outfile.write( f'\t\t\t\t\t\t\t\t\t\t<anamorphic>{ana}</anamorphic>\n') outfile.write( f'\t\t\t\t\t\t\t\t\t\t<pixelaspectratio>{pixelar}</pixelaspectratio>\n' ) outfile.write( '\t\t\t\t\t\t\t\t\t\t<fielddominance>none</fielddominance>\n' ) outfile.write('\t\t\t\t\t\t\t\t\t</samplecharacteristics>\n') outfile.write('\t\t\t\t\t\t\t\t</video>\n') outfile.write('\t\t\t\t\t\t\t\t<audio>\n') outfile.write('\t\t\t\t\t\t\t\t\t<samplecharacteristics>\n') outfile.write(f'\t\t\t\t\t\t\t\t\t\t<depth>{depth}</depth>\n') outfile.write( f'\t\t\t\t\t\t\t\t\t\t<samplerate>{sr}</samplerate>\n') outfile.write('\t\t\t\t\t\t\t\t\t</samplecharacteristics>\n') outfile.write( '\t\t\t\t\t\t\t\t\t<channelcount>2</channelcount>\n') outfile.write('\t\t\t\t\t\t\t\t</audio>\n') outfile.write('\t\t\t\t\t\t\t</media>\n') outfile.write('\t\t\t\t\t\t</file>\n') else: outfile.write(f'\t\t\t\t\t\t<file id="file-2"/>\n') # Add the speed effect if nessecary if (clip[2] != 100): outfile.write('\t\t\t\t\t\t<filter>\n') outfile.write('\t\t\t\t\t\t\t<effect>\n') outfile.write('\t\t\t\t\t\t\t\t<name>Time Remap</name>\n') outfile.write( '\t\t\t\t\t\t\t\t<effectid>timeremap</effectid>\n') outfile.write( '\t\t\t\t\t\t\t\t<effectcategory>motion</effectcategory>\n' ) outfile.write( '\t\t\t\t\t\t\t\t<effecttype>motion</effecttype>\n') outfile.write('\t\t\t\t\t\t\t\t<mediatype>video</mediatype>\n') outfile.write( '\t\t\t\t\t\t\t\t<parameter authoringApp="PremierePro">\n') outfile.write( '\t\t\t\t\t\t\t\t\t<parameterid>variablespeed</parameterid>\n' ) outfile.write('\t\t\t\t\t\t\t\t\t<name>variablespeed</name>\n') outfile.write('\t\t\t\t\t\t\t\t\t<valuemin>0</valuemin>\n') outfile.write('\t\t\t\t\t\t\t\t\t<valuemax>1</valuemax>\n') outfile.write('\t\t\t\t\t\t\t\t\t<value>0</value>\n') outfile.write('\t\t\t\t\t\t\t\t</parameter>\n') outfile.write( '\t\t\t\t\t\t\t\t<parameter authoringApp="PremierePro">\n') outfile.write( '\t\t\t\t\t\t\t\t\t<parameterid>speed</parameterid>\n') outfile.write('\t\t\t\t\t\t\t\t\t<name>speed</name>\n') outfile.write( '\t\t\t\t\t\t\t\t\t<valuemin>-100000</valuemin>\n') outfile.write( '\t\t\t\t\t\t\t\t\t<valuemax>100000</valuemax>\n') outfile.write(f'\t\t\t\t\t\t\t\t\t<value>{clip[2]}</value>\n') outfile.write('\t\t\t\t\t\t\t\t</parameter>\n') outfile.write( '\t\t\t\t\t\t\t\t<parameter authoringApp="PremierePro">\n') outfile.write( '\t\t\t\t\t\t\t\t\t<parameterid>reverse</parameterid>\n') outfile.write('\t\t\t\t\t\t\t\t\t<name>reverse</name>\n') outfile.write('\t\t\t\t\t\t\t\t\t<value>FALSE</value>\n') outfile.write('\t\t\t\t\t\t\t\t</parameter>\n') outfile.write( '\t\t\t\t\t\t\t\t<parameter authoringApp="PremierePro">\n') outfile.write( '\t\t\t\t\t\t\t\t\t<parameterid>frameblending</parameterid>\n' ) outfile.write('\t\t\t\t\t\t\t\t\t<name>frameblending</name>\n') outfile.write('\t\t\t\t\t\t\t\t\t<value>FALSE</value>\n') outfile.write('\t\t\t\t\t\t\t\t</parameter>\n') outfile.write('\t\t\t\t\t\t\t</effect>\n') outfile.write('\t\t\t\t\t\t</filter>\n') # Linking for video blocks for i in range(3): outfile.write('\t\t\t\t\t\t<link>\n') outfile.write( f'\t\t\t\t\t\t\t<linkclipref>clipitem-{(i*(len(clips)+1))+7+j}</linkclipref>\n' ) if (i == 0): outfile.write( '\t\t\t\t\t\t\t<mediatype>video</mediatype>\n') else: outfile.write( '\t\t\t\t\t\t\t<mediatype>audio</mediatype>\n') if (i == 2): outfile.write('\t\t\t\t\t\t\t<trackindex>2</trackindex>\n') else: outfile.write('\t\t\t\t\t\t\t<trackindex>1</trackindex>\n') outfile.write(f'\t\t\t\t\t\t\t<clipindex>{j+1}</clipindex>\n') if (i == 1 or i == 2): outfile.write('\t\t\t\t\t\t\t<groupindex>1</groupindex>\n') outfile.write('\t\t\t\t\t\t</link>\n') outfile.write('\t\t\t\t\t</clipitem>\n') outfile.write('\t\t\t\t</track>\n') outfile.write('\t\t\t</video>\n') outfile.write('\t\t\t<audio>\n') outfile.write('\t\t\t\t<numOutputChannels>2</numOutputChannels>\n') outfile.write('\t\t\t\t<format>\n') outfile.write('\t\t\t\t\t<samplecharacteristics>\n') outfile.write(f'\t\t\t\t\t\t<depth>{depth}</depth>\n') outfile.write(f'\t\t\t\t\t\t<samplerate>{sr}</samplerate>\n') outfile.write('\t\t\t\t\t</samplecharacteristics>\n') outfile.write('\t\t\t\t</format>\n') outfile.write( '\t\t\t\t<track PannerIsInverted="true" PannerStartKeyframe="-91445760000000000,0.5,0,0,0,0,0,0" PannerName="Balance" currentExplodedTrackIndex="0" totalExplodedTrackCount="2" premiereTrackType="Stereo">\n' ) # Audio Clips total = 0 for j, clip in enumerate(clips): outfile.write( f'\t\t\t\t\t<clipitem id="clipitem-{len(clips)+8+j}" premiereChannelType="stereo">\n' ) outfile.write( f'\t\t\t\t\t\t<masterclipid>masterclip-2</masterclipid>\n') outfile.write(f'\t\t\t\t\t\t<name>{name}</name>\n') myStart = int(total) total += (clip[1] - clip[0]) / (clip[2] / 100) myEnd = int(total) outfile.write(f'\t\t\t\t\t\t<start>{myStart}</start>\n') outfile.write(f'\t\t\t\t\t\t<end>{myEnd}</end>\n') outfile.write( f'\t\t\t\t\t\t<in>{int(clip[0] / (clip[2] / 100))}</in>\n') outfile.write( f'\t\t\t\t\t\t<out>{int(clip[1] / (clip[2] / 100))}</out>\n') outfile.write('\t\t\t\t\t\t<file id="file-2"/>\n') outfile.write('\t\t\t\t\t\t<sourcetrack>\n') outfile.write('\t\t\t\t\t\t\t<mediatype>audio</mediatype>\n') outfile.write('\t\t\t\t\t\t\t<trackindex>1</trackindex>\n') outfile.write('\t\t\t\t\t\t</sourcetrack>\n') # Add speed effect for audio blocks if (clip[2] != 100): outfile.write('\t\t\t\t\t\t<filter>\n') outfile.write('\t\t\t\t\t\t\t<effect>\n') outfile.write('\t\t\t\t\t\t\t\t<name>Time Remap</name>\n') outfile.write( '\t\t\t\t\t\t\t\t<effectid>timeremap</effectid>\n') outfile.write( '\t\t\t\t\t\t\t\t<effectcategory>motion</effectcategory>\n' ) outfile.write( '\t\t\t\t\t\t\t\t<effecttype>motion</effecttype>\n') outfile.write('\t\t\t\t\t\t\t\t<mediatype>video</mediatype>\n') outfile.write( '\t\t\t\t\t\t\t\t<parameter authoringApp="PremierePro">\n') outfile.write( '\t\t\t\t\t\t\t\t\t<parameterid>variablespeed</parameterid>\n' ) outfile.write('\t\t\t\t\t\t\t\t\t<name>variablespeed</name>\n') outfile.write('\t\t\t\t\t\t\t\t\t<valuemin>0</valuemin>\n') outfile.write('\t\t\t\t\t\t\t\t\t<valuemax>1</valuemax>\n') outfile.write('\t\t\t\t\t\t\t\t\t<value>0</value>\n') outfile.write('\t\t\t\t\t\t\t\t</parameter>\n') outfile.write( '\t\t\t\t\t\t\t\t<parameter authoringApp="PremierePro">\n') outfile.write( '\t\t\t\t\t\t\t\t\t<parameterid>speed</parameterid>\n') outfile.write('\t\t\t\t\t\t\t\t\t<name>speed</name>\n') outfile.write( '\t\t\t\t\t\t\t\t\t<valuemin>-100000</valuemin>\n') outfile.write( '\t\t\t\t\t\t\t\t\t<valuemax>100000</valuemax>\n') outfile.write(f'\t\t\t\t\t\t\t\t\t<value>{clip[2]}</value>\n') outfile.write('\t\t\t\t\t\t\t\t</parameter>\n') outfile.write( '\t\t\t\t\t\t\t\t<parameter authoringApp="PremierePro">\n') outfile.write( '\t\t\t\t\t\t\t\t\t<parameterid>reverse</parameterid>\n') outfile.write('\t\t\t\t\t\t\t\t\t<name>reverse</name>\n') outfile.write('\t\t\t\t\t\t\t\t\t<value>FALSE</value>\n') outfile.write('\t\t\t\t\t\t\t\t</parameter>\n') outfile.write( '\t\t\t\t\t\t\t\t<parameter authoringApp="PremierePro">\n') outfile.write( '\t\t\t\t\t\t\t\t\t<parameterid>frameblending</parameterid>\n' ) outfile.write('\t\t\t\t\t\t\t\t\t<name>frameblending</name>\n') outfile.write('\t\t\t\t\t\t\t\t\t<value>FALSE</value>\n') outfile.write('\t\t\t\t\t\t\t\t</parameter>\n') outfile.write('\t\t\t\t\t\t\t</effect>\n') outfile.write('\t\t\t\t\t\t</filter>\n') if (audioFile): startOn = 1 else: startOn = 0 for i in range(startOn, 3): outfile.write('\t\t\t\t\t\t<link>\n') outfile.write( f'\t\t\t\t\t\t\t<linkclipref>clipitem-{(i*(len(clips)+1))+7+j}</linkclipref>\n' ) if (i == 0): outfile.write( '\t\t\t\t\t\t\t<mediatype>video</mediatype>\n') else: outfile.write( '\t\t\t\t\t\t\t<mediatype>audio</mediatype>\n') if (i == 2): outfile.write('\t\t\t\t\t\t\t<trackindex>2</trackindex>\n') else: outfile.write('\t\t\t\t\t\t\t<trackindex>1</trackindex>\n') outfile.write(f'\t\t\t\t\t\t\t<clipindex>{j+1}</clipindex>\n') if (i == 1 or i == 2): outfile.write('\t\t\t\t\t\t\t<groupindex>1</groupindex>\n') outfile.write('\t\t\t\t\t\t</link>\n') outfile.write('\t\t\t\t\t</clipitem>\n') outfile.write('\t\t\t\t\t<outputchannelindex>1</outputchannelindex>\n') outfile.write('\t\t\t\t</track>\n') outfile.write('\t\t\t</audio>\n') outfile.write('\t\t</media>\n') outfile.write('\t</sequence>\n') outfile.write('</xmeml>') conwrite('') return newFile