mirror of
				https://github.com/janeczku/calibre-web
				synced 2025-10-30 06:43:03 +00:00 
			
		
		
		
	Merge branch 'master' into Develop
This commit is contained in:
		| @@ -89,8 +89,9 @@ Refer to the Wiki for additional installation examples: [manual installation](ht | |||||||
|  |  | ||||||
| ## Requirements | ## Requirements | ||||||
|  |  | ||||||
| - Python 3.5+ | - Python 3.7+ | ||||||
| - [Imagemagick](https://imagemagick.org/script/download.php) for cover extraction from EPUBs (Windows users may need to install [Ghostscript](https://ghostscript.com/releases/gsdnld.html) for PDF cover extraction) | - [Imagemagick](https://imagemagick.org/script/download.php) for cover extraction from EPUBs (Windows users may need to install [Ghostscript](https://ghostscript.com/releases/gsdnld.html) for PDF cover extraction) | ||||||
|  | - Windows users need to install [libmagic for 32bit python](https://gnuwin32.sourceforge.net/downlinks/file.php) or [libmagic for 64bit python](https://github.com/nscaife/file-windows/releases/tag/20170108), depending on the python version; The files need to be installed in path (e.g. script folder of your Calibre-Web venv, or in the root folder of Calibre-Web | ||||||
| - Optional: [Calibre desktop program](https://calibre-ebook.com/download) for on-the-fly conversion and metadata editing (set "calibre's converter tool" path on the setup page) | - Optional: [Calibre desktop program](https://calibre-ebook.com/download) for on-the-fly conversion and metadata editing (set "calibre's converter tool" path on the setup page) | ||||||
| - Optional: [Kepubify tool](https://github.com/pgaskin/kepubify/releases/latest) for Kobo device support (place the binary in `/opt/kepubify` on Linux or `C:\Program Files\kepubify` on Windows) | - Optional: [Kepubify tool](https://github.com/pgaskin/kepubify/releases/latest) for Kobo device support (place the binary in `/opt/kepubify` on Linux or `C:\Program Files\kepubify` on Windows) | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										20
									
								
								cps.py
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								cps.py
									
									
									
									
									
								
							| @@ -30,19 +30,15 @@ from cps.main import main | |||||||
|  |  | ||||||
| def hide_console_windows(): | def hide_console_windows(): | ||||||
|     import ctypes |     import ctypes | ||||||
|     import os |  | ||||||
|  |  | ||||||
|     hwnd = ctypes.windll.kernel32.GetConsoleWindow() |     kernel32 = ctypes.WinDLL('kernel32') | ||||||
|     if hwnd != 0: |     user32 = ctypes.WinDLL('user32') | ||||||
|         try: |  | ||||||
|             import win32process |     SW_HIDE = 0 | ||||||
|         except ImportError: |  | ||||||
|             print("To hide console window install 'pywin32' using 'pip install pywin32'") |     hWnd = kernel32.GetConsoleWindow() | ||||||
|             return |     if hWnd: | ||||||
|         ctypes.windll.user32.ShowWindow(hwnd, 0) |         user32.ShowWindow(hWnd, SW_HIDE) | ||||||
|         ctypes.windll.kernel32.CloseHandle(hwnd) |  | ||||||
|         _, pid = win32process.GetWindowThreadProcessId(hwnd) |  | ||||||
|         os.system('taskkill /PID ' + str(pid) + ' /f') |  | ||||||
|  |  | ||||||
|  |  | ||||||
| if __name__ == '__main__': | if __name__ == '__main__': | ||||||
|   | |||||||
| @@ -70,8 +70,8 @@ mimetypes.add_type('image/vnd.djv', '.djv') | |||||||
| mimetypes.add_type('image/vnd.djv', '.djvu') | mimetypes.add_type('image/vnd.djv', '.djvu') | ||||||
| mimetypes.add_type('application/mpeg', '.mpeg') | mimetypes.add_type('application/mpeg', '.mpeg') | ||||||
| mimetypes.add_type('audio/mpeg', '.mp3') | mimetypes.add_type('audio/mpeg', '.mp3') | ||||||
| mimetypes.add_type('application/mp4', '.m4a') | mimetypes.add_type('audio/x-m4a', '.m4a') | ||||||
| mimetypes.add_type('application/mp4', '.m4b') | mimetypes.add_type('audio/x-m4a', '.m4b') | ||||||
| mimetypes.add_type('audio/ogg', '.ogg') | mimetypes.add_type('audio/ogg', '.ogg') | ||||||
| mimetypes.add_type('application/ogg', '.oga') | mimetypes.add_type('application/ogg', '.oga') | ||||||
| mimetypes.add_type('text/css', '.css') | mimetypes.add_type('text/css', '.css') | ||||||
|   | |||||||
| @@ -29,8 +29,9 @@ log = logger.create() | |||||||
|  |  | ||||||
| try: | try: | ||||||
|     import magic |     import magic | ||||||
|  |     error = None | ||||||
| except ImportError as e: | except ImportError as e: | ||||||
|     log.error("Cannot import python-magic, checking uploaded file metadata will not work: %s", e) |     error = "Cannot import python-magic, checking uploaded file metadata will not work: {}".format(e) | ||||||
|  |  | ||||||
|  |  | ||||||
| def get_temp_dir(): | def get_temp_dir(): | ||||||
| @@ -46,12 +47,15 @@ def del_temp_dir(): | |||||||
|  |  | ||||||
|  |  | ||||||
| def validate_mime_type(file_buffer, allowed_extensions): | def validate_mime_type(file_buffer, allowed_extensions): | ||||||
|  |     if error: | ||||||
|  |         log.error(error) | ||||||
|  |         return False | ||||||
|     mime = magic.Magic(mime=True) |     mime = magic.Magic(mime=True) | ||||||
|     allowed_mimetypes = list() |     allowed_mimetypes = list() | ||||||
|     for x in allowed_extensions: |     for x in allowed_extensions: | ||||||
|         try: |         try: | ||||||
|             allowed_mimetypes.append(mimetypes.types_map["." + x]) |             allowed_mimetypes.append(mimetypes.types_map["." + x]) | ||||||
|         except KeyError as e: |         except KeyError: | ||||||
|             log.error("Unkown mimetype for Extension: {}".format(x)) |             log.error("Unkown mimetype for Extension: {}".format(x)) | ||||||
|     tmp_mime_type = mime.from_buffer(file_buffer.read()) |     tmp_mime_type = mime.from_buffer(file_buffer.read()) | ||||||
|     file_buffer.seek(0) |     file_buffer.seek(0) | ||||||
| @@ -66,6 +70,5 @@ def validate_mime_type(file_buffer, allowed_extensions): | |||||||
|                     return True |                     return True | ||||||
|         except: |         except: | ||||||
|             file_buffer.seek(0) |             file_buffer.seek(0) | ||||||
|             pass |     log.error("Mimetype '{}' not found in allowed types".format(tmp_mime_type)) | ||||||
|      |  | ||||||
|     return False |     return False | ||||||
|   | |||||||
| @@ -562,7 +562,7 @@ def move_files_on_change(calibre_path, new_author_dir, new_titledir, localbook, | |||||||
|             if not os.path.isdir(new_path): |             if not os.path.isdir(new_path): | ||||||
|                 os.makedirs(new_path) |                 os.makedirs(new_path) | ||||||
|             shutil.move(original_filepath, os.path.join(new_path, db_filename)) |             shutil.move(original_filepath, os.path.join(new_path, db_filename)) | ||||||
|             log.debug("Moving title: %s to %s/%s", original_filepath, new_path) |             log.debug("Moving title: %s to %s", original_filepath, new_path) | ||||||
|         else: |         else: | ||||||
|             # Check new path is not valid path |             # Check new path is not valid path | ||||||
|             if not os.path.exists(new_path): |             if not os.path.exists(new_path): | ||||||
|   | |||||||
| @@ -269,10 +269,18 @@ class TaskConvert(CalibreTask): | |||||||
|                                '--with-library', library_path] |                                '--with-library', library_path] | ||||||
|                 p = process_open(opf_command, quotes, my_env) |                 p = process_open(opf_command, quotes, my_env) | ||||||
|                 p.wait() |                 p.wait() | ||||||
|  |                 check = p.returncode | ||||||
|  |                 calibre_traceback = p.stderr.readlines() | ||||||
|  |                 if check == 0: | ||||||
|                     path_tmp_opf = os.path.join(tmp_dir, "metadata_" + str(uuid4()) + ".opf") |                     path_tmp_opf = os.path.join(tmp_dir, "metadata_" + str(uuid4()) + ".opf") | ||||||
|                     with open(path_tmp_opf, 'w') as fd: |                     with open(path_tmp_opf, 'w') as fd: | ||||||
|                         copyfileobj(p.stdout, fd) |                         copyfileobj(p.stdout, fd) | ||||||
|  |                 else: | ||||||
|  |                     error_message = "" | ||||||
|  |                     for ele in calibre_traceback: | ||||||
|  |                         if not ele.startswith('Traceback') and not ele.startswith('  File'): | ||||||
|  |                             error_message = N_("Calibre failed with error: %(error)s", error=ele) | ||||||
|  |                     return check, error_message | ||||||
|             quotes = [1, 2, 4, 6] |             quotes = [1, 2, 4, 6] | ||||||
|             command = [config.config_converterpath, (file_path + format_old_ext), |             command = [config.config_converterpath, (file_path + format_old_ext), | ||||||
|                        (file_path + format_new_ext)] |                        (file_path + format_new_ext)] | ||||||
|   | |||||||
| @@ -43,6 +43,3 @@ comicapi>=2.2.0,<3.3.0 | |||||||
|  |  | ||||||
| # Kobo integration | # Kobo integration | ||||||
| jsonschema>=3.2.0,<4.24.0 | jsonschema>=3.2.0,<4.24.0 | ||||||
|  |  | ||||||
| # Hide console Window on Windows |  | ||||||
| pywin32>=220,<310 ; sys_platform == 'win32' |  | ||||||
|   | |||||||
| @@ -37,20 +37,20 @@ | |||||||
|       <div class="row"> |       <div class="row"> | ||||||
|         <div class="col-xs-6 col-md-6 col-sm-offset-3" style="margin-top:50px;"> |         <div class="col-xs-6 col-md-6 col-sm-offset-3" style="margin-top:50px;"> | ||||||
|              |              | ||||||
|             <p class='text-justify attribute'><strong>Start Time: </strong>2024-07-17 20:06:46</p> |             <p class='text-justify attribute'><strong>Start Time: </strong>2024-07-18 20:53:44</p> | ||||||
|              |              | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|       <div class="row"> |       <div class="row"> | ||||||
|         <div class="col-xs-6 col-md-6 col-sm-offset-3"> |         <div class="col-xs-6 col-md-6 col-sm-offset-3"> | ||||||
|              |              | ||||||
|             <p class='text-justify attribute'><strong>Stop Time: </strong>2024-07-18 03:16:31</p> |             <p class='text-justify attribute'><strong>Stop Time: </strong>2024-07-19 03:48:09</p> | ||||||
|              |              | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|       <div class="row"> |       <div class="row"> | ||||||
|         <div class="col-xs-6 col-md-6 col-sm-offset-3"> |         <div class="col-xs-6 col-md-6 col-sm-offset-3"> | ||||||
|            <p class='text-justify attribute'><strong>Duration: </strong>5h 58 min</p> |            <p class='text-justify attribute'><strong>Duration: </strong>5h 43 min</p> | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|       </div> |       </div> | ||||||
| @@ -1619,13 +1619,13 @@ | |||||||
|  |  | ||||||
|     <tr id="su" class="passClass"> |     <tr id="su" class="passClass"> | ||||||
|         <td>TestEditAuthors</td> |         <td>TestEditAuthors</td> | ||||||
|         <td class="text-center">8</td> |         <td class="text-center">9</td> | ||||||
|         <td class="text-center">8</td> |         <td class="text-center">9</td> | ||||||
|         <td class="text-center">0</td> |         <td class="text-center">0</td> | ||||||
|         <td class="text-center">0</td> |         <td class="text-center">0</td> | ||||||
|         <td class="text-center">0</td> |         <td class="text-center">0</td> | ||||||
|         <td class="text-center"> |         <td class="text-center"> | ||||||
|             <a onclick="showClassDetail('c14', 8)">Detail</a> |             <a onclick="showClassDetail('c14', 9)">Detail</a> | ||||||
|         </td> |         </td> | ||||||
|     </tr> |     </tr> | ||||||
|  |  | ||||||
| @@ -1687,7 +1687,7 @@ | |||||||
|      |      | ||||||
|         <tr id='pt14.7' class='hiddenRow bg-success'> |         <tr id='pt14.7' class='hiddenRow bg-success'> | ||||||
|             <td> |             <td> | ||||||
|                 <div class='testcase'>TestEditAuthors - test_rename_author_accent_onupload</div> |                 <div class='testcase'>TestEditAuthors - test_rename_author_emphasis_mark_onupload</div> | ||||||
|             </td> |             </td> | ||||||
|             <td colspan='6' align='center'>PASS</td> |             <td colspan='6' align='center'>PASS</td> | ||||||
|         </tr> |         </tr> | ||||||
| @@ -1703,6 +1703,15 @@ | |||||||
|      |      | ||||||
|      |      | ||||||
|      |      | ||||||
|  |         <tr id='pt14.9' class='hiddenRow bg-success'> | ||||||
|  |             <td> | ||||||
|  |                 <div class='testcase'>TestEditAuthors - test_rename_tag_emphasis_mark_onupload</div> | ||||||
|  |             </td> | ||||||
|  |             <td colspan='6' align='center'>PASS</td> | ||||||
|  |         </tr> | ||||||
|  |      | ||||||
|  |      | ||||||
|  |  | ||||||
|  |  | ||||||
|     <tr id="su" class="passClass"> |     <tr id="su" class="passClass"> | ||||||
|         <td>TestEditAuthorsGdrive</td> |         <td>TestEditAuthorsGdrive</td> | ||||||
| @@ -2635,11 +2644,11 @@ IndexError: list index out of range</pre> | |||||||
|      |      | ||||||
|  |  | ||||||
|  |  | ||||||
|     <tr id="su" class="failClass"> |     <tr id="su" class="passClass"> | ||||||
|         <td>TestKoboSync</td> |         <td>TestKoboSync</td> | ||||||
|         <td class="text-center">12</td> |         <td class="text-center">12</td> | ||||||
|         <td class="text-center">11</td> |         <td class="text-center">12</td> | ||||||
|         <td class="text-center">1</td> |         <td class="text-center">0</td> | ||||||
|         <td class="text-center">0</td> |         <td class="text-center">0</td> | ||||||
|         <td class="text-center">0</td> |         <td class="text-center">0</td> | ||||||
|         <td class="text-center"> |         <td class="text-center"> | ||||||
| @@ -2730,31 +2739,11 @@ IndexError: list index out of range</pre> | |||||||
|      |      | ||||||
|      |      | ||||||
|      |      | ||||||
|         <tr id="ft29.10" class="none bg-danger"> |         <tr id='pt29.10' class='hiddenRow bg-success'> | ||||||
|             <td> |             <td> | ||||||
|                 <div class='testcase'>TestKoboSync - test_sync_shelf</div> |                 <div class='testcase'>TestKoboSync - test_sync_shelf</div> | ||||||
|             </td> |             </td> | ||||||
|             <td colspan='6'> |             <td colspan='6' align='center'>PASS</td> | ||||||
|                 <div class="text-center"> |  | ||||||
|                     <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_ft29.10')">FAIL</a> |  | ||||||
|                 </div> |  | ||||||
|                 <!--css div popup start--> |  | ||||||
|                 <div id="div_ft29.10" class="popup_window test_output" style="display:block;"> |  | ||||||
|                     <div class='close_button pull-right'> |  | ||||||
|                         <button type="button" class="close" aria-label="Close" onfocus="this.blur();" |  | ||||||
|                                 onclick="document.getElementById('div_ft29.10').style.display='none'"><span |  | ||||||
|                                 aria-hidden="true">×</span></button> |  | ||||||
|                     </div> |  | ||||||
|                     <div class="text-left pull-left"> |  | ||||||
|                         <pre class="text-left">Traceback (most recent call last): |  | ||||||
|   File "/home/ozzie/Development/calibre-web-test/test/test_kobo_sync.py", line 368, in test_sync_shelf |  | ||||||
|     self.assertEqual(1, len(data), data) |  | ||||||
| AssertionError: 1 != 0 : []</pre> |  | ||||||
|                     </div> |  | ||||||
|                     <div class="clearfix"></div> |  | ||||||
|                 </div> |  | ||||||
|                 <!--css div popup end--> |  | ||||||
|             </td> |  | ||||||
|         </tr> |         </tr> | ||||||
|      |      | ||||||
|      |      | ||||||
| @@ -5624,9 +5613,9 @@ ModuleNotFoundError: No module named 'build_release'</pre> | |||||||
|  |  | ||||||
|     <tr id='total_row' class="text-center bg-grey"> |     <tr id='total_row' class="text-center bg-grey"> | ||||||
|         <td>Total</td> |         <td>Total</td> | ||||||
|         <td>498</td> |         <td>499</td> | ||||||
|         <td>485</td> |         <td>487</td> | ||||||
|         <td>1</td> |         <td>0</td> | ||||||
|         <td>2</td> |         <td>2</td> | ||||||
|         <td>10</td> |         <td>10</td> | ||||||
|         <td> </td> |         <td> </td> | ||||||
| @@ -5656,7 +5645,7 @@ ModuleNotFoundError: No module named 'build_release'</pre> | |||||||
|            |            | ||||||
|             <tr> |             <tr> | ||||||
|               <th>Platform</th> |               <th>Platform</th> | ||||||
|               <td>Linux 6.5.0-41-generic #41~22.04.2-Ubuntu SMP PREEMPT_DYNAMIC Mon Jun  3 11:32:55 UTC 2 x86_64 x86_64</td> |               <td>Linux 6.5.0-44-generic #44~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Tue Jun 18 14:36:16 UTC 2 x86_64 x86_64</td> | ||||||
|               <td>Basic</td> |               <td>Basic</td> | ||||||
|             </tr> |             </tr> | ||||||
|            |            | ||||||
| @@ -6160,7 +6149,7 @@ ModuleNotFoundError: No module named 'build_release'</pre> | |||||||
| </div> | </div> | ||||||
|  |  | ||||||
| <script> | <script> | ||||||
|     drawCircle(485, 1, 2, 10); |     drawCircle(487, 0, 2, 10); | ||||||
|     showCase(5); |     showCase(5); | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Ozzie Isaacs
					Ozzie Isaacs