def __init__(self, parent, video, vid_id, clipboard, status_bar): QWidget.__init__(self, parent=parent) self.clipboard = clipboard self.status_bar = status_bar self.video = video self.id = vid_id self.parent = parent self.root = parent.root # MainWindow # parent.parent.parent.bind('<KeyPress-ctrl>', self.key_press_ctrl) # parent.parent.parent.bind('<KeyRelease-ctrl>', self.key_release_ctrl) self.pref_height = read_config('Gui', 'tile_pref_height') self.pref_width = read_config('Gui', 'tile_pref_width') self.setFixedSize(self.pref_width, self.pref_height) self.layout = QVBoxLayout() self.layout.setContentsMargins(0, 0, 0, 4) self.thumbnail_widget = self.init_thumbnailtile() self.layout.addWidget(self.thumbnail_widget) self.title_widget = TitleTile(video.title, self) self.layout.addWidget(self.title_widget) self.channel_widget = ChannelTile(video.channel_title, self) self.layout.addWidget(self.channel_widget) self.date_widget = DateTile('', self) self.layout.addWidget(self.date_widget) self.setLayout(self.layout) self.set_video(video)
def __init__(self, parent, root, main_model: MainModel): super(GridView, self).__init__(parent=parent) self.logger = create_logger(__name__) self.setMinimumSize(0, 0) self.parent = parent self.root = root # MainWindow self.clipboard = self.root.clipboard self.status_bar = self.root.status_bar self.buffer = 10 self.bar_correction = 0 self.main_model = main_model self.pref_tile_height = read_config('Gui', 'tile_pref_height') self.pref_tile_width = read_config('Gui', 'tile_pref_width') self.q_labels = {} self.grid = QGridLayout() self.items_x = read_config('Gui', 'grid_view_x') self.items_y = read_config('Gui', 'grid_view_y') self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) self.grid.setContentsMargins(5, 5, 0, 0) self.grid.setSpacing(2) self.grid.setAlignment(Qt.AlignTop) self.setLayout(self.grid) self.setAutoFillBackground(True) self.set_bgcolor() self.main_model.grid_view_listener.redrawVideos.connect(self.redraw_videos)
def download_thumbnails_threaded(input_vid_list, progress_listener=None): logger = create_logger(__name__ + ".download_thumbnails_threaded") thread_list = [] thread_limit = int(read_config('Threading', 'img_threads')) force_dl_best = read_config('Thumbnails', 'force_download_best') if progress_listener: progress_listener.setText.emit('Downloading thumbnails') vid_list = [] chunk_size = math.ceil(len(input_vid_list) / thread_limit) for i in range(0, len(input_vid_list), max(chunk_size, 1)): vid_list.append(input_vid_list[i:i + chunk_size]) counter = 0 logger.info( "Starting thumbnail download threads for {} videos in {} threads". format(len(input_vid_list), len(vid_list))) for vid_list_chunk in tqdm(vid_list, desc="Starting thumbnail threads", disable=read_config('Debug', 'disable_tqdm')): t = DownloadThumbnail(vid_list_chunk, force_dl_best=force_dl_best, progress_listener=progress_listener) thread_list.append(t) t.start() counter += 1 if progress_listener: progress_listener.updateProgress.emit() for t in tqdm(thread_list, desc="Waiting on thumbnail threads", disable=read_config('Debug', 'disable_tqdm')): t.join()
def run(self): app = QApplication(sys.argv) self.logger.info("Running Controller instance") vid_limit = read_config('Model', 'loaded_videos') start_with_stored_videos = read_config('Debug', 'start_with_stored_videos') model = MainModel([], vid_limit) if start_with_stored_videos: model.db_update_videos() else: model.remote_update_videos() model.db_update_downloaded_videos() self.logger.info( "Created MainModel: len(subscription_feed) = {}, vid_limit = {}". format(len(model.filtered_videos), vid_limit)) self.logger.info("Created QApplication({})".format(sys.argv)) window = MainWindow(app, model) window.show() self.logger.info("Executing Qt Application") app.exec_() self.logger.info("*** APPLICATION EXIT ***\n")
def run(self): """ Override threading.Thread.run() with its own code :return: """ try: # youtube = youtube_auth_keys() # self.videos = get_channel_uploads(self.youtube, channel_id) use_tests = read_config('Requests', 'use_tests') if self.deep_search: temp_videos = [] list_uploaded_videos_search(self.youtube, self.channel_id, temp_videos, self.search_pages) list_uploaded_videos(self.youtube, temp_videos, self.playlist_id, self.list_pages) self.merge_same_videos_in_list(temp_videos) self.videos.extend(temp_videos) elif use_tests: channel = db_session.query(Channel).get(self.channel_id) miss = read_config('Requests', 'miss_limit') pages = read_config('Requests', 'test_pages') extra_pages = read_config('Requests', 'extra_list_pages') list_pages = 0 list_videos = [] search_videos = [] for test in channel.tests: if test.test_pages > list_pages: list_pages = test.test_pages if test.test_miss < miss or test.test_pages > pages: db_session.remove() list_uploaded_videos_search(self.youtube, self.channel_id, search_videos, self.search_pages) break db_session.remove() list_uploaded_videos(self.youtube, list_videos, self.playlist_id, min(pages + extra_pages, list_pages + extra_pages)) if len(search_videos) > 0: return_videos = self.merge_two_videos_list_grab_info(list_videos, search_videos) else: return_videos = list_videos self.videos.extend(return_videos) else: use_playlist_items = read_config('Debug', 'use_playlistItems') if use_playlist_items: list_uploaded_videos(self.youtube, self.videos, self.playlist_id, self.list_pages) else: list_uploaded_videos_search(self.youtube, self.channel_id, self.videos, self.search_pages) except Exception as e: # Save the exception details, but don't rethrow. self.exc = e pass self.job_done = True
def config_get_filter_downloaded(self): show_watched = read_config('GridView', 'show_watched') show_dismissed = read_config('GridView', 'show_dismissed') update_filter = (Video.downloaded, ) if not show_watched: update_filter += (or_(Video.watched == false(), Video.watched == None), ) if not show_dismissed: update_filter += (~Video.discarded, ) return update_filter
def __init__(self, model): super().__init__() self.logger = create_logger(__name__ + '.YtDirListener') self.model = model self.newFile.connect(self.new_file) self.manualCheck.connect(self.manual_check) disable_dir_observer = read_config('Play', 'disable_dir_listener') if not disable_dir_observer: path = read_config('Play', 'yt_file_path', literal_eval=False) event_handler = VidEventHandler(self) self.observer = Observer() self.observer.schedule(event_handler, path) self.observer.start()
def create_logger(facility, logfile='debug.log'): # create logger if read_config('Logging', 'use_socket_log'): return log.getChild(facility) else: log_instance = logging.getLogger(facility) log_instance.setLevel(logging.DEBUG) # create file handler which logs even debug messages if not os.path.exists(LOGDIR): os.makedirs(LOGDIR) fh = logging.FileHandler(os.path.join(LOGDIR, logfile), encoding="UTF-8") fh.setLevel(logging.DEBUG) # create console handler with a higher log level ch = logging.StreamHandler() ch.setLevel(logging.ERROR) # patch the default logging formatter to use unicode format string logging._defaultFormatter = logging.Formatter(u"%(message)s") # create formatter and add it to the handlers formatter = logging.Formatter( u'%(asctime)s - %(name)s - %(levelname)s - %(message)s') fh.setFormatter(formatter) ch.setFormatter(formatter) # add the handlers to the logger log_instance.addHandler(fh) log_instance.addHandler(ch) return log_instance
def set_video(self, video): self.video = video self.set_tool_tip() self.title_widget.update_font() show_grab_method = read_config('Debug', 'show_grab_method') if show_grab_method: grab_method = '' grab_methods = video.grab_methods if len(grab_methods) > 0: grab_method = grab_methods[0] for grab in grab_methods[1:]: grab_method = '{}, {}'.format(grab_method, grab) self.channel_widget.setText("{} | {}".format( video.channel_title, grab_method)) else: self.channel_widget.setText(self.video.channel_title) vid_age = datetime.datetime.utcnow() - self.video.date_published self.date_widget.setText( self.strfdelta(vid_age, "{hours}:{minutes}:{seconds}", "{days} days ")) self.old_videos(vid_age) self.thumbnail_widget.setPixmap(QPixmap(video.thumbnail_path)) self.update()
def get_subs(self): """ Retrieve Channels table from DB :return: """ self.logger.info("Getting subscriptions") self.subs = get_subscriptions(read_config('Debug', 'cached_subs'))
def scroll_reached_end_play(self): add_value = read_config("Model", "loaded_videos") self.model.downloaded_videos_limit = self.model.downloaded_videos_limit + add_value self.logger.info( "Scroll for Play View reached end, updating videos limit to {}". format(self.model.downloaded_videos_limit)) self.model.db_update_downloaded_videos()
def scroll_reached_end_grid(self): add_value = read_config("Model", "loaded_videos") self.model.videos_limit = self.model.videos_limit + add_value self.logger.info( "Scroll for Sub Feed reached end, updating videos limit to {}". format(self.model.videos_limit)) self.model.db_update_videos()
def compare_db_filtered(videos, limit, discarded=False, downloaded=False): logger.info("Comparing filtered videos with DB") return_list = [] counter = 0 filter_days = read_config('Requests', 'filter_videos_days_old') DatabaseListener.static_instance.startRead.emit(threading.get_ident()) for video in videos: if filter_days >= 0: date = datetime.datetime.utcnow() - datetime.timedelta( days=filter_days) if video.date_published < date: break db_vid = get_vid_by_id(video.video_id) if db_vid: if db_vid.downloaded: if downloaded: continue if db_vid.discarded: if discarded: continue return_list.append(Video.to_video_d(video)) counter += 1 else: return_list.append(video) counter += 1 if counter >= limit: break DatabaseListener.static_instance.finishRead.emit(threading.get_ident()) db_session.remove() return return_list
def paintEvent(self, event): if self.p: if self.p.isNull(): self.logger.warning( "QPixmap self.p was NULL, replacing with 'Thumbnail N/A' image!" ) self.p = QPixmap(THUMBNAIL_NA_PATH) painter = QPainter(self) if read_config('Gui', 'keep_thumb_ar'): thumb = self.p.scaled(self.width(), self.height(), Qt.KeepAspectRatio, Qt.SmoothTransformation) painter.drawPixmap(0, 0, thumb) else: thumb = self painter.drawPixmap(thumb.rect(), thumb.p) pen = QPen(Qt.white) painter.setPen(pen) point = QPoint(thumb.width() * 0.65, thumb.height() * 0.85) rect = QRect(point, QSize(thumb.width() * 0.28, thumb.height() * 0.12)) painter.fillRect(rect, QBrush(QColor(0, 0, 0, 180))) painter.drawText(rect, Qt.AlignCenter, format(self.parent.video.duration)) self.add_overlay(painter, thumb)
def db_update_videos(self, filtered=True): self.logger.info("Getting newest stored videos from DB") # FIXME: only does filtered videos if filtered: show_downloaded = read_config('SubFeed', 'show_downloaded') show_dismissed = read_config('GridView', 'show_dismissed') update_filter = () if not show_downloaded: update_filter += (~Video.downloaded, ) if not show_dismissed: update_filter += (~Video.discarded, ) self.filtered_videos = get_newest_stored_videos( self.videos_limit, filters=update_filter) self.grid_view_listener.hiddenVideosChanged.emit() else: self.videos = get_newest_stored_videos(self.videos_limit, filtered)
def get_default_player(self): config_default_player = read_config('Player', 'default_player', literal_eval=False) if config_default_player: return config_default_player else: return None
def list_uploaded_videos(youtube_key, videos, uploads_playlist_id, req_limit): """ Get a list of videos in a playlist :param req_limit: :param videos: :param youtube_key: :param uploads_playlist_id: :return: [list(dict): videos, dict: statistics] """ # Retrieve the list of videos uploaded to the authenticated user's channel. playlistitems_list_request = youtube_key.playlistItems().list( maxResults=50, part='snippet', playlistId=uploads_playlist_id) searched_pages = 0 while playlistitems_list_request: searched_pages += 1 playlistitems_list_response = playlistitems_list_request.execute() # Grab information about each video. for search_result in playlistitems_list_response['items']: if read_config('Debug', 'log_list') and read_config( 'Debug', 'log_needle') != 'unset': if search_result['snippet']['channelTitle'] == str( read_config('Debug', 'log_needle')): logger_list_search.debug( "list():\t {} ({}) - {} | Desc: {}".format( search_result['snippet']['channelTitle'], search_result['snippet']['publishedAt'], search_result['snippet']['title'], search_result['snippet']['description'])) if read_config('Debug', 'log_list') and read_config( 'Debug', 'log_needle') == 'unset': logger_list_search.debug("list():\t {} ({}) - {}".format( search_result['snippet']['channelTitle'], search_result['snippet']['publishedAt'], search_result['snippet']['title'])) videos.append( VideoD.playlist_item_new_video_d( search_result, grab_methods=[GRAB_METHOD_LIST])) if searched_pages >= req_limit: break playlistitems_list_request = youtube_key.playlistItems().list_next( playlistitems_list_request, playlistitems_list_response)
def get_best_thumbnail(vid): for i in range(5): quality = read_config('Thumbnails', '{}'.format(i)) if quality in vid.thumbnails: return_dict = vid.thumbnails[quality] return_dict.update({'quality': quality}) return return_dict return {}
def toggle_ascending_sort(self): """ Sends a testChannels signal :return: """ toggle = read_config('PlaySort', 'ascending_date') set_config('PlaySort', 'ascending_date', format(not toggle)) self.main_model.grid_view_listener.updateFromDb.emit()
def add_overlay(self, painter, thumb): url_as_path = read_config('Play', 'use_url_as_path') show_watched = read_config('GridView', 'show_watched') show_dismissed = read_config('GridView', 'show_dismissed') if ((not self.parent.video.vid_path) and not url_as_path) or (self.parent.video.watched and show_watched) \ or (self.parent.video.discarded and show_dismissed): if not self.parent.video.vid_path: overlay = QPixmap(OVERLAY_NO_FILE_PATH) elif self.parent.video.watched: overlay = QPixmap(OVERLAY_WATCHED_PATH) else: overlay = QPixmap(OVERLAY_DISMISSED_PATH) resize_ratio = min(thumb.width() * 0.7 / thumb.width(), thumb.height() * 0.3 / thumb.height()) new_size = QSize(thumb.width() * resize_ratio, thumb.height() * resize_ratio) overlay = overlay.scaled(new_size, Qt.KeepAspectRatio, Qt.SmoothTransformation) point = QPoint(thumb.width() - overlay.width(), 0) painter.drawPixmap(point, overlay)
def manual_check(self): youtube_folder = read_config("Play", "yt_file_path", literal_eval=False) CheckYoutubeFolderForNew( youtube_folder, db_listeners=[ self.model.grid_view_listener.downloadedVideosChangedinDB, self.model.grid_view_listener.updateGridViewFromDb ]).start()
def config_get_sort_downloaded(self): ascending_date = read_config('PlaySort', 'ascending_date') update_sort = (asc(Video.watch_prio), ) if ascending_date: update_sort += (asc(Video.date_downloaded), asc(Video.date_published)) else: update_sort += (desc(Video.date_downloaded), desc(Video.date_published)) return update_sort
def open_with_default_application(file_path): """ Determines and launches file with its default application: Image :param file_path: :return: """ # Determine file type if is_image(file_path): custom_app = read_config('DefaultApp', 'Image', literal_eval=False) print(custom_app) else: logger.error( "No default application extensions specified for {}".format( file_path)) return try: if sys.platform.startswith('linux'): if custom_app: try: subprocess.call(custom_app, file_path) except Exception as e: logger.error("{} (custom)".format( FAIL_LOG_MSG.format(file_path, custom_app)), exc_info=e) pass else: for open_file_handler in OPEN_FILE_HANDLERS_LINUX: try: subprocess.call([open_file_handler, file_path]) except Exception as e: logger.debug(FAIL_LOG_MSG.format( file_path, open_file_handler), exc_info=e) pass # Reached end of loop, no valid applications logger.error(FAIL_ALL_LOG_MSG.format(file_path)) else: # OS = Windows if custom_app: try: subprocess.Popen([ custom_app, file_path ]) # os.startfile doesn't really do arguments... except Exception as e: logger.error("{} (custom)".format( FAIL_LOG_MSG.format(file_path, custom_app)), exc_info=e) pass else: os.startfile(file_path) except Exception as e_anything: logger.exception(e_anything) pass
def set_tool_tip(self): if not read_config('Debug', 'disable_tooltips'): if read_config('Gui', 'tooltip_pictures'): text_element = read_config('Gui', 'tooltip_picture_size') thumb_width = read_config('Gui', 'tooltip_picture_width') thumb_height = read_config('Gui', 'tooltip_picture_height') resized_thumb = resize_thumbnail(self.video.thumbnail_path, thumb_width, thumb_height) self.setToolTip( "<{} style='text-align:center;'><img src={} style='float:below'>{}: {}</{}>" .format(text_element, resized_thumb, self.video.channel_title, self.video.title, text_element)) # if self.root.hotkey_ctrl_down: # print(self.root.hotkey_ctrl_down) # self.showTooltip() else: self.setToolTip("{}: {}".format(self.video.channel_title, self.video.title))
def old_videos(self, vid_age): if read_config('Gui', 'grey_old_videos'): if vid_age > datetime.timedelta(days=1): pal = self.palette() pal.setColor(QPalette.Background, Qt.lightGray) self.setAutoFillBackground(True) self.setPalette(pal) else: pal = self.palette() pal.setColor(QPalette.Background, Qt.white) self.setAutoFillBackground(True) self.setPalette(pal)
def mark_downloaded(self): """ Mark the video as downloaded :return: """ logger.info('Mark downloaded: {:2d}: {} {} - {}'.format( self.id, self.video.url_video, self.video.channel_title, self.video.title)) update_history('Downloaded:\t{}\t{} - {} '.format( self.video.url_video, self.video.channel_title, self.video.title)) self.video.downloaded = True self.video.date_downloaded = datetime.datetime.utcnow() self.parent.main_model.grid_view_listener.tileDownloaded.emit( self.video) if read_config('Gui', 'enable_auto_copy_to_clipboard'): self.copy_url() if read_config('Youtube-dl', 'use_youtube_dl'): self.status_bar.showMessage( 'Downloading video with youtube-dl: {} ({} - {})'.format( self.video.url_video, self.video.channel_title, self.video.title))
def remote_update_videos(self, filtered=True, refresh_type=LISTENER_SIGNAL_NORMAL_REFRESH): self.logger.info("Reloading and getting newest videos from YouTube") if filtered: show_downloaded = not read_config('SubFeed', 'show_downloaded') show_dismissed = not read_config('GridView', 'show_dismissed') self.filtered_videos = refresh_and_get_newest_videos( self.videos_limit, progress_listener=self.status_bar_listener, refresh_type=refresh_type, filter_discarded=show_dismissed, filter_downloaded=show_downloaded) self.grid_view_listener.hiddenVideosChanged.emit() else: self.videos = refresh_and_get_newest_videos( self.videos_limit, filtered, self.status_bar_listener, refresh_type=refresh_type)
def refresh_videos(self, refresh_type): """ Fetches new videos and reloads the subscription feed :return: """ self.logger.info("Reloading subfeed") hide_downloaded = read_config('Gui', 'hide_downloaded') if hide_downloaded: self.model.remote_update_videos(refresh_type=refresh_type) # self.model.grid_view_listener.hiddenVideosChanged.emit() else: self.model.remote_update_videos(refresh_type=refresh_type) self.logger.error('NOT IMPLEMENTED: disabled hide_downloaded')
def cli(no_gui, test_channels, update_watch_prio, set_watched_day): logger = create_logger(__name__) if no_gui: run_print() if update_watch_prio: videos = db_session.query(Video).all() watch_prio = read_config('Play', 'default_watch_prio') logger.debug("Setting watch_prio {}, for: {} videos".format(watch_prio, len(videos))) for video in videos: video.watch_prio = watch_prio db_session.commit() return if set_watched_day: videos = db_session.query(Video).filter(or_(Video.downloaded == True, (Video.vid_path.is_(None)))).all() for video in videos: vid_age = datetime.datetime.utcnow() - video.date_published if vid_age > datetime.timedelta(days=int(set_watched_day)): logger.debug("Setting watched, {} - {} - {}".format(vid_age, video.title, video.__dict__)) video.watched = True db_session.commit() return if test_channels: run_channels_test() else: """ PyQT raises and catches exceptions, but doesn't pass them along. Instead it just exits with a status of 1 to show an exception was caught. """ # Back up the reference to the exceptionhook sys._excepthook = sys.excepthook def my_exception_hook(exctype, value, traceback): # Ignore KeyboardInterrupt so a console python program can exit with Ctrl + C. if issubclass(exctype, KeyboardInterrupt): sys.__excepthook__(exctype, value, traceback) return # Log the exception with the logger logger.critical("Intercepted Exception", exc_info=(exctype, value, traceback)) # Call the normal Exception hook after sys._excepthook(exctype, value, traceback) # sys.exit(1) # Alternatively, exit # Set the exception hook to our wrapping function sys.excepthook = my_exception_hook run_with_gui()
def download_video(video, db_update_listeners=None, youtube_dl_finished_listener=None): use_youtube_dl = read_config('Youtube-dl', 'use_youtube_dl') video.downloaded = True video.date_downloaded = datetime.datetime.utcnow() UpdateVideo(video, update_existing=True, finished_listeners=db_update_listeners).start() if use_youtube_dl: download_progress_signal = DownloadHandler.download_using_youtube_dl( video, youtube_dl_finished_listener) DownloadHandler.static_self.newYTDLDownlaod.emit( download_progress_signal)