Version 380

This commit is contained in:
Hydrus Network Developer 2020-01-15 20:08:23 -06:00
parent 1f2f058a9c
commit 70671ee703
47 changed files with 1776 additions and 649 deletions

View File

@ -1,16 +1,16 @@
## Hydrus Network (Client and Server)
The hydrus network client is an application written for Anon and other internet-fluent media nerds who have large image/swf/webm collections. It browses with tags instead of folders, a little like a *booru on your desktop. Tags and files can be anonymously shared through custom servers that any user may run. Everything is free, and the source code is included with the release. It is developed for Windows, but fairly functional builds for Linux and OS X are released at the same time.
The hydrus network client is an application written for Anon and other internet-fluent media nerds who have large image/swf/webm collections. It browses with tags instead of folders, a little like a booru on your desktop. Advanced users can share tags and files anonymously through custom servers that any user may run. Everything is free, privacy is the first concern, and the source code is included with the release. Releases are available for Windows, Linux, and macOS.
I am continually working on the software and try to put out a new release every Wednesday by 8pm Eastern.
I am continually working on the software and try to put out a new release every Wednesday by 8pm EST.
This github repository is currently a weekly sync with my home dev environment, where I work on hydrus by myself. Feel free to fork, but please don't make pull requests at this time. I am also not active on Github, so if you have feedback of any sort, please email me, post on my 8chan board, or message me on tumblr or twitter or the discord.
This github repository is currently a weekly sync with my home dev environment, where I work on hydrus by myself. Feel free to fork, but please do not make pull requests. I am also not active on Github, so if you have feedback of any sort, please email me, post on my 8kun or Endchan boards, or message me on tumblr or twitter or the discord.
The client can do quite a lot! Please check out the help inside the release or [here](http://hydrusnetwork.github.io/hydrus/help), which includes a comprehensive getting started guide.
* [homepage](http://hydrusnetwork.github.io/hydrus/)
* [email](mailto:hydrus.admin@gmail.com)
* [8chan board](https://8ch.net/hydrus/index.html)
* [8kun board](https://8ch.net/hydrus/index.html)
* [endchan bunker](https://endchan.net/hydrus/)
* [twitter](https://twitter.com/hydrusnetwork)
* [tumblr](http://hydrus.tumblr.com/)

View File

@ -8,6 +8,72 @@
<div class="content">
<h3>changelog</h3>
<ul>
<li><h3>version 380</h3></li>
<ul>
<li>basic mpv support is added. it comes with the windows build this week, and is a prototype meant for initial testing. the library is optional. users who run from source will want 'python-mpv' added via pip and libmpv available on their PATH, more details in running_from_source help</li>
<li>took an qt-mpv example kindly provided by a user, updated it to work with the hydrus environment, and integrated it into the client as a new choosable view type under audio/video filetypes under options->media for advanced users</li>
<li>reworked how the 'start paused' and 'start with embed button' media viewer options work under options->media. these are now separate checkboxes, not combined with the underlying 'show action'. existing embed/paused show actions should be converted automatically to the correct new values</li>
<li>unfortunately, due to some python/qt/libmpv wrapper mouse interaction issues, mpv's 'on screen controller' overlay is not available</li>
<li>for now, left click pause/plays the mpv window, just like the native mpv window.</li>
<li>preview/next frame shortcuts should work for the mpv window when playing video</li>
<li>no volume/mute controls yet, these will come in the coming weeks, including global mute settings</li>
<li>updated media show and sizing code to account for mpv widgets</li>
<li>reworked my animation scanbar to talk to mpv, and for my mpv window to talk back to it</li>
<li>improved the animation scanbar to be more flexible when frame position and num_frames are not available, both in displaying info and calculating scanbar seek clicks</li>
<li>mpv api version added to help->about</li>
<li>.</li>
<li>new downloader objects:</li>
<li>thanks to a user, updated the 'pixiv artist page' url class to a new object that covers more situations. the defunct 'pixiv artist gallery page' url class is removed</li>
<li>added 8kun and vch.moe download support. I got started on julay, smug, and endchan, but they were a little more tricky and I couldn't finish them in time--fingers crossed, next week</li>
<li>.</li>
<li>menu quality of life:</li>
<li>a right-click on thumbnail whitespace will now not send a 'deselect all' event! feel free to right-click in empty space to do an easy remove->selected</li>
<li>remorked the tag menu layout to move less frequently used actions down:</li>
<li>- moved the discard/require/permit/exclude search predicate actions down</li>
<li>- moved 'open in a new page' below select and copy</li>
<li>- moved copy above select</li>
<li>and some misc menu layout improvement on this menu</li>
<li>fixed some labelling with the discard/require/permit/exclude verbs on negated tags</li>
<li>right-clicking on system search predicates now shows the 'copy' menu correctly</li>
<li>system predicates that offer easy inverse versions (like inbox/archive) should now offer the 'exclude' verb</li>
<li>when right-clicking on a single tag that has siblings, its siblings and those siblings' subtags will now be listed in the copy menu!</li>
<li>copying 'all' tags from a list menu, with or without counts, will now always copy them in the list order</li>
<li>across the program, all menu 'labels' (menu text items that do not have a submenu and have no associated action, like 'imported 3 years 7 months ago') will now copy their text to the clipboard. let's see how it goes</li>
<li>.</li>
<li>other ui quality of life:</li>
<li>across the program's UI, filetypes are now referred to with simpler terms rather than technical mimetypes. instead of 'image/jpg', it is now typically just 'jpeg'</li>
<li>the 'remove selected' buttons on the gallery and watcher pages are now smaller trash icon buttons</li>
<li>the new page chooser will now auto-dismiss if it loses focus--so if you accidentally launch it with a middle-/double-click somewhere, just click again and it'll go away</li>
<li>hitting enter or return on the new page chooser now picks the 'first' button, scanning from the top-left. hitting enter twice now typically opens a new 'my files' search page</li>
<li>added pause_media and pause_play_media shortcuts to the media_viewer shortcut set. new clients will start with space keypress performing pause_play_media</li>
<li>added pause_play_slideshow shortcut to the media_viewer_browser shortcut set. this shortcut is no longer hardcoded by space keypress</li>
<li>the six default shortcut sets now have a small description text on their edit panels</li>
<li>the options->media edit panels now enable/disable widgets better based on current media/preview action</li>
<li>added a checkbox to _options->gui pages_ to set whether middle-clicking a tag in the media viewer or a child tag manager to open a tag search page will switch to the main gui. default is false</li>
<li>mr bones now reports total files, total filesize, and average filesize</li>
<li>mr bones now loads your fate asynchronously</li>
<li>.</li>
<li>the rest:</li>
<li>added tentative and simple realvideo (.rm) and realaudio (.ra) support--seems to work ok, but some weirder variable bit rate formats may not, and I have collapsed the various different extensions just down to .rm or .ra</li>
<li>added trueaudio (.tta) audio support</li>
<li>fixed a bug from the recent search optimisations where a bare inbox search would not cross-reference with the file domain (so some trash could show up in a simple inbox/'my files' query)</li>
<li>fixed an issue with searching for known urls by url class where the class was for a third-or-higher-level domain and was not set to match subdomains (this hit 4chan file urls for a few users)</li>
<li>fixed the issue with 'open externally' button panel not clearing their backgrounds properly</li>
<li>fixed some of the new unusual stretchy layouts in the options dialog</li>
<li>removed overhead from subscriptions' 'separate' operation, which should stop super CPU hang when trying to split a subscription with hundreds of thousands of urls</li>
<li>fixed an issue where the advanced file delete dialog would not show the simple 'permanent delete' option when launched from the media viewer's right-click menu</li>
<li>fixed the select/remove actions for local/remote</li>
<li>fixed 'set_media_focus' from manage tags to correctly activate the underlying media viewer as well as set focus</li>
<li>stopped the 'file lookup script' status control from resizing so wide when it fetches a url</li>
<li>fixed a rare mouse wheel event handling bug in the media viewer</li>
<li>reduced db overhead of the 'loading x/y' results generation routine. this _may_ help some users who had very slow media result loading</li>
<li>cleaned up how the server reports a bootup-action error such as 'cannot shut down server since it is not running'--this is now a simple statement to console, not a full error with trace</li>
<li>improved client shutdown when a system session shutdown call arrives at the same time as a user shutdown request--the core shutdown routine should now only occur once</li>
<li>fixed an issue with thumbnail presentation on collections that have their contents deleted during the thumbnail generation call</li>
<li>misc wx->Qt layout conversion improvements</li>
<li>updated the github readme to reflect some new links and so on</li>
<li>misc code cleanup</li>
</ul>
<li><h3>version 379</h3></li>
<ul>
<li>downloaders:</li>

View File

@ -71,7 +71,7 @@
<p>Shut the client down while you run the backup, obviously.</p>
</li>
</ul>
<p class="warning">Do not put your live database in a folder than continuously syncs to a cloud backup. Many of these services will interfere with a running client and can cause database corruption. If you still want to use a system like this, either turn the sync off while the client is running, or use the above backup workflows to safely backup your client to a separate folder that syncs to the cloud.</p>
<p class="warning">Do not put your live database in a folder that continuously syncs to a cloud backup. Many of these services will interfere with a running client and can cause database corruption. If you still want to use a system like this, either turn the sync off while the client is running, or use the above backup workflows to safely backup your client to a separate folder that syncs to the cloud.</p>
<p>I recommend you always backup before you update, just in case there is a problem with my code that breaks your database. If that happens, please <a href="contact.html">contact me</a>, describing the problem, and revert to the functioning older version. I'll get on any problems like that immediately.</p>
<p class="right"><a href="getting_started_files.html">Let's import some files! ----></a></p>
</div>

View File

@ -967,8 +967,16 @@ class ThumbnailCache( object ):
display_media = media.GetDisplayMedia()
magic_score = self._magic_mime_thumbnail_ease_score_lookup[ display_media.GetMime() ]
hash = display_media.GetHash()
if display_media is None:
magic_score = self._magic_mime_thumbnail_ease_score_lookup[ None ]
hash = ''
else:
magic_score = self._magic_mime_thumbnail_ease_score_lookup[ display_media.GetMime() ]
hash = display_media.GetHash()
return ( magic_score, hash )
@ -1230,9 +1238,12 @@ class ThumbnailCache( object ):
( page_key, media ) = result
self.GetThumbnail( media )
page_keys_to_rendered_medias[ page_key ].append( media )
if media.GetDisplayMedia() is not None:
self.GetThumbnail( media )
page_keys_to_rendered_medias[ page_key ].append( media )
if len( page_keys_to_rendered_medias ) > 0:

View File

@ -157,58 +157,63 @@ import_folder_string_lookup[ IMPORT_FOLDER_DELETE ] = 'delete the file'
import_folder_string_lookup[ IMPORT_FOLDER_IGNORE ] = 'leave the file alone, do not reattempt it'
import_folder_string_lookup[ IMPORT_FOLDER_MOVE ] = 'move the file'
MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL = 0
MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL_PAUSED = 1
MEDIA_VIEWER_ACTION_SHOW_WITH_NATIVE = 0
MEDIA_VIEWER_ACTION_SHOW_WITH_NATIVE_PAUSED = 1
MEDIA_VIEWER_ACTION_SHOW_BEHIND_EMBED = 2
MEDIA_VIEWER_ACTION_SHOW_BEHIND_EMBED_PAUSED = 3
MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON = 4
MEDIA_VIEWER_ACTION_DO_NOT_SHOW_ON_ACTIVATION_OPEN_EXTERNALLY = 5
MEDIA_VIEWER_ACTION_DO_NOT_SHOW = 6
MEDIA_VIEWER_ACTION_SHOW_WITH_MPV = 7
media_viewer_action_string_lookup = {}
media_viewer_action_string_lookup[ MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL ] = 'show as normal'
media_viewer_action_string_lookup[ MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL_PAUSED ] = 'show as normal, but start paused'
media_viewer_action_string_lookup[ MEDIA_VIEWER_ACTION_SHOW_BEHIND_EMBED ] = 'show, but initially behind an embed button'
media_viewer_action_string_lookup[ MEDIA_VIEWER_ACTION_SHOW_BEHIND_EMBED_PAUSED ] = 'show, but initially behind an embed button, and start paused'
media_viewer_action_string_lookup[ MEDIA_VIEWER_ACTION_SHOW_WITH_NATIVE ] = 'show with native hydrus viewer'
media_viewer_action_string_lookup[ MEDIA_VIEWER_ACTION_SHOW_WITH_NATIVE_PAUSED ] = 'show as normal, but start paused -- obselete'
media_viewer_action_string_lookup[ MEDIA_VIEWER_ACTION_SHOW_BEHIND_EMBED ] = 'show, but initially behind an embed button -- obselete'
media_viewer_action_string_lookup[ MEDIA_VIEWER_ACTION_SHOW_BEHIND_EMBED_PAUSED ] = 'show, but initially behind an embed button, and start paused -- obselete'
media_viewer_action_string_lookup[ MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON ] = 'show an \'open externally\' button'
media_viewer_action_string_lookup[ MEDIA_VIEWER_ACTION_DO_NOT_SHOW_ON_ACTIVATION_OPEN_EXTERNALLY ] = 'do not show in the media viewer. on thumbnail activation, open externally'
media_viewer_action_string_lookup[ MEDIA_VIEWER_ACTION_DO_NOT_SHOW ] = 'do not show at all'
media_viewer_action_string_lookup[ MEDIA_VIEWER_ACTION_SHOW_WITH_MPV ] = 'show using mpv'
static_full_support = [ MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, MEDIA_VIEWER_ACTION_SHOW_BEHIND_EMBED, MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, MEDIA_VIEWER_ACTION_DO_NOT_SHOW_ON_ACTIVATION_OPEN_EXTERNALLY ]
animated_full_support = [ MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL_PAUSED, MEDIA_VIEWER_ACTION_SHOW_BEHIND_EMBED, MEDIA_VIEWER_ACTION_SHOW_BEHIND_EMBED_PAUSED, MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, MEDIA_VIEWER_ACTION_DO_NOT_SHOW_ON_ACTIVATION_OPEN_EXTERNALLY ]
no_support = [ MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, MEDIA_VIEWER_ACTION_DO_NOT_SHOW_ON_ACTIVATION_OPEN_EXTERNALLY, MEDIA_VIEWER_ACTION_DO_NOT_SHOW ]
unsupported_media_actions = [ MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, MEDIA_VIEWER_ACTION_DO_NOT_SHOW_ON_ACTIVATION_OPEN_EXTERNALLY, MEDIA_VIEWER_ACTION_DO_NOT_SHOW ]
static_media_actions = [ MEDIA_VIEWER_ACTION_SHOW_WITH_NATIVE ] + unsupported_media_actions
animated_media_actions = [ MEDIA_VIEWER_ACTION_SHOW_WITH_MPV ] + static_media_actions
audio_media_actions = [ MEDIA_VIEWER_ACTION_SHOW_WITH_MPV ] + unsupported_media_actions
# actions, can_start_paused, can_start_with_embed
static_full_support = ( static_media_actions, False, True )
animated_full_support = ( animated_media_actions, True, True )
audio_full_support = ( audio_media_actions, True, True )
no_support = ( unsupported_media_actions, False, False )
media_viewer_capabilities = {}
media_viewer_capabilities[ HC.IMAGE_JPEG ] = static_full_support
media_viewer_capabilities[ HC.IMAGE_PNG ] = static_full_support
media_viewer_capabilities[ HC.IMAGE_WEBP ] = static_full_support
media_viewer_capabilities[ HC.IMAGE_TIFF ] = static_full_support
media_viewer_capabilities[ HC.IMAGE_ICON ] = static_full_support
media_viewer_capabilities[ HC.IMAGE_APNG ] = animated_full_support
media_viewer_capabilities[ HC.IMAGE_GIF ] = animated_full_support
media_viewer_capabilities[ HC.APPLICATION_FLASH ] = no_support
for mime in HC.SEARCHABLE_MIMES:
if mime in HC.IMAGES_THAT_CAN_HAVE_ANIMATION:
media_viewer_capabilities[ mime ] = animated_full_support
elif mime in HC.IMAGES:
media_viewer_capabilities[ mime ] = static_full_support
elif mime in HC.VIDEO:
media_viewer_capabilities[ mime ] = animated_full_support
elif mime in HC.AUDIO:
media_viewer_capabilities[ mime ] = audio_full_support
else:
media_viewer_capabilities[ mime ] = no_support
media_viewer_capabilities[ HC.APPLICATION_PDF ] = no_support
media_viewer_capabilities[ HC.APPLICATION_PSD ] = no_support
media_viewer_capabilities[ HC.APPLICATION_ZIP ] = no_support
media_viewer_capabilities[ HC.APPLICATION_7Z ] = no_support
media_viewer_capabilities[ HC.APPLICATION_RAR ] = no_support
media_viewer_capabilities[ HC.VIDEO_AVI ] = animated_full_support
media_viewer_capabilities[ HC.VIDEO_FLV ] = animated_full_support
media_viewer_capabilities[ HC.VIDEO_MOV ] = animated_full_support
media_viewer_capabilities[ HC.VIDEO_MP4 ] = animated_full_support
media_viewer_capabilities[ HC.VIDEO_MKV ] = animated_full_support
media_viewer_capabilities[ HC.VIDEO_WEBM ] = animated_full_support
media_viewer_capabilities[ HC.VIDEO_MPEG ] = animated_full_support
media_viewer_capabilities[ HC.VIDEO_WMV ] = animated_full_support
media_viewer_capabilities[ HC.AUDIO_M4A ] = no_support
media_viewer_capabilities[ HC.AUDIO_MP3 ] = no_support
media_viewer_capabilities[ HC.AUDIO_OGG ] = no_support
media_viewer_capabilities[ HC.AUDIO_FLAC ] = no_support
media_viewer_capabilities[ HC.AUDIO_WMA ] = no_support
media_viewer_capabilities[ HC.APPLICATION_HYDRUS_UPDATE_CONTENT ] = no_support
media_viewer_capabilities[ HC.APPLICATION_HYDRUS_UPDATE_DEFINITIONS ] = no_support
@ -406,11 +411,20 @@ shortcut_mouse_string_lookup[ SHORTCUT_MOUSE_SCROLL_RIGHT ] = 'scroll right'
SHORTCUTS_RESERVED_NAMES = [ 'archive_delete_filter', 'duplicate_filter', 'media', 'main_gui', 'media_viewer_browser', 'media_viewer' ]
shortcut_names_to_descriptions = {}
shortcut_names_to_descriptions[ 'archive_delete_filter' ] = 'Navigation actions for the media viewer during an archive/delete filter. Mouse shortcuts should work.'
shortcut_names_to_descriptions[ 'duplicate_filter' ] = 'Navigation actions for the media viewer during a duplicate filter. Mouse shortcuts should work.'
shortcut_names_to_descriptions[ 'media' ] = 'Actions to alter metadata for media in the media viewer or the thumbnail grid.'
shortcut_names_to_descriptions[ 'main_gui' ] = 'Actions to control pages in the main window of the program.'
shortcut_names_to_descriptions[ 'media_viewer_browser' ] = 'Navigation actions for the regular browsable media viewer.'
shortcut_names_to_descriptions[ 'media_viewer' ] = 'Zoom and pan actions for any media viewer.'
# shortcut commands
SHORTCUTS_MEDIA_ACTIONS = [ 'manage_file_tags', 'manage_file_ratings', 'manage_file_urls', 'manage_file_notes', 'archive_file', 'inbox_file', 'delete_file', 'export_files', 'export_files_quick_auto_export', 'remove_file_from_view', 'open_file_in_external_program', 'open_selection_in_new_page', 'launch_the_archive_delete_filter', 'copy_bmp', 'copy_file', 'copy_path', 'copy_sha256_hash', 'get_similar_to_exact', 'get_similar_to_very_similar', 'get_similar_to_similar', 'get_similar_to_speculative', 'duplicate_media_set_alternate', 'duplicate_media_set_alternate_collections', 'duplicate_media_set_custom', 'duplicate_media_set_focused_better', 'duplicate_media_set_focused_king', 'duplicate_media_set_same_quality', 'open_known_url' ]
SHORTCUTS_MEDIA_VIEWER_ACTIONS = [ 'move_animation_to_previous_frame', 'move_animation_to_next_frame', 'switch_between_fullscreen_borderless_and_regular_framed_window', 'pan_up', 'pan_down', 'pan_left', 'pan_right', 'pan_top_edge', 'pan_bottom_edge', 'pan_left_edge', 'pan_right_edge', 'pan_vertical_center', 'pan_horizontal_center', 'zoom_in', 'zoom_out', 'switch_between_100_percent_and_canvas_zoom', 'flip_darkmode' ]
SHORTCUTS_MEDIA_VIEWER_BROWSER_ACTIONS = [ 'view_next', 'view_first', 'view_last', 'view_previous' ]
SHORTCUTS_MEDIA_VIEWER_ACTIONS = [ 'pause_media', 'pause_play_media', 'move_animation_to_previous_frame', 'move_animation_to_next_frame', 'switch_between_fullscreen_borderless_and_regular_framed_window', 'pan_up', 'pan_down', 'pan_left', 'pan_right', 'pan_top_edge', 'pan_bottom_edge', 'pan_left_edge', 'pan_right_edge', 'pan_vertical_center', 'pan_horizontal_center', 'zoom_in', 'zoom_out', 'switch_between_100_percent_and_canvas_zoom', 'flip_darkmode' ]
SHORTCUTS_MEDIA_VIEWER_BROWSER_ACTIONS = [ 'view_next', 'view_first', 'view_last', 'view_previous', 'pause_play_slideshow' ]
SHORTCUTS_MAIN_GUI_ACTIONS = [ 'refresh', 'refresh_all_pages', 'refresh_page_of_pages_pages', 'new_page', 'new_page_of_pages', 'new_duplicate_filter_page', 'new_gallery_downloader_page', 'new_url_downloader_page', 'new_simple_downloader_page', 'new_watcher_downloader_page', 'synchronised_wait_switch', 'set_media_focus', 'show_hide_splitters', 'set_search_focus', 'unclose_page', 'close_page', 'redo', 'undo', 'flip_darkmode', 'check_all_import_folders', 'flip_debug_force_idle_mode_do_not_set_this', 'show_and_focus_manage_tags_favourite_tags', 'show_and_focus_manage_tags_related_tags', 'show_and_focus_manage_tags_file_lookup_script_tags', 'show_and_focus_manage_tags_recent_tags', 'focus_media_viewer' ]
SHORTCUTS_DUPLICATE_FILTER_ACTIONS = [ 'duplicate_filter_this_is_better_and_delete_other', 'duplicate_filter_this_is_better_but_keep_both', 'duplicate_filter_exactly_the_same', 'duplicate_filter_alternates', 'duplicate_filter_false_positive', 'duplicate_filter_custom_action', 'duplicate_filter_skip', 'duplicate_filter_back' ]
SHORTCUTS_ARCHIVE_DELETE_FILTER_ACTIONS = [ 'archive_delete_filter_keep', 'archive_delete_filter_delete', 'archive_delete_filter_skip', 'archive_delete_filter_back' ]

View File

@ -85,7 +85,7 @@ class App( QW.QApplication ):
HG.emergency_exit = True
if hasattr( HG.client_controller, 'gui' ) and HG.client_controller.gui is not None:
if hasattr( HG.client_controller, 'gui' ) and HG.client_controller.gui is not None and QP.isValid( HG.client_controller.gui ):
HG.client_controller.gui.SaveAndClose()

View File

@ -5527,17 +5527,17 @@ class DB( HydrusDB.HydrusDB ):
done_files_info_predicates = False
if query_hash_ids is None:
if query_hash_ids is None or ( is_inbox and len( query_hash_ids ) == len( self._inbox_hash_ids ) ):
if file_service_key == CC.COMBINED_FILE_SERVICE_KEY:
query_hash_ids = self._GetHashIdsThatHaveTags( tag_service_key, include_current_tags, include_pending_tags )
query_hash_ids = intersection_update_qhi( query_hash_ids, self._GetHashIdsThatHaveTags( tag_service_key, include_current_tags, include_pending_tags ) )
else:
files_info_predicates.insert( 0, 'service_id = ' + str( file_service_id ) )
query_hash_ids = self._STS( self._c.execute( 'SELECT hash_id FROM current_files NATURAL JOIN files_info WHERE ' + ' AND '.join( files_info_predicates ) + ';' ) )
query_hash_ids = intersection_update_qhi( query_hash_ids, self._STS( self._c.execute( 'SELECT hash_id FROM current_files NATURAL JOIN files_info WHERE ' + ' AND '.join( files_info_predicates ) + ';' ) ) )
done_files_info_predicates = True
@ -6869,27 +6869,30 @@ class DB( HydrusDB.HydrusDB ):
self._PopulateHashIdsToHashesCache( hash_ids )
hash_ids_to_info = { hash_id : ClientMedia.FileInfoManager( hash_id, self._hash_ids_to_hashes_cache[ hash_id ], size, mime, width, height, duration, num_frames, has_audio, num_words ) for ( hash_id, size, mime, width, height, duration, num_frames, has_audio, num_words ) in self._ExecuteManySelectSingleParam( 'SELECT * FROM files_info WHERE hash_id = ?;', hash_ids ) }
hash_ids_to_current_file_service_ids_and_timestamps = HydrusData.BuildKeyToListDict( ( ( hash_id, ( service_id, timestamp ) ) for ( hash_id, service_id, timestamp ) in self._ExecuteManySelectSingleParam( 'SELECT hash_id, service_id, timestamp FROM current_files WHERE hash_id = ?;', hash_ids ) ) )
hash_ids_to_deleted_file_service_ids = HydrusData.BuildKeyToListDict( self._ExecuteManySelectSingleParam( 'SELECT hash_id, service_id FROM deleted_files WHERE hash_id = ?;', hash_ids ) )
hash_ids_to_pending_file_service_ids = HydrusData.BuildKeyToListDict( self._ExecuteManySelectSingleParam( 'SELECT hash_id, service_id FROM file_transfers WHERE hash_id = ?;', hash_ids ) )
hash_ids_to_petitioned_file_service_ids = HydrusData.BuildKeyToListDict( self._ExecuteManySelectSingleParam( 'SELECT hash_id, service_id FROM file_petitions WHERE hash_id = ?;', hash_ids ) )
hash_ids_to_urls = HydrusData.BuildKeyToSetDict( self._ExecuteManySelectSingleParam( 'SELECT hash_id, url FROM url_map NATURAL JOIN urls WHERE hash_id = ?;', hash_ids ) )
hash_ids_to_service_ids_and_filenames = HydrusData.BuildKeyToListDict( ( ( hash_id, ( service_id, filename ) ) for ( hash_id, service_id, filename ) in self._ExecuteManySelectSingleParam( 'SELECT hash_id, service_id, filename FROM service_filenames WHERE hash_id = ?;', hash_ids ) ) )
hash_ids_to_local_ratings = HydrusData.BuildKeyToListDict( ( ( hash_id, ( service_id, rating ) ) for ( service_id, hash_id, rating ) in self._ExecuteManySelectSingleParam( 'SELECT service_id, hash_id, rating FROM local_ratings WHERE hash_id = ?;', hash_ids ) ) )
hash_ids_to_file_viewing_stats_managers = { hash_id : ClientMedia.FileViewingStatsManager( preview_views, preview_viewtime, media_views, media_viewtime ) for ( hash_id, preview_views, preview_viewtime, media_views, media_viewtime ) in self._ExecuteManySelectSingleParam( 'SELECT hash_id, preview_views, preview_viewtime, media_views, media_viewtime FROM file_viewing_stats WHERE hash_id = ?;', hash_ids ) }
hash_ids_to_file_modified_timestamps = dict( self._ExecuteManySelectSingleParam( 'SELECT hash_id, file_modified_timestamp FROM file_modified_timestamps WHERE hash_id = ?;', hash_ids ) )
#
with HydrusDB.TemporaryIntegerTable( self._c, hash_ids, 'hash_id' ) as temp_table_name:
self._AnalyzeTempTable( temp_table_name )
hash_ids_to_info = { hash_id : ClientMedia.FileInfoManager( hash_id, self._hash_ids_to_hashes_cache[ hash_id ], size, mime, width, height, duration, num_frames, has_audio, num_words ) for ( hash_id, size, mime, width, height, duration, num_frames, has_audio, num_words ) in self._c.execute( 'SELECT * FROM files_info NATURAL JOIN {};'.format( temp_table_name ) ) }
hash_ids_to_current_file_service_ids_and_timestamps = HydrusData.BuildKeyToListDict( ( ( hash_id, ( service_id, timestamp ) ) for ( hash_id, service_id, timestamp ) in self._c.execute( 'SELECT hash_id, service_id, timestamp FROM current_files NATURAL JOIN {};'.format( temp_table_name ) ) ) )
hash_ids_to_deleted_file_service_ids = HydrusData.BuildKeyToListDict( self._c.execute( 'SELECT hash_id, service_id FROM deleted_files NATURAL JOIN {};'.format( temp_table_name ) ) )
hash_ids_to_pending_file_service_ids = HydrusData.BuildKeyToListDict( self._c.execute( 'SELECT hash_id, service_id FROM file_transfers NATURAL JOIN {};'.format( temp_table_name ) ) )
hash_ids_to_petitioned_file_service_ids = HydrusData.BuildKeyToListDict( self._c.execute( 'SELECT hash_id, service_id FROM file_petitions NATURAL JOIN {};'.format( temp_table_name ) ) )
hash_ids_to_urls = HydrusData.BuildKeyToSetDict( self._c.execute( 'SELECT hash_id, url FROM url_map NATURAL JOIN urls NATURAL JOIN {};'.format( temp_table_name ) ) )
hash_ids_to_service_ids_and_filenames = HydrusData.BuildKeyToListDict( ( ( hash_id, ( service_id, filename ) ) for ( hash_id, service_id, filename ) in self._c.execute( 'SELECT hash_id, service_id, filename FROM service_filenames NATURAL JOIN {};'.format( temp_table_name ) ) ) )
hash_ids_to_local_ratings = HydrusData.BuildKeyToListDict( ( ( hash_id, ( service_id, rating ) ) for ( service_id, hash_id, rating ) in self._c.execute( 'SELECT service_id, hash_id, rating FROM local_ratings NATURAL JOIN {};'.format( temp_table_name ) ) ) )
hash_ids_to_file_viewing_stats_managers = { hash_id : ClientMedia.FileViewingStatsManager( preview_views, preview_viewtime, media_views, media_viewtime ) for ( hash_id, preview_views, preview_viewtime, media_views, media_viewtime ) in self._c.execute( 'SELECT hash_id, preview_views, preview_viewtime, media_views, media_viewtime FROM file_viewing_stats NATURAL JOIN {};'.format( temp_table_name ) ) }
hash_ids_to_file_modified_timestamps = dict( self._c.execute( 'SELECT hash_id, file_modified_timestamp FROM file_modified_timestamps NATURAL JOIN {};'.format( temp_table_name ) ) )
hash_ids_to_current_file_service_ids = { hash_id : [ file_service_id for ( file_service_id, timestamp ) in file_service_ids_and_timestamps ] for ( hash_id, file_service_ids_and_timestamps ) in list(hash_ids_to_current_file_service_ids_and_timestamps.items()) }
@ -13389,6 +13392,42 @@ class DB( HydrusDB.HydrusDB ):
if version == 379:
try:
domain_manager = self._GetJSONDump( HydrusSerialisable.SERIALISABLE_TYPE_NETWORK_DOMAIN_MANAGER )
domain_manager.Initialise()
#
domain_manager.OverwriteDefaultURLClasses( [ 'pixiv artist page', '8kun thread', '8kun thread json api', 'vch.moe thread', 'vch.moe thread json api' ] )
domain_manager.DeleteURLClasses( [ 'pixiv artist gallery page' ] )
#
domain_manager.OverwriteDefaultParsers( [ '4chan-style thread api parser', '8kun thread api parser' ] )
#
domain_manager.TryToLinkURLClassesAndParsers()
#
self._SetJSONDump( domain_manager )
except Exception as e:
HydrusData.PrintException( e )
message = 'Trying to update some downloader objects failed! Please let hydrus dev know!'
self.pub_initial_message( message )
self._controller.pub( 'splash_set_title_text', 'updated db to v' + str( version + 1 ) )
self._c.execute( 'UPDATE version SET version = ?;', ( version + 1, ) )

View File

@ -394,6 +394,8 @@ def GetDefaultShortcuts():
media_viewer = ClientGUIShortcuts.ShortcutSet( 'media_viewer' )
media_viewer.SetCommand( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_SPECIAL, CC.SHORTCUT_KEY_SPECIAL_SPACE, [] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'pause_play_media' ) )
media_viewer.SetCommand( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( 'B' ), [ CC.SHORTCUT_MODIFIER_CTRL ] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'move_animation_to_previous_frame' ) )
media_viewer.SetCommand( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( 'N' ), [ CC.SHORTCUT_MODIFIER_CTRL ] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'move_animation_to_next_frame' ) )

View File

@ -17,6 +17,7 @@ from . import ClientGUIImport
from . import ClientGUILogin
from . import ClientGUIManagement
from . import ClientGUIMenus
from . import ClientGUIMPV
from . import ClientGUIPages
from . import ClientGUIParsing
from . import ClientGUIPopupMessages
@ -250,6 +251,37 @@ def THREADUploadPending( service_key ):
HG.client_controller.pub( 'notify_new_pending' )
class BonedUpdater( ClientGUIAsync.AsyncQtUpdater ):
def _getResult( self ):
boned_stats = HG.client_controller.Read( 'boned_stats' )
return boned_stats
def _publishLoading( self ):
self._job_key = ClientThreading.JobKey()
self._job_key.SetVariable( 'popup_text_1', 'Loading Statistics\u2026' )
HG.client_controller.pub( 'message', self._job_key )
def _publishResult( self, result ):
self._job_key.Delete()
boned_stats = result
frame = ClientGUITopLevelWindows.FrameThatTakesScrollablePanel( self._win, 'review your fate' )
panel = ClientGUIScrolledPanelsReview.ReviewHowBonedAmI( frame, boned_stats )
frame.SetPanel( panel )
class MenuUpdaterFile( ClientGUIAsync.AsyncQtUpdater ):
def _getResult( self ):
@ -368,6 +400,8 @@ class FrameGUI( ClientGUITopLevelWindows.MainFrameThatResizes ):
self._first_session_loaded = False
self._done_save_and_close = False
self._notebook = ClientGUIPages.PagesNotebook( self, self._controller, 'top page notebook' )
self._garbage_snapshot = collections.Counter()
@ -461,9 +495,15 @@ class FrameGUI( ClientGUITopLevelWindows.MainFrameThatResizes ):
library_versions.append( ( 'OpenCV', cv2.__version__ ) )
library_versions.append( ( 'openssl', ssl.OPENSSL_VERSION ) )
library_versions.append( ( 'Pillow', PIL.__version__ ) )
library_versions.append( ( 'html5lib present: ', str( ClientParsing.HTML5LIB_IS_OK ) ) )
library_versions.append( ( 'lxml present: ', str( ClientParsing.LXML_IS_OK ) ) )
library_versions.append( ( 'lz4 present: ', str( ClientRendering.LZ4_OK ) ) )
if ClientGUIMPV.MPV_IS_AVAILABLE:
library_versions.append( ( 'mpv api version: ', ClientGUIMPV.GetClientAPIVersionString() ) )
else:
library_versions.append( ( 'mpv', 'not available' ) )
# 2.7.12 (v2.7.12:d33e0cf91556, Jun 27 2016, 15:24:40) [MSC v.1500 64 bit (AMD64)]
v = sys.version
@ -494,6 +534,10 @@ class FrameGUI( ClientGUITopLevelWindows.MainFrameThatResizes ):
library_versions.append( ( 'sip', SIP_VERSION_STR ) )
library_versions.append( ( 'Qt', QC.__version__ ) )
library_versions.append( ( 'html5lib present: ', str( ClientParsing.HTML5LIB_IS_OK ) ) )
library_versions.append( ( 'lxml present: ', str( ClientParsing.LXML_IS_OK ) ) )
library_versions.append( ( 'lz4 present: ', str( ClientRendering.LZ4_OK ) ) )
library_versions.append( ( 'temp dir', HydrusPaths.GetCurrentTempDir() ) )
import locale
@ -1584,13 +1628,7 @@ class FrameGUI( ClientGUITopLevelWindows.MainFrameThatResizes ):
self._controller.file_viewing_stats_manager.Flush()
boned_stats = self._controller.Read( 'boned_stats' )
frame = ClientGUITopLevelWindows.FrameThatTakesScrollablePanel( self, 'review your fate' )
panel = ClientGUIScrolledPanelsReview.ReviewHowBonedAmI( frame, boned_stats )
frame.SetPanel( panel )
self._boned_updater.update()
def _ImportDownloaders( self ):
@ -1831,6 +1869,8 @@ class FrameGUI( ClientGUITopLevelWindows.MainFrameThatResizes ):
self._menu_updater_pages = MenuUpdaterPages( self )
self._menu_updater_pending = MenuUpdaterPending( self )
self._boned_updater = BonedUpdater( self )
self.setMenuBar( self._menubar )
for name in MENU_ORDER:
@ -3869,123 +3909,6 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
def TryToSaveAndClose( self, restart = False, force_shutdown_maintenance = False ):
# the return value here is 'exit allowed'
if not HG.emergency_exit:
able_to_close_statement = self._notebook.GetTestAbleToCloseStatement()
if HC.options[ 'confirm_client_exit' ] or able_to_close_statement is not None:
if restart:
text = 'Are you sure you want to restart the client? (Will auto-yes in 15 seconds)'
else:
text = 'Are you sure you want to exit the client? (Will auto-yes in 15 seconds)'
if able_to_close_statement is not None:
text += os.linesep * 2
text += able_to_close_statement
result = ClientGUIDialogsQuick.GetYesNo( self, text, auto_yes_time = 15 )
if result == QW.QDialog.Rejected:
return False
if restart:
HG.restart = True
if force_shutdown_maintenance:
HG.do_idle_shutdown_work = True
self.SaveAndClose()
return True
def SaveAndClose( self ):
try:
if self._message_manager:
self._message_manager.CleanBeforeDestroy()
self._message_manager.hide()
#
if self._new_options.GetBoolean( 'saving_sash_positions_on_exit' ):
self._SaveSplitterPositions()
ClientGUITopLevelWindows.SaveTLWSizeAndPosition( self, self._frame_key )
for tlw in QW.QApplication.topLevelWidgets():
tlw.hide()
#
session = self._notebook.GetCurrentGUISession( 'last session' )
self._controller.SaveGUISession( session )
session.SetName( 'exit session' )
self._controller.SaveGUISession( session )
#
self._DestroyTimers()
self.DeleteAllClosedPages() # Obsolote comment, preserved just in case: wx crashes if any are left in here, wew
self._notebook.CleanBeforeDestroy()
self._controller.WriteSynchronous( 'save_options', HC.options )
self._controller.WriteSynchronous( 'serialisable', self._new_options )
except Exception as e:
HydrusData.PrintException( e )
if HG.emergency_exit:
self.deleteLater()
self._controller.Exit()
else:
self._controller.CreateSplash()
QP.CallAfter( self._controller.Exit )
self.deleteLater()
def FlipDarkmode( self ):
current_colourset = self._new_options.GetString( 'current_colourset' )
@ -5106,7 +5029,7 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
self._notebook.NewPage( management_controller, on_deepest_notebook = True )
def NewPageQuery( self, service_key, initial_hashes = None, initial_predicates = None, page_name = None, do_sort = False ):
def NewPageQuery( self, service_key, initial_hashes = None, initial_predicates = None, page_name = None, do_sort = False, select_page = True, activate_window = False ):
if initial_hashes is None:
@ -5118,7 +5041,12 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
initial_predicates = []
self._notebook.NewPageQuery( service_key, initial_hashes = initial_hashes, initial_predicates = initial_predicates, page_name = page_name, on_deepest_notebook = True, do_sort = do_sort )
self._notebook.NewPageQuery( service_key, initial_hashes = initial_hashes, initial_predicates = initial_predicates, page_name = page_name, on_deepest_notebook = True, do_sort = do_sort, select_page = select_page )
if activate_window and not self.isActiveWindow():
self.activateWindow()
def NotifyClosedPage( self, page ):
@ -5738,6 +5666,81 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
def SaveAndClose( self ):
if self._done_save_and_close:
return
try:
if QP.isValid( self._message_manager ):
self._message_manager.CleanBeforeDestroy()
self._message_manager.hide()
#
if self._new_options.GetBoolean( 'saving_sash_positions_on_exit' ):
self._SaveSplitterPositions()
ClientGUITopLevelWindows.SaveTLWSizeAndPosition( self, self._frame_key )
for tlw in QW.QApplication.topLevelWidgets():
tlw.hide()
#
session = self._notebook.GetCurrentGUISession( 'last session' )
self._controller.SaveGUISession( session )
session.SetName( 'exit session' )
self._controller.SaveGUISession( session )
#
self._DestroyTimers()
self.DeleteAllClosedPages()
self._notebook.CleanBeforeDestroy()
self._controller.WriteSynchronous( 'save_options', HC.options )
self._controller.WriteSynchronous( 'serialisable', self._new_options )
self._done_save_and_close = True
except Exception as e:
HydrusData.PrintException( e )
if HG.emergency_exit:
self.deleteLater()
self._controller.Exit()
else:
self._controller.CreateSplash()
QP.CallAfter( self._controller.Exit )
self.deleteLater()
def SetMediaFocus( self ):
self._SetMediaFocus()
@ -5760,6 +5763,55 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
self._notebook.ShowPage( page )
def TryToSaveAndClose( self, restart = False, force_shutdown_maintenance = False ):
# the return value here is 'exit allowed'
if not HG.emergency_exit:
able_to_close_statement = self._notebook.GetTestAbleToCloseStatement()
if HC.options[ 'confirm_client_exit' ] or able_to_close_statement is not None:
if restart:
text = 'Are you sure you want to restart the client? (Will auto-yes in 15 seconds)'
else:
text = 'Are you sure you want to exit the client? (Will auto-yes in 15 seconds)'
if able_to_close_statement is not None:
text += os.linesep * 2
text += able_to_close_statement
result = ClientGUIDialogsQuick.GetYesNo( self, text, auto_yes_time = 15 )
if result == QW.QDialog.Rejected:
return False
if restart:
HG.restart = True
if force_shutdown_maintenance:
HG.do_idle_shutdown_work = True
self.SaveAndClose()
return True
def UnregisterAnimationUpdateWindow( self, window ):
self._animation_update_windows.discard( window )

View File

@ -14,6 +14,7 @@ from . import ClientGUIFunctions
from . import ClientGUIHoverFrames
from . import ClientGUIMedia
from . import ClientGUIMenus
from . import ClientGUIMPV
from . import ClientGUIScrolledPanels
from . import ClientGUIScrolledPanelsButtonQuestions
from . import ClientGUIScrolledPanelsEdit
@ -34,6 +35,7 @@ from . import HydrusPaths
from . import HydrusSerialisable
from . import HydrusTags
import os
import time
from . import QtPorting as QP
from qtpy import QtCore as QC
from qtpy import QtWidgets as QW
@ -44,11 +46,11 @@ from . import QtPorting as QP
OPEN_EXTERNALLY_BUTTON_SIZE = ( 200, 45 )
def CalculateCanvasMediaSize( media, canvas_size ):
def CalculateCanvasMediaSize( media, canvas_size, show_action ):
( canvas_width, canvas_height ) = canvas_size.toTuple()
if ShouldHaveAnimationBar( media ):
if ShouldHaveAnimationBar( media, show_action ):
animated_scanbar_height = HG.client_controller.new_options.GetInteger( 'animated_scanbar_height' )
@ -81,7 +83,7 @@ def CalculateCanvasZooms( canvas, media, show_action ):
new_options = HG.client_controller.new_options
( canvas_width, canvas_height ) = CalculateCanvasMediaSize( media, canvas.size() )
( canvas_width, canvas_height ) = CalculateCanvasMediaSize( media, canvas.size(), show_action )
width_zoom = canvas_width / media_width
@ -174,13 +176,13 @@ def CalculateCanvasZooms( canvas, media, show_action ):
return ( default_zoom, canvas_zoom )
def CalculateMediaContainerSize( media, zoom, action ):
def CalculateMediaContainerSize( media, zoom, show_action ):
if action in ( CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW_ON_ACTIVATION_OPEN_EXTERNALLY, CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW ):
if show_action in ( CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW_ON_ACTIVATION_OPEN_EXTERNALLY, CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW ):
raise Exception( 'This media should not be shown in the media viewer!' )
elif action == CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON:
elif show_action == CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON:
( width, height ) = OPEN_EXTERNALLY_BUTTON_SIZE
@ -197,7 +199,7 @@ def CalculateMediaContainerSize( media, zoom, action ):
( media_width, media_height ) = CalculateMediaSize( media, zoom )
if ShouldHaveAnimationBar( media ):
if ShouldHaveAnimationBar( media, show_action ):
animated_scanbar_height = HG.client_controller.new_options.GetInteger( 'animated_scanbar_height' )
@ -209,28 +211,44 @@ def CalculateMediaContainerSize( media, zoom, action ):
def CalculateMediaSize( media, zoom ):
( original_width, original_height ) = media.GetResolution()
media_width = int( round( zoom * original_width ) )
media_height = int( round( zoom * original_height ) )
if media.GetMime() in HC.AUDIO:
media_width = 360
media_height = 240
else:
( original_width, original_height ) = media.GetResolution()
media_width = int( round( zoom * original_width ) )
media_height = int( round( zoom * original_height ) )
return ( media_width, media_height )
def IsStaticImage( media ):
def ShouldHaveAnimationBar( media, show_action ):
return media.GetMime() in HC.IMAGES and not ShouldHaveAnimationBar( media )
if show_action not in ( CC.MEDIA_VIEWER_ACTION_SHOW_WITH_NATIVE, CC.MEDIA_VIEWER_ACTION_SHOW_WITH_MPV ):
return False
def ShouldHaveAnimationBar( media ):
is_audio = media.GetMime() in HC.AUDIO
is_animated_gif = media.GetMime() == HC.IMAGE_GIF and media.HasDuration()
if is_audio:
return True
is_native_video = media.GetMime() in HC.NATIVE_VIDEO
is_animated_image = media.GetMime() in HC.IMAGES_THAT_CAN_HAVE_ANIMATION and media.HasDuration()
is_video = media.GetMime() in HC.VIDEO
num_frames = media.GetNumFrames()
has_more_than_one_frame = num_frames is not None and num_frames > 1
return is_animated_gif or is_native_video
return ( is_animated_image or is_video ) and has_more_than_one_frame
class Animation( QW.QWidget ):
@ -242,7 +260,6 @@ class Animation( QW.QWidget ):
self._media = None
self._drag_happened = False
self._left_down_event = None
self._something_valid_has_been_drawn = False
@ -528,7 +545,6 @@ class Animation( QW.QWidget ):
self._media = media
self._drag_happened = False
self._left_down_event = None
self._ClearCanvasBitmap()
@ -672,7 +688,7 @@ class AnimationBar( QW.QWidget ):
def _GetXFromFrameIndex( self, index, width_offset = 0 ):
if self._num_frames < 2:
if self._num_frames is None or self._num_frames < 2:
return 0
@ -682,6 +698,13 @@ class AnimationBar( QW.QWidget ):
return int( ( my_width - width_offset ) * index / ( self._num_frames - 1 ) )
def _GetXFromTimestamp( self, timestamp_ms, width_offset = 0 ):
( my_width, my_height ) = self.size().toTuple()
return int( ( my_width - width_offset ) * timestamp_ms / self._duration_ms )
def _Redraw( self, painter ):
self._last_drawn_info = self._GetAnimationBarStatus()
@ -761,24 +784,46 @@ class AnimationBar( QW.QWidget ):
animated_scanbar_nub_width = HG.client_controller.new_options.GetInteger( 'animated_scanbar_nub_width' )
nub_x = self._GetXFromFrameIndex( current_frame_index, width_offset = animated_scanbar_nub_width )
nub_x = None
painter.drawRect( nub_x, 0, animated_scanbar_nub_width, animated_scanbar_height )
if self._num_frames is not None and current_frame_index is not None:
nub_x = self._GetXFromFrameIndex( current_frame_index, width_offset = animated_scanbar_nub_width )
elif self._duration_ms is not None and current_timestamp_ms is not None:
nub_x = self._GetXFromTimestamp( current_timestamp_ms, width_offset = animated_scanbar_nub_width )
if nub_x is not None:
painter.drawRect( nub_x, 0, animated_scanbar_nub_width, animated_scanbar_height )
#
painter.setPen( QG.QPen() )
s = HydrusData.ConvertValueRangeToPrettyString( current_frame_index + 1, self._num_frames )
progress_strings = []
if self._num_frames is not None:
progress_strings.append( HydrusData.ConvertValueRangeToPrettyString( current_frame_index + 1, self._num_frames ) )
if current_timestamp_ms is not None:
s += ' - {}'.format( HydrusData.ConvertValueRangeToScanbarTimestampsMS( current_timestamp_ms, self._duration_ms ) )
progress_strings.append( HydrusData.ConvertValueRangeToScanbarTimestampsMS( current_timestamp_ms, self._duration_ms ) )
( x, y ) = painter.fontMetrics().size( QC.Qt.TextSingleLine, s ).toTuple()
s = ' - '.join( progress_strings )
QP.DrawText( painter, my_width-x-3, 3, s )
if len( s ) > 0:
( x, y ) = painter.fontMetrics().size( QC.Qt.TextSingleLine, s ).toTuple()
QP.DrawText( painter, my_width-x-3, 3, s )
def EventMouse( self, event ):
@ -808,7 +853,7 @@ class AnimationBar( QW.QWidget ):
( my_width, my_height ) = self.size().toTuple()
if event.buttons() != QC.Qt.NoButton and event.type() == QC.QEvent.MouseMove:
if event.type() == QC.QEvent.MouseMove and event.buttons() != QC.Qt.NoButton:
self._currently_in_a_drag = True
@ -833,11 +878,20 @@ class AnimationBar( QW.QWidget ):
if proportion < 0: proportion = 0
if proportion > 1: proportion = 1
current_frame_index = int( proportion * ( self._num_frames - 1 ) + 0.5 )
self.update()
self._media_window.GotoFrame( current_frame_index )
if isinstance( self._media_window, Animation ):
current_frame_index = int( proportion * ( self._num_frames - 1 ) + 0.5 )
self._media_window.GotoFrame( current_frame_index )
elif isinstance( self._media_window, ClientGUIMPV.mpvWidget ):
time_index_ms = int( proportion * self._duration_ms )
self._media_window.Seek( time_index_ms )
elif event.type() == QC.QEvent.MouseButtonRelease:
@ -869,7 +923,18 @@ class AnimationBar( QW.QWidget ):
self._media_window = media_window
self._duration_ms = max( media.GetDuration(), 1 )
self._num_frames = max( media.GetNumFrames(), 1 )
num_frames = media.GetNumFrames()
if num_frames is None:
self._num_frames = num_frames
else:
self._num_frames = max( num_frames, 1 )
self._last_drawn_info = None
self._has_experienced_mouse_down = False
@ -901,8 +966,6 @@ class AnimationBar( QW.QWidget ):
return
frame_index = self._media_window.CurrentFrame()
if self._last_drawn_info != self._GetAnimationBarStatus():
self.update()
@ -1027,6 +1090,8 @@ class CanvasFrame( ClientGUITopLevelWindows.FrameThatResizes ):
def TakeFocusForUser( self ):
self.activateWindow()
self._canvas_window.setFocus( QC.Qt.OtherFocusReason )
@ -1108,7 +1173,9 @@ class Canvas( QW.QWidget ):
return False
if self._GetShowAction( media ) in ( CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW_ON_ACTIVATION_OPEN_EXTERNALLY, CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW ):
( media_show_action, media_start_paused, media_start_with_embed ) = self._GetShowAction( media )
if media_show_action in ( CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW_ON_ACTIVATION_OPEN_EXTERNALLY, CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW ):
return False
@ -1338,16 +1405,21 @@ class Canvas( QW.QWidget ):
def _GetShowAction( self, media ):
start_paused = False
start_with_embed = False
bad_result = ( CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW, start_paused, start_with_embed )
if media is None:
return CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW
return bad_result
mime = media.GetMime()
if mime not in HC.ALLOWED_MIMES: # stopgap to catch a collection or application_unknown due to unusual import order/media moving
return CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW
return bad_result
if self.PREVIEW_WINDOW:
@ -1369,9 +1441,9 @@ class Canvas( QW.QWidget ):
( my_width, my_height ) = self.size().toTuple()
action = self._GetShowAction( self._current_media )
( media_show_action, media_start_paused, media_start_with_embed ) = self._GetShowAction( self._current_media )
( media_width, media_height ) = CalculateMediaContainerSize( self._current_media, self._current_zoom, action )
( media_width, media_height ) = CalculateMediaContainerSize( self._current_media, self._current_zoom, media_show_action )
new_size = ( media_width, media_height )
@ -1395,7 +1467,9 @@ class Canvas( QW.QWidget ):
return False
return self._GetShowAction( self._current_media ) not in ( CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW_ON_ACTIVATION_OPEN_EXTERNALLY, CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW )
( media_show_action, media_start_paused, media_start_with_embed ) = self._GetShowAction( self._current_media )
return media_show_action not in ( CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW_ON_ACTIVATION_OPEN_EXTERNALLY, CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW )
def _IShouldCatchShortcutEvent( self, event = None ):
@ -1418,9 +1492,9 @@ class Canvas( QW.QWidget ):
# set up canvas zoom
show_action = self._GetShowAction( self._current_media )
( media_show_action, media_start_paused, media_start_with_embed ) = self._GetShowAction( self._current_media )
( gumpf_current_zoom, self._canvas_zoom ) = CalculateCanvasZooms( self, self._current_media, show_action )
( gumpf_current_zoom, self._canvas_zoom ) = CalculateCanvasZooms( self, self._current_media, media_show_action )
# for init zoom, we want the _width_ to stay the same as previous
@ -1661,6 +1735,16 @@ class Canvas( QW.QWidget ):
self._media_container.Pause()
def _PausePlayCurrentMedia( self ):
if self._current_media is None:
return
self._media_container.PausePlay()
def _PrefetchNeighbours( self ):
pass
@ -1691,9 +1775,9 @@ class Canvas( QW.QWidget ):
return
show_action = self._GetShowAction( self._current_media )
( media_show_action, media_start_paused, media_start_with_embed ) = self._GetShowAction( self._current_media )
( self._current_zoom, self._canvas_zoom ) = CalculateCanvasZooms( self, self._current_media, show_action )
( self._current_zoom, self._canvas_zoom ) = CalculateCanvasZooms( self, self._current_media, media_show_action )
HG.client_controller.pub( 'canvas_new_zoom', self._canvas_key, self._current_zoom )
@ -1707,9 +1791,9 @@ class Canvas( QW.QWidget ):
my_size = self.size()
action = self._GetShowAction( self._current_media )
( media_show_action, media_start_paused, media_start_with_embed ) = self._GetShowAction( self._current_media )
( media_width, media_height ) = CalculateMediaContainerSize( self._current_media, self._current_zoom, action )
( media_width, media_height ) = CalculateMediaContainerSize( self._current_media, self._current_zoom, media_show_action )
x = ( my_size.width() - media_width ) // 2
y = ( my_size.height() - media_height ) // 2
@ -2220,6 +2304,10 @@ class Canvas( QW.QWidget ):
self._PauseCurrentMedia()
elif action == 'pause_play_media':
self._PausePlayCurrentMedia()
elif action == 'move_animation_to_previous_frame':
self._media_container.GotoPreviousOrNextFrame( -1 )
@ -2315,11 +2403,11 @@ class Canvas( QW.QWidget ):
if self._current_media.GetLocationsManager().IsLocal() and initial_width > 0 and initial_height > 0:
show_action = self._GetShowAction( self._current_media )
( media_show_action, media_start_paused, media_start_with_embed ) = self._GetShowAction( self._current_media )
pos = ( self._media_window_pos.x(), self._media_window_pos.y() )
self._media_container.SetMedia( self._current_media, initial_size, pos, show_action )
self._media_container.SetMedia( self._current_media, initial_size, pos, media_show_action, media_start_paused, media_start_with_embed )
self._PrefetchNeighbours()
@ -2441,11 +2529,11 @@ class CanvasPanel( Canvas ):
if CC.LOCAL_FILE_SERVICE_KEY in locations_manager.GetCurrent():
ClientGUIMenus.AppendMenuItem( menu, 'delete', 'Delete this file.', self._Delete, file_service_key=CC.LOCAL_FILE_SERVICE_KEY )
ClientGUIMenus.AppendMenuItem( menu, 'delete', 'Delete this file.', self._Delete, file_service_key = CC.LOCAL_FILE_SERVICE_KEY )
elif CC.TRASH_SERVICE_KEY in locations_manager.GetCurrent():
ClientGUIMenus.AppendMenuItem( menu, 'delete completely', 'Physically delete this file from disk.', self._Delete, file_service_key=CC.TRASH_SERVICE_KEY )
ClientGUIMenus.AppendMenuItem( menu, 'delete completely', 'Physically delete this file from disk.', self._Delete, file_service_key = CC.TRASH_SERVICE_KEY )
ClientGUIMenus.AppendMenuItem( menu, 'undelete', 'Take this file out of the trash.', self._Undelete )
@ -3683,15 +3771,15 @@ class CanvasFilterDuplicates( CanvasWithHovers ):
caught = True
if event.button() == QC.Qt.LeftButton and event.type() == QC.QEvent.MouseButtonPress:
if event.type() == QC.QEvent.MouseButtonPress and event.button() == QC.Qt.LeftButton:
self.EventDragBegin( event )
elif event.button() == QC.Qt.LeftButton and event.type() == QC.QEvent.MouseButtonRelease:
elif event.type() == QC.QEvent.MouseButtonRelease and event.button() == QC.Qt.LeftButton:
self.EventDragEnd( event )
elif event.buttons() != QC.Qt.NoButton and event.type() == QC.QEvent.MouseMove:
elif event.type() == QC.QEvent.MouseMove and event.buttons() != QC.Qt.NoButton:
self.EventMouseMove( event )
@ -4031,7 +4119,7 @@ class CanvasMediaList( ClientMedia.ListeningMediaList, CanvasWithHovers ):
hash = media.GetHash()
mime = media.GetMime()
if IsStaticImage( media ):
if media.IsStaticImage():
if not image_cache.HasImageRenderer( hash ):
@ -4359,21 +4447,21 @@ class CanvasMediaListFilterArchiveDelete( CanvasMediaList ):
if event.modifiers() & QC.Qt.ShiftModifier:
caught = True
if event.button() == QC.Qt.LeftButton and event.type() == QC.QEvent.MouseButtonPress:
if event.type() == QC.QEvent.MouseButtonPress and event.button() == QC.Qt.LeftButton:
self.EventDragBegin( event )
elif event.button() == QC.Qt.LeftButton and event.type() == QC.QEvent.MouseButtonRelease:
elif event.type() == QC.QEvent.MouseButtonRelease and event.button() == QC.Qt.LeftButton:
self.EventDragEnd( event )
elif event.buttons() != QC.Qt.NoButton and event.type() == QC.QEvent.MouseMove:
elif event.type() == QC.QEvent.MouseMove and event.buttons() != QC.Qt.NoButton:
self.EventMouseMove( event )
else:
caught = False
@ -4944,7 +5032,6 @@ class CanvasMediaListBrowser( CanvasMediaListNavigable ):
if modifier == QC.Qt.NoModifier and key in CC.DELETE_KEYS: self._Delete()
elif modifier == QC.Qt.ShiftModifier and key in CC.DELETE_KEYS: self._Undelete()
elif modifier == QC.Qt.NoModifier and key in ( QC.Qt.Key_Space, ): self._PausePlaySlideshow()
elif key in ( QC.Qt.Key_Enter, QC.Qt.Key_Return, QC.Qt.Key_Escape ):
self._TryToCloseWindow()
@ -4960,6 +5047,44 @@ class CanvasMediaListBrowser( CanvasMediaListNavigable ):
def ProcessApplicationCommand( self, command, canvas_key = None ):
if canvas_key is not None and canvas_key != self._canvas_key:
return False
command_processed = True
command_type = command.GetCommandType()
data = command.GetData()
if command_type == CC.APPLICATION_COMMAND_TYPE_SIMPLE:
action = data
if action == 'pause_play_slideshow':
self._PausePlaySlideshow()
else:
command_processed = False
else:
command_processed = False
if not command_processed:
command_processed = CanvasMediaListNavigable.ProcessApplicationCommand( self, command )
return command_processed
def wheelEvent( self, event ):
if self._IShouldCatchShortcutEvent( event = event ):
@ -5004,7 +5129,9 @@ class MediaContainer( QW.QWidget ):
self.setAttribute( QC.Qt.WA_OpaquePaintEvent, True )
self._media = None
self._show_action = None
self._show_action = CC.MEDIA_VIEWER_ACTION_SHOW_WITH_NATIVE
self._start_paused = False
self._start_with_embed = False
self._media_window = None
@ -5016,6 +5143,7 @@ class MediaContainer( QW.QWidget ):
self._animation_window = Animation( self )
self._animation_bar = AnimationBar( self )
self._mpv_window = None
self._static_image_window = StaticImage( self )
self._animation_window.hide()
@ -5035,6 +5163,18 @@ class MediaContainer( QW.QWidget ):
media_window.SetNoneMedia()
media_window.hide()
elif isinstance( media_window, ClientGUIMPV.mpvWidget ):
self._mpv_window = None
media_window.SetNoneMedia()
time.sleep( 0.1 ) # anti crash wew
media_window.deleteLater()
time.sleep( 0.1 ) # anti crash wew
else:
media_window.deleteLater()
@ -5054,6 +5194,13 @@ class MediaContainer( QW.QWidget ):
old_media_window = self._media_window
destroy_old_media_window = True
if self._show_action == CC.MEDIA_VIEWER_ACTION_SHOW_WITH_MPV and not ClientGUIMPV.MPV_IS_AVAILABLE:
self._show_action = CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON
HydrusData.ShowText( 'MPV is not available!' )
if self._show_action in ( CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW_ON_ACTIVATION_OPEN_EXTERNALLY, CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW ):
raise Exception( 'This media should not be shown in the media viewer!' )
@ -5062,40 +5209,9 @@ class MediaContainer( QW.QWidget ):
self._media_window = OpenExternallyPanel( self, self._media )
self._HideAnimationBar()
elif self._show_action == CC.MEDIA_VIEWER_ACTION_SHOW_WITH_NATIVE:
else:
start_paused = self._show_action in ( CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL_PAUSED, CC.MEDIA_VIEWER_ACTION_SHOW_BEHIND_EMBED_PAUSED )
if ShouldHaveAnimationBar( self._media ):
if isinstance( self._media_window, Animation ):
destroy_old_media_window = False
else:
self._animation_window.show()
self._media_window = self._animation_window
self._media_window.SetMedia( self._media, start_paused = start_paused )
if ShouldHaveAnimationBar( self._media ):
self._animation_bar.show()
self._animation_bar.SetMediaAndWindow( self._media, self._media_window )
else:
self._HideAnimationBar()
else:
if self._media.IsStaticImage():
if isinstance( self._media_window, StaticImage ):
@ -5110,8 +5226,52 @@ class MediaContainer( QW.QWidget ):
self._media_window.SetMedia( self._media )
self._HideAnimationBar()
else:
if isinstance( self._media_window, Animation ):
destroy_old_media_window = False
else:
self._animation_window.show()
self._media_window = self._animation_window
self._media_window.SetMedia( self._media, start_paused = self._start_paused )
elif self._show_action == CC.MEDIA_VIEWER_ACTION_SHOW_WITH_MPV:
if isinstance( self._media_window, ClientGUIMPV.mpvWidget ):
destroy_old_media_window = False
else:
if self._mpv_window is None:
self._mpv_window = ClientGUIMPV.mpvWidget( self )
self._mpv_window.show()
self._media_window = self._mpv_window
self._mpv_window.SetMedia( self._media, start_paused = self._start_paused )
if ShouldHaveAnimationBar( self._media, self._show_action ):
self._animation_bar.show()
self._animation_bar.SetMediaAndWindow( self._media, self._media_window )
else:
self._HideAnimationBar()
if old_media_window is not None and destroy_old_media_window:
@ -5137,7 +5297,7 @@ class MediaContainer( QW.QWidget ):
( media_width, media_height ) = ( my_width, my_height )
if ShouldHaveAnimationBar( self._media ) and not is_open_externally:
if ShouldHaveAnimationBar( self._media, self._show_action ) and not is_open_externally:
animated_scanbar_height = HG.client_controller.new_options.GetInteger( 'animated_scanbar_height' )
@ -5145,6 +5305,7 @@ class MediaContainer( QW.QWidget ):
self._animation_bar.setFixedSize( QP.TupleToQSize( ( my_width, animated_scanbar_height ) ) )
self._animation_bar.move( QP.TupleToQPoint( ( 0, my_height - animated_scanbar_height ) ) )
self._media_window.setFixedSize( QP.TupleToQSize( ( media_width, media_height ) ) )
self._media_window.move( QP.TupleToQPoint( ( 0, 0 ) ) )
@ -5177,36 +5338,43 @@ class MediaContainer( QW.QWidget ):
if self._media is not None:
if ShouldHaveAnimationBar( self._media ):
if ShouldHaveAnimationBar( self._media, self._show_action ):
current_frame_index = self._media_window.CurrentFrame()
num_frames = self._media.GetNumFrames()
if direction == 1:
if isinstance( self._media_window, Animation ):
if current_frame_index == num_frames - 1:
current_frame_index = self._media_window.CurrentFrame()
num_frames = self._media.GetNumFrames()
if direction == 1:
current_frame_index = 0
if current_frame_index == num_frames - 1:
current_frame_index = 0
else:
current_frame_index += 1
else:
current_frame_index += 1
if current_frame_index == 0:
current_frame_index = num_frames - 1
else:
current_frame_index -= 1
else:
self._media_window.GotoFrame( current_frame_index )
if current_frame_index == 0:
current_frame_index = num_frames - 1
else:
current_frame_index -= 1
elif isinstance( self._media_window, ClientGUIMPV.mpvWidget ):
self._media_window.GotoPreviousOrNextFrame( direction )
self._media_window.GotoFrame( current_frame_index )
@ -5219,7 +5387,7 @@ class MediaContainer( QW.QWidget ):
else:
if ShouldHaveAnimationBar( self._media ):
if ShouldHaveAnimationBar( self._media, self._show_action ):
animation_bar_mouse_pos = self._animation_bar.mapFromGlobal( QG.QCursor.pos() )
@ -5240,13 +5408,24 @@ class MediaContainer( QW.QWidget ):
if self._media is not None:
if isinstance( self._media_window, Animation ):
if isinstance( self._media_window, ( Animation, ClientGUIMPV.mpvWidget ) ):
self._media_window.Pause()
def PausePlay( self ):
if self._media is not None:
if isinstance( self._media_window, ( Animation, ClientGUIMPV.mpvWidget ) ):
self._media_window.PausePlay()
def ReadyToSlideshow( self ):
if self._media is None:
@ -5255,7 +5434,7 @@ class MediaContainer( QW.QWidget ):
else:
if isinstance( self._media_window, Animation ):
if isinstance( self._media_window, ( Animation, ClientGUIMPV.mpvWidget ) ):
if self._media_window.IsPlaying() and not self._media_window.HasPlayedOnceThrough():
@ -5288,15 +5467,17 @@ class MediaContainer( QW.QWidget ):
self._embed_button.show()
def SetMedia( self, media, initial_size, initial_position, show_action ):
def SetMedia( self, media, initial_size, initial_position, show_action, start_paused, start_with_embed ):
self._media = media
self.hide()
self._show_action = show_action
self._start_paused = start_paused
self._start_with_embed = start_with_embed
if self._show_action in ( CC.MEDIA_VIEWER_ACTION_SHOW_BEHIND_EMBED, CC.MEDIA_VIEWER_ACTION_SHOW_BEHIND_EMBED_PAUSED ):
if self._start_with_embed:
self.SetEmbedButton()
@ -5313,6 +5494,7 @@ class MediaContainer( QW.QWidget ):
self._SizeAndPositionChildren()
self.show()
def SetNoneMedia( self ):
@ -5358,16 +5540,6 @@ class EmbedButton( QW.QWidget ):
if self._thumbnail_qt_pixmap is not None:
if ShouldHaveAnimationBar( self._media ):
# animations will have the animation bar space underneath in this case, so colour it in
painter.setBackground( QG.QBrush( QP.GetSystemColour( QG.QPalette.Button ) ) )
animated_scanbar_height = HG.client_controller.new_options.GetInteger( 'animated_scanbar_height' )
painter.drawRect( 0, y - animated_scanbar_height, x, animated_scanbar_height )
( thumb_width, thumb_height ) = self._thumbnail_qt_pixmap.size().toTuple()
scale = x / thumb_width
@ -5458,8 +5630,6 @@ class OpenExternallyPanel( QW.QWidget ):
self._new_options = HG.client_controller.new_options
QP.SetBackgroundColour( self, self._new_options.GetColour(CC.COLOUR_MEDIA_BACKGROUND) )
self._media = media
vbox = QP.VBoxLayout()
@ -5480,10 +5650,10 @@ class OpenExternallyPanel( QW.QWidget ):
m_text = HC.mime_string_lookup[ media.GetMime() ]
button = QW.QPushButton( 'open ' + m_text + ' externally', self )
button.setFixedSize( QP.TupleToQSize( OPEN_EXTERNALLY_BUTTON_SIZE ) )
button.setFocusPolicy( QC.Qt.NoFocus )
QP.AddToLayout( vbox, button, CC.FLAGS_CENTER )
QP.AddToLayout( vbox, button, CC.FLAGS_EXPAND_BOTH_WAYS )
self.setLayout( vbox )
@ -5504,6 +5674,19 @@ class OpenExternallyPanel( QW.QWidget ):
def paintEvent( self, event ):
# have to manually repaint background because of parent WA_OpaquePaintEvent
painter = QG.QPainter( self )
background_colour = self._new_options.GetColour( CC.COLOUR_MEDIA_BACKGROUND )
painter.setBackground( QG.QBrush( background_colour ) )
painter.eraseRect( painter.viewport() )
def LaunchFile( self ):
hash = self._media.GetHash()

View File

@ -971,18 +971,13 @@ class ListBox( QW.QScrollArea ):
if isinstance( term, ClientSearch.Predicate ):
predicate_type = term.GetType()
include_predicates.append( term )
if predicate_type in ( HC.PREDICATE_TYPE_TAG, HC.PREDICATE_TYPE_NAMESPACE, HC.PREDICATE_TYPE_WILDCARD ):
possible_inverse = term.GetInverseCopy()
if possible_inverse is not None:
value = term.GetValue()
include_predicates.append( ClientSearch.Predicate( predicate_type, value ) )
exclude_predicates.append( ClientSearch.Predicate( predicate_type, value, False ) )
else:
include_predicates.append( term )
exclude_predicates.append( possible_inverse )
else:
@ -1600,9 +1595,7 @@ class ListBoxTags( ListBox ):
def _GetAllTagsForClipboard( self, with_counts = False ):
texts = list( self._terms_to_texts.values() )
texts.sort()
texts = [ self._terms_to_texts[ term ] for term in self._ordered_terms ]
return texts
@ -1693,7 +1686,9 @@ class ListBoxTags( ListBox ):
page_name = ', '.join( s )
HG.client_controller.pub( 'new_page_query', CC.LOCAL_FILE_SERVICE_KEY, initial_predicates = predicates, page_name = page_name )
activate_window = HG.client_controller.new_options.GetBoolean( 'activate_window_on_tag_search_page_activation' )
HG.client_controller.pub( 'new_page_query', CC.LOCAL_FILE_SERVICE_KEY, initial_predicates = predicates, page_name = page_name, activate_window = activate_window )
@ -1752,13 +1747,20 @@ class ListBoxTags( ListBox ):
text = os.linesep.join( texts )
elif command == 'copy_all_tags':
elif command in ( 'copy_all_tags', 'copy_all_tags_with_counts' ):
text = os.linesep.join( self._GetAllTagsForClipboard( with_counts = False ) )
if command == 'copy_all_tags':
with_counts = False
else:
with_counts = True
elif command == 'copy_all_tags_with_counts':
texts = self._GetAllTagsForClipboard( with_counts = with_counts )
text = os.linesep.join( self._GetAllTagsForClipboard( with_counts = True ) )
text = os.linesep.join( texts )
HG.client_controller.pub( 'clipboard', 'text', text )
@ -1884,24 +1886,35 @@ class ListBoxTags( ListBox ):
siblings = []
if len( selected_tags ) == 1:
( selected_tag, ) = selected_tags
( selected_namespace, selected_subtag ) = HydrusTags.SplitTag( selected_tag )
siblings = set( HG.client_controller.tag_siblings_manager.GetAllSiblings( CC.COMBINED_TAG_SERVICE_KEY, selected_tag ) )
siblings.discard( selected_tag )
siblings.discard( selected_subtag )
siblings = list( siblings )
HydrusTags.SortNumericTags( siblings )
if len( self._selected_terms ) == 1:
( term, ) = self._selected_terms
if isinstance( term, ClientSearch.Predicate ):
if term.GetType() == HC.PREDICATE_TYPE_TAG:
selection_string = '"' + term.GetValue() + '"'
else:
selection_string = '"' + term.ToString( with_count = False ) + '"'
selection_string = term.ToString( with_count = False )
else:
selection_string = '"' + str( term ) + '"'
selection_string = str( term )
else:
@ -1909,47 +1922,68 @@ class ListBoxTags( ListBox ):
selection_string = 'selected'
if self._get_current_predicates_callable is not None:
ClientGUIMenus.AppendMenuItem( copy_menu, selection_string, 'Copy the selected predicates to your clipboard.', self._ProcessMenuCopyEvent, 'copy_terms' )
if len( self._selected_terms ) == 1:
current_predicates = self._get_current_predicates_callable()
( namespace, subtag ) = HydrusTags.SplitTag( selection_string )
if current_predicates is not None:
if namespace != '':
( include_predicates, exclude_predicates ) = self._GetSelectedIncludeExcludePredicates()
sub_selection_string = subtag
if True in ( include_predicate in current_predicates for include_predicate in include_predicates ):
ClientGUIMenus.AppendMenuItem( menu, 'discard ' + selection_string + ' from current search', 'Remove the selected predicates from the current search.', self._ProcessMenuPredicateEvent, 'remove_include_predicates' )
if True in ( include_predicate not in current_predicates for include_predicate in include_predicates ):
ClientGUIMenus.AppendMenuItem( menu, 'require ' + selection_string + ' for current search', 'Add the selected predicates from the current search.', self._ProcessMenuPredicateEvent, 'add_include_predicates' )
if True in ( exclude_predicate in current_predicates for exclude_predicate in exclude_predicates ):
ClientGUIMenus.AppendMenuItem( menu, 'permit ' + selection_string + ' for current search', 'Stop disallowing the selected predicates from the current search.', self._ProcessMenuPredicateEvent, 'remove_exclude_predicates' )
if True in ( exclude_predicate not in current_predicates for exclude_predicate in exclude_predicates ):
ClientGUIMenus.AppendMenuItem( menu, 'exclude ' + selection_string + ' from current search', 'Disallow the selected predicates for the current search.', self._ProcessMenuPredicateEvent, 'add_exclude_predicates' )
ClientGUIMenus.AppendMenuItem( copy_menu, sub_selection_string, 'Copy the selected sub-predicate to your clipboard.', self._ProcessMenuCopyEvent, 'copy_sub_terms' )
else:
ClientGUIMenus.AppendMenuItem( copy_menu, 'selected subtags', 'Copy the selected sub-predicates to your clipboard.', self._ProcessMenuCopyEvent, 'copy_sub_terms' )
ClientGUIMenus.AppendSeparator( menu )
if self.can_spawn_new_windows:
if len( siblings ) > 0:
ClientGUIMenus.AppendMenuItem( menu, 'open a new search page for ' + selection_string, 'Open a new search page starting with the selected predicates.', self._NewSearchPage )
siblings_menu = QW.QMenu( copy_menu )
if len( self._selected_terms ) > 1:
ClientGUIMenus.AppendMenuItem( menu, 'open new search pages for each in selection', 'Open one new search page for each selected predicate.', self._NewSearchPageForEach )
for sibling in siblings:
ClientGUIMenus.AppendMenuItem( siblings_menu, sibling, 'Copy the selected tag sibling to your clipboard.', HG.client_controller.pub, 'clipboard', 'text', sibling )
if len( self._selected_terms ) == 1:
( sibling_namespace, sibling_subtag ) = HydrusTags.SplitTag( sibling )
if sibling_namespace != '':
ClientGUIMenus.AppendMenuItem( siblings_menu, sibling_subtag, 'Copy the selected sibling subtag to your clipboard.', HG.client_controller.pub, 'clipboard', 'text', sibling_subtag )
ClientGUIMenus.AppendMenu( copy_menu, siblings_menu, 'siblings' )
if len( self._ordered_terms ) > len( self._selected_terms ):
ClientGUIMenus.AppendSeparator( copy_menu )
ClientGUIMenus.AppendMenuItem( copy_menu, 'all tags', 'Copy all the predicates in this list to your clipboard.', self._ProcessMenuCopyEvent, 'copy_all_tags' )
if self.has_counts:
ClientGUIMenus.AppendMenuItem( copy_menu, 'all tags with counts', 'Copy all the predicates in this list, with their counts, to your clipboard.', self._ProcessMenuCopyEvent, 'copy_all_tags_with_counts' )
if len( self._ordered_terms ) > 0:
ClientGUIMenus.AppendMenu( menu, copy_menu, 'copy' )
if len( self._selected_terms ) > 0:
if len( selected_tags ) > 0:
ClientGUIMenus.AppendSeparator( menu )
if self._page_key is not None:
select_menu = QW.QMenu( menu )
@ -1993,49 +2027,60 @@ class ListBoxTags( ListBox ):
ClientGUIMenus.AppendMenu( menu, select_menu, 'select' )
ClientGUIMenus.AppendMenuItem( copy_menu, selection_string, 'Copy the selected predicates to your clipboard.', self._ProcessMenuCopyEvent, 'copy_terms' )
if self.can_spawn_new_windows:
if len( self._selected_terms ) == 1:
ClientGUIMenus.AppendSeparator( menu )
ClientGUIMenus.AppendMenuItem( menu, 'open a new search page for ' + selection_string, 'Open a new search page starting with the selected predicates.', self._NewSearchPage )
if len( self._selected_terms ) > 1:
( namespace, subtag ) = HydrusTags.SplitTag( selection_string )
ClientGUIMenus.AppendMenuItem( menu, 'open new search pages for each in selection', 'Open one new search page for each selected predicate.', self._NewSearchPageForEach )
if namespace != '':
if self._get_current_predicates_callable is not None:
ClientGUIMenus.AppendSeparator( menu )
current_predicates = self._get_current_predicates_callable()
if current_predicates is not None:
( include_predicates, exclude_predicates ) = self._GetSelectedIncludeExcludePredicates()
if True in ( include_predicate in current_predicates for include_predicate in include_predicates ):
sub_selection_string = '"' + subtag
ClientGUIMenus.AppendMenuItem( copy_menu, sub_selection_string, 'Copy the selected sub-predicates to your clipboard.', self._ProcessMenuCopyEvent, 'copy_sub_terms' )
ClientGUIMenus.AppendMenuItem( menu, 'discard ' + selection_string + ' from current search', 'Remove the selected predicates from the current search.', self._ProcessMenuPredicateEvent, 'remove_include_predicates' )
else:
if True in ( include_predicate not in current_predicates for include_predicate in include_predicates ):
ClientGUIMenus.AppendMenuItem( menu, 'require ' + selection_string + ' for current search', 'Add the selected predicates from the current search.', self._ProcessMenuPredicateEvent, 'add_include_predicates' )
ClientGUIMenus.AppendMenuItem( copy_menu, 'selected subtags', 'Copy the selected sub-predicates to your clipboard.', self._ProcessMenuCopyEvent, 'copy_sub_terms' )
if True in ( exclude_predicate in current_predicates for exclude_predicate in exclude_predicates ):
ClientGUIMenus.AppendMenuItem( menu, 'permit ' + selection_string + ' for current search', 'Stop disallowing the selected predicates from the current search.', self._ProcessMenuPredicateEvent, 'remove_exclude_predicates' )
if True in ( exclude_predicate not in current_predicates for exclude_predicate in exclude_predicates ):
ClientGUIMenus.AppendMenuItem( menu, 'exclude ' + selection_string + ' from current search', 'Disallow the selected predicates for the current search.', self._ProcessMenuPredicateEvent, 'add_exclude_predicates' )
if len( self._ordered_terms ) > len( self._selected_terms ):
ClientGUIMenus.AppendSeparator( copy_menu )
ClientGUIMenus.AppendMenuItem( copy_menu, 'all tags', 'Copy all the predicates in this list to your clipboard.', self._ProcessMenuCopyEvent, 'copy_all_tags' )
if self.has_counts:
ClientGUIMenus.AppendMenuItem( copy_menu, 'all tags with counts', 'Copy all the predicates in this list, with their counts, to your clipboard.', self._ProcessMenuCopyEvent, 'copy_all_tags_with_counts' )
if len( self._ordered_terms ) > 0:
ClientGUIMenus.AppendMenu( menu, copy_menu, 'copy' )
if len( selected_tags ) == 1:
( tag, ) = selected_tags
if self._tag_display_type in ( ClientTags.TAG_DISPLAY_SINGLE_MEDIA, ClientTags.TAG_DISPLAY_SELECTION_LIST ):
ClientGUIMenus.AppendSeparator( menu )
( namespace, subtag ) = HydrusTags.SplitTag( tag )
hide_menu = QW.QMenu( menu )
@ -2164,7 +2209,7 @@ class ListBoxTagsPredicates( ListBoxTags ):
def _GetAllTagsForClipboard( self, with_counts = False ):
return [ term.ToString( with_counts ) for term in self._terms ]
return [ term.ToString( with_counts ) for term in self._ordered_terms ]
def _GetMutuallyExclusivePredicates( self, predicate ):
@ -2969,7 +3014,7 @@ class ListBoxTagsSelection( ListBoxTags ):
else:
return self._ordered_terms
return list( self._ordered_terms )

269
include/ClientGUIMPV.py Normal file
View File

@ -0,0 +1,269 @@
from . import HydrusConstants as HC
from . import HydrusData
from . import HydrusGlobals as HG
from . import HydrusPaths
from qtpy import QtCore as QC
from qtpy import QtWidgets as QW
from qtpy import QtGui as QG
import locale
try:
import mpv
MPV_IS_AVAILABLE = True
except:
MPV_IS_AVAILABLE = False
def GetClientAPIVersionString():
try:
( major, minor ) = mpv._mpv_client_api_version()
return '{}.{}'.format( major, minor )
except:
return 'unknown'
#Not sure how well this works with hardware acceleration. This just renders to a QWidget. In my tests it seems fine, even with vdpau video out, but I'm not 100% sure it actually uses hardware acceleration.
#Here is an example on how to render into a QOpenGLWidget instead: https://gist.github.com/cosven/b313de2acce1b7e15afda263779c0afc
class mpvWidget( QW.QWidget ):
def __init__( self, parent ):
QW.QWidget.__init__( self, parent )
# This is necessary since PyQT stomps over the locale settings needed by libmpv.
# This needs to happen after importing PyQT before creating the first mpv.MPV instance.
locale.setlocale( locale.LC_NUMERIC, 'C' )
self.setAttribute( QC.Qt.WA_DontCreateNativeAncestors )
self.setAttribute( QC.Qt.WA_NativeWindow )
self._player = mpv.MPV( wid = str( int( self.winId() ) ), log_handler = print, loglevel = 'fatal' )
self._player.profile = 'gpu-hq'
# hydev notes on OSC:
# OSC is by default off, default input bindings are by default off
# difficult to get this to intercept mouse/key events naturally, so you have to pipe them to the window with 'command', but this is not excellent
# general recommendation when using libmpv is to just implement your own stuff anyway, so let's do that for prototype
#self._player[ 'input-default-bindings' ] = True
#To load an existing config file (by default it doesn't load the user/global config like standalone mpv does):
#mpv._mpv_load_config_file(self._player.handle,'blah/mpv.conf'.encode('utf-8') )
#self._player.osc = True #Set to enable the mpv UI. Requires that mpv captures mouse/key events, otherwise it won't work.
self._player.loop = True
self._player.force_window = True
#self.setMouseTracking( True )#Needed to get mouse move events
#self.setFocusPolicy(QC.Qt.StrongFocus)#Needed to get key events
self._player.input_cursor = False#Disable mpv mouse move/click event capture
self._player.input_vo_keyboard = False#Disable mpv key event capture, might also need to set input_x11_keyboard
self._media = None
self._has_played_once_through = False
self.destroyed.connect( self._player.terminate )
def GetAnimationBarStatus( self ):
buffer_indices = None
if self._media is None:
current_frame_index = 0
current_timestamp_ms = 0
paused = True
else:
current_timestamp_s = self._player.time_pos
if current_timestamp_s is None:
current_frame_index = 0
current_timestamp_ms = None
else:
current_timestamp_ms = current_timestamp_s * 1000
num_frames = self._media.GetNumFrames()
if num_frames is None:
current_frame_index = 0
else:
current_frame_index = int( round( ( current_timestamp_ms / self._media.GetDuration() ) * num_frames ) )
paused = self._player.pause
return ( current_frame_index, current_timestamp_ms, paused, buffer_indices )
def GotoPreviousOrNextFrame( self, direction ):
command = 'frame-step'
if direction == 1:
command = 'frame-step'
elif direction == -1:
command = 'frame-back-step'
self._player.command( command )
def Seek( self, time_index_ms ):
time_index_s = time_index_ms / 1000
self._player.seek( time_index_s, reference = 'absolute' )
def HasPlayedOnceThrough( self ):
return self._has_played_once_through
def IsPlaying( self ):
return not self._player.pause
def mouseDoubleClickEvent( self, event ):
if not ( event.modifiers() & ( QC.Qt.ShiftModifier | QC.Qt.ControlModifier | QC.Qt.AltModifier) ):
if event.button() == QC.Qt.LeftButton:
self.Pause()
hash = self._media.GetHash()
mime = self._media.GetMime()
client_files_manager = HG.client_controller.client_files_manager
path = client_files_manager.GetFilePath( hash, mime )
new_options = HG.client_controller.new_options
launch_path = new_options.GetMimeLaunch( mime )
HydrusPaths.LaunchFile( path, launch_path )
event.accept()
return
event.ignore()
#def mouseMoveEvent( self, event ):
# same deal here as with mousereleaseevent--osc is non-interactable with commands, so let's not use it for now
#self._player.command( 'mouse', event.x(), event.y() )
#event.ignore()
def mouseReleaseEvent( self, event ):
if event.button() == QC.Qt.LeftButton:
self.PausePlay()
else:
event.ignore()
return
# left index = 0
# right index = 2
# the issue with using this guy is it sends a mouse press or mouse down event, and the OSC only responds to mouse up
#self._player.command( 'mouse', event.x(), event.y(), index, 'single' )
event.accept()
def Pause( self ):
self._player.pause = True
def PausePlay( self ):
self._player.pause = not self._player.pause
def Play( self ):
self._player.pause = False
def SetMedia( self, media, start_paused = False ):
self._media = media
if self._media is None:
self._player.pause = True
self._player.command( 'playlist-remove', 'current' )
else:
hash = self._media.GetHash()
mime = self._media.GetMime()
client_files_manager = HG.client_controller.client_files_manager
path = client_files_manager.GetFilePath( hash, mime )
self._has_played_once_through = False
self._player.visibility = 'always'
volume = 70
mute = False
self._player.pause = True
self._player.loadfile( path )
self._player.pause = start_paused
self._player.volume = volume
self._player.mute = mute
def SetNoneMedia( self ):
self.SetMedia( None )

View File

@ -1568,12 +1568,12 @@ class ManagementPanelImporterMultipleGallery( ManagementPanelImporter ):
self._gallery_importers_listctrl_panel.AddBitmapButton( CC.GlobalPixmaps.clear_highlight, self._ClearExistingHighlightAndPanel, tooltip = 'clear highlight', enabled_check_func = self._CanClearHighlight )
self._gallery_importers_listctrl_panel.AddBitmapButton( CC.GlobalPixmaps.file_pause, self._PausePlayFiles, tooltip = 'pause/play files', enabled_only_on_selection = True )
self._gallery_importers_listctrl_panel.AddBitmapButton( CC.GlobalPixmaps.gallery_pause, self._PausePlayGallery, tooltip = 'pause/play search', enabled_only_on_selection = True )
self._gallery_importers_listctrl_panel.AddBitmapButton( CC.GlobalPixmaps.trash, self._RemoveGalleryImports, tooltip = 'remove selected', enabled_only_on_selection = True )
self._gallery_importers_listctrl_panel.NewButtonRow()
self._gallery_importers_listctrl_panel.AddButton( 'retry failed', self._RetryFailed, enabled_check_func = self._CanRetryFailed )
self._gallery_importers_listctrl_panel.AddButton( 'retry ignored', self._RetryIgnored, enabled_check_func = self._CanRetryIgnored )
self._gallery_importers_listctrl_panel.AddButton( 'remove selected', self._RemoveGalleryImports, enabled_only_on_selection = True )
self._gallery_importers_listctrl_panel.NewButtonRow()
@ -2269,13 +2269,13 @@ class ManagementPanelImporterMultipleWatcher( ManagementPanelImporter ):
self._watchers_listctrl_panel.AddBitmapButton( CC.GlobalPixmaps.clear_highlight, self._ClearExistingHighlightAndPanel, tooltip = 'clear highlight', enabled_check_func = self._CanClearHighlight )
self._watchers_listctrl_panel.AddBitmapButton( CC.GlobalPixmaps.file_pause, self._PausePlayFiles, tooltip = 'pause/play files', enabled_only_on_selection = True )
self._watchers_listctrl_panel.AddBitmapButton( CC.GlobalPixmaps.gallery_pause, self._PausePlayChecking, tooltip = 'pause/play checking', enabled_only_on_selection = True )
self._watchers_listctrl_panel.AddBitmapButton( CC.GlobalPixmaps.trash, self._RemoveWatchers, tooltip = 'remove selected', enabled_only_on_selection = True )
self._watchers_listctrl_panel.AddButton( 'check now', self._CheckNow, enabled_only_on_selection = True )
self._watchers_listctrl_panel.NewButtonRow()
self._watchers_listctrl_panel.AddButton( 'retry failed', self._RetryFailed, enabled_check_func = self._CanRetryFailed )
self._watchers_listctrl_panel.AddButton( 'retry ignored', self._RetryIgnored, enabled_check_func = self._CanRetryIgnored )
self._watchers_listctrl_panel.AddButton( 'remove', self._RemoveWatchers, enabled_only_on_selection = True )
self._watchers_listctrl_panel.NewButtonRow()

View File

@ -933,7 +933,7 @@ class MediaPanel( ClientMedia.ListeningMediaList, QW.QScrollArea ):
new_options = HG.client_controller.new_options
media_show_action = new_options.GetMediaShowAction( display_media.GetMime() )
( media_show_action, media_start_paused, media_start_with_embed ) = new_options.GetMediaShowAction( display_media.GetMime() )
if media_show_action == CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW_ON_ACTIVATION_OPEN_EXTERNALLY:
@ -3433,7 +3433,14 @@ class MediaPanelThumbnails( MediaPanel ):
self._parent._drag_init_coordinates = QG.QCursor.pos()
self._parent._HitMedia( self._parent._GetThumbnailUnderMouse( event ), event.modifiers() & QC.Qt.ControlModifier, event.modifiers() & QC.Qt.ShiftModifier )
thumb = self._parent._GetThumbnailUnderMouse( event )
right_on_whitespace = event.button() == QC.Qt.RightButton and thumb is None
if not right_on_whitespace:
self._parent._HitMedia( thumb, event.modifiers() & QC.Qt.ControlModifier, event.modifiers() & QC.Qt.ShiftModifier )
# this specifically does not scroll to media, as for clicking (esp. double-clicking attempts), the scroll can be jarring

View File

@ -7,7 +7,6 @@ from qtpy import QtCore as QC
from qtpy import QtWidgets as QW
from qtpy import QtGui as QG
from . import QtPorting as QP
from . import QtPorting as QP
def AppendMenu( menu, submenu, label ):
@ -112,6 +111,8 @@ def AppendMenuLabel( menu, label, description = '' ):
menu.addAction( menu_item )
BindMenuItem( menu_item, HG.client_controller.pub, 'clipboard', 'text', label )
return menu_item
def AppendSeparator( menu ):

View File

@ -30,17 +30,24 @@ class OptionsPanelMimes( OptionsPanel ):
OptionsPanel.__init__( self, parent )
self._selectable_mimes = set( selectable_mimes )
self._mimes_to_checkboxes = {}
self._mime_groups_to_checkboxes = {}
self._mime_groups_to_values = {}
mime_groups = [ HC.APPLICATIONS, HC.AUDIO, HC.IMAGES, HC.VIDEO ]
mime_groups = []
mime_groups.append( ( HC.APPLICATIONS, HC.GENERAL_APPLICATION ) )
mime_groups.append( ( HC.AUDIO, HC.GENERAL_AUDIO ) )
mime_groups.append( ( HC.IMAGES, HC.GENERAL_IMAGE ) )
mime_groups.append( ( HC.VIDEO, HC.GENERAL_VIDEO ) )
mime_groups_to_mimes = collections.defaultdict( list )
for mime in selectable_mimes:
for mime in self._selectable_mimes:
for mime_group in mime_groups:
for ( mime_group, mime_group_type ) in mime_groups:
if mime in mime_group:
@ -55,11 +62,11 @@ class OptionsPanelMimes( OptionsPanel ):
gridbox.setColumnStretch( 1, 1 )
for mime_group in mime_groups:
for ( mime_group, mime_group_type ) in mime_groups:
mimes = mime_groups_to_mimes[ mime_group ]
mg_checkbox = QW.QCheckBox( HC.mime_string_lookup[mime_group], self )
mg_checkbox = QW.QCheckBox( HC.mime_string_lookup[ mime_group_type ], self )
mg_checkbox.clicked.connect( self.EventMimeGroupCheckbox )
self._mime_groups_to_checkboxes[ mime_group ] = mg_checkbox
@ -71,9 +78,11 @@ class OptionsPanelMimes( OptionsPanel ):
for mime in mimes:
m_checkbox = QW.QCheckBox( HC.mime_string_lookup[mime], self )
m_checkbox = QW.QCheckBox( HC.mime_string_lookup[ mime ], self )
m_checkbox.clicked.connect( self.EventMimeCheckbox )
#m_checkbox.hide()
self._mimes_to_checkboxes[ mime ] = m_checkbox
QP.AddToLayout( vbox, m_checkbox, CC.FLAGS_EXPAND_PERPENDICULAR )
@ -84,12 +93,38 @@ class OptionsPanelMimes( OptionsPanel ):
self.setLayout( gridbox )
self._HideShowSubCheckboxes()
def _HideShowSubCheckboxes( self ):
pass
# this is insufficient. we need to have mime groups done by three-state checkboxes or something.
# and it'd be nice to have a border around groups or something.
'''
for ( mime_group, all_true ) in self._mime_groups_to_values.items():
should_show = not all_true
for mime in mime_group:
if mime not in self._selectable_mimes:
continue
self._mimes_to_checkboxes[ mime ].setVisible( should_show )
'''
def _UpdateMimeGroupCheckboxes( self ):
for ( mime_group, mg_checkbox ) in list(self._mime_groups_to_checkboxes.items()):
for ( mime_group, mg_checkbox ) in self._mime_groups_to_checkboxes.items():
respective_checkbox_values = [m_checkbox.isChecked() for (mime, m_checkbox) in list( self._mimes_to_checkboxes.items() ) if mime in mime_group]
respective_checkbox_values = [ m_checkbox.isChecked() for ( mime, m_checkbox ) in list( self._mimes_to_checkboxes.items() ) if mime in mime_group ]
all_true = False not in respective_checkbox_values
@ -97,6 +132,8 @@ class OptionsPanelMimes( OptionsPanel ):
self._mime_groups_to_values[ mime_group ] = all_true
self._HideShowSubCheckboxes()
def EventMimeCheckbox( self ):
@ -127,10 +164,12 @@ class OptionsPanelMimes( OptionsPanel ):
self._HideShowSubCheckboxes()
def GetValue( self ):
mimes = tuple( [mime for ( mime, checkbox ) in list(self._mimes_to_checkboxes.items()) if checkbox.isChecked() == True] )
mimes = tuple( [ mime for ( mime, checkbox ) in list(self._mimes_to_checkboxes.items()) if checkbox.isChecked() == True ] )
return mimes

View File

@ -41,6 +41,8 @@ class DialogPageChooser( ClientGUIDialogs.Dialog ):
self._controller = controller
self._action_picked = False
self._result = None
# spawn and add to layout in this order, so focus precipitates from the graphical top
@ -127,10 +129,6 @@ class DialogPageChooser( ClientGUIDialogs.Dialog ):
self._button_2.clicked.connect( lambda: self._HitButton( 2 ) )
self._button_3.clicked.connect( lambda: self._HitButton( 3 ) )
#
self.show()
def _AddEntry( self, button, entry ):
@ -242,6 +240,8 @@ class DialogPageChooser( ClientGUIDialogs.Dialog ):
self._result = ( 'page', ClientGUIManagement.CreateManagementControllerPetitions( petition_service_key ) )
self._action_picked = True
self.done( QW.QDialog.Accepted )
@ -335,6 +335,20 @@ class DialogPageChooser( ClientGUIDialogs.Dialog ):
def event( self, event ):
if event.type() == QC.QEvent.WindowDeactivate and not self._action_picked:
self.done( QW.QDialog.Rejected )
return True
else:
return ClientGUIDialogs.Dialog.event( self, event )
def keyPressEvent( self, event ):
id = None
@ -354,6 +368,20 @@ class DialogPageChooser( ClientGUIDialogs.Dialog ):
elif key == QC.Qt.Key_7 and modifier == QC.Qt.KeypadModifier: id = 7
elif key == QC.Qt.Key_8 and modifier == QC.Qt.KeypadModifier: id = 8
elif key == QC.Qt.Key_9 and modifier == QC.Qt.KeypadModifier: id = 9
elif key in ( QC.Qt.Key_Enter, QC.Qt.Key_Return ):
# get the 'first', scanning from top-left
for possible_id in ( 7, 8, 9, 4, 5, 6, 1, 2, 3 ):
if possible_id in self._command_dict:
id = possible_id
break
elif key == QC.Qt.Key_Escape:
self.done( QW.QDialog.Rejected )
@ -885,6 +913,7 @@ class PagesNotebook( QP.TabWidgetWithDnD ):
QP.TabWidgetWithDnD.__init__( self, parent )
# this is disabled for now because it seems borked in Qt
if controller.new_options.GetBoolean( 'notebook_tabs_on_left' ):
self.setTabPosition( QW.QTabWidget.West )

View File

@ -4067,6 +4067,8 @@ class ScriptManagementControl( QW.QWidget ):
self._status = ClientGUICommon.BetterStaticText( main_panel )
self._gauge = ClientGUICommon.Gauge( main_panel )
self._status.setWordWrap( True )
self._link_button = ClientGUICommon.BetterBitmapButton( main_panel, CC.GlobalPixmaps.link, self.LinkButton )
self._link_button.setToolTip( 'urls found by the script' )

View File

@ -15,6 +15,7 @@ from . import ClientGUIMenus
from . import ClientGUIScrolledPanels
from . import ClientGUIFileSeedCache
from . import ClientGUIGallerySeedLog
from . import ClientGUIMPV
from . import ClientGUITags
from . import ClientGUITime
from . import ClientGUITopLevelWindows
@ -844,10 +845,20 @@ class EditDeleteFilesPanel( ClientGUIScrolledPanels.EditPanel ):
if suggested_file_service_key is None:
suggested_file_service_key = CC.LOCAL_FILE_SERVICE_KEY
if suggested_file_service_key == CC.LOCAL_FILE_SERVICE_KEY:
possible_file_service_keys.append( CC.LOCAL_FILE_SERVICE_KEY )
possible_file_service_keys.append( CC.TRASH_SERVICE_KEY )
possible_file_service_keys.append( CC.COMBINED_LOCAL_FILE_SERVICE_KEY )
elif suggested_file_service_key == CC.TRASH_SERVICE_KEY:
possible_file_service_keys.append( CC.TRASH_SERVICE_KEY )
possible_file_service_keys.append( CC.COMBINED_LOCAL_FILE_SERVICE_KEY )
else:
possible_file_service_keys.append( suggested_file_service_key )
@ -914,8 +925,14 @@ class EditDeleteFilesPanel( ClientGUIScrolledPanels.EditPanel ):
if len( hashes ) > 0:
if num_to_delete == 1: text = 'Permanently delete this file and do not save a deletion record?'
else: text = 'Permanently delete these ' + HydrusData.ToHumanInt( num_to_delete ) + ' files and do not save a deletion record?'
if num_to_delete == 1:
text = 'Permanently delete this file and do not save a deletion record?'
else:
text = 'Permanently delete these ' + HydrusData.ToHumanInt( num_to_delete ) + ' files and do not save a deletion record?'
self._permitted_action_choices.append( ( text, ( 'clear_delete', hashes, text ) ) )
@ -2652,20 +2669,31 @@ class EditMediaViewOptionsPanel( ClientGUIScrolledPanels.EditPanel ):
self._original_info = info
( self._mime, media_show_action, preview_show_action, ( media_scale_up, media_scale_down, preview_scale_up, preview_scale_down, exact_zooms_only, scale_up_quality, scale_down_quality ) ) = self._original_info
( self._mime, media_show_action, media_start_paused, media_start_with_embed, preview_show_action, preview_start_paused, preview_start_with_embed, ( media_scale_up, media_scale_down, preview_scale_up, preview_scale_down, exact_zooms_only, scale_up_quality, scale_down_quality ) ) = self._original_info
possible_actions = CC.media_viewer_capabilities[ self._mime ]
( possible_show_actions, can_start_paused, can_start_with_embed ) = CC.media_viewer_capabilities[ self._mime ]
self._media_show_action = ClientGUICommon.BetterChoice( self )
self._media_start_paused = QW.QCheckBox( self )
self._media_start_with_embed = QW.QCheckBox( self )
self._preview_show_action = ClientGUICommon.BetterChoice( self )
self._preview_start_paused = QW.QCheckBox( self )
self._preview_start_with_embed = QW.QCheckBox( self )
for action in possible_actions:
advanced_mode = HG.client_controller.new_options.GetBoolean( 'advanced_mode' )
for action in possible_show_actions:
self._media_show_action.addItem( CC.media_viewer_action_string_lookup[ action], action )
if action == CC.MEDIA_VIEWER_ACTION_SHOW_WITH_MPV and ( not advanced_mode or not ClientGUIMPV.MPV_IS_AVAILABLE ):
continue
self._media_show_action.addItem( CC.media_viewer_action_string_lookup[ action ], action )
if action != CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW_ON_ACTIVATION_OPEN_EXTERNALLY:
self._preview_show_action.addItem( CC.media_viewer_action_string_lookup[ action], action )
self._preview_show_action.addItem( CC.media_viewer_action_string_lookup[ action ], action )
@ -2708,7 +2736,12 @@ class EditMediaViewOptionsPanel( ClientGUIScrolledPanels.EditPanel ):
#
self._media_show_action.SetValue( media_show_action )
self._media_start_paused.setChecked( media_start_paused )
self._media_start_with_embed.setChecked( media_start_with_embed )
self._preview_show_action.SetValue( preview_show_action )
self._preview_start_paused.setChecked( preview_start_paused )
self._preview_start_with_embed.setChecked( preview_start_with_embed )
self._media_scale_up.SetValue( media_scale_up )
self._media_scale_down.SetValue( media_scale_down )
@ -2726,11 +2759,32 @@ class EditMediaViewOptionsPanel( ClientGUIScrolledPanels.EditPanel ):
text = 'Setting media view options for ' + HC.mime_string_lookup[ self._mime ] + '.'
QP.AddToLayout( vbox, ClientGUICommon.BetterStaticText(self,text), CC.FLAGS_EXPAND_PERPENDICULAR )
QP.AddToLayout( vbox, ClientGUICommon.WrapInText(self._media_show_action,self,'media viewer show action:'), CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
QP.AddToLayout( vbox, ClientGUICommon.WrapInText(self._preview_show_action,self,'preview show action:'), CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
if not ClientGUIMPV.MPV_IS_AVAILABLE:
text += ' MPV is not available for this client.'
if possible_actions == CC.no_support:
if not advanced_mode:
text += ' Only advanced mode users can select MPV for now.'
QP.AddToLayout( vbox, ClientGUICommon.BetterStaticText(self,text), CC.FLAGS_EXPAND_PERPENDICULAR )
rows = []
rows.append( ( 'media viewer show action: ', self._media_show_action ) )
rows.append( ( 'media starts paused: ', self._media_start_paused ) )
rows.append( ( 'media starts covered with an embed button: ', self._media_start_with_embed ) )
rows.append( ( 'preview viewer show action: ', self._preview_show_action ) )
rows.append( ( 'preview starts paused: ', self._preview_start_paused ) )
rows.append( ( 'preview starts covered with an embed button: ', self._preview_start_with_embed ) )
gridbox = ClientGUICommon.WrapInGrid( self, rows )
QP.AddToLayout( vbox, gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
if len( set( possible_show_actions ).intersection( { CC.MEDIA_VIEWER_ACTION_SHOW_WITH_NATIVE, CC.MEDIA_VIEWER_ACTION_SHOW_WITH_MPV } ) ) == 0:
self._media_scale_up.hide()
self._media_scale_down.hide()
@ -2771,45 +2825,78 @@ class EditMediaViewOptionsPanel( ClientGUIScrolledPanels.EditPanel ):
self.widget().setLayout( vbox )
def EventActionChange( self, index ):
self._UpdateControls()
if self._media_show_action.GetValue() in CC.no_support and self._preview_show_action.GetValue() in CC.no_support:
self._media_scale_up.setEnabled( False )
self._media_scale_down.setEnabled( False )
self._preview_scale_up.setEnabled( False )
self._preview_scale_down.setEnabled( False )
self._exact_zooms_only.setEnabled( False )
self._scale_up_quality.setEnabled( False )
self._scale_down_quality.setEnabled( False )
else:
self._media_scale_up.setEnabled( True )
self._media_scale_down.setEnabled( True )
self._preview_scale_up.setEnabled( True )
self._preview_scale_down.setEnabled( True )
def _UpdateControls( self ):
media_ok = self._media_show_action.GetValue() not in CC.unsupported_media_actions
preview_ok = self._preview_show_action.GetValue() not in CC.unsupported_media_actions
if media_ok or preview_ok:
self._exact_zooms_only.setEnabled( True )
self._scale_up_quality.setEnabled( True )
self._scale_down_quality.setEnabled( True )
if self._mime == HC.APPLICATION_FLASH:
else:
self._exact_zooms_only.setEnabled( False )
self._scale_up_quality.setEnabled( False )
self._scale_down_quality.setEnabled( False )
if media_ok:
self._media_scale_up.setEnabled( True )
self._media_scale_down.setEnabled( True )
self._media_start_paused.setEnabled( True )
self._media_start_with_embed.setEnabled( True )
else:
self._media_scale_up.setEnabled( False )
self._media_scale_down.setEnabled( False )
self._media_start_paused.setEnabled( False )
self._media_start_with_embed.setEnabled( False )
if preview_ok:
self._preview_scale_up.setEnabled( True )
self._preview_scale_down.setEnabled( True )
self._preview_start_paused.setEnabled( True )
self._preview_start_with_embed.setEnabled( True )
else:
self._preview_scale_up.setEnabled( False )
self._preview_scale_down.setEnabled( False )
self._preview_start_paused.setEnabled( False )
self._preview_start_with_embed.setEnabled( False )
def EventActionChange( self, index ):
self._UpdateControls()
def GetValue( self ):
media_show_action = self._media_show_action.GetValue()
media_start_paused = self._media_start_paused.isChecked()
media_start_with_embed = self._media_start_with_embed.isChecked()
preview_show_action = self._preview_show_action.GetValue()
preview_start_paused = self._preview_start_paused.isChecked()
preview_start_with_embed = self._preview_start_with_embed.isChecked()
media_scale_up = self._media_scale_up.GetValue()
media_scale_down = self._media_scale_down.GetValue()
@ -2821,7 +2908,9 @@ class EditMediaViewOptionsPanel( ClientGUIScrolledPanels.EditPanel ):
scale_up_quality = self._scale_up_quality.GetValue()
scale_down_quality = self._scale_down_quality.GetValue()
return ( self._mime, media_show_action, preview_show_action, ( media_scale_up, media_scale_down, preview_scale_up, preview_scale_down, exact_zooms_only, scale_up_quality, scale_down_quality ) )
zoom_info = ( media_scale_up, media_scale_down, preview_scale_up, preview_scale_down, exact_zooms_only, scale_up_quality, scale_down_quality )
return ( self._mime, media_show_action, media_start_paused, media_start_with_embed, preview_show_action, preview_start_paused, preview_start_with_embed, zoom_info )
class EditNetworkContextPanel( ClientGUIScrolledPanels.EditPanel ):
@ -6351,9 +6440,9 @@ class EditURLClassPanel( ClientGUIScrolledPanels.EditPanel ):
self._normalised_url = QW.QLineEdit( self )
self._normalised_url.setReadOnly( True )
tt = 'The same url can be expressed in different ways. The parameters can be reordered, and descriptive \'sugar\' like "/123456/some-changing-tags-here" can be appended and then altered at a later date. In order to collapse all the different expressions of a url down to a single comparable form, the client will \'normalise\' them based on the essential definitions in their url class. Parameters will be alphebatised and non-defined elements will be removed.'
tt = 'The same url can be expressed in different ways. The parameters can be reordered, and descriptive \'sugar\' like "/123456/bodysuit-samus_aran" can be altered at a later date, say to "/123456/bodysuit-green_eyes-samus_aran". In order to collapse all the different expressions of a url down to a single comparable form, the client will \'normalise\' them based on the essential definitions in their url class. Parameters will be alphebatised and non-defined elements will be removed.'
tt += os.linesep * 2
tt += 'All normalisation will switch to the preferred scheme, but the alphabetisation of parameters and stripping out of non-defined elements will only occur if this url is associated with files or uses an API Lookup. (In general, you can define gallery and watchable urls a little more loosely since they generally do not need to be compared, but if you will be saving it with a file or need to perform some regex transformation into an API URL, you\'ll want a rigorously defined url class that will normalise to something reliable and pretty.)'
tt += 'All normalisation will switch to the preferred scheme (http/https). The alphabetisation of parameters and stripping out of non-defined elements will occur for all URLs except Gallery URLs or Watchable URLs that do not use an API Lookup. (In general, you can define gallery and watchable urls a little more loosely since they generally do not need to be compared, but if you will be saving it with a file or need to perform some regex transformation into an API URL, you\'ll want a rigorously defined url class that will normalise to something reliable and pretty.)'
self._normalised_url.setToolTip( tt )

View File

@ -1570,6 +1570,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
gridbox = ClientGUICommon.WrapInGrid( self, rows )
QP.AddToLayout( vbox, gridbox, CC.FLAGS_EXPAND_PERPENDICULAR )
QP.AddToLayout( vbox, QW.QWidget( self ), CC.FLAGS_EXPAND_BOTH_WAYS )
self.setLayout( vbox )
@ -1680,7 +1681,8 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
vbox = QP.VBoxLayout()
QP.AddToLayout( vbox, coloursets_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
QP.AddToLayout( vbox, coloursets_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
QP.AddToLayout( vbox, QW.QWidget( self ), CC.FLAGS_EXPAND_BOTH_WAYS )
self.setLayout( vbox )
@ -1834,6 +1836,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
QP.AddToLayout( vbox, general, CC.FLAGS_EXPAND_PERPENDICULAR )
QP.AddToLayout( vbox, proxy_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
QP.AddToLayout( vbox, QW.QWidget( self ), CC.FLAGS_EXPAND_BOTH_WAYS )
self.setLayout( vbox )
@ -1976,7 +1979,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
gridbox = ClientGUICommon.WrapInGrid( gallery_downloader, rows )
gallery_downloader.Add( gridbox, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
gallery_downloader.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
#
@ -1989,7 +1992,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
gridbox = ClientGUICommon.WrapInGrid( subscriptions, rows )
subscriptions.Add( gridbox, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
subscriptions.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
subscriptions.Add( self._subscription_checker_options, CC.FLAGS_EXPAND_PERPENDICULAR )
#
@ -2001,7 +2004,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
gridbox = ClientGUICommon.WrapInGrid( watchers, rows )
watchers.Add( gridbox, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
watchers.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
watchers.Add( self._watcher_checker_options, CC.FLAGS_EXPAND_PERPENDICULAR )
#
@ -2029,6 +2032,8 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
QP.AddToLayout( vbox, watchers, CC.FLAGS_EXPAND_PERPENDICULAR )
QP.AddToLayout( vbox, misc, CC.FLAGS_EXPAND_PERPENDICULAR )
QP.AddToLayout( vbox, QW.QWidget( self ), CC.FLAGS_EXPAND_BOTH_WAYS )
self.setLayout( vbox )
@ -2134,6 +2139,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
gridbox = ClientGUICommon.WrapInGrid( self, rows )
QP.AddToLayout( vbox, gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
QP.AddToLayout( vbox, QW.QWidget( self ), CC.FLAGS_EXPAND_BOTH_WAYS )
self.setLayout( vbox )
@ -2186,9 +2192,8 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
vbox = QP.VBoxLayout()
QP.AddToLayout( vbox, self._always_show_system_everything, CC.FLAGS_VCENTER )
QP.AddToLayout( vbox, self._filter_inbox_and_archive_predicates, CC.FLAGS_VCENTER )
QP.AddToLayout( vbox, (20,20), CC.FLAGS_EXPAND_PERPENDICULAR )
QP.AddToLayout( vbox, self._always_show_system_everything, CC.FLAGS_EXPAND_PERPENDICULAR )
QP.AddToLayout( vbox, self._filter_inbox_and_archive_predicates, CC.FLAGS_EXPAND_PERPENDICULAR )
QP.AddToLayout( vbox, self._file_system_predicate_age, CC.FLAGS_EXPAND_PERPENDICULAR )
QP.AddToLayout( vbox, self._file_system_predicate_duration, CC.FLAGS_EXPAND_PERPENDICULAR )
QP.AddToLayout( vbox, self._file_system_predicate_height, CC.FLAGS_EXPAND_PERPENDICULAR )
@ -2201,6 +2206,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
QP.AddToLayout( vbox, self._file_system_predicate_similar_to, CC.FLAGS_EXPAND_PERPENDICULAR )
QP.AddToLayout( vbox, self._file_system_predicate_size, CC.FLAGS_EXPAND_PERPENDICULAR )
QP.AddToLayout( vbox, self._file_system_predicate_width, CC.FLAGS_EXPAND_PERPENDICULAR )
QP.AddToLayout( vbox, QW.QWidget( self ), CC.FLAGS_EXPAND_BOTH_WAYS )
self.setLayout( vbox )
@ -2810,6 +2816,12 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
self._default_new_page_goes.addItem( CC.new_page_goes_string_lookup[ value], value )
self._activate_window_on_tag_search_page_activation = QW.QCheckBox( self )
tt = 'Middle-clicking one or more tags in a taglist will cause the creation of a new search page for those tags. If you do this from the media viewer or a child manage tags dialog, do you want to switch immediately to the main gui?'
self._activate_window_on_tag_search_page_activation.setToolTip( tt )
self._notebook_tabs_on_left = QW.QCheckBox( self )
self._max_page_name_chars = QP.MakeQSpinBox( self, min=1, max=256 )
@ -2863,6 +2875,8 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
self._default_new_page_goes.SetValue( self._new_options.GetInteger( 'default_new_page_goes' ) )
self._activate_window_on_tag_search_page_activation.setChecked( self._new_options.GetBoolean( 'activate_window_on_tag_search_page_activation' ) )
self._notebook_tabs_on_left.setChecked( self._new_options.GetBoolean( 'notebook_tabs_on_left' ) )
self._max_page_name_chars.setValue( self._new_options.GetInteger( 'max_page_name_chars' ) )
@ -2887,6 +2901,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
rows.append( ( 'Number of session backups to keep: ', self._number_of_gui_session_backups ) )
rows.append( ( 'By default, put new page tabs on (requires restart): ', self._default_new_page_goes ) )
rows.append( ( 'When switching to a page, focus its input field (if any): ', self._set_search_focus_on_page_change ) )
rows.append( ( 'Switch to main window when opening tag search page from media viewer: ', self._activate_window_on_tag_search_page_activation ) )
rows.append( ( 'Line notebook tabs down the left: ', self._notebook_tabs_on_left ) )
rows.append( ( 'Max characters to display in a page name: ', self._max_page_name_chars ) )
rows.append( ( 'Show page file count after its name: ', self._page_file_count_display ) )
@ -2907,6 +2922,8 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
HC.options[ 'default_gui_session' ] = self._default_gui_session.currentText()
self._new_options.SetBoolean( 'activate_window_on_tag_search_page_activation', self._activate_window_on_tag_search_page_activation.isChecked() )
self._new_options.SetBoolean( 'notebook_tabs_on_left', self._notebook_tabs_on_left.isChecked() )
self._new_options.SetInteger( 'last_session_save_period_minutes', self._last_session_save_period_minutes.value() )
@ -2970,6 +2987,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
vbox = QP.VBoxLayout()
QP.AddToLayout( vbox, default_fios, CC.FLAGS_EXPAND_PERPENDICULAR )
QP.AddToLayout( vbox, QW.QWidget( self ), CC.FLAGS_EXPAND_BOTH_WAYS )
self.setLayout( vbox )
@ -3161,6 +3179,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
QP.AddToLayout( vbox, self._jobs_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
QP.AddToLayout( vbox, self._file_maintenance_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
QP.AddToLayout( vbox, self._vacuum_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
QP.AddToLayout( vbox, QW.QWidget( self ), CC.FLAGS_EXPAND_BOTH_WAYS )
self.setLayout( vbox )
@ -3196,6 +3215,8 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
self._shutdown_work_period.setEnabled( True )
self._idle_shutdown_max_minutes.setEnabled( True )
def UpdateOptions( self ):
HC.options[ 'idle_normal' ] = self._idle_normal.isChecked()
@ -3281,7 +3302,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
self._media_zooms.setText( ','.join( ( str( media_zoom ) for media_zoom in media_zooms ) ) )
mimes_in_correct_order = ( HC.IMAGE_JPEG, HC.IMAGE_PNG, HC.IMAGE_APNG, HC.IMAGE_GIF, HC.IMAGE_WEBP, HC.IMAGE_TIFF, HC.IMAGE_ICON, HC.APPLICATION_FLASH, HC.APPLICATION_PDF, HC.APPLICATION_PSD, HC.APPLICATION_ZIP, HC.APPLICATION_RAR, HC.APPLICATION_7Z, HC.APPLICATION_HYDRUS_UPDATE_CONTENT, HC.APPLICATION_HYDRUS_UPDATE_DEFINITIONS, HC.VIDEO_AVI, HC.VIDEO_FLV, HC.VIDEO_MOV, HC.VIDEO_MP4, HC.VIDEO_MKV, HC.VIDEO_MPEG, HC.VIDEO_WEBM, HC.VIDEO_WMV, HC.AUDIO_MP3, HC.AUDIO_M4A, HC.AUDIO_OGG, HC.AUDIO_FLAC, HC.AUDIO_WMA )
mimes_in_correct_order = ( HC.IMAGE_JPEG, HC.IMAGE_PNG, HC.IMAGE_APNG, HC.IMAGE_GIF, HC.IMAGE_WEBP, HC.IMAGE_TIFF, HC.IMAGE_ICON, HC.APPLICATION_FLASH, HC.APPLICATION_PDF, HC.APPLICATION_PSD, HC.APPLICATION_ZIP, HC.APPLICATION_RAR, HC.APPLICATION_7Z, HC.APPLICATION_HYDRUS_UPDATE_CONTENT, HC.APPLICATION_HYDRUS_UPDATE_DEFINITIONS, HC.VIDEO_AVI, HC.VIDEO_FLV, HC.VIDEO_MOV, HC.VIDEO_MP4, HC.VIDEO_MKV, HC.VIDEO_WEBM, HC.VIDEO_WMV, HC.VIDEO_MPEG, HC.VIDEO_REALMEDIA, HC.AUDIO_MP3, HC.AUDIO_M4A, HC.AUDIO_OGG, HC.AUDIO_FLAC, HC.AUDIO_WMA, HC.AUDIO_REALMEDIA, HC.AUDIO_TRUEAUDIO )
for mime in mimes_in_correct_order:
@ -3292,7 +3313,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
self._media_viewer_options.AddDatas( ( data, ) )
#self._media_viewer_options.SortListItems( col = 0 )
self._media_viewer_options.Sort()
#
@ -3324,13 +3345,47 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
def _GetListCtrlData( self, data ):
( mime, media_show_action, preview_show_action, zoom_info ) = data
( mime, media_show_action, media_start_paused, media_start_with_embed, preview_show_action, preview_start_paused, preview_start_with_embed, zoom_info ) = data
pretty_mime = HC.mime_string_lookup[ mime ]
for general_mime in HC.GENERAL_FILETYPES:
mime_group = HC.general_mimetypes_to_mime_groups[ general_mime ]
if mime in mime_group:
pretty_mime = '{}: {}'.format( HC.mime_string_lookup[ general_mime ], pretty_mime )
break
pretty_media_show_action = CC.media_viewer_action_string_lookup[ media_show_action ]
if media_start_paused:
pretty_media_show_action += ', start paused'
if media_start_with_embed:
pretty_media_show_action += ', start with embed button'
pretty_preview_show_action = CC.media_viewer_action_string_lookup[ preview_show_action ]
no_show = media_show_action in CC.no_support and preview_show_action in CC.no_support
if preview_start_paused:
pretty_preview_show_action += ', start paused'
if preview_start_with_embed:
pretty_preview_show_action += ', start with embed button'
no_show = len( set( ( media_show_action, preview_show_action ) ).intersection( { CC.MEDIA_VIEWER_ACTION_SHOW_WITH_NATIVE, CC.MEDIA_VIEWER_ACTION_SHOW_WITH_MPV } ) ) == 0
if no_show:
@ -3778,6 +3833,8 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
#
QP.AddToLayout( vbox, QW.QWidget( self ), CC.FLAGS_EXPAND_BOTH_WAYS )
self.setLayout( vbox )
#
@ -5112,7 +5169,19 @@ class ManageShortcutsPanel( ClientGUIScrolledPanels.ManagePanel ):
vbox = QP.VBoxLayout()
QP.AddToLayout( vbox, ClientGUICommon.WrapInText(self._name,self,'name: '), CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
QP.AddToLayout( vbox, ClientGUICommon.WrapInText( self._name, self, 'name: ' ), CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
if name in CC.shortcut_names_to_descriptions:
description_text = CC.shortcut_names_to_descriptions[ name ]
description = ClientGUICommon.BetterStaticText( self, description_text, description_text )
description.setWordWrap( True )
QP.AddToLayout( vbox, description, CC.FLAGS_EXPAND_PERPENDICULAR )
QP.AddToLayout( vbox, self._shortcuts, CC.FLAGS_EXPAND_BOTH_WAYS )
QP.AddToLayout( vbox, action_buttons, CC.FLAGS_BUTTON_SIZER )

View File

@ -2823,6 +2823,14 @@ class ReviewHowBonedAmI( ClientGUIScrolledPanels.ReviewPanel ):
else:
average_filesize = size_total // num_total
summary_label = 'Total: {} files, totalling {}, averaging {}'.format( HydrusData.ToHumanInt( num_total ), HydrusData.ToHumanBytes( size_total ), HydrusData.ToHumanBytes( average_filesize ) )
summary_st = ClientGUICommon.BetterStaticText( self, label = summary_label )
QP.AddToLayout( vbox, summary_st, CC.FLAGS_CENTER )
num_archive_percent = num_archive / num_total
size_archive_percent = size_archive / size_total

View File

@ -117,15 +117,15 @@ def ConvertMouseEventToShortcut( event ):
key = None
if (event.type() == QC.QEvent.MouseButtonPress and event.buttons() & QC.Qt.LeftButton) or (event.type() == QC.QEvent.MouseButtonDblClick and event.button() == QC.Qt.LeftButton):
if ( event.type() == QC.QEvent.MouseButtonPress and event.buttons() & QC.Qt.LeftButton ) or ( event.type() == QC.QEvent.MouseButtonDblClick and event.button() == QC.Qt.LeftButton ):
key = CC.SHORTCUT_MOUSE_LEFT
elif (event.type() == QC.QEvent.MouseButtonPress and event.buttons() & QC.Qt.MiddleButton) or (event.type() == QC.QEvent.MouseButtonDblClick and event.button() == QC.Qt.MiddleButton):
elif ( event.type() == QC.QEvent.MouseButtonPress and event.buttons() & QC.Qt.MiddleButton ) or ( event.type() == QC.QEvent.MouseButtonDblClick and event.button() == QC.Qt.MiddleButton ):
key = CC.SHORTCUT_MOUSE_MIDDLE
elif (event.type() == QC.QEvent.MouseButtonPress and event.buttons() & QC.Qt.RightButton) or (event.type() == QC.QEvent.MouseButtonDblClick and event.button() == QC.Qt.RightButton):
elif ( event.type() == QC.QEvent.MouseButtonPress and event.buttons() & QC.Qt.RightButton ) or ( event.type() == QC.QEvent.MouseButtonDblClick and event.button() == QC.Qt.RightButton ):
key = CC.SHORTCUT_MOUSE_RIGHT

View File

@ -1263,25 +1263,33 @@ class Subscription( HydrusSerialisable.SerialisableBaseNamed ):
only_these_queries = set( only_these_queries )
my_queries = self._queries
self._queries = []
base_sub = self.Duplicate()
self._queries = my_queries
subscriptions = []
for query in self._queries:
for query in my_queries:
if query not in only_these_queries:
continue
subscription = self.Duplicate()
subscription = base_sub.Duplicate()
subscription._queries = [ query.Duplicate() ]
subscription._queries = [ query ]
subscription.SetName( base_name + ': ' + query.GetHumanName() )
subscriptions.append( subscription )
self._queries = [ query for query in self._queries if query not in only_these_queries ]
self._queries = [ query for query in my_queries if query not in only_these_queries ]
return subscriptions

View File

@ -1449,7 +1449,7 @@ class HydrusResourceClientAPIRestrictedGetFilesFileMetadata( HydrusResourceClien
metadata_row[ 'file_id' ] = file_info_manager.hash_id
metadata_row[ 'hash' ] = file_info_manager.hash.hex()
metadata_row[ 'size' ] = file_info_manager.size
metadata_row[ 'mime' ] = HC.mime_string_lookup[ file_info_manager.mime ]
metadata_row[ 'mime' ] = HC.mime_mimetype_string_lookup[ file_info_manager.mime ]
metadata_row[ 'width' ] = file_info_manager.width
metadata_row[ 'height' ] = file_info_manager.height
metadata_row[ 'duration' ] = file_info_manager.duration

View File

@ -1564,11 +1564,11 @@ class MediaList( object ):
elif file_filter.filter_type == FILE_FILTER_LOCAL:
filtered_media = { m for m in self._sorted_media if file_service_key in m.GetLocationsManager().IsLocal() }
filtered_media = { m for m in self._sorted_media if m.GetLocationsManager().IsLocal() }
elif file_filter.filter_type == FILE_FILTER_REMOTE:
filtered_media = { m for m in self._sorted_media if file_service_key in m.GetLocationsManager().IsRemote() }
filtered_media = { m for m in self._sorted_media if m.GetLocationsManager().IsRemote() }
elif file_filter.filter_type == FILE_FILTER_TAGS:
@ -1666,7 +1666,7 @@ class MediaList( object ):
new_options = HG.client_controller.new_options
media_show_action = new_options.GetMediaShowAction( media.GetMime() )
( media_show_action, media_start_paused, media_start_with_embed ) = new_options.GetMediaShowAction( media.GetMime() )
if media_show_action in ( CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW_ON_ACTIVATION_OPEN_EXTERNALLY, CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW ):
@ -1953,6 +1953,7 @@ FILE_FILTER_LOCAL = 6
FILE_FILTER_REMOTE = 7
FILE_FILTER_TAGS = 8
FILE_FILTER_SELECTED = 9
FILE_FILTER_MIME = 10
file_filter_str_lookup = {}
@ -1966,6 +1967,7 @@ file_filter_str_lookup[ FILE_FILTER_FILE_SERVICE ] = 'file service'
file_filter_str_lookup[ FILE_FILTER_LOCAL ] = 'local'
file_filter_str_lookup[ FILE_FILTER_REMOTE ] = 'remote'
file_filter_str_lookup[ FILE_FILTER_TAGS ] = 'tags'
file_filter_str_lookup[ FILE_FILTER_MIME ] = 'filetype'
class FileFilter( object ):
@ -1998,18 +2000,18 @@ class FileFilter( object ):
return
quick_calculations = {}
quick_inverse_lookups= {}
quick_calculations[ FileFilter( FILE_FILTER_INBOX ) ] = FileFilter( FILE_FILTER_ARCHIVE )
quick_calculations[ FileFilter( FILE_FILTER_ARCHIVE ) ] = FileFilter( FILE_FILTER_INBOX )
quick_calculations[ FileFilter( FILE_FILTER_SELECTED ) ] = FileFilter( FILE_FILTER_NOT_SELECTED )
quick_calculations[ FileFilter( FILE_FILTER_NOT_SELECTED ) ] = FileFilter( FILE_FILTER_SELECTED )
quick_calculations[ FileFilter( FILE_FILTER_LOCAL ) ] = FileFilter( FILE_FILTER_REMOTE )
quick_calculations[ FileFilter( FILE_FILTER_REMOTE ) ] = FileFilter( FILE_FILTER_LOCAL )
quick_inverse_lookups[ FileFilter( FILE_FILTER_INBOX ) ] = FileFilter( FILE_FILTER_ARCHIVE )
quick_inverse_lookups[ FileFilter( FILE_FILTER_ARCHIVE ) ] = FileFilter( FILE_FILTER_INBOX )
quick_inverse_lookups[ FileFilter( FILE_FILTER_SELECTED ) ] = FileFilter( FILE_FILTER_NOT_SELECTED )
quick_inverse_lookups[ FileFilter( FILE_FILTER_NOT_SELECTED ) ] = FileFilter( FILE_FILTER_SELECTED )
quick_inverse_lookups[ FileFilter( FILE_FILTER_LOCAL ) ] = FileFilter( FILE_FILTER_REMOTE )
quick_inverse_lookups[ FileFilter( FILE_FILTER_REMOTE ) ] = FileFilter( FILE_FILTER_LOCAL )
if self in quick_calculations:
if self in quick_inverse_lookups:
inverse = quick_calculations[ self ]
inverse = quick_inverse_lookups[ self ]
all_filter = FileFilter( FILE_FILTER_ALL )
@ -2050,6 +2052,12 @@ class FileFilter( object ):
s = HydrusText.ElideText( s, 64 )
elif self.filter_type == FILE_FILTER_MIME:
mime = self.filter_data
s = HC.mime_string_lookup[ mime ]
else:
s = file_filter_str_lookup[ self.filter_type ]
@ -2231,7 +2239,16 @@ class MediaCollection( MediaList, Media ):
def GetDisplayMedia( self ):
return self._GetFirst().GetDisplayMedia()
first = self._GetFirst()
if first is None:
return None
else:
return first.GetDisplayMedia()
def GetDuration( self ):

View File

@ -390,7 +390,7 @@ class NetworkDomainManager( HydrusSerialisable.SerialisableBase ):
self._url_class_keys_to_display = set()
self._url_class_keys_to_parser_keys = HydrusSerialisable.SerialisableBytesDictionary()
self._domains_to_url_classes = collections.defaultdict( list )
self._second_level_domains_to_url_classes = collections.defaultdict( list )
from . import ClientImportOptions
@ -552,9 +552,9 @@ class NetworkDomainManager( HydrusSerialisable.SerialisableBase ):
domain = ConvertDomainIntoSecondLevelDomain( ConvertURLIntoDomain( url ) )
if domain in self._domains_to_url_classes:
if domain in self._second_level_domains_to_url_classes:
url_classes = self._domains_to_url_classes[ domain ]
url_classes = self._second_level_domains_to_url_classes[ domain ]
for url_class in url_classes:
@ -635,16 +635,16 @@ class NetworkDomainManager( HydrusSerialisable.SerialisableBase ):
def _RecalcCache( self ):
self._domains_to_url_classes = collections.defaultdict( list )
self._second_level_domains_to_url_classes = collections.defaultdict( list )
for url_class in self._url_classes:
domain = url_class.GetDomain()
domain = ConvertDomainIntoSecondLevelDomain( url_class.GetDomain() )
self._domains_to_url_classes[ domain ].append( url_class )
self._second_level_domains_to_url_classes[ domain ].append( url_class )
for url_classes in list(self._domains_to_url_classes.values()):
for url_classes in self._second_level_domains_to_url_classes.values():
NetworkDomainManager.STATICSortURLClassesDescendingComplexity( url_classes )
@ -1150,6 +1150,16 @@ class NetworkDomainManager( HydrusSerialisable.SerialisableBase ):
self.SetGUGs( gugs )
def DeleteURLClasses( self, deletee_names ):
with self._lock:
url_classes = [ url_class for url_class in self._url_classes if url_class.GetName() not in deletee_names ]
self.SetURLClasses( url_classes )
def GenerateValidationPopupProcess( self, network_contexts ):
with self._lock:
@ -2936,7 +2946,7 @@ class URLClass( HydrusSerialisable.SerialisableBaseNamed ):
def GetDomain( self ):
return ConvertDomainIntoSecondLevelDomain( self._netloc )
return self._netloc
def GetExampleURL( self ):

View File

@ -18,7 +18,7 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_CLIENT_OPTIONS
SERIALISABLE_NAME = 'Client Options'
SERIALISABLE_VERSION = 3
SERIALISABLE_VERSION = 4
def __init__( self ):
@ -60,6 +60,8 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
self._dictionary[ 'booleans' ][ 'replace_siblings_on_manage_tags' ] = True
self._dictionary[ 'booleans' ][ 'yes_no_on_remove_on_manage_tags' ] = True
self._dictionary[ 'booleans' ][ 'activate_window_on_tag_search_page_activation' ] = False
self._dictionary[ 'booleans' ][ 'show_related_tags' ] = False
self._dictionary[ 'booleans' ][ 'show_file_lookup_script_tags' ] = False
self._dictionary[ 'booleans' ][ 'hide_message_manager_on_gui_iconise' ] = HC.PLATFORM_MACOS
@ -468,7 +470,7 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
self._dictionary[ 'media_view' ] = HydrusSerialisable.SerialisableDictionary() # integer keys, so got to be cleverer dict
# media_show_action, preview_show_action, ( media_scale_up, media_scale_down, preview_scale_up, preview_scale_down, exact_zooms_only, scale_up_quality, scale_down_quality ) )
# media_show_action, media_start_paused, media_start_with_embed, preview_show_action, preview_start_paused, preview_start_with_embed, ( media_scale_up, media_scale_down, preview_scale_up, preview_scale_down, exact_zooms_only, scale_up_quality, scale_down_quality ) )
image_zoom_info = ( CC.MEDIA_VIEWER_SCALE_TO_CANVAS, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, False, CC.ZOOM_LANCZOS4, CC.ZOOM_AREA )
gif_zoom_info = ( CC.MEDIA_VIEWER_SCALE_MAX_REGULAR, CC.MEDIA_VIEWER_SCALE_MAX_REGULAR, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, True, CC.ZOOM_LANCZOS4, CC.ZOOM_AREA )
@ -476,37 +478,37 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
video_zoom_info = ( CC.MEDIA_VIEWER_SCALE_100, CC.MEDIA_VIEWER_SCALE_MAX_REGULAR, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, True, CC.ZOOM_LANCZOS4, CC.ZOOM_AREA )
null_zoom_info = ( CC.MEDIA_VIEWER_SCALE_100, CC.MEDIA_VIEWER_SCALE_100, CC.MEDIA_VIEWER_SCALE_100, CC.MEDIA_VIEWER_SCALE_100, False, CC.ZOOM_LINEAR, CC.ZOOM_LINEAR )
self._dictionary[ 'media_view' ][ HC.IMAGE_JPEG ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, image_zoom_info )
self._dictionary[ 'media_view' ][ HC.IMAGE_PNG ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, image_zoom_info )
self._dictionary[ 'media_view' ][ HC.IMAGE_APNG ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, gif_zoom_info )
self._dictionary[ 'media_view' ][ HC.IMAGE_GIF ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, gif_zoom_info )
self._dictionary[ 'media_view' ][ HC.IMAGE_WEBP ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, image_zoom_info )
self._dictionary[ 'media_view' ][ HC.IMAGE_TIFF ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, image_zoom_info )
self._dictionary[ 'media_view' ][ HC.IMAGE_ICON ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, image_zoom_info )
media_start_paused = False
media_start_with_embed = False
preview_start_paused = False
preview_start_with_embed = False
self._dictionary[ 'media_view' ][ HC.APPLICATION_FLASH ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, null_zoom_info )
for mime in HC.SEARCHABLE_MIMES:
if mime in HC.IMAGES_THAT_CAN_HAVE_ANIMATION:
self._dictionary[ 'media_view' ][ mime ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_WITH_NATIVE, media_start_paused, media_start_with_embed, CC.MEDIA_VIEWER_ACTION_SHOW_WITH_NATIVE, preview_start_paused, preview_start_with_embed, gif_zoom_info )
elif mime in HC.IMAGES:
self._dictionary[ 'media_view' ][ mime ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_WITH_NATIVE, media_start_paused, media_start_with_embed, CC.MEDIA_VIEWER_ACTION_SHOW_WITH_NATIVE, preview_start_paused, preview_start_with_embed, image_zoom_info )
elif mime in HC.VIDEO:
self._dictionary[ 'media_view' ][ mime ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_WITH_NATIVE, media_start_paused, media_start_with_embed, CC.MEDIA_VIEWER_ACTION_SHOW_WITH_NATIVE, preview_start_paused, preview_start_with_embed, video_zoom_info )
elif mime in HC.AUDIO:
self._dictionary[ 'media_view' ][ mime ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, media_start_paused, media_start_with_embed, CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, preview_start_paused, preview_start_with_embed, null_zoom_info )
elif mime in HC.APPLICATIONS:
self._dictionary[ 'media_view' ][ mime ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, media_start_paused, media_start_with_embed, CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, preview_start_paused, preview_start_with_embed, null_zoom_info )
self._dictionary[ 'media_view' ][ HC.APPLICATION_PDF ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, null_zoom_info )
self._dictionary[ 'media_view' ][ HC.APPLICATION_PSD ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, null_zoom_info )
self._dictionary[ 'media_view' ][ HC.APPLICATION_ZIP ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, null_zoom_info )
self._dictionary[ 'media_view' ][ HC.APPLICATION_7Z ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, null_zoom_info )
self._dictionary[ 'media_view' ][ HC.APPLICATION_RAR ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, null_zoom_info )
self._dictionary[ 'media_view' ][ HC.APPLICATION_HYDRUS_UPDATE_CONTENT ] = ( CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW, CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW, null_zoom_info )
self._dictionary[ 'media_view' ][ HC.APPLICATION_HYDRUS_UPDATE_DEFINITIONS ] = ( CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW, CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW, null_zoom_info )
self._dictionary[ 'media_view' ][ HC.VIDEO_AVI ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, video_zoom_info )
self._dictionary[ 'media_view' ][ HC.VIDEO_FLV ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, video_zoom_info )
self._dictionary[ 'media_view' ][ HC.VIDEO_MOV ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, video_zoom_info )
self._dictionary[ 'media_view' ][ HC.VIDEO_MP4 ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, video_zoom_info )
self._dictionary[ 'media_view' ][ HC.VIDEO_MPEG ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, video_zoom_info )
self._dictionary[ 'media_view' ][ HC.VIDEO_MKV ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, video_zoom_info )
self._dictionary[ 'media_view' ][ HC.VIDEO_WEBM ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, video_zoom_info )
self._dictionary[ 'media_view' ][ HC.VIDEO_WMV ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, video_zoom_info )
self._dictionary[ 'media_view' ][ HC.AUDIO_M4A ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, null_zoom_info )
self._dictionary[ 'media_view' ][ HC.AUDIO_MP3 ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, null_zoom_info )
self._dictionary[ 'media_view' ][ HC.AUDIO_OGG ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, null_zoom_info )
self._dictionary[ 'media_view' ][ HC.AUDIO_FLAC ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, null_zoom_info )
self._dictionary[ 'media_view' ][ HC.AUDIO_WMA ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, null_zoom_info )
self._dictionary[ 'media_view' ][ HC.APPLICATION_HYDRUS_UPDATE_CONTENT ] = ( CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW, media_start_paused, media_start_with_embed, CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW, preview_start_paused, preview_start_with_embed, null_zoom_info )
self._dictionary[ 'media_view' ][ HC.APPLICATION_HYDRUS_UPDATE_DEFINITIONS ] = ( CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW, media_start_paused, media_start_with_embed, CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW, preview_start_paused, preview_start_with_embed, null_zoom_info )
self._dictionary[ 'media_zooms' ] = [ 0.01, 0.05, 0.1, 0.15, 0.2, 0.3, 0.5, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.5, 2.0, 3.0, 5.0, 10.0, 20.0 ]
@ -666,6 +668,39 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
return ( 3, new_serialisable_info )
if version == 3:
loaded_dictionary = HydrusSerialisable.CreateFromSerialisableTuple( old_serialisable_info )
if 'media_view' in loaded_dictionary:
def convert_show_action( show_action ):
start_paused = show_action in ( CC.MEDIA_VIEWER_ACTION_SHOW_BEHIND_EMBED_PAUSED, CC.MEDIA_VIEWER_ACTION_SHOW_WITH_NATIVE_PAUSED )
start_with_embed = show_action in ( CC.MEDIA_VIEWER_ACTION_SHOW_BEHIND_EMBED, CC.MEDIA_VIEWER_ACTION_SHOW_BEHIND_EMBED_PAUSED )
if start_paused or start_with_embed:
show_action = CC.MEDIA_VIEWER_ACTION_SHOW_WITH_NATIVE
return ( show_action, start_paused, start_with_embed )
for ( mime, ( media_show_action, preview_show_action, zoom_info ) ) in list( loaded_dictionary[ 'media_view' ].items() ):
( media_show_action, media_start_paused, media_start_with_embed ) = convert_show_action( media_show_action )
( preview_show_action, preview_start_paused, preview_start_with_embed ) = convert_show_action( preview_show_action )
loaded_dictionary[ 'media_view' ][ mime ] = ( media_show_action, media_start_paused, media_start_with_embed, preview_show_action, preview_start_paused, preview_start_with_embed, zoom_info )
new_serialisable_info = loaded_dictionary.GetSerialisableTuple()
return ( 4, new_serialisable_info )
def FlipBoolean( self, name ):
@ -813,14 +848,16 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
with self._lock:
( media_show_action, preview_show_action, zoom_info ) = self._dictionary[ 'media_view' ][ mime ]
( media_show_action, media_start_paused, media_start_with_embed, preview_show_action, preview_start_paused, preview_start_with_embed, zoom_info ) = self._dictionary[ 'media_view' ][ mime ]
if media_show_action not in CC.media_viewer_capabilities[ mime ]:
( possible_show_actions, can_start_paused, can_start_with_embed ) = CC.media_viewer_capabilities[ mime ]
if media_show_action not in possible_show_actions:
return CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON
return ( CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, False, False )
return media_show_action
return ( media_show_action, media_start_paused, media_start_with_embed )
@ -836,7 +873,7 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
with self._lock:
( media_show_action, preview_show_action, zoom_info ) = self._dictionary[ 'media_view' ][ mime ]
( media_show_action, media_start_paused, media_start_with_embed, preview_show_action, preview_start_paused, preview_start_with_embed, zoom_info ) = self._dictionary[ 'media_view' ][ mime ]
return zoom_info
@ -854,7 +891,9 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
with self._lock:
( media_show_action, preview_show_action, ( media_scale_up, media_scale_down, preview_scale_up, preview_scale_down, exact_zooms_only, scale_up_quality, scale_down_quality ) ) = self._dictionary[ 'media_view' ][ mime ]
( media_show_action, media_start_paused, media_start_with_embed, preview_show_action, preview_start_paused, preview_start_with_embed, zoom_info ) = self._dictionary[ 'media_view' ][ mime ]
( media_scale_up, media_scale_down, preview_scale_up, preview_scale_down, exact_zooms_only, scale_up_quality, scale_down_quality ) = zoom_info
return ( scale_up_quality, scale_down_quality )
@ -893,14 +932,16 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
with self._lock:
( media_show_action, preview_show_action, zoom_info ) = self._dictionary[ 'media_view' ][ mime ]
( media_show_action, media_start_paused, media_start_with_embed, preview_show_action, preview_start_paused, preview_start_with_embed, zoom_info ) = self._dictionary[ 'media_view' ][ mime ]
if preview_show_action not in CC.media_viewer_capabilities[ mime ]:
( possible_show_actions, can_start_paused, can_start_with_embed ) = CC.media_viewer_capabilities[ mime ]
if preview_show_action not in possible_show_actions:
return CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON
return ( CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, False, False )
return preview_show_action
return ( preview_show_action, preview_start_paused, preview_start_with_embed )

View File

@ -2356,7 +2356,7 @@ class ParseNodeContentLink( HydrusSerialisable.SerialisableBase ):
for search_url in search_urls:
job_key.SetVariable( 'script_status', 'fetching ' + search_url )
job_key.SetVariable( 'script_status', 'fetching ' + HydrusText.ElideText( search_url, 32 ) )
network_job = ClientNetworkingJobs.NetworkJob( 'GET', search_url, referral_url = referral_url )
@ -2386,7 +2386,7 @@ class ParseNodeContentLink( HydrusSerialisable.SerialisableBase ):
job_key.SetVariable( 'script_status', 'Network error! Details written to log.' )
HydrusData.Print( 'Problem fetching ' + search_url + ':' )
HydrusData.Print( 'Problem fetching ' + HydrusText.ElideText( search_url, 32 ) + ':' )
HydrusData.PrintException( e )
time.sleep( 2 )
@ -2605,7 +2605,7 @@ class ParseRootFileLookup( HydrusSerialisable.SerialisableBaseNamed ):
full_request_url = self._url + '?' + ClientNetworkingDomain.ConvertQueryDictToText( request_args )
job_key.SetVariable( 'script_status', 'fetching ' + full_request_url )
job_key.SetVariable( 'script_status', 'fetching ' + HydrusText.ElideText( full_request_url, 32 ) )
job_key.AddURL( full_request_url )

View File

@ -976,7 +976,7 @@ class ServiceRestricted( ServiceRemote ):
if content_type is not None:
network_job.AddAdditionalHeader( 'Content-Type', HC.mime_string_lookup[ content_type ] )
network_job.AddAdditionalHeader( 'Content-Type', HC.mime_mimetype_string_lookup[ content_type ] )
HG.client_controller.network_engine.AddJob( network_job )
@ -2645,7 +2645,7 @@ class ServiceIPFS( ServiceRemote ):
url = api_base_url + 'add'
mime_string = HC.mime_string_lookup[ mime ]
mime_string = HC.mime_mimetype_string_lookup[ mime ]
files = { 'path' : ( hash.hex(), f, mime_string ) }

View File

@ -67,7 +67,7 @@ options = {}
# Misc
NETWORK_VERSION = 18
SOFTWARE_VERSION = 379
SOFTWARE_VERSION = 380
CLIENT_API_VERSION = 11
SERVER_THUMBNAIL_DIMENSIONS = ( 200, 200 )
@ -481,30 +481,47 @@ IMAGE_WEBP = 33
IMAGE_TIFF = 34
APPLICATION_PSD = 35
AUDIO_M4A = 36
VIDEO_REALMEDIA = 37
AUDIO_REALMEDIA = 38
AUDIO_TRUEAUDIO = 39
GENERAL_AUDIO = 40
GENERAL_IMAGE = 41
GENERAL_VIDEO = 42
GENERAL_APPLICATION = 43
APPLICATION_OCTET_STREAM = 100
APPLICATION_UNKNOWN = 101
ALLOWED_MIMES = ( IMAGE_JPEG, IMAGE_PNG, IMAGE_APNG, IMAGE_GIF, IMAGE_BMP, IMAGE_WEBP, IMAGE_TIFF, IMAGE_ICON, APPLICATION_FLASH, VIDEO_AVI, VIDEO_FLV, VIDEO_MOV, VIDEO_MP4, VIDEO_MKV, VIDEO_WEBM, VIDEO_MPEG, APPLICATION_PSD, APPLICATION_PDF, APPLICATION_ZIP, APPLICATION_RAR, APPLICATION_7Z, AUDIO_M4A, AUDIO_MP3, AUDIO_OGG, AUDIO_FLAC, AUDIO_WMA, VIDEO_WMV, APPLICATION_HYDRUS_UPDATE_CONTENT, APPLICATION_HYDRUS_UPDATE_DEFINITIONS )
SEARCHABLE_MIMES = ( IMAGE_JPEG, IMAGE_PNG, IMAGE_APNG, IMAGE_GIF, IMAGE_WEBP, IMAGE_TIFF, IMAGE_ICON, APPLICATION_FLASH, VIDEO_AVI, VIDEO_FLV, VIDEO_MOV, VIDEO_MP4, VIDEO_MKV, VIDEO_WEBM, VIDEO_MPEG, APPLICATION_PSD, APPLICATION_PDF, APPLICATION_ZIP, APPLICATION_RAR, APPLICATION_7Z, AUDIO_M4A, AUDIO_MP3, AUDIO_OGG, AUDIO_FLAC, AUDIO_WMA, VIDEO_WMV )
GENERAL_FILETYPES = ( GENERAL_APPLICATION, GENERAL_AUDIO, GENERAL_IMAGE, GENERAL_VIDEO )
SEARCHABLE_MIMES = { IMAGE_JPEG, IMAGE_PNG, IMAGE_APNG, IMAGE_GIF, IMAGE_WEBP, IMAGE_TIFF, IMAGE_ICON, APPLICATION_FLASH, VIDEO_AVI, VIDEO_FLV, VIDEO_MOV, VIDEO_MP4, VIDEO_MKV, VIDEO_REALMEDIA, VIDEO_WEBM, VIDEO_MPEG, APPLICATION_PSD, APPLICATION_PDF, APPLICATION_ZIP, APPLICATION_RAR, APPLICATION_7Z, AUDIO_M4A, AUDIO_MP3, AUDIO_REALMEDIA, AUDIO_OGG, AUDIO_FLAC, AUDIO_TRUEAUDIO, AUDIO_WMA, VIDEO_WMV }
ALLOWED_MIMES = set( SEARCHABLE_MIMES ).union( { IMAGE_BMP, APPLICATION_HYDRUS_UPDATE_CONTENT, APPLICATION_HYDRUS_UPDATE_DEFINITIONS } )
DECOMPRESSION_BOMB_IMAGES = ( IMAGE_JPEG, IMAGE_PNG )
IMAGES = ( IMAGE_JPEG, IMAGE_PNG, IMAGE_APNG, IMAGE_GIF, IMAGE_BMP, IMAGE_WEBP, IMAGE_TIFF, IMAGE_ICON )
AUDIO = ( AUDIO_M4A, AUDIO_MP3, AUDIO_OGG, AUDIO_FLAC, AUDIO_WMA )
IMAGES_THAT_CAN_HAVE_ANIMATION = ( IMAGE_GIF, IMAGE_APNG )
VIDEO = ( VIDEO_AVI, VIDEO_FLV, VIDEO_MOV, VIDEO_MP4, VIDEO_WMV, VIDEO_MKV, VIDEO_WEBM, VIDEO_MPEG )
AUDIO = ( AUDIO_M4A, AUDIO_MP3, AUDIO_OGG, AUDIO_FLAC, AUDIO_WMA, AUDIO_REALMEDIA, AUDIO_TRUEAUDIO )
NATIVE_VIDEO = ( IMAGE_APNG, VIDEO_AVI, VIDEO_FLV, VIDEO_MOV, VIDEO_MP4, VIDEO_WMV, VIDEO_MKV, VIDEO_WEBM, VIDEO_MPEG )
VIDEO = ( VIDEO_AVI, VIDEO_FLV, VIDEO_MOV, VIDEO_MP4, VIDEO_WMV, VIDEO_MKV, VIDEO_REALMEDIA, VIDEO_WEBM, VIDEO_MPEG )
APPLICATIONS = ( APPLICATION_FLASH, APPLICATION_PSD, APPLICATION_PDF, APPLICATION_ZIP, APPLICATION_RAR, APPLICATION_7Z )
general_mimetypes_to_mime_groups = {}
general_mimetypes_to_mime_groups[ GENERAL_APPLICATION ] = APPLICATIONS
general_mimetypes_to_mime_groups[ GENERAL_AUDIO ] = AUDIO
general_mimetypes_to_mime_groups[ GENERAL_IMAGE ] = IMAGES
general_mimetypes_to_mime_groups[ GENERAL_VIDEO ] = VIDEO
MIMES_THAT_DEFINITELY_HAVE_AUDIO = tuple( [ APPLICATION_FLASH ] + list( AUDIO ) )
MIMES_THAT_MAY_HAVE_AUDIO = tuple( list( MIMES_THAT_DEFINITELY_HAVE_AUDIO ) + list( VIDEO ) )
ARCHIVES = ( APPLICATION_ZIP, APPLICATION_HYDRUS_ENCRYPTED_ZIP, APPLICATION_RAR, APPLICATION_7Z )
MIMES_WITH_THUMBNAILS = ( APPLICATION_FLASH, IMAGE_JPEG, IMAGE_PNG, IMAGE_APNG, IMAGE_GIF, IMAGE_BMP, IMAGE_WEBP, IMAGE_TIFF, IMAGE_ICON, VIDEO_AVI, VIDEO_FLV, VIDEO_MOV, VIDEO_MP4, VIDEO_WMV, VIDEO_MKV, VIDEO_WEBM, VIDEO_MPEG )
MIMES_WITH_THUMBNAILS = ( APPLICATION_FLASH, IMAGE_JPEG, IMAGE_PNG, IMAGE_APNG, IMAGE_GIF, IMAGE_BMP, IMAGE_WEBP, IMAGE_TIFF, IMAGE_ICON, VIDEO_AVI, VIDEO_FLV, VIDEO_MOV, VIDEO_MP4, VIDEO_WMV, VIDEO_MKV, VIDEO_REALMEDIA, VIDEO_WEBM, VIDEO_MPEG )
HYDRUS_UPDATE_FILES = ( APPLICATION_HYDRUS_UPDATE_DEFINITIONS, APPLICATION_HYDRUS_UPDATE_CONTENT )
@ -544,6 +561,8 @@ mime_enum_lookup[ 'application' ] = APPLICATIONS
mime_enum_lookup[ 'audio/mp4' ] = AUDIO_M4A
mime_enum_lookup[ 'audio/mp3' ] = AUDIO_MP3
mime_enum_lookup[ 'audio/ogg' ] = AUDIO_OGG
mime_enum_lookup[ 'audio/vnd.rn-realaudio' ] = AUDIO_REALMEDIA
mime_enum_lookup[ 'audio/x-tta' ] = AUDIO_TRUEAUDIO
mime_enum_lookup[ 'audio/flac' ] = AUDIO_FLAC
mime_enum_lookup[ 'audio/x-ms-wma' ] = AUDIO_WMA
mime_enum_lookup[ 'text/html' ] = TEXT_HTML
@ -555,6 +574,8 @@ mime_enum_lookup[ 'video/mp4' ] = VIDEO_MP4
mime_enum_lookup[ 'video/mpeg' ] = VIDEO_MPEG
mime_enum_lookup[ 'video/x-ms-wmv' ] = VIDEO_WMV
mime_enum_lookup[ 'video/x-matroska' ] = VIDEO_MKV
mime_enum_lookup[ 'video/vnd.rn-realvideo' ] = VIDEO_REALMEDIA
mime_enum_lookup[ 'application/vnd.rn-realmedia' ] = VIDEO_REALMEDIA
mime_enum_lookup[ 'video/webm' ] = VIDEO_WEBM
mime_enum_lookup[ 'video' ] = VIDEO
mime_enum_lookup[ 'unknown mime' ] = APPLICATION_UNKNOWN
@ -562,47 +583,98 @@ mime_enum_lookup[ 'unknown mime' ] = APPLICATION_UNKNOWN
mime_string_lookup = {}
mime_string_lookup[ APPLICATION_HYDRUS_CLIENT_COLLECTION ] = 'collection'
mime_string_lookup[ IMAGE_JPEG ] = 'image/jpg'
mime_string_lookup[ IMAGE_PNG ] = 'image/png'
mime_string_lookup[ IMAGE_APNG ] = 'image/apng'
mime_string_lookup[ IMAGE_GIF ] = 'image/gif'
mime_string_lookup[ IMAGE_BMP ] = 'image/bmp'
mime_string_lookup[ IMAGE_WEBP ] = 'image/webp'
mime_string_lookup[ IMAGE_TIFF ] = 'image/tiff'
mime_string_lookup[ IMAGE_ICON ] = 'image/x-icon'
mime_string_lookup[ IMAGES ] = 'image'
mime_string_lookup[ APPLICATION_FLASH ] = 'application/x-shockwave-flash'
mime_string_lookup[ IMAGE_JPEG ] = 'jpeg'
mime_string_lookup[ IMAGE_PNG ] = 'png'
mime_string_lookup[ IMAGE_APNG ] = 'apng'
mime_string_lookup[ IMAGE_GIF ] = 'gif'
mime_string_lookup[ IMAGE_BMP ] = 'bmp'
mime_string_lookup[ IMAGE_WEBP ] = 'webp'
mime_string_lookup[ IMAGE_TIFF ] = 'tiff'
mime_string_lookup[ IMAGE_ICON ] = 'icon'
mime_string_lookup[ APPLICATION_FLASH ] = 'flash'
mime_string_lookup[ APPLICATION_OCTET_STREAM ] = 'application/octet-stream'
mime_string_lookup[ APPLICATION_YAML ] = 'application/x-yaml'
mime_string_lookup[ APPLICATION_JSON ] = 'application/json'
mime_string_lookup[ APPLICATION_PDF ] = 'application/pdf'
mime_string_lookup[ APPLICATION_PSD ] = 'application/x-photoshop'
mime_string_lookup[ APPLICATION_ZIP ] = 'application/zip'
mime_string_lookup[ APPLICATION_RAR ] = 'application/vnd.rar'
mime_string_lookup[ APPLICATION_7Z ] = 'application/x-7z-compressed'
mime_string_lookup[ APPLICATION_YAML ] = 'yaml'
mime_string_lookup[ APPLICATION_JSON ] = 'json'
mime_string_lookup[ APPLICATION_PDF ] = 'pdf'
mime_string_lookup[ APPLICATION_PSD ] = 'photoshop psd'
mime_string_lookup[ APPLICATION_ZIP ] = 'zip'
mime_string_lookup[ APPLICATION_RAR ] = 'rar'
mime_string_lookup[ APPLICATION_7Z ] = '7z'
mime_string_lookup[ APPLICATION_HYDRUS_ENCRYPTED_ZIP ] = 'application/hydrus-encrypted-zip'
mime_string_lookup[ APPLICATION_HYDRUS_UPDATE_CONTENT ] = 'application/hydrus-update-content'
mime_string_lookup[ APPLICATION_HYDRUS_UPDATE_DEFINITIONS ] = 'application/hydrus-update-definitions'
mime_string_lookup[ APPLICATIONS ] = 'application'
mime_string_lookup[ AUDIO_M4A ] = 'audio/mp4'
mime_string_lookup[ AUDIO_MP3 ] = 'audio/mp3'
mime_string_lookup[ AUDIO_OGG ] = 'audio/ogg'
mime_string_lookup[ AUDIO_FLAC ] = 'audio/flac'
mime_string_lookup[ AUDIO_WMA ] = 'audio/x-ms-wma'
mime_string_lookup[ AUDIO ] = 'audio'
mime_string_lookup[ TEXT_HTML ] = 'text/html'
mime_string_lookup[ TEXT_PLAIN ] = 'text/plain'
mime_string_lookup[ VIDEO_AVI ] = 'video/x-msvideo'
mime_string_lookup[ VIDEO_FLV ] = 'video/x-flv'
mime_string_lookup[ VIDEO_MOV ] = 'video/quicktime'
mime_string_lookup[ VIDEO_MP4 ] = 'video/mp4'
mime_string_lookup[ VIDEO_MPEG ] = 'video/mpeg'
mime_string_lookup[ VIDEO_WMV ] = 'video/x-ms-wmv'
mime_string_lookup[ VIDEO_MKV ] = 'video/x-matroska'
mime_string_lookup[ VIDEO_WEBM ] = 'video/webm'
mime_string_lookup[ VIDEO ] = 'video'
mime_string_lookup[ UNDETERMINED_WM ] = 'audio/x-ms-wma or video/x-ms-wmv'
mime_string_lookup[ APPLICATION_UNKNOWN ] = 'unknown mime'
mime_string_lookup[ AUDIO_M4A ] = 'm4a'
mime_string_lookup[ AUDIO_MP3 ] = 'mp3'
mime_string_lookup[ AUDIO_OGG ] = 'ogg'
mime_string_lookup[ AUDIO_FLAC ] = 'flac'
mime_string_lookup[ AUDIO_REALMEDIA ] = 'realaudio'
mime_string_lookup[ AUDIO_TRUEAUDIO ] = 'tta'
mime_string_lookup[ AUDIO_WMA ] = 'wma'
mime_string_lookup[ TEXT_HTML ] = 'html'
mime_string_lookup[ TEXT_PLAIN ] = 'plaintext'
mime_string_lookup[ VIDEO_AVI ] = 'avi'
mime_string_lookup[ VIDEO_FLV ] = 'flv'
mime_string_lookup[ VIDEO_MOV ] = 'quicktime'
mime_string_lookup[ VIDEO_MP4 ] = 'mp4'
mime_string_lookup[ VIDEO_MPEG ] = 'mpeg'
mime_string_lookup[ VIDEO_WMV ] = 'wmv'
mime_string_lookup[ VIDEO_MKV ] = 'matroska'
mime_string_lookup[ VIDEO_REALMEDIA ] = 'realvideo'
mime_string_lookup[ VIDEO_WEBM ] = 'webm'
mime_string_lookup[ UNDETERMINED_WM ] = 'wma or wmv'
mime_string_lookup[ APPLICATION_UNKNOWN ] = 'unknown filetype'
mime_string_lookup[ GENERAL_APPLICATION ] = 'application'
mime_string_lookup[ GENERAL_AUDIO ] = 'audio'
mime_string_lookup[ GENERAL_IMAGE ] = 'image'
mime_string_lookup[ GENERAL_VIDEO ] = 'video'
mime_mimetype_string_lookup = {}
mime_mimetype_string_lookup[ APPLICATION_HYDRUS_CLIENT_COLLECTION ] = 'collection'
mime_mimetype_string_lookup[ IMAGE_JPEG ] = 'image/jpg'
mime_mimetype_string_lookup[ IMAGE_PNG ] = 'image/png'
mime_mimetype_string_lookup[ IMAGE_APNG ] = 'image/apng'
mime_mimetype_string_lookup[ IMAGE_GIF ] = 'image/gif'
mime_mimetype_string_lookup[ IMAGE_BMP ] = 'image/bmp'
mime_mimetype_string_lookup[ IMAGE_WEBP ] = 'image/webp'
mime_mimetype_string_lookup[ IMAGE_TIFF ] = 'image/tiff'
mime_mimetype_string_lookup[ IMAGE_ICON ] = 'image/x-icon'
mime_mimetype_string_lookup[ APPLICATION_FLASH ] = 'application/x-shockwave-flash'
mime_mimetype_string_lookup[ APPLICATION_OCTET_STREAM ] = 'application/octet-stream'
mime_mimetype_string_lookup[ APPLICATION_YAML ] = 'application/x-yaml'
mime_mimetype_string_lookup[ APPLICATION_JSON ] = 'application/json'
mime_mimetype_string_lookup[ APPLICATION_PDF ] = 'application/pdf'
mime_mimetype_string_lookup[ APPLICATION_PSD ] = 'application/x-photoshop'
mime_mimetype_string_lookup[ APPLICATION_ZIP ] = 'application/zip'
mime_mimetype_string_lookup[ APPLICATION_RAR ] = 'application/vnd.rar'
mime_mimetype_string_lookup[ APPLICATION_7Z ] = 'application/x-7z-compressed'
mime_mimetype_string_lookup[ APPLICATION_HYDRUS_ENCRYPTED_ZIP ] = 'application/hydrus-encrypted-zip'
mime_mimetype_string_lookup[ APPLICATION_HYDRUS_UPDATE_CONTENT ] = 'application/hydrus-update-content'
mime_mimetype_string_lookup[ APPLICATION_HYDRUS_UPDATE_DEFINITIONS ] = 'application/hydrus-update-definitions'
mime_mimetype_string_lookup[ AUDIO_M4A ] = 'audio/mp4'
mime_mimetype_string_lookup[ AUDIO_MP3 ] = 'audio/mp3'
mime_mimetype_string_lookup[ AUDIO_OGG ] = 'audio/ogg'
mime_mimetype_string_lookup[ AUDIO_FLAC ] = 'audio/flac'
mime_mimetype_string_lookup[ AUDIO_REALMEDIA ] = 'audio/vnd.rn-realaudio'
mime_mimetype_string_lookup[ AUDIO_TRUEAUDIO ] = 'audio/x-tta'
mime_mimetype_string_lookup[ AUDIO_WMA ] = 'audio/x-ms-wma'
mime_mimetype_string_lookup[ TEXT_HTML ] = 'text/html'
mime_mimetype_string_lookup[ TEXT_PLAIN ] = 'text/plain'
mime_mimetype_string_lookup[ VIDEO_AVI ] = 'video/x-msvideo'
mime_mimetype_string_lookup[ VIDEO_FLV ] = 'video/x-flv'
mime_mimetype_string_lookup[ VIDEO_MOV ] = 'video/quicktime'
mime_mimetype_string_lookup[ VIDEO_MP4 ] = 'video/mp4'
mime_mimetype_string_lookup[ VIDEO_MPEG ] = 'video/mpeg'
mime_mimetype_string_lookup[ VIDEO_WMV ] = 'video/x-ms-wmv'
mime_mimetype_string_lookup[ VIDEO_MKV ] = 'video/x-matroska'
mime_mimetype_string_lookup[ VIDEO_REALMEDIA ] = 'video/vnd.rn-realvideo'
mime_mimetype_string_lookup[ VIDEO_WEBM ] = 'video/webm'
mime_mimetype_string_lookup[ UNDETERMINED_WM ] = 'audio/x-ms-wma or video/x-ms-wmv'
mime_mimetype_string_lookup[ APPLICATION_UNKNOWN ] = 'unknown mime'
mime_mimetype_string_lookup[ GENERAL_APPLICATION ] = 'application'
mime_mimetype_string_lookup[ GENERAL_AUDIO ] = 'audio'
mime_mimetype_string_lookup[ GENERAL_IMAGE ] = 'image'
mime_mimetype_string_lookup[ GENERAL_VIDEO ] = 'video'
mime_ext_lookup = {}
@ -630,7 +702,9 @@ mime_ext_lookup[ APPLICATION_HYDRUS_UPDATE_DEFINITIONS ] = ''
mime_ext_lookup[ AUDIO_M4A ] = '.m4a'
mime_ext_lookup[ AUDIO_MP3 ] = '.mp3'
mime_ext_lookup[ AUDIO_OGG ] = '.ogg'
mime_ext_lookup[ AUDIO_REALMEDIA ] = '.ra'
mime_ext_lookup[ AUDIO_FLAC ] = '.flac'
mime_ext_lookup[ AUDIO_TRUEAUDIO ] = '.tta'
mime_ext_lookup[ AUDIO_WMA ] = '.wma'
mime_ext_lookup[ TEXT_HTML ] = '.html'
mime_ext_lookup[ TEXT_PLAIN ] = '.txt'
@ -641,6 +715,7 @@ mime_ext_lookup[ VIDEO_MP4 ] = '.mp4'
mime_ext_lookup[ VIDEO_MPEG ] = '.mpeg'
mime_ext_lookup[ VIDEO_WMV ] = '.wmv'
mime_ext_lookup[ VIDEO_MKV ] = '.mkv'
mime_ext_lookup[ VIDEO_REALMEDIA ] = '.rm'
mime_ext_lookup[ VIDEO_WEBM ] = '.webm'
mime_ext_lookup[ APPLICATION_UNKNOWN ] = ''
#mime_ext_lookup[ 'application/x-rar-compressed' ] = '.rar'

View File

@ -180,7 +180,7 @@ def GetFileInfo( path, mime = None, ok_to_look_for_hydrus_updates = False ):
( ( width, height ), duration, num_frames ) = HydrusFlashHandling.GetFlashProperties( path )
elif mime in ( HC.IMAGE_APNG, HC.VIDEO_AVI, HC.VIDEO_FLV, HC.VIDEO_WMV, HC.VIDEO_MOV, HC.VIDEO_MP4, HC.VIDEO_MKV, HC.VIDEO_WEBM, HC.VIDEO_MPEG ):
elif mime in ( HC.IMAGE_APNG, HC.VIDEO_AVI, HC.VIDEO_FLV, HC.VIDEO_WMV, HC.VIDEO_MOV, HC.VIDEO_MP4, HC.VIDEO_MKV, HC.VIDEO_REALMEDIA, HC.VIDEO_WEBM, HC.VIDEO_MPEG ):
( ( width, height ), duration, num_frames ) = HydrusVideoHandling.GetFFMPEGVideoProperties( path )

View File

@ -436,7 +436,7 @@ class HydrusResource( Resource ):
mime = response_context.GetMime()
content_type = HC.mime_string_lookup[ mime ]
content_type = HC.mime_mimetype_string_lookup[ mime ]
content_length = size
@ -465,7 +465,7 @@ class HydrusResource( Resource ):
body_bytes = response_context.GetBodyBytes()
content_type = HC.mime_string_lookup[ mime ]
content_type = HC.mime_mimetype_string_lookup[ mime ]
content_length = len( body_bytes )

View File

@ -355,6 +355,10 @@ def GetMime( path ):
return HC.AUDIO_MP3
elif mime_text == 'tta':
return HC.AUDIO_TRUEAUDIO
elif 'mp4' in mime_text:
if has_audio and ( not has_video or 'mjpeg' in video_format ):
@ -370,6 +374,18 @@ def GetMime( path ):
return HC.AUDIO_OGG
elif 'rm' in mime_text:
if ParseFFMPEGHasVideo( lines ):
return HC.VIDEO_REALMEDIA
else:
return HC.AUDIO_REALMEDIA
elif mime_text == 'asf':
if ParseFFMPEGHasVideo( lines ):

View File

@ -938,6 +938,7 @@ def AddToLayout( layout, item, flag = None, alignment = None, sizePolicy = None
item.setSizePolicy( sizePolicy[0], sizePolicy[1] )
expand_both_ways = flag in ( CC.FLAGS_EXPAND_BOTH_WAYS, CC.FLAGS_EXPAND_BOTH_WAYS_POLITE, CC.FLAGS_EXPAND_BOTH_WAYS_SHY )
zero_border = False
# This is kind of a mess right now, adjustments might be needed
@ -957,30 +958,30 @@ def AddToLayout( layout, item, flag = None, alignment = None, sizePolicy = None
#item.setContentsMargins( 2, 2, 2, 2 )
#wx.SizerFlags( 0 ).Border( wx.ALL, 2 )
elif flag == CC.FLAGS_EXPAND_PERPENDICULAR:
pass
#if isinstance( item, QW.QWidget ): item.setSizePolicy( QW.QSizePolicy.Expanding, QW.QSizePolicy.Expanding )
if isinstance( item, QW.QWidget ):
if isinstance( layout, QW.QHBoxLayout ):
h_policy = QW.QSizePolicy.Fixed
v_policy = QW.QSizePolicy.Expanding
else:
h_policy = QW.QSizePolicy.Expanding
v_policy = QW.QSizePolicy.Fixed
item.setSizePolicy( h_policy, v_policy )
#wx.SizerFlags( 0 ).Border( wx.ALL, 2 ).Expand()
#item.setContentsMargins( 2, 2, 2, 2 )
elif flag == CC.FLAGS_EXPAND_BOTH_WAYS:
#if isinstance( item, QW.QWidget ): item.setSizePolicy( QW.QSizePolicy.Expanding, QW.QSizePolicy.Expanding )
if isinstance( layout, QW.QVBoxLayout ) or isinstance( layout, QW.QHBoxLayout ): layout.setStretchFactor( item, 5 )
#item.setContentsMargins( 2, 2, 2, 2 )
#wx.SizerFlags( 5 ).Border( wx.ALL, 2 ).Expand()
elif flag == CC.FLAGS_EXPAND_DEPTH_ONLY:
#if isinstance( item, QW.QWidget ): item.setSizePolicy( item.sizePolicy().verticalPolicy(), QW.QSizePolicy.Expanding )
if isinstance( layout, QW.QVBoxLayout ) or isinstance( layout, QW.QHBoxLayout ): layout.setStretchFactor( item, 5 )
#item.setContentsMargins( 2, 2, 2, 2 )
#wx.SizerFlags( 5 ).Border( wx.ALL, 2 ).Align( wx.ALIGN_CENTER_VERTICAL )
elif flag == CC.FLAGS_EXPAND_BOTH_WAYS_POLITE:
if isinstance( item, QW.QWidget ): item.setSizePolicy( item.sizePolicy().verticalPolicy(), QW.QSizePolicy.Expanding )
if isinstance( layout, QW.QVBoxLayout ) or isinstance( layout, QW.QHBoxLayout ): layout.setStretchFactor( item, 3 )
#item.setContentsMargins( 2, 2, 2, 2 )
#wx.SizerFlags( 3 ).Border( wx.ALL, 2 ).Expand()
elif flag == CC.FLAGS_EXPAND_BOTH_WAYS_SHY:
if isinstance( item, QW.QWidget ): item.setSizePolicy( QW.QSizePolicy.Expanding, QW.QSizePolicy.Expanding )
if isinstance( layout, QW.QVBoxLayout ) or isinstance( layout, QW.QHBoxLayout ): layout.setStretchFactor( item, 1 )
#item.setContentsMargins( 2, 2, 2, 2 )
#wx.SizerFlags( 1 ).Border( wx.ALL, 2 ).Expand()
elif flag == CC.FLAGS_SIZER_CENTER:
if isinstance( layout, QW.QVBoxLayout ) or isinstance( layout, QW.QHBoxLayout ): layout.setStretchFactor( item, 5 )
layout.setAlignment( item, QC.Qt.AlignHCenter | QC.Qt.AlignVCenter )
@ -992,7 +993,7 @@ def AddToLayout( layout, item, flag = None, alignment = None, sizePolicy = None
#wx.SizerFlags( 0 ).Expand()
#item.setContentsMargins( 0, 0, 0, 0 )
elif flag == CC.FLAGS_EXPAND_SIZER_BOTH_WAYS:
zero_border = True
if isinstance( layout, QW.QVBoxLayout ) or isinstance( layout, QW.QHBoxLayout ):
layout.setStretchFactor( item, 5 )
@ -1034,9 +1035,33 @@ def AddToLayout( layout, item, flag = None, alignment = None, sizePolicy = None
zero_border = True
#item.setContentsMargins( 2, 2, 2, 2 )
#wx.SizerFlags( 5 ).Border( wx.ALL, 2 ).Align( wx.ALIGN_CENTER_VERTICAL )
else:
if expand_both_ways:
if isinstance( item, QW.QWidget ):
item.setSizePolicy( QW.QSizePolicy.Expanding, QW.QSizePolicy.Expanding )
if isinstance( layout, QW.QVBoxLayout ) or isinstance( layout, QW.QHBoxLayout ):
if flag == CC.FLAGS_EXPAND_BOTH_WAYS:
stretch_factor = 5
elif flag == CC.FLAGS_EXPAND_BOTH_WAYS_POLITE:
stretch_factor = 3
elif flag == CC.FLAGS_EXPAND_BOTH_WAYS_SHY:
stretch_factor = 1
layout.setStretchFactor( item, stretch_factor )
raise ValueError( 'Unknown legacy sizer flag' )
if zero_border:

View File

@ -68,6 +68,8 @@ def ProcessStartingAction( db_dir, action ):
else:
HydrusData.Print( 'Did not find an already running instance of the server--changing boot command from \'restart\' to \'start\'.' )
return 'start'

View File

@ -306,7 +306,7 @@ class TestClientAPI( unittest.TestCase ):
# fail
headers = { 'Content-Type' : HC.mime_string_lookup[ HC.APPLICATION_JSON ] }
headers = { 'Content-Type' : HC.mime_mimetype_string_lookup[ HC.APPLICATION_JSON ] }
hash = os.urandom( 32 )
hash_hex = hash.hex()
@ -359,7 +359,7 @@ class TestClientAPI( unittest.TestCase ):
session_key_hex = body_dict[ 'session_key' ]
headers = { 'Content-Type' : HC.mime_string_lookup[ HC.APPLICATION_JSON ] }
headers = { 'Content-Type' : HC.mime_mimetype_string_lookup[ HC.APPLICATION_JSON ] }
hash = os.urandom( 32 )
hash_hex = hash.hex()
@ -455,7 +455,7 @@ class TestClientAPI( unittest.TestCase ):
HG.test_controller.SetRead( 'hash_status', ( CC.STATUS_UNKNOWN, None, '' ) )
headers = { 'Hydrus-Client-API-Access-Key' : access_key_hex, 'Content-Type' : HC.mime_string_lookup[ HC.APPLICATION_OCTET_STREAM ] }
headers = { 'Hydrus-Client-API-Access-Key' : access_key_hex, 'Content-Type' : HC.mime_mimetype_string_lookup[ HC.APPLICATION_OCTET_STREAM ] }
path = '/add_files/add_file'
@ -486,7 +486,7 @@ class TestClientAPI( unittest.TestCase ):
HYDRUS_PNG_BYTES = f.read()
headers = { 'Hydrus-Client-API-Access-Key' : access_key_hex, 'Content-Type' : HC.mime_string_lookup[ HC.APPLICATION_OCTET_STREAM ] }
headers = { 'Hydrus-Client-API-Access-Key' : access_key_hex, 'Content-Type' : HC.mime_mimetype_string_lookup[ HC.APPLICATION_OCTET_STREAM ] }
path = '/add_files/add_file'
@ -510,7 +510,7 @@ class TestClientAPI( unittest.TestCase ):
# do hydrus png as path
headers = { 'Hydrus-Client-API-Access-Key' : access_key_hex, 'Content-Type' : HC.mime_string_lookup[ HC.APPLICATION_JSON ] }
headers = { 'Hydrus-Client-API-Access-Key' : access_key_hex, 'Content-Type' : HC.mime_mimetype_string_lookup[ HC.APPLICATION_JSON ] }
path = '/add_files/add_file'
@ -600,7 +600,7 @@ class TestClientAPI( unittest.TestCase ):
# add tags
headers = { 'Hydrus-Client-API-Access-Key' : access_key_hex, 'Content-Type' : HC.mime_string_lookup[ HC.APPLICATION_JSON ] }
headers = { 'Hydrus-Client-API-Access-Key' : access_key_hex, 'Content-Type' : HC.mime_mimetype_string_lookup[ HC.APPLICATION_JSON ] }
hash = os.urandom( 32 )
hash_hex = hash.hex()
@ -900,9 +900,6 @@ class TestClientAPI( unittest.TestCase ):
expected_answer[ 'normalised_url' ] = normalised_url
expected_answer[ 'url_file_statuses' ] = json_url_file_statuses
HydrusData.Print( d )
HydrusData.Print( expected_answer )
self.assertEqual( d, expected_answer )
# get url info
@ -1006,7 +1003,7 @@ class TestClientAPI( unittest.TestCase ):
HG.test_controller.ClearWrites( 'import_url_test' )
headers = { 'Hydrus-Client-API-Access-Key' : access_key_hex, 'Content-Type' : HC.mime_string_lookup[ HC.APPLICATION_JSON ] }
headers = { 'Hydrus-Client-API-Access-Key' : access_key_hex, 'Content-Type' : HC.mime_mimetype_string_lookup[ HC.APPLICATION_JSON ] }
url = 'http://8ch.net/tv/res/1846574.html'
@ -1258,7 +1255,7 @@ class TestClientAPI( unittest.TestCase ):
#
headers = { 'Hydrus-Client-API-Access-Key' : access_key_hex, 'Content-Type' : HC.mime_string_lookup[ HC.APPLICATION_JSON ] }
headers = { 'Hydrus-Client-API-Access-Key' : access_key_hex, 'Content-Type' : HC.mime_mimetype_string_lookup[ HC.APPLICATION_JSON ] }
path = '/manage_cookies/set_cookies'
@ -1304,7 +1301,7 @@ class TestClientAPI( unittest.TestCase ):
#
headers = { 'Hydrus-Client-API-Access-Key' : access_key_hex, 'Content-Type' : HC.mime_string_lookup[ HC.APPLICATION_JSON ] }
headers = { 'Hydrus-Client-API-Access-Key' : access_key_hex, 'Content-Type' : HC.mime_mimetype_string_lookup[ HC.APPLICATION_JSON ] }
path = '/manage_cookies/set_cookies'
@ -1382,7 +1379,7 @@ class TestClientAPI( unittest.TestCase ):
#
headers = { 'Hydrus-Client-API-Access-Key' : access_key_hex, 'Content-Type' : HC.mime_string_lookup[ HC.APPLICATION_JSON ] }
headers = { 'Hydrus-Client-API-Access-Key' : access_key_hex, 'Content-Type' : HC.mime_mimetype_string_lookup[ HC.APPLICATION_JSON ] }
path = '/manage_pages/focus_page'
@ -1626,7 +1623,7 @@ class TestClientAPI( unittest.TestCase ):
metadata_row[ 'file_id' ] = file_info_manager.hash_id
metadata_row[ 'hash' ] = file_info_manager.hash.hex()
metadata_row[ 'size' ] = file_info_manager.size
metadata_row[ 'mime' ] = HC.mime_string_lookup[ file_info_manager.mime ]
metadata_row[ 'mime' ] = HC.mime_mimetype_string_lookup[ file_info_manager.mime ]
metadata_row[ 'width' ] = file_info_manager.width
metadata_row[ 'height' ] = file_info_manager.height
metadata_row[ 'duration' ] = file_info_manager.duration

View File

@ -290,7 +290,7 @@ class TestClientDB( unittest.TestCase ):
self.assertEqual( written_note, '' )
self.assertEqual( file_import_job.GetHash(), hash )
time.sleep( 1 )
time.sleep( 1.1 ) # to get timestamps right
#
@ -414,6 +414,8 @@ class TestClientDB( unittest.TestCase ):
self._write( 'content_updates', service_keys_to_content_updates )
time.sleep( 0.5 )
#
tests = []

View File

@ -528,13 +528,13 @@ class TestTagObjects( unittest.TestCase ):
p = ClientSearch.Predicate( HC.PREDICATE_TYPE_SYSTEM_MIME, ( HC.VIDEO_WEBM, ) )
self.assertEqual( p.ToString(), 'system:filetype is video/webm' )
self.assertEqual( p.ToString(), 'system:filetype is webm' )
self.assertEqual( p.GetNamespace(), 'system' )
self.assertEqual( p.GetTextsAndNamespaces(), [ ( p.ToString(), p.GetNamespace() ) ] )
p = ClientSearch.Predicate( HC.PREDICATE_TYPE_SYSTEM_MIME, ( HC.VIDEO_WEBM, HC.IMAGE_GIF ) )
self.assertEqual( p.ToString(), 'system:filetype is video/webm, image/gif' )
self.assertEqual( p.ToString(), 'system:filetype is webm, gif' )
self.assertEqual( p.GetNamespace(), 'system' )
self.assertEqual( p.GetTextsAndNamespaces(), [ ( p.ToString(), p.GetNamespace() ) ] )

View File

@ -115,7 +115,16 @@ try:
#
action = ServerController.ProcessStartingAction( db_dir, action )
try:
action = ServerController.ProcessStartingAction( db_dir, action )
except HydrusExceptions.ShutdownException as e:
HydrusData.Print( e )
action = 'exit'
if action == 'exit':

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB