from subprocess import call, Popen, PIPE, check_output from os import listdir, remove from os.path import splitext from re import match from sys import argv from enum import Enum from datetime import timedelta from math import ceil class Stream(Enum): AUDIO = 1 VIDEO = 2 class File(Enum): LIST = 1 LOOP = 2 OUTPUT = 3 def getCmdStdErr(command): process = Popen(command, stderr=PIPE, stdout=PIPE) out, err = process.communicate() return err def getDuration(ffprobe_output): durationPattern = r'.*Duration:\s(.+),\sstart.*' regex = match(durationPattern, str(ffprobe_output)) duration = regex.groups()[0] if regex else None if not duration: raise ValueError('Cannot process ffprobe output!') return duration def downloadStreams(): call(('/usr/bin/env', 'youtube-dl', '--extract-audio', '--output', '{}.%(ext)s'.format(FILES[Stream.AUDIO]), URL)) call(('/usr/bin/env', 'youtube-dl', '--output', '{}.%(ext)s'.format(FILES[Stream.VIDEO]), URL)) def readExtensions(): for file in listdir(): for filename in FILES: if match('^{}.*'.format(FILES[filename]), file): FILES[filename] = file FILES = {Stream.AUDIO: 'audio', Stream.VIDEO: 'video', File.LIST: 'list.txt', File.LOOP: 'loop', File.OUTPUT: 'output.mp4'} OUTPUT_KEYS = [File.OUTPUT] URL = argv[1] if len(argv) > 0 else '' # youtube-dl error message will be shown if '' downloadStreams() readExtensions() # get stream lengths via ffprobe audioData= getDuration(getCmdStdErr(('/usr/bin/env', 'ffprobe', FILES[Stream.AUDIO]))).split(':') videoData = getDuration(getCmdStdErr(('/usr/bin/env', 'ffprobe', FILES[Stream.VIDEO]))).split(':') audioLen = timedelta(hours=float(audioData[0]), minutes=float(audioData[1]), seconds=float(audioData[2])) videoLen = timedelta(hours=float(videoData[0]), minutes=float(videoData[1]), seconds=float(videoData[2])) # decide which stream needs some looping longer = audioLen if audioLen > videoLen else videoLen shorter = audioLen if audioLen < videoLen else videoLen shorterFile = FILES[Stream.AUDIO] if audioLen < videoLen else FILES[Stream.VIDEO] FILES[File.LOOP] += splitext(shorterFile)[1] timesLoop = ceil(longer.seconds / shorter.seconds) # write concat helper file for ffmpeg with open(FILES[File.LIST], 'w') as f: for i in range(timesLoop): print("file '{}'".format(shorterFile), file=f) # fetch video title FILES[File.OUTPUT] = check_output(('/usr/bin/env', 'youtube-dl', '--get-title', argv[1])).decode('utf-8').strip() + '.mp4' # loop & mux call(('/usr/bin/env', 'ffmpeg', '-f', 'concat', '-i', FILES[File.LIST], '-c', 'copy', FILES[File.LOOP])) call(('/usr/bin/env', 'ffmpeg', '-i', FILES[File.LOOP], '-i', FILES[Stream.AUDIO], '-map', '0', '-map', '1', '-c', 'copy', FILES[File.OUTPUT])) # cleanup for key in FILES: if key not in OUTPUT_KEYS: remove(FILES[key])