def test_loglevel(): ff = FFmpeg() ff.loglevel = 'fa' path = "./" o = os.path.join(path, 'f.wav') opt = ['-i', i, o] ff.options(opt) assert ff.loglevel != 'fa'
def test_loglevel(): ff = FFmpeg() ff.loglevel = 'fa' path = os.path.join(cwd, '_test') #i = os.path.join(path, 'f.mp3') o = os.path.join(path, 'f.wav') opt = ['-i', i, o] ff.options(opt) assert ff.loglevel != 'fa'
def download_video(resolution, file_dir, subtitles, prefformat, output_type, vid_url): ff = FFmpeg() current_time = int(time.time()) video = YouTube(vid_url) underscore_name = video.title.replace(" ", "_") if not file_dir: file_dir = os.getcwd() download_object = video.streams.filter(resolution=resolution, file_extension="mp4", progressive=True).first() if download_object: print("[INFO] Progressive stream found. Direct download available") download_object.download(filename=video.title, output_path=file_dir) sg.Popup("Success", "Video successfully downloaded!") else: print( "[INFO] Progressive stream not found. Searching for adaptive streams." ) audio_object = video.streams.filter(only_audio=True, mime_type="audio/mp4").first() video_object = video.streams.filter(resolution=resolution, file_extension="mp4").first() try: print("[INFO] Audio Downloading") audio_object.download(filename=f'audio-{current_time}') print("[INFO] Video Downloading") video_object.download(filename=f'video-{current_time}') except AttributeError as download_error: # It technically shouldn't be possible to get here (using res check) print("[ERROR] Couldn't find stream at desired resolution.") sg.Popup( "Download fail", "Couldn't find a stream at your desired resolution. Choose a lower quality" ) return try: ff.options( f'-i audio-{current_time}.mp4 -i video-{current_time}.mp4 -acodec copy -vcodec copy {file_dir}/{underscore_name}.{prefformat}' ) except: # Horrible hack to fix videos with filenames that break pyffmpeg print( "[WARN] Error whilst converting. Defaulting back to generic filename" ) ff.options( f'-i audio-{current_time}.mp4 -i video-{current_time}.mp4 -acodec copy -vcodec copy {file_dir}/download-{current_time}.{prefformat}' ) print("[INFO] Cleaning up...") os.remove(f'audio-{current_time}.mp4') os.remove(f'video-{current_time}.mp4') sg.Popup("Success!", "Video downloaded!") return
def download_playlist_video(url, resolution, file_dir, prefformat): ff = FFmpeg() if not file_dir: file_dir = os.getcwd() current_time = int(time.time()) print(f'[INFO] Attempting to download {url} at max resolution') video = YouTube(url) underscore_name = video.title.replace(" ", "_") max_res = calculate_available_resolutions(video) if max_res[-1] == "1080p": video_object = video.streams.filter(resolution="1080p", file_extension="mp4").first() audio_object = video.streams.filter(only_audio=True, mime_type="audio/mp4").first() try: video_object.download(filename=f'video-{current_time}') audio_object.download(filename=f'audio-{current_time}') except: sg.Popup("Error", f'Error downloading {video.title}. Sorry!') return try: ff.options( f'-i audio-{current_time}.mp4 -i video-{current_time}.mp4 -acodec copy -vcodec copy {file_dir}/{underscore_name}.{prefformat}' ) os.remove(f'audio-{current_time}.mp4') os.remove(f'video-{current_time}.mp4') print("[INFO] Downloaded and converted 1080p video!") return except: # Horrible hack print( "[WARN] Error whilst converting. Defaulting back to generic filename" ) ff.options( f'-i audio-{current_time}.mp4 -i video-{current_time}.mp4 -acodec copy -vcodec copy {file_dir}/download-{current_time}.{prefformat}' ) os.remove(f'audio-{current_time}.mp4') os.remove(f'video-{current_time}.mp4') print("[INFO] Downloaded and converted 1080p video (generic name)") return else: video_object = video.streams.filter(resolution=max_res[-1], progressive=True, file_extension="mp4").first() print("[INFO] Attempting to download video object") video_object.download(output_path=file_dir) print("[INFO] Video object downloaded") return
def test_options(): path = os.path.join(cwd, '_test') #i = os.path.join(path, 'f.mp3') o = os.path.join(path, 'f.wav') opt = ['-i', i, o] a = FFmpeg() ret = a.options(opt) #os.remove(o) assert b'' == ret
def test_options(): path = Paths().home_path o = os.path.join(path, 'f.wav') opt = ['-i', i, o] ff = FFmpeg() print(f'in and out: {i}, {o}') ret = ff.options(opt) if ff.error: if 'Output' in ff.error: assert True else: print(ff.error) assert False else: assert True
class QVideo(QQuickItem): """ """ def __init__(self, parent=None, frames_per=None): super().__init__(parent) temp_f = Paths().temp self.convert_folder = self.fix_splashes(temp_f) + '/soloman/convert' self.temp_folder = self.convert_folder + '/temp' + str(randrange(1, 1000000)) os.makedirs(self.temp_folder) self._same_session = False # FFmpeg self._ffmpeg_inst = FFmpeg() # Video self._source = '' self._curr_file = "" self.folder = "" self._current_frame = '' if frames_per: self.fps = frames_per else: self.fps = 29.97 self._frame_no = 0 self._supported_vid_files = [ 'mp4', "asf", "avi", "flv", "gif", "mov", "3gp", "3gpp", "mkv", "webm"] self._user_stills = False self._stills_content = [] self._curr_stills_index = 0 self._stills_len = 10000000 self._stills_type = "" self._stills_converted = False self.sync = True self._seeked = False self._seek_frame = 0 self._seek_calls = 0 # Audio self._audio_inst = Audio(saveFolder=self.temp_folder) self._has_audio = True self._play_audio = True self._sync_audio = True self.auto_sync_time: int = 3 # controls self._stopped = False self._paused = False # Timer self._start_time = 0 self._total_time = 0 self._total_elapsed_time = 0.0 # Opencv self._cv2_frames_len = 0 self._cv2_tmp_frames_len = 0 self._cv2_session = False # Qml property self._aspect_ratio = True self._current_frame = '' self._delay = 0.0 self._duration: str = '' self._tile = 0 self._tile_enumeration = False aboutToPlay = pyqtSignal(float, arguments=['delayValue']) aspectRatioChanged = pyqtSignal(bool, arguments=['aspectRatio']) delayChanged = pyqtSignal(int, arguments=['delay']) durationChanged = pyqtSignal(str, arguments=['duration']) frameUpdate = pyqtSignal(str, arguments=['updateFrame']) tileChanged = pyqtSignal(int, arguments=['tileChange']) tileEnumChanged = pyqtSignal(bool, arguments=['tileEnum']) destroyed = pyqtSignal() def append_stills_content(self): if self.sync: a_thread = threading.Thread(target=self._append_stills_content) a_thread.daemon = True a_thread.start() else: self._append_stills_content() def _append_stills_content(self): # wait for the FFmpeg to start at least sleep(1) while self.sync and not self._stills_converted: listed = os.listdir(self.folder) self._stills_content.extend(listed[self._curr_stills_index:]) self._curr_stills_index = len(listed) - 1 sleep(0.1) else: self._stills_content = os.listdir(self.folder) self._stills_len = len(self._stills_content) def auto_sync_audio(self): a_thread = threading.Thread(target=self._auto_sync_audio) a_thread.daemon = True a_thread.start() def _auto_sync_audio(self): while not self._stopped: if self._paused: sleep(1) else: seconds = int(self._total_elapsed_time / 1000) self._audio_inst.seek(seconds+0.7) sleep(self.auto_sync_time) def convert_to_stills(self, fileName): if self.sync: c_thread = threading.Thread( target=self._convert_to_stills, args=[fileName]) c_thread.daemon = True c_thread.start() else: self._convert_to_stills(fileName) def _convert_to_stills(self, fileName): """ convert the video files to stills """ rand_str = str(randrange(1000, 4000)) self.folder = self.convert_folder + "/" + rand_str + "/" os.makedirs(self.folder) self._stills_type = 'jpg' out = self.folder + "vid_%01d.jpg" cmd = f"-i {fileName} -r {str(self.fps)} {out}" self._ffmpeg_inst.options(cmd) # Signal and end to conversion if self._seeked: sleep(0.1) self._stills_converted = True self._stills_converted = True self._stills_len = len(os.listdir(self.folder)) def convert_seeked(self, time: str, start_frame: int): c_thread = threading.Thread( target=self._convert_seeked, args=[time, start_frame]) c_thread.daemon = True if self._seek_calls > 1: return c_thread.start() def _convert_seeked(self, time: str, start_frame: int): """ This function seeks to a point in time of the video and then start converting from that time. time -> means that time that it should seek to: hh:mm::ss.ms start_frame -> means that frame number that corresponds with the time for instance start_frame = 96 corresponds with 00:00:04 if the video has a framerate of 24 fps """ out = self.folder + "vid_%01d.jpg" start_frame = str(start_frame) cmd = f"-ss {time} -i {self._curr_file}" cmd += f" -r {str(self.fps)} -start_number {start_frame} {out}" sleep(0.1) # self._ffmpeg_inst.quit() self._ffmpeg_inst.options(cmd) if self._seek_calls > 1: return # Signal and end to conversion sleep(0.1) self._stills_converted = True # send length of the stills lists = os.listdir(self.folder) lists.sort(key=lambda item: int(item.split('_')[1].split('.')[0])) l_ind = lists[-1].split('vid_')[1].split('.')[0] self._stills_len = int(l_ind) def cv2_updater(self): # Avoid multiple playing instances Not multiple objects though self._stopped = True self._frame_no = 0 sleep(0.5) u_thread = threading.Thread(target = self._cv2_updater) u_thread.daemon = True u_thread.start() def _cv2_updater(self): """ The updater for cv2 """ # if user has called the stop or pause function # we will need to reset it in order to restart play self._stopped = False self._paused = False self._start_time = time() # set the universal start time self.setTime() self.setFrameNo() # if no show frame has been called sleep and loop while self._cv2_frames_len == 0: sleep(1/3) # Avoid showing frame 0 if self._frame_no == 0: self._frame_no += 1 while not self._stopped and self._frame_no <= self._cv2_frames_len: if not self._paused: self._current_frame = 'file:///' + self.folder + '/' + str(self._frame_no) + ".jpg" self.updateFrame('') sleep(1/self.fps) else: sleep(1/10) # stop showing the last frame self._current_frame = '' self.updateFrame('') self._stopped = True # stop all other processs; will cause no trouble self._cv2_session = False def fix_splashes(self, fileName): """ Replace backslash with forward slash """ abs_path = os.path.abspath(fileName) return abs_path.replace("\\", '/') def get_current_frame(self): return self._current_frame def is_stills(self, fileName): ext = os.path.splitext(fileName)[1][1:] if ext not in self._supported_vid_files: # stills self._user_stills = True try: os.listdir(fileName) self.folder = fileName except: self.folder = os.path.dirname(fileName) self._stills_type = ext return True else: # video return False def make_cv2_frame(self, frame): c_thread = threading.Thread(target=self._make_cv2_frame, args=[frame]) c_thread.daemon = True c_thread.start() def _make_cv2_frame(self, frame): # create the frame image self._cv2_frames_len += 1 filename = self.folder + "/" + str(self._cv2_frames_len) + ".jpg" cv2.imwrite(filename, frame) def _make_temp_cv2_frame(self, frame): # create the frame image self._cv2_tmp_frames_len += 1 filename = self.temp_folder + "/" + str(self._cv2_tmp_frames_len) + ".jpg" cv2.imwrite(filename, frame) return filename def monitor(self): u_thread = threading.Thread(target = self._monitor) u_thread.daemon = True u_thread.start() def _monitor(self): total = 0 prev = 0 micro = round(1000 / self.fps, 2) for x in range(30): t1 = time() t2 = 0 while t2-t1 < 1: t2 = time() total = self._frame_no - prev prev = self._frame_no print( 'Total: {}, Frame no: {}, Elapsed time: {}, Elapsed Time / {}: {}'.format(total, self._frame_no, self._total_elapsed_time, micro, (self._total_elapsed_time/micro)) ) def _pause(self): self._paused = True if self._sync_audio: self._audio_inst._not_paused = False def _play(self, fileName): # play video self._curr_file = fileName if not self._same_session: self._user_stills = False filename = self.fix_splashes(fileName) if self.is_stills(filename): self._stills_converted = True self._append_stills_content() # call without a thread self.stills_updater() return else: # not stills # set fps based on file probe = FFprobe(filename) fps = probe.fps self._duration = probe.duration # remove later if not fps: fps = 24 if abs(fps - self.fps) > 1: self.fps = fps if self._has_audio: self._prepare_audio_file() self.convert_to_stills(filename) self.append_stills_content() self._same_session = True self.updater() # self.monitor() # allow this only in debug mode def prepare_audio_file(self): a_thread = threading.Thread(target=self._prepare_audio_file) a_thread.daemon = True a_thread.start() def _prepare_audio_file(self): fileName = self.fix_splashes(self._curr_file) self._audio_inst.prepare(fileName) print(self._audio_inst.file) def play_audio_file(self, delay: float): a_thread = threading.Thread( target=self._play_audio_file, args=[delay]) a_thread.daemon = True a_thread.start() def _play_audio_file(self, delay: float): print('delay: ', delay) self._audio_inst.delay_play(delay) self.auto_sync_audio() def _resume(self): self._paused = False if self._sync_audio: self._audio_inst._not_paused = True def _seek(self, seconds): status = 'continue' t1 = time() while round(time() - t1, 2) * 1000 < 100: if self._seek_calls > 1: status = '' self._seek_calls -= 1 break if status: self._seek_handler(seconds) def _seek_handler(self, seconds): # sleep to ensure we can reset if self._seek_calls > 1: return sleep(0.1) self._ffmpeg_inst.quit() sleep(0.2) self._seeked = True self._seek_calls = 1 frame_no = int(self.fps * seconds) # Calculate the time string h_dec = seconds / 3600 hrs, m_dec = str(h_dec).split('.') m_dec = '.' + m_dec mins, s_dec = str(float(m_dec) * 60).split('.') s_dec = '.' + s_dec secs = float(s_dec) * 60 if int(hrs) < 10: hrs_str = '0' + hrs else: hrs_str = str(hrs) if int(mins) < 10: mins_str = '0' + mins else: mins_str = str(mins) if secs < 10: secs_str = '0' + str(int(secs)) else: secs_str = str(secs) s_time = f"{hrs_str}:{mins_str}:{secs_str}" if self._seek_calls > 1: return self.convert_seeked(s_time, frame_no) fpsth = f'vid_{str(int(frame_no + self.fps))}.{self._stills_type}' f_path = os.path.join(self.folder, fpsth) sleep(0.5) # just in case it was a reverse seek while not os.path.exists(f_path) and self._seek_calls < 2: sleep(1) if self._seek_calls > 1: return # self._total_elapsed_time = seconds * 1000 self._seeked = False self._seek_calls -= 1 # maybe should be zero self._seek_frame = frame_no self._start_time = time() self.setTime() if self._sync_audio: print('heres, ', seconds) self._audio_inst.seek(seconds) def setFrameNo(self): # start the setTime thread s_thread = threading.Thread(target=self._setFrameNo) s_thread.daemon = True s_thread.start() def _setFrameNo(self): """ Sets the frame based on the current time. For instance; for 24fps if the current time is the first second, the current frame should be frame no. 24, on the second second, the current frame is 48 and so on. """ # 24fps 41.6 micro # 10fps 100 micro refresh_time = 1000 / self.fps sleep_time = 1 / (self.fps) while not self._stopped: if self._paused: sleep(0.1) continue elap = self._total_elapsed_time / refresh_time self._frame_no = round(elap) + self._seek_frame sleep(sleep_time) def setTime(self): # start the setTime thread s_thread = threading.Thread(target=self._setTime) s_thread.daemon = True s_thread.start() def _setTime(self): # set the time every 10 milliseconds # this will be used to know which frame is up t1 = self._start_time tm = 0 while not self._stopped: # *** very very important code; The speed at which # the time will be refreshed. if self._seeked: break if self._paused: # reset the time to pause the frame ts = time() - t1 - tm t1 += ts sleep(0.1) continue sleep(0.01) # *** t2 = time() tm = t2 - t1 #t1 = t2 # this is much accurate micro = round(tm, 2) * 1000 # this convert to microseconds self._total_elapsed_time = micro def show_cv2_frame(self, frame): c_thread = threading.Thread(target=self._show_cv2_frame, args=[frame]) c_thread.daemon = True c_thread.start() def _show_cv2_frame(self, frame): name = self._make_temp_cv2_frame(frame) self._current_frame = 'file:///' + name self.updateFrame('') def start_cv2(self): sleep(1/randrange(10, 40)) # in case of multiple threaded instances if os.path.exists(self.convert_folder): fold_len = len(os.listdir(self.convert_folder)) + 1 else: fold_len = 1 self.folder = self.convert_folder + "/" + str(fold_len) os.makedirs(self.folder) self.cv2_updater() def stills_updater(self): # Avoid multiple playing instances self._stopped = True self._frame_no = 0 sleep(0.3) u_thread = threading.Thread(target = self._stills_updater) u_thread.daemon = True u_thread.start() def _stills_updater(self): self._stopped = False self._paused = False self._seek_frame = 0 # initialize remaining delay rem_delay = 0.0 # Make sure convertion is done if len(self._stills_content) < 1: return if rem_delay < 0: rem_delay = self._delay # about to play self.aboutToPlay.emit(rem_delay) # Delay if self._delay: # sleep remaining delay sleep(rem_delay) # print(self._audio_inst.playing, 'yep') self._start_time = time() # set the universal start time self.setTime() self.setFrameNo() while not self._stopped and self._frame_no != self._stills_len: #t1 = time() filename = self._stills_content[self._frame_no] # use still type if not self._paused: self._current_frame = f"file:///{self.folder}/{filename}" self.updateFrame('') sleep(1/self.fps) # sleep equivalent of FPS else: sleep(1/10) # stop showing the last frame self._stopped = True # stop all other processs; will cause no trouble self._current_frame = '' self.updateFrame('') def _stop(self): self._stopped = True if self._sync_audio: self._audio_inst._not_stopped = False self._ffmpeg_inst.quit() def updateFrame(self, frame): self.frameUpdate.emit(frame) def _updater(self): #conts = os.listdir(self.folder) # if user has called the stop or pause function # we will need to reset it in order to restart play self._stopped = False self._paused = False self._seek_frame = 0 # initialize remaining delay rem_delay = 0.0 # Make sure convertion has started if len(self._stills_content) < 1: rem_delay = self._delay - 1.5 sleep(1.5) if rem_delay < 0: rem_delay = self._delay # about to play self.aboutToPlay.emit(rem_delay) # Play audio if self._play_audio and self._has_audio: self.play_audio_file(rem_delay) # Delay if self._delay: # sleep remaining delay sleep(rem_delay) if self._play_audio and self._has_audio: while not self._audio_inst.playing: sleep(0.1) sleep(0.5) # print(self._audio_inst.playing, 'yep') self._start_time = time() # set the universal start time self.setTime() self.setFrameNo() while not self._stopped and self._frame_no != self._stills_len: #t1 = time() filename = f'vid_{str(self._frame_no+1)}.jpg' # use still type if not self._paused: self._current_frame = f"file:///{self.folder}/{filename}" self.updateFrame('') sleep(1/self.fps) # sleep equivalent of FPS else: sleep(1/10) # stop showing the last frame self._stopped = True # stop all other processs; will cause no trouble self._current_frame = '' self.updateFrame('') @pyqtProperty(bool, notify=aspectRatioChanged) def aspectRatio(self): return self._aspect_ratio @aspectRatio.setter def aspectRatio(self, value): self._aspect_ratio = value @pyqtProperty('QString', notify=frameUpdate) def currentFrame(self): return self._current_frame @currentFrame.setter def currentFrame(self, frame): self._current_frame = frame @pyqtProperty(bool, notify=delayChanged) def delay(self): return self._delay @delay.setter def delay(self, value): self._delay = value @pyqtProperty(str, notify=durationChanged) def duration(self): return self._duration @duration.setter def duration(self, value): self._duration = value @pyqtProperty('int') def framesPerSecond(self): return self.fps @framesPerSecond.setter def framesPerSecond(self, fps): self.fps = fps @pyqtSlot() def pause(self): u_thread = threading.Thread(target = self._pause) u_thread.daemon = True u_thread.start() @pyqtSlot(str) def play(self, fileName): u_thread = threading.Thread(target = self._play, args=[fileName]) u_thread.daemon = True u_thread.start() @pyqtSlot() def resume(self): u_thread = threading.Thread(target = self._resume) u_thread.daemon = True u_thread.start() @pyqtSlot(int) def seek(self, seconds): u_thread = threading.Thread(target = self._seek, args=[seconds]) u_thread.daemon = True self._seek_calls += 1 u_thread.start() @pyqtProperty('QString') def source(self): return self._source @source.setter def source(self, source): self._source = source @pyqtSlot() def stop(self): u_thread = threading.Thread(target = self._stop) u_thread.daemon = True u_thread.start() @pyqtProperty(int, notify=tileChanged) def tile(self): return self._tile @tile.setter def tile(self, value): self._tile = value if self._tile > 2 and self._tile < 6: self._tile_enumeration = value @pyqtProperty(int, notify=tileEnumChanged) def tileEnumeration(self): return self._tile_enumeration @tileEnumeration.setter def tileEnumeration(self, value): pass @pyqtSlot() def updater(self): # Avoid multiple playing instances self._stopped = True self._frame_no = 0 sleep(0.3) u_thread = threading.Thread(target = self._updater) u_thread.daemon = True u_thread.start()