mirror of
https://github.com/Escartem/AnimeWwise.git
synced 2026-06-11 04:05:37 +08:00
437
LICENCE.md
Normal file
437
LICENCE.md
Normal file
@@ -0,0 +1,437 @@
|
|||||||
|
Attribution-NonCommercial-ShareAlike 4.0 International
|
||||||
|
|
||||||
|
=======================================================================
|
||||||
|
|
||||||
|
Creative Commons Corporation ("Creative Commons") is not a law firm and
|
||||||
|
does not provide legal services or legal advice. Distribution of
|
||||||
|
Creative Commons public licenses does not create a lawyer-client or
|
||||||
|
other relationship. Creative Commons makes its licenses and related
|
||||||
|
information available on an "as-is" basis. Creative Commons gives no
|
||||||
|
warranties regarding its licenses, any material licensed under their
|
||||||
|
terms and conditions, or any related information. Creative Commons
|
||||||
|
disclaims all liability for damages resulting from their use to the
|
||||||
|
fullest extent possible.
|
||||||
|
|
||||||
|
Using Creative Commons Public Licenses
|
||||||
|
|
||||||
|
Creative Commons public licenses provide a standard set of terms and
|
||||||
|
conditions that creators and other rights holders may use to share
|
||||||
|
original works of authorship and other material subject to copyright
|
||||||
|
and certain other rights specified in the public license below. The
|
||||||
|
following considerations are for informational purposes only, are not
|
||||||
|
exhaustive, and do not form part of our licenses.
|
||||||
|
|
||||||
|
Considerations for licensors: Our public licenses are
|
||||||
|
intended for use by those authorized to give the public
|
||||||
|
permission to use material in ways otherwise restricted by
|
||||||
|
copyright and certain other rights. Our licenses are
|
||||||
|
irrevocable. Licensors should read and understand the terms
|
||||||
|
and conditions of the license they choose before applying it.
|
||||||
|
Licensors should also secure all rights necessary before
|
||||||
|
applying our licenses so that the public can reuse the
|
||||||
|
material as expected. Licensors should clearly mark any
|
||||||
|
material not subject to the license. This includes other CC-
|
||||||
|
licensed material, or material used under an exception or
|
||||||
|
limitation to copyright. More considerations for licensors:
|
||||||
|
wiki.creativecommons.org/Considerations_for_licensors
|
||||||
|
|
||||||
|
Considerations for the public: By using one of our public
|
||||||
|
licenses, a licensor grants the public permission to use the
|
||||||
|
licensed material under specified terms and conditions. If
|
||||||
|
the licensor's permission is not necessary for any reason--for
|
||||||
|
example, because of any applicable exception or limitation to
|
||||||
|
copyright--then that use is not regulated by the license. Our
|
||||||
|
licenses grant only permissions under copyright and certain
|
||||||
|
other rights that a licensor has authority to grant. Use of
|
||||||
|
the licensed material may still be restricted for other
|
||||||
|
reasons, including because others have copyright or other
|
||||||
|
rights in the material. A licensor may make special requests,
|
||||||
|
such as asking that all changes be marked or described.
|
||||||
|
Although not required by our licenses, you are encouraged to
|
||||||
|
respect those requests where reasonable. More considerations
|
||||||
|
for the public:
|
||||||
|
wiki.creativecommons.org/Considerations_for_licensees
|
||||||
|
|
||||||
|
=======================================================================
|
||||||
|
|
||||||
|
Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International
|
||||||
|
Public License
|
||||||
|
|
||||||
|
By exercising the Licensed Rights (defined below), You accept and agree
|
||||||
|
to be bound by the terms and conditions of this Creative Commons
|
||||||
|
Attribution-NonCommercial-ShareAlike 4.0 International Public License
|
||||||
|
("Public License"). To the extent this Public License may be
|
||||||
|
interpreted as a contract, You are granted the Licensed Rights in
|
||||||
|
consideration of Your acceptance of these terms and conditions, and the
|
||||||
|
Licensor grants You such rights in consideration of benefits the
|
||||||
|
Licensor receives from making the Licensed Material available under
|
||||||
|
these terms and conditions.
|
||||||
|
|
||||||
|
|
||||||
|
Section 1 -- Definitions.
|
||||||
|
|
||||||
|
a. Adapted Material means material subject to Copyright and Similar
|
||||||
|
Rights that is derived from or based upon the Licensed Material
|
||||||
|
and in which the Licensed Material is translated, altered,
|
||||||
|
arranged, transformed, or otherwise modified in a manner requiring
|
||||||
|
permission under the Copyright and Similar Rights held by the
|
||||||
|
Licensor. For purposes of this Public License, where the Licensed
|
||||||
|
Material is a musical work, performance, or sound recording,
|
||||||
|
Adapted Material is always produced where the Licensed Material is
|
||||||
|
synched in timed relation with a moving image.
|
||||||
|
|
||||||
|
b. Adapter's License means the license You apply to Your Copyright
|
||||||
|
and Similar Rights in Your contributions to Adapted Material in
|
||||||
|
accordance with the terms and conditions of this Public License.
|
||||||
|
|
||||||
|
c. BY-NC-SA Compatible License means a license listed at
|
||||||
|
creativecommons.org/compatiblelicenses, approved by Creative
|
||||||
|
Commons as essentially the equivalent of this Public License.
|
||||||
|
|
||||||
|
d. Copyright and Similar Rights means copyright and/or similar rights
|
||||||
|
closely related to copyright including, without limitation,
|
||||||
|
performance, broadcast, sound recording, and Sui Generis Database
|
||||||
|
Rights, without regard to how the rights are labeled or
|
||||||
|
categorized. For purposes of this Public License, the rights
|
||||||
|
specified in Section 2(b)(1)-(2) are not Copyright and Similar
|
||||||
|
Rights.
|
||||||
|
|
||||||
|
e. Effective Technological Measures means those measures that, in the
|
||||||
|
absence of proper authority, may not be circumvented under laws
|
||||||
|
fulfilling obligations under Article 11 of the WIPO Copyright
|
||||||
|
Treaty adopted on December 20, 1996, and/or similar international
|
||||||
|
agreements.
|
||||||
|
|
||||||
|
f. Exceptions and Limitations means fair use, fair dealing, and/or
|
||||||
|
any other exception or limitation to Copyright and Similar Rights
|
||||||
|
that applies to Your use of the Licensed Material.
|
||||||
|
|
||||||
|
g. License Elements means the license attributes listed in the name
|
||||||
|
of a Creative Commons Public License. The License Elements of this
|
||||||
|
Public License are Attribution, NonCommercial, and ShareAlike.
|
||||||
|
|
||||||
|
h. Licensed Material means the artistic or literary work, database,
|
||||||
|
or other material to which the Licensor applied this Public
|
||||||
|
License.
|
||||||
|
|
||||||
|
i. Licensed Rights means the rights granted to You subject to the
|
||||||
|
terms and conditions of this Public License, which are limited to
|
||||||
|
all Copyright and Similar Rights that apply to Your use of the
|
||||||
|
Licensed Material and that the Licensor has authority to license.
|
||||||
|
|
||||||
|
j. Licensor means the individual(s) or entity(ies) granting rights
|
||||||
|
under this Public License.
|
||||||
|
|
||||||
|
k. NonCommercial means not primarily intended for or directed towards
|
||||||
|
commercial advantage or monetary compensation. For purposes of
|
||||||
|
this Public License, the exchange of the Licensed Material for
|
||||||
|
other material subject to Copyright and Similar Rights by digital
|
||||||
|
file-sharing or similar means is NonCommercial provided there is
|
||||||
|
no payment of monetary compensation in connection with the
|
||||||
|
exchange.
|
||||||
|
|
||||||
|
l. Share means to provide material to the public by any means or
|
||||||
|
process that requires permission under the Licensed Rights, such
|
||||||
|
as reproduction, public display, public performance, distribution,
|
||||||
|
dissemination, communication, or importation, and to make material
|
||||||
|
available to the public including in ways that members of the
|
||||||
|
public may access the material from a place and at a time
|
||||||
|
individually chosen by them.
|
||||||
|
|
||||||
|
m. Sui Generis Database Rights means rights other than copyright
|
||||||
|
resulting from Directive 96/9/EC of the European Parliament and of
|
||||||
|
the Council of 11 March 1996 on the legal protection of databases,
|
||||||
|
as amended and/or succeeded, as well as other essentially
|
||||||
|
equivalent rights anywhere in the world.
|
||||||
|
|
||||||
|
n. You means the individual or entity exercising the Licensed Rights
|
||||||
|
under this Public License. Your has a corresponding meaning.
|
||||||
|
|
||||||
|
|
||||||
|
Section 2 -- Scope.
|
||||||
|
|
||||||
|
a. License grant.
|
||||||
|
|
||||||
|
1. Subject to the terms and conditions of this Public License,
|
||||||
|
the Licensor hereby grants You a worldwide, royalty-free,
|
||||||
|
non-sublicensable, non-exclusive, irrevocable license to
|
||||||
|
exercise the Licensed Rights in the Licensed Material to:
|
||||||
|
|
||||||
|
a. reproduce and Share the Licensed Material, in whole or
|
||||||
|
in part, for NonCommercial purposes only; and
|
||||||
|
|
||||||
|
b. produce, reproduce, and Share Adapted Material for
|
||||||
|
NonCommercial purposes only.
|
||||||
|
|
||||||
|
2. Exceptions and Limitations. For the avoidance of doubt, where
|
||||||
|
Exceptions and Limitations apply to Your use, this Public
|
||||||
|
License does not apply, and You do not need to comply with
|
||||||
|
its terms and conditions.
|
||||||
|
|
||||||
|
3. Term. The term of this Public License is specified in Section
|
||||||
|
6(a).
|
||||||
|
|
||||||
|
4. Media and formats; technical modifications allowed. The
|
||||||
|
Licensor authorizes You to exercise the Licensed Rights in
|
||||||
|
all media and formats whether now known or hereafter created,
|
||||||
|
and to make technical modifications necessary to do so. The
|
||||||
|
Licensor waives and/or agrees not to assert any right or
|
||||||
|
authority to forbid You from making technical modifications
|
||||||
|
necessary to exercise the Licensed Rights, including
|
||||||
|
technical modifications necessary to circumvent Effective
|
||||||
|
Technological Measures. For purposes of this Public License,
|
||||||
|
simply making modifications authorized by this Section 2(a)
|
||||||
|
(4) never produces Adapted Material.
|
||||||
|
|
||||||
|
5. Downstream recipients.
|
||||||
|
|
||||||
|
a. Offer from the Licensor -- Licensed Material. Every
|
||||||
|
recipient of the Licensed Material automatically
|
||||||
|
receives an offer from the Licensor to exercise the
|
||||||
|
Licensed Rights under the terms and conditions of this
|
||||||
|
Public License.
|
||||||
|
|
||||||
|
b. Additional offer from the Licensor -- Adapted Material.
|
||||||
|
Every recipient of Adapted Material from You
|
||||||
|
automatically receives an offer from the Licensor to
|
||||||
|
exercise the Licensed Rights in the Adapted Material
|
||||||
|
under the conditions of the Adapter's License You apply.
|
||||||
|
|
||||||
|
c. No downstream restrictions. You may not offer or impose
|
||||||
|
any additional or different terms or conditions on, or
|
||||||
|
apply any Effective Technological Measures to, the
|
||||||
|
Licensed Material if doing so restricts exercise of the
|
||||||
|
Licensed Rights by any recipient of the Licensed
|
||||||
|
Material.
|
||||||
|
|
||||||
|
6. No endorsement. Nothing in this Public License constitutes or
|
||||||
|
may be construed as permission to assert or imply that You
|
||||||
|
are, or that Your use of the Licensed Material is, connected
|
||||||
|
with, or sponsored, endorsed, or granted official status by,
|
||||||
|
the Licensor or others designated to receive attribution as
|
||||||
|
provided in Section 3(a)(1)(A)(i).
|
||||||
|
|
||||||
|
b. Other rights.
|
||||||
|
|
||||||
|
1. Moral rights, such as the right of integrity, are not
|
||||||
|
licensed under this Public License, nor are publicity,
|
||||||
|
privacy, and/or other similar personality rights; however, to
|
||||||
|
the extent possible, the Licensor waives and/or agrees not to
|
||||||
|
assert any such rights held by the Licensor to the limited
|
||||||
|
extent necessary to allow You to exercise the Licensed
|
||||||
|
Rights, but not otherwise.
|
||||||
|
|
||||||
|
2. Patent and trademark rights are not licensed under this
|
||||||
|
Public License.
|
||||||
|
|
||||||
|
3. To the extent possible, the Licensor waives any right to
|
||||||
|
collect royalties from You for the exercise of the Licensed
|
||||||
|
Rights, whether directly or through a collecting society
|
||||||
|
under any voluntary or waivable statutory or compulsory
|
||||||
|
licensing scheme. In all other cases the Licensor expressly
|
||||||
|
reserves any right to collect such royalties, including when
|
||||||
|
the Licensed Material is used other than for NonCommercial
|
||||||
|
purposes.
|
||||||
|
|
||||||
|
|
||||||
|
Section 3 -- License Conditions.
|
||||||
|
|
||||||
|
Your exercise of the Licensed Rights is expressly made subject to the
|
||||||
|
following conditions.
|
||||||
|
|
||||||
|
a. Attribution.
|
||||||
|
|
||||||
|
1. If You Share the Licensed Material (including in modified
|
||||||
|
form), You must:
|
||||||
|
|
||||||
|
a. retain the following if it is supplied by the Licensor
|
||||||
|
with the Licensed Material:
|
||||||
|
|
||||||
|
i. identification of the creator(s) of the Licensed
|
||||||
|
Material and any others designated to receive
|
||||||
|
attribution, in any reasonable manner requested by
|
||||||
|
the Licensor (including by pseudonym if
|
||||||
|
designated);
|
||||||
|
|
||||||
|
ii. a copyright notice;
|
||||||
|
|
||||||
|
iii. a notice that refers to this Public License;
|
||||||
|
|
||||||
|
iv. a notice that refers to the disclaimer of
|
||||||
|
warranties;
|
||||||
|
|
||||||
|
v. a URI or hyperlink to the Licensed Material to the
|
||||||
|
extent reasonably practicable;
|
||||||
|
|
||||||
|
b. indicate if You modified the Licensed Material and
|
||||||
|
retain an indication of any previous modifications; and
|
||||||
|
|
||||||
|
c. indicate the Licensed Material is licensed under this
|
||||||
|
Public License, and include the text of, or the URI or
|
||||||
|
hyperlink to, this Public License.
|
||||||
|
|
||||||
|
2. You may satisfy the conditions in Section 3(a)(1) in any
|
||||||
|
reasonable manner based on the medium, means, and context in
|
||||||
|
which You Share the Licensed Material. For example, it may be
|
||||||
|
reasonable to satisfy the conditions by providing a URI or
|
||||||
|
hyperlink to a resource that includes the required
|
||||||
|
information.
|
||||||
|
3. If requested by the Licensor, You must remove any of the
|
||||||
|
information required by Section 3(a)(1)(A) to the extent
|
||||||
|
reasonably practicable.
|
||||||
|
|
||||||
|
b. ShareAlike.
|
||||||
|
|
||||||
|
In addition to the conditions in Section 3(a), if You Share
|
||||||
|
Adapted Material You produce, the following conditions also apply.
|
||||||
|
|
||||||
|
1. The Adapter's License You apply must be a Creative Commons
|
||||||
|
license with the same License Elements, this version or
|
||||||
|
later, or a BY-NC-SA Compatible License.
|
||||||
|
|
||||||
|
2. You must include the text of, or the URI or hyperlink to, the
|
||||||
|
Adapter's License You apply. You may satisfy this condition
|
||||||
|
in any reasonable manner based on the medium, means, and
|
||||||
|
context in which You Share Adapted Material.
|
||||||
|
|
||||||
|
3. You may not offer or impose any additional or different terms
|
||||||
|
or conditions on, or apply any Effective Technological
|
||||||
|
Measures to, Adapted Material that restrict exercise of the
|
||||||
|
rights granted under the Adapter's License You apply.
|
||||||
|
|
||||||
|
|
||||||
|
Section 4 -- Sui Generis Database Rights.
|
||||||
|
|
||||||
|
Where the Licensed Rights include Sui Generis Database Rights that
|
||||||
|
apply to Your use of the Licensed Material:
|
||||||
|
|
||||||
|
a. for the avoidance of doubt, Section 2(a)(1) grants You the right
|
||||||
|
to extract, reuse, reproduce, and Share all or a substantial
|
||||||
|
portion of the contents of the database for NonCommercial purposes
|
||||||
|
only;
|
||||||
|
|
||||||
|
b. if You include all or a substantial portion of the database
|
||||||
|
contents in a database in which You have Sui Generis Database
|
||||||
|
Rights, then the database in which You have Sui Generis Database
|
||||||
|
Rights (but not its individual contents) is Adapted Material,
|
||||||
|
including for purposes of Section 3(b); and
|
||||||
|
|
||||||
|
c. You must comply with the conditions in Section 3(a) if You Share
|
||||||
|
all or a substantial portion of the contents of the database.
|
||||||
|
|
||||||
|
For the avoidance of doubt, this Section 4 supplements and does not
|
||||||
|
replace Your obligations under this Public License where the Licensed
|
||||||
|
Rights include other Copyright and Similar Rights.
|
||||||
|
|
||||||
|
|
||||||
|
Section 5 -- Disclaimer of Warranties and Limitation of Liability.
|
||||||
|
|
||||||
|
a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
|
||||||
|
EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
|
||||||
|
AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
|
||||||
|
ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
|
||||||
|
IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
|
||||||
|
WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
|
||||||
|
ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
|
||||||
|
KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
|
||||||
|
ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
|
||||||
|
|
||||||
|
b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
|
||||||
|
TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
|
||||||
|
NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
|
||||||
|
INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
|
||||||
|
COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
|
||||||
|
USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
|
||||||
|
ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
|
||||||
|
DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
|
||||||
|
IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
|
||||||
|
|
||||||
|
c. The disclaimer of warranties and limitation of liability provided
|
||||||
|
above shall be interpreted in a manner that, to the extent
|
||||||
|
possible, most closely approximates an absolute disclaimer and
|
||||||
|
waiver of all liability.
|
||||||
|
|
||||||
|
|
||||||
|
Section 6 -- Term and Termination.
|
||||||
|
|
||||||
|
a. This Public License applies for the term of the Copyright and
|
||||||
|
Similar Rights licensed here. However, if You fail to comply with
|
||||||
|
this Public License, then Your rights under this Public License
|
||||||
|
terminate automatically.
|
||||||
|
|
||||||
|
b. Where Your right to use the Licensed Material has terminated under
|
||||||
|
Section 6(a), it reinstates:
|
||||||
|
|
||||||
|
1. automatically as of the date the violation is cured, provided
|
||||||
|
it is cured within 30 days of Your discovery of the
|
||||||
|
violation; or
|
||||||
|
|
||||||
|
2. upon express reinstatement by the Licensor.
|
||||||
|
|
||||||
|
For the avoidance of doubt, this Section 6(b) does not affect any
|
||||||
|
right the Licensor may have to seek remedies for Your violations
|
||||||
|
of this Public License.
|
||||||
|
|
||||||
|
c. For the avoidance of doubt, the Licensor may also offer the
|
||||||
|
Licensed Material under separate terms or conditions or stop
|
||||||
|
distributing the Licensed Material at any time; however, doing so
|
||||||
|
will not terminate this Public License.
|
||||||
|
|
||||||
|
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
|
||||||
|
License.
|
||||||
|
|
||||||
|
|
||||||
|
Section 7 -- Other Terms and Conditions.
|
||||||
|
|
||||||
|
a. The Licensor shall not be bound by any additional or different
|
||||||
|
terms or conditions communicated by You unless expressly agreed.
|
||||||
|
|
||||||
|
b. Any arrangements, understandings, or agreements regarding the
|
||||||
|
Licensed Material not stated herein are separate from and
|
||||||
|
independent of the terms and conditions of this Public License.
|
||||||
|
|
||||||
|
|
||||||
|
Section 8 -- Interpretation.
|
||||||
|
|
||||||
|
a. For the avoidance of doubt, this Public License does not, and
|
||||||
|
shall not be interpreted to, reduce, limit, restrict, or impose
|
||||||
|
conditions on any use of the Licensed Material that could lawfully
|
||||||
|
be made without permission under this Public License.
|
||||||
|
|
||||||
|
b. To the extent possible, if any provision of this Public License is
|
||||||
|
deemed unenforceable, it shall be automatically reformed to the
|
||||||
|
minimum extent necessary to make it enforceable. If the provision
|
||||||
|
cannot be reformed, it shall be severed from this Public License
|
||||||
|
without affecting the enforceability of the remaining terms and
|
||||||
|
conditions.
|
||||||
|
|
||||||
|
c. No term or condition of this Public License will be waived and no
|
||||||
|
failure to comply consented to unless expressly agreed to by the
|
||||||
|
Licensor.
|
||||||
|
|
||||||
|
d. Nothing in this Public License constitutes or may be interpreted
|
||||||
|
as a limitation upon, or waiver of, any privileges and immunities
|
||||||
|
that apply to the Licensor or You, including from the legal
|
||||||
|
processes of any jurisdiction or authority.
|
||||||
|
|
||||||
|
=======================================================================
|
||||||
|
|
||||||
|
Creative Commons is not a party to its public
|
||||||
|
licenses. Notwithstanding, Creative Commons may elect to apply one of
|
||||||
|
its public licenses to material it publishes and in those instances
|
||||||
|
will be considered the “Licensor.” The text of the Creative Commons
|
||||||
|
public licenses is dedicated to the public domain under the CC0 Public
|
||||||
|
Domain Dedication. Except for the limited purpose of indicating that
|
||||||
|
material is shared under a Creative Commons public license or as
|
||||||
|
otherwise permitted by the Creative Commons policies published at
|
||||||
|
creativecommons.org/policies, Creative Commons does not authorize the
|
||||||
|
use of the trademark "Creative Commons" or any other trademark or logo
|
||||||
|
of Creative Commons without its prior written consent including,
|
||||||
|
without limitation, in connection with any unauthorized modifications
|
||||||
|
to any of its public licenses or any other arrangements,
|
||||||
|
understandings, or agreements concerning use of licensed material. For
|
||||||
|
the avoidance of doubt, this paragraph does not form part of the
|
||||||
|
public licenses.
|
||||||
|
|
||||||
|
Creative Commons may be contacted at creativecommons.org.
|
||||||
255
app.py
255
app.py
@@ -4,11 +4,12 @@ import json
|
|||||||
import math
|
import math
|
||||||
import extract
|
import extract
|
||||||
import platform
|
import platform
|
||||||
|
import webbrowser
|
||||||
from PyQt5 import uic
|
from PyQt5 import uic
|
||||||
from requests import get
|
from requests import get
|
||||||
from PyQt5.QtGui import QTextCursor
|
from PyQt5.QtGui import QTextCursor
|
||||||
from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot, QThread, QMetaType, Qt
|
from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot, QThread, QMetaType, Qt
|
||||||
from PyQt5.QtWidgets import QMessageBox, QMainWindow, QApplication, QFileDialog, QHeaderView, QAbstractItemView, QTreeWidgetItem
|
from PyQt5.QtWidgets import QMessageBox, QMainWindow, QApplication, QFileDialog, QHeaderView, QAbstractItemView, QTreeWidgetItem, QAction, QActionGroup
|
||||||
|
|
||||||
QMetaType.type("QTextCursor")
|
QMetaType.type("QTextCursor")
|
||||||
|
|
||||||
@@ -72,76 +73,142 @@ class AnimeWwise(QMainWindow):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(AnimeWwise, self).__init__()
|
super(AnimeWwise, self).__init__()
|
||||||
uic.loadUi("gui.ui", self)
|
uic.loadUi("gui.ui", self)
|
||||||
self.maps = self.getMaps()
|
self.maps = self.getJson("maps/index")
|
||||||
|
self.setWindowTitle(f'AnimeWwise | v{".".join(list(str(self.getJson("version")["version"])))}')
|
||||||
self.folders = {
|
self.folders = {
|
||||||
"input": "",
|
"input": "",
|
||||||
"output": "",
|
"output": "",
|
||||||
"diff": ""
|
"diff": ""
|
||||||
}
|
}
|
||||||
|
self.format = "wav"
|
||||||
|
self.fileStructure = {"folders": {}, "files": []}
|
||||||
self.setupActions()
|
self.setupActions()
|
||||||
sys.stdout = TextEditStream(self.console)
|
sys.stdout = TextEditStream(self.console)
|
||||||
self.extract = extract.WwiseExtract()
|
self.extract = extract.WwiseExtract()
|
||||||
self.checkMapsUpdates()
|
self.checkUpdates()
|
||||||
|
|
||||||
# utils
|
# utils
|
||||||
self.selectFolder = lambda: QFileDialog.getExistingDirectory(self, "Select Folder")
|
self.selectFolder = lambda: QFileDialog.getExistingDirectory(self, "Select Folder")
|
||||||
|
|
||||||
def checkMapsUpdates(self):
|
def checkUpdates(self):
|
||||||
print("Checking updates")
|
print("Checking for updates...")
|
||||||
try:
|
try:
|
||||||
getVersion = lambda m: sum([int(e["version"].replace(".", "")) for e in m["maps"]])
|
currentVersion = self.getJson("version")
|
||||||
mapsVersion = getVersion(self.maps)
|
latestVersionReq = get("https://raw.githubusercontent.com/Escartem/AnimeWwise/master/version.json")
|
||||||
latestMaps = get("https://raw.githubusercontent.com/Escartem/AnimeWwise/master/maps/index.json")
|
|
||||||
|
|
||||||
if latestMaps.status_code == 200:
|
if latestVersionReq.status_code == 200:
|
||||||
latestVersion = getVersion(json.loads(latestMaps.text))
|
latestVersion = json.loads(latestMaps.text)
|
||||||
|
|
||||||
if mapsVersion < latestVersion:
|
if currentVersion["version"] < latestVersion["version"]:
|
||||||
print("Update found")
|
print("Update found !")
|
||||||
QMessageBox.information(None, "Info", "Newer version of the mappings are availble, please update the program", QMessageBox.Ok)
|
QMessageBox.information(None, "Info", "Newer version of the program is availble, please update.", QMessageBox.Ok)
|
||||||
|
elif currentVersion["mapsVersion"] < latestVersion["mapsVersion"]:
|
||||||
|
print("Update found !")
|
||||||
|
QMessageBox.information(None, "Info", "Newer version of the mappings are availble, please update the program.", QMessageBox.Ok)
|
||||||
else:
|
else:
|
||||||
print("No updates")
|
print("No updates")
|
||||||
except:
|
except:
|
||||||
print("Failed to check updates")
|
print("Failed to check updates :(")
|
||||||
|
|
||||||
def getMaps(self):
|
def getJson(self, path):
|
||||||
with open("maps/index.json", "r") as f:
|
with open(f"{path}.json", "r") as f:
|
||||||
maps = json.loads(f.read())
|
data = json.loads(f.read())
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
return maps
|
return data
|
||||||
|
|
||||||
def setFolder(self, elem, folder):
|
def setFolder(self, elem=None, folder=None):
|
||||||
path = self.selectFolder()
|
path = self.selectFolder()
|
||||||
self.folders[folder] = path
|
self.folders[folder] = path
|
||||||
elem.setText(path)
|
if elem:
|
||||||
|
elem.setText(path)
|
||||||
|
|
||||||
def setupActions(self):
|
def setupActions(self):
|
||||||
self.changeInput.clicked.connect(lambda: self.setFolder(self.inputPath, "input"))
|
|
||||||
self.changeAltInput.clicked.connect(lambda: self.setFolder(self.altInputPath, "diff"))
|
self.changeAltInput.clicked.connect(lambda: self.setFolder(self.altInputPath, "diff"))
|
||||||
self.changeOutput.clicked.connect(lambda: self.setFolder(self.outputPath, "output"))
|
|
||||||
|
|
||||||
self.outputFormat.addItems(["wem (fastest)", "wav (fast)", "mp3 (slow)", "ogg (slow)"])
|
self.pckLoadTypeCombo.addItems(["Folder", "File"])
|
||||||
|
self.pckLoadTypeCombo.currentIndexChanged.connect(self.loadTypeChange)
|
||||||
|
self.loadType = "folder"
|
||||||
|
|
||||||
self.assetMap.addItems(["No map", *[f'{e["game"]} - v{e["version"]}' for e in self.maps["maps"]]])
|
self.assetMap.addItems(["No map", *[f'{e["game"]} - v{e["version"]}' for e in self.maps["maps"]]])
|
||||||
|
|
||||||
self.tabs.setTabEnabled(1, False)
|
self.setExtractionState(False)
|
||||||
self.tabs.setTabEnabled(2, False)
|
|
||||||
|
self.updateTreeWidget(self.fileStructure)
|
||||||
|
|
||||||
self.loadFilesButton.clicked.connect(lambda: self.loadFiles())
|
self.loadFilesButton.clicked.connect(lambda: self.loadFiles())
|
||||||
|
|
||||||
self.actionReset.triggered.connect(lambda: self.resetApp())
|
self.actionReset.triggered.connect(lambda: self.resetApp())
|
||||||
self.actionExit.triggered.connect(lambda: self.close())
|
self.actionExit.triggered.connect(lambda: self.close())
|
||||||
|
|
||||||
self.extractSelected.clicked.connect(lambda: self.extractItems(False))
|
self.actionExpand_all.triggered.connect(lambda: self.treeWidget.expandAll())
|
||||||
self.extractAll.clicked.connect(lambda: self.extractItems(True))
|
self.actionCollapse_all.triggered.connect(lambda: self.treeWidget.collapseAll())
|
||||||
|
|
||||||
|
self.actionExtract_Selected.triggered.connect(lambda: self.extractItems(False))
|
||||||
|
self.actionExtract_All.triggered.connect(lambda: self.extractItems(True))
|
||||||
|
|
||||||
|
self.actionReport_a_bug.triggered.connect(lambda: self.openLink(0))
|
||||||
|
self.actionSource_code.triggered.connect(lambda: self.openLink(1))
|
||||||
|
self.actionDiscord.triggered.connect(lambda: self.openLink(2))
|
||||||
|
|
||||||
self.searchAsset.textChanged.connect(lambda: self.filterAsset())
|
self.searchAsset.textChanged.connect(lambda: self.filterAsset())
|
||||||
|
|
||||||
|
# output format
|
||||||
|
formats = ["wem (fastest)", "wav (fast)", "mp3 (slow)", "ogg (slow)"]
|
||||||
|
action_group = QActionGroup(self)
|
||||||
|
action_group.setExclusive(True)
|
||||||
|
|
||||||
|
for index, item_name in enumerate(formats):
|
||||||
|
action = QAction(item_name, self)
|
||||||
|
action.setCheckable(True)
|
||||||
|
if index == 1:
|
||||||
|
action.setChecked(True)
|
||||||
|
self.menuOutput_format.addAction(action)
|
||||||
|
action_group.addAction(action)
|
||||||
|
|
||||||
|
action_group.triggered.connect(self.updateFormat)
|
||||||
|
|
||||||
|
# utils
|
||||||
|
def loadTypeChange(self, event):
|
||||||
|
if event == 0:
|
||||||
|
self.pckSubFold.setEnabled(True)
|
||||||
|
self.loadType = "folder"
|
||||||
|
elif event == 1:
|
||||||
|
self.pckSubFold.setEnabled(False)
|
||||||
|
self.loadType = "file"
|
||||||
|
|
||||||
|
def updateFormat(self, event):
|
||||||
|
text = event.text()
|
||||||
|
self.format = text.split(" ")[0]
|
||||||
|
|
||||||
|
def openLink(self, id):
|
||||||
|
urls = [
|
||||||
|
"https://github.com/Escartem/AnimeWwise/issues/new",
|
||||||
|
"https://github.com/Escartem/AnimeWwise",
|
||||||
|
"https://discord.gg/fzRdtVh"
|
||||||
|
]
|
||||||
|
|
||||||
|
webbrowser.open(urls[id])
|
||||||
|
|
||||||
|
def setExtractionState(self, state):
|
||||||
|
self.actionExtract_Selected.setEnabled(state)
|
||||||
|
self.actionExtract_All.setEnabled(state)
|
||||||
|
self.actionExpand_all.setEnabled(state)
|
||||||
|
self.actionCollapse_all.setEnabled(state)
|
||||||
|
|
||||||
|
def displaySize(self, size):
|
||||||
|
if size < 1024:
|
||||||
|
return f"{size} b"
|
||||||
|
elif size > 1024 and size < 1048576:
|
||||||
|
return f"{size//1024} KiB"
|
||||||
|
elif size > 1048576 and size < 1073741824:
|
||||||
|
return f"{size//1048576} MiB"
|
||||||
|
elif size > 1073741824:
|
||||||
|
return f"{size//1073741824} GiB"
|
||||||
|
|
||||||
# workers
|
# workers
|
||||||
@pyqtSlot(list)
|
@pyqtSlot(list)
|
||||||
def progressBarSlot(self, progress):
|
def progressBarSlot(self, progress):
|
||||||
if progress[0] == "load":
|
|
||||||
self.loadProgress.setValue(math.ceil(progress[1]))
|
|
||||||
if progress[0] == "total":
|
if progress[0] == "total":
|
||||||
self.totalProgress.setValue(math.ceil(progress[1]))
|
self.totalProgress.setValue(math.ceil(progress[1]))
|
||||||
elif progress[0] == "file":
|
elif progress[0] == "file":
|
||||||
@@ -152,23 +219,19 @@ class AnimeWwise(QMainWindow):
|
|||||||
if data["action"] == "load":
|
if data["action"] == "load":
|
||||||
self.fileStructure = data["content"]
|
self.fileStructure = data["content"]
|
||||||
self.updateTreeWidget(self.fileStructure)
|
self.updateTreeWidget(self.fileStructure)
|
||||||
self.tabs.setTabEnabled(0, False)
|
self.loadFilesButton.setEnabled(True)
|
||||||
self.tabs.setTabEnabled(1, True)
|
self.setExtractionState(True)
|
||||||
self.tabs.setTabEnabled(2, True)
|
|
||||||
self.tabs.setCurrentIndex(1)
|
self.tabs.setCurrentIndex(1)
|
||||||
print("Done !")
|
print("Done !")
|
||||||
if data["action"] == "error":
|
if data["action"] == "error":
|
||||||
QMessageBox.warning(None, "Warning", data["content"]["msg"], QMessageBox.Ok)
|
QMessageBox.warning(None, "Warning", data["content"]["msg"], QMessageBox.Ok)
|
||||||
state = data["content"]["state"]
|
state = data["content"]["state"]
|
||||||
if state == 1:
|
if state == 1:
|
||||||
self.tabs.setTabEnabled(0, True)
|
self.loadFilesButton.setEnabled(True)
|
||||||
elif state == 2:
|
if state == 2:
|
||||||
self.tabs.setTabEnabled(1, True)
|
self.setExtractionState(True)
|
||||||
self.tabs.setTabEnabled(2, True)
|
|
||||||
if data["action"] == "extract":
|
if data["action"] == "extract":
|
||||||
self.tabs.setTabEnabled(1, True)
|
self.setExtractionState(True)
|
||||||
self.tabs.setTabEnabled(2, True)
|
|
||||||
self.tabs.setCurrentIndex(2)
|
|
||||||
print("Finished extracting everything !")
|
print("Finished extracting everything !")
|
||||||
|
|
||||||
if platform.system() == "Windows":
|
if platform.system() == "Windows":
|
||||||
@@ -176,22 +239,38 @@ class AnimeWwise(QMainWindow):
|
|||||||
|
|
||||||
# page 1 - config
|
# page 1 - config
|
||||||
def loadFiles(self):
|
def loadFiles(self):
|
||||||
if self.folders["input"] == "":
|
if self.loadType == "folder":
|
||||||
QMessageBox.warning(None, "Warning", "Missing input folder !", QMessageBox.Ok)
|
self.setFolder(folder="input")
|
||||||
|
files = []
|
||||||
|
if self.folders["input"]:
|
||||||
|
if self.pckSubFold.isChecked():
|
||||||
|
files = [os.path.join(root, f) for root, dirs, files_in_dir in os.walk(self.folders["input"]) for f in files_in_dir if f.endswith(".pck")]
|
||||||
|
else:
|
||||||
|
files = [os.path.join(self.folders["input"], f) for f in os.listdir(self.folders["input"]) if f.endswith(".pck")]
|
||||||
|
elif self.loadType == "file":
|
||||||
|
path = QFileDialog.getOpenFileName(self, "Select .pck File", "", "PCK Files (*.pck)", options=QFileDialog.Options())
|
||||||
|
files = [path[0]]
|
||||||
|
|
||||||
|
if len(files) == 0 or files[0] == "":
|
||||||
|
QMessageBox.warning(None, "Warning", "Nothing to load !", QMessageBox.Ok)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
self.currentInput = self.folders["input"]
|
||||||
|
if not self.folders["input"]:
|
||||||
|
self.currentInput = os.path.dirname(path[0])
|
||||||
|
|
||||||
_map = self.assetMap.currentIndex()
|
_map = self.assetMap.currentIndex()
|
||||||
if _map != 0:
|
if _map != 0:
|
||||||
_map = self.maps["maps"][_map-1]["name"]
|
_map = self.maps["maps"][_map-1]["name"]
|
||||||
else:
|
else:
|
||||||
_map = None
|
_map = None
|
||||||
|
|
||||||
self.tabs.setTabEnabled(0, False)
|
|
||||||
self.resetTreeWidget()
|
self.resetTreeWidget()
|
||||||
|
self.loadFilesButton.setEnabled(False)
|
||||||
|
|
||||||
# why is all this required for threading damnit
|
# why is all this required for threading damnit
|
||||||
self.backgroundThread = QThread()
|
self.backgroundThread = QThread()
|
||||||
self.backgroundWorker = BackgroundWorker("load", self.extract, {"input": self.folders["input"], "map": _map, "diff": self.folders["diff"]})
|
self.backgroundWorker = BackgroundWorker("load", self.extract, {"input": files, "map": _map, "diff": self.folders["diff"]})
|
||||||
self.backgroundWorker.moveToThread(self.backgroundThread)
|
self.backgroundWorker.moveToThread(self.backgroundThread)
|
||||||
self.backgroundThread.started.connect(self.backgroundWorker.run)
|
self.backgroundThread.started.connect(self.backgroundWorker.run)
|
||||||
self.backgroundWorker.finished.connect(self.handleFinished)
|
self.backgroundWorker.finished.connect(self.handleFinished)
|
||||||
@@ -211,7 +290,7 @@ class AnimeWwise(QMainWindow):
|
|||||||
result = self.searchFiles(self.fileStructure, search)
|
result = self.searchFiles(self.fileStructure, search)
|
||||||
self.updateTreeWidget(result)
|
self.updateTreeWidget(result)
|
||||||
|
|
||||||
def searchFiles(self, data, substring, current_path=""):
|
def searchFiles(self, data, substring, current_path="", flatten=False):
|
||||||
result = {"folders": {}, "files": []}
|
result = {"folders": {}, "files": []}
|
||||||
|
|
||||||
result["files"] = [file for file in data.get("files", []) if substring in file[0]]
|
result["files"] = [file for file in data.get("files", []) if substring in file[0]]
|
||||||
@@ -221,32 +300,69 @@ class AnimeWwise(QMainWindow):
|
|||||||
if subfolder_result["files"] or subfolder_result["folders"]:
|
if subfolder_result["files"] or subfolder_result["folders"]:
|
||||||
result["folders"][folder_name] = subfolder_result
|
result["folders"][folder_name] = subfolder_result
|
||||||
|
|
||||||
|
if flatten:
|
||||||
|
while result["files"] == []:
|
||||||
|
if len(result["folders"]) == 0:
|
||||||
|
break
|
||||||
|
result = list(result["folders"].values())[0]
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def resetTreeWidget(self):
|
def resetTreeWidget(self):
|
||||||
self.treeWidget.clear()
|
self.treeWidget.clear()
|
||||||
self.tabs.setTabEnabled(1, False)
|
self.fileStructure = {"folders": {}, "files": []}
|
||||||
|
self.audioInfoLabel.setText("Click on an audio file to get more infos !")
|
||||||
|
self.setExtractionState(False)
|
||||||
|
|
||||||
def updateTreeWidget(self, structure):
|
def updateTreeWidget(self, structure):
|
||||||
self.treeWidget.clear()
|
self.treeWidget.clear()
|
||||||
self.treeWidget.setColumnCount(3)
|
self.treeWidget.setColumnCount(4)
|
||||||
self.treeWidget.setHeaderLabels(["Name", "Offset", "Size", "Source"])
|
self.treeWidget.setHeaderLabels(["Name", "Duration", "Compressed Size", "Source", "Offset"])
|
||||||
|
|
||||||
self.addItems(None, structure)
|
self.addItems(None, structure)
|
||||||
|
|
||||||
self.treeWidget.expandAll()
|
self.treeWidget.expandAll()
|
||||||
self.treeWidget.header().setSectionResizeMode(0, QHeaderView.Stretch)
|
|
||||||
self.treeWidget.header().setSectionResizeMode(1, QHeaderView.ResizeToContents)
|
self.treeWidget.header().setSectionResizeMode(0, QHeaderView.ResizeToContents)
|
||||||
self.treeWidget.header().setSectionResizeMode(2, QHeaderView.ResizeToContents)
|
self.treeWidget.header().setSectionResizeMode(1, QHeaderView.Stretch)
|
||||||
|
self.treeWidget.header().setSectionResizeMode(2, QHeaderView.Stretch)
|
||||||
|
self.treeWidget.header().setSectionResizeMode(3, QHeaderView.Stretch)
|
||||||
|
|
||||||
self.treeWidget.setHeaderHidden(False)
|
self.treeWidget.setHeaderHidden(False)
|
||||||
|
|
||||||
self.treeWidget.setEditTriggers(QAbstractItemView.NoEditTriggers)
|
self.treeWidget.setEditTriggers(QAbstractItemView.NoEditTriggers)
|
||||||
self.treeWidget.setDragDropMode(QAbstractItemView.NoDragDrop)
|
self.treeWidget.setDragDropMode(QAbstractItemView.NoDragDrop)
|
||||||
|
self.treeWidget.itemClicked.connect(self.updateAudioPreview)
|
||||||
|
|
||||||
|
def computeFolderSize(self, folder):
|
||||||
|
total_size = 0
|
||||||
|
|
||||||
|
for file in folder.get("files", []):
|
||||||
|
total_size += file[1]["size"]
|
||||||
|
|
||||||
|
for subfolder_name, subfolder in folder.get("folders", {}).items():
|
||||||
|
subfolder_size = self.computeFolderSize(subfolder)
|
||||||
|
total_size += subfolder_size
|
||||||
|
|
||||||
|
return total_size
|
||||||
|
|
||||||
|
def updateAudioPreview(self, item, column):
|
||||||
|
file_data = self.searchFiles(self.fileStructure, item.text(0), flatten=True)
|
||||||
|
|
||||||
|
if file_data == {"folders": {}, "files": []}:
|
||||||
|
self.audioInfoLabel.setText("Click on an audio file to get more infos !")
|
||||||
|
return
|
||||||
|
|
||||||
|
meta = file_data["files"][0][1]["metadata"]
|
||||||
|
|
||||||
|
# show meta
|
||||||
|
text = f'Infos for {item.text(0)} => Channels : {meta["channels"]} | Sample rate : {meta["sampleRate"]} Hz | Bitrate : {meta["avgBitrate"]} kbps | Codec : {meta["codecDisplay"]} | Layout type : {meta["layoutType"]}'
|
||||||
|
self.audioInfoLabel.setText(text)
|
||||||
|
|
||||||
def addItems(self, parent, element):
|
def addItems(self, parent, element):
|
||||||
for folder_name in sorted(element.get("folders", {}).keys()):
|
for folder_name in sorted(element.get("folders", {}).keys()):
|
||||||
folder_content = element["folders"][folder_name]
|
folder_content = element["folders"][folder_name]
|
||||||
folder_item = QTreeWidgetItem([folder_name, "", "", ""])
|
folder_item = QTreeWidgetItem([folder_name, "", self.displaySize(self.computeFolderSize(folder_content)), "", ""])
|
||||||
folder_item.setFlags(folder_item.flags() | Qt.ItemIsTristate | Qt.ItemIsUserCheckable)
|
folder_item.setFlags(folder_item.flags() | Qt.ItemIsTristate | Qt.ItemIsUserCheckable)
|
||||||
folder_item.setCheckState(0, Qt.Unchecked)
|
folder_item.setCheckState(0, Qt.Unchecked)
|
||||||
if parent is None:
|
if parent is None:
|
||||||
@@ -255,8 +371,9 @@ class AnimeWwise(QMainWindow):
|
|||||||
parent.addChild(folder_item)
|
parent.addChild(folder_item)
|
||||||
self.addItems(folder_item, folder_content)
|
self.addItems(folder_item, folder_content)
|
||||||
|
|
||||||
for file in sorted(element.get("files", [])):
|
for file in sorted(element.get("files", []), key=lambda x: x[0]):
|
||||||
file_item = QTreeWidgetItem([str(file[0]), str(hex(file[1])), str(file[2]), str(file[3])])
|
file_meta = file[1]
|
||||||
|
file_item = QTreeWidgetItem([file[0], f'{round(file_meta["metadata"]["duration"], 1)} seconds', self.displaySize(file_meta["size"]), file_meta["source"], str(hex(file_meta["offset"]))])
|
||||||
file_item.setFlags(file_item.flags() | Qt.ItemIsUserCheckable)
|
file_item.setFlags(file_item.flags() | Qt.ItemIsUserCheckable)
|
||||||
file_item.setCheckState(0, Qt.Unchecked)
|
file_item.setCheckState(0, Qt.Unchecked)
|
||||||
if parent is None:
|
if parent is None:
|
||||||
@@ -266,9 +383,7 @@ class AnimeWwise(QMainWindow):
|
|||||||
|
|
||||||
# page 3 - extraction
|
# page 3 - extraction
|
||||||
def extractItems(self, _all):
|
def extractItems(self, _all):
|
||||||
if self.folders["output"] == "":
|
self.setFolder(folder="output")
|
||||||
QMessageBox.warning(None, "Warning", "Missing output folder !", QMessageBox.Ok)
|
|
||||||
return
|
|
||||||
|
|
||||||
checked_items = []
|
checked_items = []
|
||||||
|
|
||||||
@@ -282,13 +397,11 @@ class AnimeWwise(QMainWindow):
|
|||||||
for i in range(self.treeWidget.topLevelItemCount()):
|
for i in range(self.treeWidget.topLevelItemCount()):
|
||||||
check_items(self.treeWidget.topLevelItem(i), _all)
|
check_items(self.treeWidget.topLevelItem(i), _all)
|
||||||
|
|
||||||
self.tabs.setTabEnabled(1, False)
|
self.setExtractionState(False)
|
||||||
self.tabs.setTabEnabled(2, False)
|
|
||||||
self.tabs.setCurrentIndex(2)
|
|
||||||
|
|
||||||
# yet another block of threading bs
|
# yet another block of threading bs
|
||||||
self.backgroundThread = QThread()
|
self.backgroundThread = QThread()
|
||||||
self.backgroundWorker = BackgroundWorker("extract", self.extract, {"input": self.folders["input"], "files": checked_items, "format": self.outputFormat.currentText()[:3], "output": self.folders["output"]})
|
self.backgroundWorker = BackgroundWorker("extract", self.extract, {"input": self.currentInput, "files": checked_items, "format": self.format, "output": self.folders["output"]})
|
||||||
self.backgroundWorker.moveToThread(self.backgroundThread)
|
self.backgroundWorker.moveToThread(self.backgroundThread)
|
||||||
self.backgroundThread.started.connect(self.backgroundWorker.run)
|
self.backgroundThread.started.connect(self.backgroundWorker.run)
|
||||||
self.backgroundWorker.finished.connect(self.handleFinished)
|
self.backgroundWorker.finished.connect(self.handleFinished)
|
||||||
@@ -306,22 +419,28 @@ class AnimeWwise(QMainWindow):
|
|||||||
while current_item is not None:
|
while current_item is not None:
|
||||||
path.insert(0, current_item.text(0))
|
path.insert(0, current_item.text(0))
|
||||||
current_item = current_item.parent()
|
current_item = current_item.parent()
|
||||||
|
|
||||||
|
meta = self.searchFiles(self.fileStructure, item.text(0), flatten=True)["files"][0]
|
||||||
|
name = meta[0]
|
||||||
|
meta = meta[1] # move inside
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"name": item.text(0),
|
"name": item.text(0),
|
||||||
"path": path[:-1] if path[0] in ["changed_files", "new_files"] else path[1:-1],
|
"path": path[:-1],
|
||||||
"source": item.text(3),
|
"source": meta["source"],
|
||||||
"offset": int(item.text(1), 16),
|
"offset": meta["offset"],
|
||||||
"size": int(item.text(2))
|
"size": meta["size"]
|
||||||
}
|
}
|
||||||
|
|
||||||
# misc
|
# misc
|
||||||
def resetApp(self):
|
def resetApp(self):
|
||||||
self.resetTreeWidget()
|
self.resetTreeWidget()
|
||||||
self.extract.reset()
|
self.extract.reset()
|
||||||
self.tabs.setTabEnabled(0, True)
|
self.currentInput = None
|
||||||
self.tabs.setTabEnabled(1, False)
|
self.setExtractionState(False)
|
||||||
self.tabs.setTabEnabled(2, False)
|
self.tabs.setCurrentIndex(0)
|
||||||
|
self.totalProgress.setValue(0)
|
||||||
|
self.fileProgress.setValue(0)
|
||||||
print("Reset !")
|
print("Reset !")
|
||||||
|
|
||||||
def _appendText(self, text):
|
def _appendText(self, text):
|
||||||
|
|||||||
79
extract.py
79
extract.py
@@ -1,9 +1,10 @@
|
|||||||
import os
|
import os
|
||||||
import io
|
import io
|
||||||
|
import wwise
|
||||||
import tempfile
|
import tempfile
|
||||||
import wavescan
|
import wavescan
|
||||||
import subprocess
|
|
||||||
import platform
|
import platform
|
||||||
|
import subprocess
|
||||||
from mapper import Mapper
|
from mapper import Mapper
|
||||||
from allocator import Allocator
|
from allocator import Allocator
|
||||||
from filereader import FileReader
|
from filereader import FileReader
|
||||||
@@ -16,19 +17,43 @@ class WwiseExtract:
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.allocator = Allocator()
|
self.allocator = Allocator()
|
||||||
self.hdiff_dir = None
|
self.hdiff_dir = None
|
||||||
|
self.maps = {}
|
||||||
|
|
||||||
### loading files ###
|
### loading files ###
|
||||||
|
|
||||||
def load_folder(self, _map, folder_path, diff_path, progress):
|
def load_map(self, _map):
|
||||||
|
map_name = _map.split(".")[0]
|
||||||
|
|
||||||
|
if map_name not in self.maps or self.maps[map_name] is None:
|
||||||
|
print("Map load required !")
|
||||||
|
mapper = Mapper(path(cwd, f"maps/{_map}"))
|
||||||
|
self.maps[map_name] = mapper
|
||||||
|
else:
|
||||||
|
print("Mapping already loaded, skipping")
|
||||||
|
|
||||||
|
return self.maps[map_name]
|
||||||
|
|
||||||
|
def load_folder(self, _map, files, diff_path, progress):
|
||||||
|
self.progress = progress
|
||||||
|
self.steps = 1
|
||||||
|
|
||||||
self.mapper = None
|
self.mapper = None
|
||||||
if _map is not None:
|
if _map is not None:
|
||||||
self.mapper = Mapper(path(cwd, f"maps/{_map}"))
|
self.mapper = self.load_map(_map)
|
||||||
|
|
||||||
self.file_structure = {"folders": {}, "files": []}
|
self.file_structure = {"folders": {}, "files": []}
|
||||||
|
|
||||||
files = [f for f in os.listdir(folder_path) if f.endswith(".pck")]
|
|
||||||
hdiff_files = []
|
hdiff_files = []
|
||||||
if diff_path != "":
|
if diff_path != "":
|
||||||
hdiff_files = [f for f in os.listdir(diff_path) if f.endswith(".pck.hdiff")]
|
hdiff_files = [f for f in os.listdir(diff_path) if f.endswith(".pck.hdiff")]
|
||||||
|
|
||||||
|
# TODO: hdiff mode will only use .hdiff files and ignore .pck even in the update folder, i need to implement it, eventually
|
||||||
|
|
||||||
|
# remove alone pck / hdiff
|
||||||
|
base_files = [os.path.basename(f) for f in files]
|
||||||
|
hdiff_files = [f for f in hdiff_files if os.path.basename(f.replace(".hdiff", "")) in base_files]
|
||||||
|
base_hfiles = [os.path.basename(f) for f in hdiff_files]
|
||||||
|
files = [f for f in files if f"{os.path.basename(f)}.hdiff" in base_hfiles]
|
||||||
|
|
||||||
if len(files) == 0:
|
if len(files) == 0:
|
||||||
return None
|
return None
|
||||||
@@ -37,12 +62,12 @@ class WwiseExtract:
|
|||||||
print(f"\nLoading {len(files)} files...")
|
print(f"\nLoading {len(files)} files...")
|
||||||
for file in files:
|
for file in files:
|
||||||
pos += 1
|
pos += 1
|
||||||
progress(["load", pos * 100 // len(files)])
|
self.update_progress(pos, len(files), 1)
|
||||||
|
|
||||||
hdiff = None
|
hdiff = None
|
||||||
if f"{file}.hdiff" in hdiff_files:
|
if f"{os.path.basename(file)}.hdiff" in hdiff_files:
|
||||||
hdiff = path(diff_path, hdiff_files[hdiff_files.index(f"{file}.hdiff")])
|
hdiff = path(diff_path, hdiff_files[hdiff_files.index(f"{os.path.basename(file)}.hdiff")])
|
||||||
self.load_file(path(folder_path, file), hdiff)
|
self.load_file(file, hdiff)
|
||||||
|
|
||||||
return self.file_structure
|
return self.file_structure
|
||||||
|
|
||||||
@@ -55,14 +80,16 @@ class WwiseExtract:
|
|||||||
def get_wems(self, data, filename, hdiff):
|
def get_wems(self, data, filename, hdiff):
|
||||||
reader = FileReader(io.BytesIO(data), "little")
|
reader = FileReader(io.BytesIO(data), "little")
|
||||||
files = wavescan.get_data(reader, filename)
|
files = wavescan.get_data(reader, filename)
|
||||||
|
|
||||||
if hdiff is not None:
|
if hdiff is not None:
|
||||||
with open(hdiff, "rb") as f:
|
with open(hdiff, "rb") as f:
|
||||||
hdiff_data = f.read()
|
hdiff_data = f.read()
|
||||||
f.close()
|
f.close()
|
||||||
hdiff_files = self.get_hdiff_files(data, hdiff_data, filename)
|
|
||||||
|
hdiff_files, data = self.get_hdiff_files(data, hdiff_data, filename)
|
||||||
files = self.compare_diff(files, hdiff_files)
|
files = self.compare_diff(files, hdiff_files)
|
||||||
|
|
||||||
self.map_names(files, filename, hdiff is not None)
|
self.map_names(files, filename, hdiff is not None, data)
|
||||||
|
|
||||||
def compare_diff(self, old, new):
|
def compare_diff(self, old, new):
|
||||||
old_dict = {file[0]:file[2] for file in old}
|
old_dict = {file[0]:file[2] for file in old}
|
||||||
@@ -97,6 +124,10 @@ class WwiseExtract:
|
|||||||
|
|
||||||
call(args)
|
call(args)
|
||||||
|
|
||||||
|
if not os.path.exists(path(working_dir.name, "patch.pck")):
|
||||||
|
print(f"[ERROR] failed to patch {source_name}, skipping")
|
||||||
|
return []
|
||||||
|
|
||||||
with open(path(working_dir.name, "patch.pck"), "rb") as f:
|
with open(path(working_dir.name, "patch.pck"), "rb") as f:
|
||||||
data = f.read()
|
data = f.read()
|
||||||
f.close()
|
f.close()
|
||||||
@@ -110,9 +141,9 @@ class WwiseExtract:
|
|||||||
|
|
||||||
working_dir.cleanup()
|
working_dir.cleanup()
|
||||||
|
|
||||||
return files
|
return files, data
|
||||||
|
|
||||||
def map_names(self, files, filename, hdiff=False, skip_source=True):
|
def map_names(self, files, filename, hdiff=False, data=None, skip_source=True):
|
||||||
# disable skip source if required
|
# disable skip source if required
|
||||||
mapper = self.mapper
|
mapper = self.mapper
|
||||||
base = self.file_structure
|
base = self.file_structure
|
||||||
@@ -128,6 +159,18 @@ class WwiseExtract:
|
|||||||
else:
|
else:
|
||||||
key = None
|
key = None
|
||||||
|
|
||||||
|
file_data = {
|
||||||
|
"source": file[3],
|
||||||
|
"size": file[2],
|
||||||
|
"offset": file[1],
|
||||||
|
"metadata": {}
|
||||||
|
}
|
||||||
|
|
||||||
|
wem_data = data[file_data["offset"]:file_data["offset"]+file_data["size"]]
|
||||||
|
parsed_wem = wwise.parse_wwise(FileReader(io.BytesIO(wem_data), "little", name=f"{file[3]}:{file[0]}:{file[1]}"))
|
||||||
|
|
||||||
|
file_data["metadata"] = parsed_wem
|
||||||
|
|
||||||
if key is not None:
|
if key is not None:
|
||||||
if hdiff:
|
if hdiff:
|
||||||
if file in old_files[0]:
|
if file in old_files[0]:
|
||||||
@@ -139,7 +182,7 @@ class WwiseExtract:
|
|||||||
if skip_source:
|
if skip_source:
|
||||||
parts = parts[1:]
|
parts = parts[1:]
|
||||||
|
|
||||||
self.add_to_structure(parts, [file[1], file[2], file[3]])
|
self.add_to_structure(parts, file_data)
|
||||||
else:
|
else:
|
||||||
temp = base["folders"]
|
temp = base["folders"]
|
||||||
|
|
||||||
@@ -161,7 +204,7 @@ class WwiseExtract:
|
|||||||
|
|
||||||
if "unmapped" not in temp:
|
if "unmapped" not in temp:
|
||||||
temp["unmapped"] = {"folders": {}, "files": []}
|
temp["unmapped"] = {"folders": {}, "files": []}
|
||||||
temp["unmapped"]["files"].append(file)
|
temp["unmapped"]["files"].append([file[0], file_data])
|
||||||
|
|
||||||
self.file_structure = base
|
self.file_structure = base
|
||||||
|
|
||||||
@@ -175,7 +218,7 @@ class WwiseExtract:
|
|||||||
current_level = current_level["folders"][part]
|
current_level = current_level["folders"][part]
|
||||||
if "files" not in current_level:
|
if "files" not in current_level:
|
||||||
current_level["files"] = []
|
current_level["files"] = []
|
||||||
current_level["files"].append([parts[-1], meta[0], meta[1], meta[2]])
|
current_level["files"].append([parts[-1], meta])
|
||||||
|
|
||||||
### extracting files ###
|
### extracting files ###
|
||||||
|
|
||||||
@@ -331,8 +374,10 @@ class WwiseExtract:
|
|||||||
self.progress(["file", current * 100 // total])
|
self.progress(["file", current * 100 // total])
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
if self.mapper is not None:
|
self.mapper = None
|
||||||
self.mapper.reset()
|
for e in self.maps.values():
|
||||||
|
e.reset()
|
||||||
|
self.maps.clear()
|
||||||
self.allocator.free_mem()
|
self.allocator.free_mem()
|
||||||
if self.hdiff_dir is not None:
|
if self.hdiff_dir is not None:
|
||||||
self.hdiff_dir.cleanup()
|
self.hdiff_dir.cleanup()
|
||||||
|
|||||||
@@ -8,52 +8,63 @@ class FileReader:
|
|||||||
File reader for files, not much too say
|
File reader for files, not much too say
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, file, endianness:str):
|
def __init__(self, file, endianness:str, name:str=None):
|
||||||
self.stream = file
|
self.stream = file
|
||||||
self.endianness = endianness
|
self.endianness = endianness
|
||||||
|
if name:
|
||||||
|
self.name = name
|
||||||
|
|
||||||
def _read(self, mode:str, bufferLength:int, endianness:str=None) -> bytes:
|
def _read(self, mode:str, bufferLength:int, endianness:str=None, pos:int=None) -> bytes:
|
||||||
# endianness override
|
# endianness override
|
||||||
if endianness is None:
|
if endianness is None:
|
||||||
endianness = self.endianness
|
endianness = self.endianness
|
||||||
|
|
||||||
endianness = "<" if endianness == "little" else ">"
|
endianness = "<" if endianness == "little" else ">"
|
||||||
|
|
||||||
return struct.unpack(f"{endianness}{mode}", bytearray(self.stream.read(bufferLength)))[0]
|
if pos:
|
||||||
|
pos_backup = self.GetBufferPos()
|
||||||
|
self.SetBufferPos(pos)
|
||||||
|
|
||||||
|
data = struct.unpack(f"{endianness}{mode}", bytearray(self.stream.read(bufferLength)))[0]
|
||||||
|
|
||||||
|
if pos:
|
||||||
|
self.SetBufferPos(pos_backup)
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
# read methods
|
# read methods
|
||||||
def ReadInt8(self, endianness:str=None) -> int:
|
def ReadInt8(self, endianness:str=None, pos:int=None) -> int:
|
||||||
return self._read("b", 1, endianness)
|
return self._read("b", 1, endianness, pos)
|
||||||
|
|
||||||
def ReadUInt8(self, endianness:str=None) -> int:
|
def ReadUInt8(self, endianness:str=None, pos:int=None) -> int:
|
||||||
return self._read("B", 1, endianness)
|
return self._read("B", 1, endianness, pos)
|
||||||
|
|
||||||
def ReadInt16(self, endianness:str=None) -> int:
|
def ReadInt16(self, endianness:str=None, pos:int=None) -> int:
|
||||||
return self._read("h", 2, endianness)
|
return self._read("h", 2, endianness, pos)
|
||||||
|
|
||||||
def ReadUInt16(self, endianness:str=None) -> int:
|
def ReadUInt16(self, endianness:str=None, pos:int=None) -> int:
|
||||||
return self._read("H", 2, endianness)
|
return self._read("H", 2, endianness, pos)
|
||||||
|
|
||||||
def ReadInt32(self, endianness:str=None) -> int:
|
def ReadInt32(self, endianness:str=None, pos:int=None) -> int:
|
||||||
return self._read("i", 4, endianness)
|
return self._read("i", 4, endianness, pos)
|
||||||
|
|
||||||
def ReadUInt32(self, endianness:str=None) -> int:
|
def ReadUInt32(self, endianness:str=None, pos:int=None) -> int:
|
||||||
return self._read("I", 4, endianness)
|
return self._read("I", 4, endianness, pos)
|
||||||
|
|
||||||
def ReadLong(self, endianness:str=None) -> int:
|
def ReadLong(self, endianness:str=None, pos:int=None) -> int:
|
||||||
return self._read("l", 4, endianness)
|
return self._read("l", 4, endianness, pos)
|
||||||
|
|
||||||
def ReadULong(self, endianness:str=None) -> int:
|
def ReadULong(self, endianness:str=None, pos:int=None) -> int:
|
||||||
return self._read("L", 4, endianness)
|
return self._read("L", 4, endianness, pos)
|
||||||
|
|
||||||
def ReadLongLong(self, endianness:str=None) -> int:
|
def ReadLongLong(self, endianness:str=None, pos:int=None) -> int:
|
||||||
return self._read("q", 8, endianness)
|
return self._read("q", 8, endianness, pos)
|
||||||
|
|
||||||
def ReadULongLong(self, endianness:str=None) -> int:
|
def ReadULongLong(self, endianness:str=None, pos:int=None) -> int:
|
||||||
return self._read("Q", 8, endianness)
|
return self._read("Q", 8, endianness, pos)
|
||||||
|
|
||||||
def ReadBytes(self, length:int, endianness:str=None) -> bytes:
|
def ReadBytes(self, length:int, endianness:str=None, pos:int=None) -> bytes:
|
||||||
return self._read(f"{str(length)}s", int(length), endianness)
|
return self._read(f"{str(length)}s", int(length), endianness, pos)
|
||||||
|
|
||||||
# buffer utils
|
# buffer utils
|
||||||
def GetBufferPos(self) -> int:
|
def GetBufferPos(self) -> int:
|
||||||
@@ -76,3 +87,8 @@ class FileReader:
|
|||||||
|
|
||||||
def GetRemainingLength(self) -> int:
|
def GetRemainingLength(self) -> int:
|
||||||
return self.GetStreamLength() - self.GetBufferPos()
|
return self.GetStreamLength() - self.GetBufferPos()
|
||||||
|
|
||||||
|
def GetName(self) -> str:
|
||||||
|
if self.name:
|
||||||
|
return self.name
|
||||||
|
return ""
|
||||||
|
|||||||
553
gui.ui
553
gui.ui
@@ -5,24 +5,27 @@
|
|||||||
<property name="windowModality">
|
<property name="windowModality">
|
||||||
<enum>Qt::NonModal</enum>
|
<enum>Qt::NonModal</enum>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
<property name="geometry">
|
<property name="geometry">
|
||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>1100</width>
|
<width>1100</width>
|
||||||
<height>800</height>
|
<height>900</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="minimumSize">
|
<property name="minimumSize">
|
||||||
<size>
|
<size>
|
||||||
<width>1100</width>
|
<width>1100</width>
|
||||||
<height>800</height>
|
<height>900</height>
|
||||||
</size>
|
</size>
|
||||||
</property>
|
</property>
|
||||||
<property name="maximumSize">
|
<property name="maximumSize">
|
||||||
<size>
|
<size>
|
||||||
<width>1100</width>
|
<width>1100</width>
|
||||||
<height>800</height>
|
<height>900</height>
|
||||||
</size>
|
</size>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
@@ -39,7 +42,7 @@
|
|||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="currentIndex">
|
<property name="currentIndex">
|
||||||
<number>1</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="usesScrollButtons">
|
<property name="usesScrollButtons">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
@@ -74,69 +77,172 @@
|
|||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="mainVLayout">
|
<layout class="QVBoxLayout" name="mainVLayout">
|
||||||
<item>
|
<item>
|
||||||
<layout class="QGridLayout" name="ioGrid">
|
<widget class="QLabel" name="paddingB">
|
||||||
<item row="1" column="2">
|
<property name="text">
|
||||||
<widget class="QPushButton" name="changeAltInput">
|
<string/>
|
||||||
<property name="enabled">
|
</property>
|
||||||
<bool>true</bool>
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="titleCenter">
|
||||||
|
<property name="spacing">
|
||||||
|
<number>2</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="appTitle">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<pointsize>32</pointsize>
|
||||||
|
<weight>75</weight>
|
||||||
|
<bold>true</bold>
|
||||||
|
</font>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Select</string>
|
<string>Welcome to AnimeWwise !</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
<property name="alignment">
|
||||||
</item>
|
<set>Qt::AlignCenter</set>
|
||||||
<item row="0" column="2">
|
|
||||||
<widget class="QPushButton" name="changeInput">
|
|
||||||
<property name="text">
|
|
||||||
<string>Select</string>
|
|
||||||
</property>
|
|
||||||
<property name="default">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
<property name="flat">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="1">
|
|
||||||
<widget class="QLineEdit" name="altInputPath">
|
|
||||||
<property name="enabled">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
<property name="readOnly">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="0">
|
|
||||||
<widget class="QLabel" name="altInputLabel">
|
|
||||||
<property name="text">
|
|
||||||
<string>Diff folder (optional)</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="0" column="0">
|
|
||||||
<widget class="QLabel" name="inputLabel">
|
|
||||||
<property name="text">
|
|
||||||
<string>Input folder</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="0" column="1">
|
|
||||||
<widget class="QLineEdit" name="inputPath">
|
|
||||||
<property name="enabled">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string/>
|
|
||||||
</property>
|
|
||||||
<property name="readOnly">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="paddingA">
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="Line" name="lineAgain">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QTabWidget" name="extractsTabs">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="currentIndex">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="pckExtract">
|
||||||
|
<attribute name="title">
|
||||||
|
<string>Extract audio package (.pck)</string>
|
||||||
|
</attribute>
|
||||||
|
<widget class="QWidget" name="gridLayoutWidget">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>10</x>
|
||||||
|
<y>10</y>
|
||||||
|
<width>1041</width>
|
||||||
|
<height>111</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<layout class="QGridLayout" name="pckGrid">
|
||||||
|
<item row="0" column="2">
|
||||||
|
<widget class="QComboBox" name="pckLoadTypeCombo"/>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QLabel" name="pckLoadType">
|
||||||
|
<property name="text">
|
||||||
|
<string>What to load :</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="QCheckBox" name="pckSubFold">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Include subfolders ?</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="2">
|
||||||
|
<widget class="QLabel" name="pckPadding">
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
<widget class="QWidget" name="hdiffExtract">
|
||||||
|
<attribute name="title">
|
||||||
|
<string>Extract update package (.hdiff)</string>
|
||||||
|
</attribute>
|
||||||
|
<widget class="QWidget" name="gridLayoutWidget_2">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>10</x>
|
||||||
|
<y>10</y>
|
||||||
|
<width>1041</width>
|
||||||
|
<height>131</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<layout class="QGridLayout" name="hdiffGrid">
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLabel" name="altInputLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>Diff folder</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="2">
|
||||||
|
<widget class="QPushButton" name="changeAltInput">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Select</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="QLineEdit" name="altInputPath">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="readOnly">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="0" colspan="3">
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<weight>75</weight>
|
||||||
|
<bold>true</bold>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Select here the folder containing the .hdiff files present in the game update package. And for the input folder asked upon loading, select the game audio folder before the update !
|
||||||
|
Subfolders are disabled in this mode, make sure to be in the correct place. For any help check the README.md or ask on discord.</string>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignCenter</set>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="Line" name="separatorA">
|
<widget class="Line" name="separatorA">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
@@ -144,11 +250,15 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="paddingC">
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<layout class="QGridLayout" name="settingsGrid">
|
<layout class="QGridLayout" name="settingsGrid">
|
||||||
<item row="0" column="1">
|
|
||||||
<widget class="QComboBox" name="assetMap"/>
|
|
||||||
</item>
|
|
||||||
<item row="0" column="0">
|
<item row="0" column="0">
|
||||||
<widget class="QLabel" name="assetMapLabel">
|
<widget class="QLabel" name="assetMapLabel">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
@@ -156,8 +266,18 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QComboBox" name="assetMap"/>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="paddingD">
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="Line" name="separatorB">
|
<widget class="Line" name="separatorB">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
@@ -167,6 +287,13 @@
|
|||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<layout class="QVBoxLayout" name="loadLayout">
|
<layout class="QVBoxLayout" name="loadLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="paddingE">
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QPushButton" name="loadFilesButton">
|
<widget class="QPushButton" name="loadFilesButton">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
@@ -175,25 +302,11 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<layout class="QHBoxLayout" name="loadProgressLayout">
|
<widget class="QLabel" name="paddingF">
|
||||||
<item>
|
<property name="text">
|
||||||
<widget class="QLabel" name="loadLabel">
|
<string/>
|
||||||
<property name="text">
|
</property>
|
||||||
<string>Progress</string>
|
</widget>
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QProgressBar" name="loadProgress">
|
|
||||||
<property name="value">
|
|
||||||
<number>0</number>
|
|
||||||
</property>
|
|
||||||
<property name="invertedAppearance">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
@@ -210,7 +323,7 @@
|
|||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>20</y>
|
<y>20</y>
|
||||||
<width>1081</width>
|
<width>1081</width>
|
||||||
<height>591</height>
|
<height>551</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="columnCount">
|
<property name="columnCount">
|
||||||
@@ -235,136 +348,23 @@
|
|||||||
<string>Search something...</string>
|
<string>Search something...</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</widget>
|
<widget class="QWidget" name="horizontalLayoutWidget">
|
||||||
<widget class="QWidget" name="extractTab">
|
|
||||||
<attribute name="title">
|
|
||||||
<string>Extract</string>
|
|
||||||
</attribute>
|
|
||||||
<widget class="QWidget" name="verticalLayoutWidget_2">
|
|
||||||
<property name="geometry">
|
<property name="geometry">
|
||||||
<rect>
|
<rect>
|
||||||
<x>9</x>
|
<x>10</x>
|
||||||
<y>9</y>
|
<y>580</y>
|
||||||
<width>1061</width>
|
<width>1061</width>
|
||||||
<height>601</height>
|
<height>31</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="mainVLayout2">
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
<item>
|
<item>
|
||||||
<layout class="QHBoxLayout" name="outputLayout">
|
<widget class="QLabel" name="audioInfoLabel">
|
||||||
<item>
|
<property name="text">
|
||||||
<widget class="QLabel" name="outputLabel">
|
<string>Click on an audio file to get more infos !</string>
|
||||||
<property name="text">
|
|
||||||
<string>Output folder</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QLineEdit" name="outputPath">
|
|
||||||
<property name="readOnly">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QPushButton" name="changeOutput">
|
|
||||||
<property name="text">
|
|
||||||
<string>Select</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<layout class="QHBoxLayout" name="outputFormatLayout">
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="outputFormatLabel">
|
|
||||||
<property name="text">
|
|
||||||
<string>Output format</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QComboBox" name="outputFormat">
|
|
||||||
<property name="currentText">
|
|
||||||
<string/>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="Line" name="separatorC">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
|
||||||
<layout class="QVBoxLayout" name="progressWrapperLayout">
|
|
||||||
<item>
|
|
||||||
<layout class="QHBoxLayout" name="totalProgressLayout">
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="totalProgressLabel">
|
|
||||||
<property name="text">
|
|
||||||
<string>Total progress</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QProgressBar" name="totalProgress">
|
|
||||||
<property name="value">
|
|
||||||
<number>0</number>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<layout class="QHBoxLayout" name="fileProgressLayout">
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="fileProgressLabel">
|
|
||||||
<property name="text">
|
|
||||||
<string>Per file progress</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QProgressBar" name="fileProgress">
|
|
||||||
<property name="value">
|
|
||||||
<number>0</number>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="Line" name="separatorD">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<layout class="QHBoxLayout" name="extractLayout">
|
|
||||||
<item>
|
|
||||||
<widget class="QPushButton" name="extractAll">
|
|
||||||
<property name="text">
|
|
||||||
<string>Extract All</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QPushButton" name="extractSelected">
|
|
||||||
<property name="text">
|
|
||||||
<string>Extract Selected</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</widget>
|
</widget>
|
||||||
@@ -373,9 +373,9 @@
|
|||||||
<property name="geometry">
|
<property name="geometry">
|
||||||
<rect>
|
<rect>
|
||||||
<x>10</x>
|
<x>10</x>
|
||||||
<y>640</y>
|
<y>720</y>
|
||||||
<width>1081</width>
|
<width>1081</width>
|
||||||
<height>131</height>
|
<height>151</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="maximumSize">
|
<property name="maximumSize">
|
||||||
@@ -394,6 +394,54 @@
|
|||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
|
<widget class="QWidget" name="layoutWidget">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>16</x>
|
||||||
|
<y>650</y>
|
||||||
|
<width>1071</width>
|
||||||
|
<height>61</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="progressWrapperLayout">
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="totalProgressLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="totalProgressLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>Total progress</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QProgressBar" name="totalProgress">
|
||||||
|
<property name="value">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="fileProgressLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="fileProgressLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>Per file progress</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QProgressBar" name="fileProgress">
|
||||||
|
<property name="value">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QMenuBar" name="menubar">
|
<widget class="QMenuBar" name="menubar">
|
||||||
<property name="geometry">
|
<property name="geometry">
|
||||||
@@ -412,7 +460,40 @@
|
|||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="actionExit"/>
|
<addaction name="actionExit"/>
|
||||||
</widget>
|
</widget>
|
||||||
|
<widget class="QMenu" name="menuExtract">
|
||||||
|
<property name="title">
|
||||||
|
<string>Extract</string>
|
||||||
|
</property>
|
||||||
|
<widget class="QMenu" name="menuOutput_format">
|
||||||
|
<property name="title">
|
||||||
|
<string>Output format</string>
|
||||||
|
</property>
|
||||||
|
<addaction name="actionformat"/>
|
||||||
|
</widget>
|
||||||
|
<addaction name="menuOutput_format"/>
|
||||||
|
<addaction name="separator"/>
|
||||||
|
<addaction name="actionExtract_All"/>
|
||||||
|
<addaction name="actionExtract_Selected"/>
|
||||||
|
</widget>
|
||||||
|
<widget class="QMenu" name="menuOther">
|
||||||
|
<property name="title">
|
||||||
|
<string>Other</string>
|
||||||
|
</property>
|
||||||
|
<addaction name="actionReport_a_bug"/>
|
||||||
|
<addaction name="actionSource_code"/>
|
||||||
|
<addaction name="actionDiscord"/>
|
||||||
|
</widget>
|
||||||
|
<widget class="QMenu" name="menuView">
|
||||||
|
<property name="title">
|
||||||
|
<string>View</string>
|
||||||
|
</property>
|
||||||
|
<addaction name="actionExpand_all"/>
|
||||||
|
<addaction name="actionCollapse_all"/>
|
||||||
|
</widget>
|
||||||
<addaction name="menuFile"/>
|
<addaction name="menuFile"/>
|
||||||
|
<addaction name="menuView"/>
|
||||||
|
<addaction name="menuExtract"/>
|
||||||
|
<addaction name="menuOther"/>
|
||||||
</widget>
|
</widget>
|
||||||
<action name="actionnot_working_here_yet">
|
<action name="actionnot_working_here_yet">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
@@ -439,12 +520,66 @@
|
|||||||
<string>Exit</string>
|
<string>Exit</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
|
<action name="actionExtract_All">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Extract All</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionExtract_Selected">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Extract Selected</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionReport_a_bug">
|
||||||
|
<property name="text">
|
||||||
|
<string>Report a bug</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionSource_code">
|
||||||
|
<property name="text">
|
||||||
|
<string>Source code</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionDiscord">
|
||||||
|
<property name="text">
|
||||||
|
<string>Discord</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionformat">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>format</string>
|
||||||
|
</property>
|
||||||
|
<property name="visible">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionExpand_all">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Expand all</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionCollapse_all">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Collapse all</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
</widget>
|
</widget>
|
||||||
<tabstops>
|
<tabstops>
|
||||||
<tabstop>inputPath</tabstop>
|
|
||||||
<tabstop>changeInput</tabstop>
|
|
||||||
<tabstop>altInputPath</tabstop>
|
|
||||||
<tabstop>changeAltInput</tabstop>
|
|
||||||
<tabstop>tabs</tabstop>
|
<tabstop>tabs</tabstop>
|
||||||
</tabstops>
|
</tabstops>
|
||||||
<resources/>
|
<resources/>
|
||||||
|
|||||||
BIN
maps/hk4e.map
BIN
maps/hk4e.map
Binary file not shown.
@@ -2,7 +2,7 @@
|
|||||||
{
|
{
|
||||||
"name": "hk4e.map",
|
"name": "hk4e.map",
|
||||||
"game": "Genshin Impact",
|
"game": "Genshin Impact",
|
||||||
"version": "5.1"
|
"version": "5.2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "hkrpg.map",
|
"name": "hkrpg.map",
|
||||||
|
|||||||
4
version.json
Normal file
4
version.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"version": 21,
|
||||||
|
"mapsVersion": 89
|
||||||
|
}
|
||||||
187
wwise.py
Normal file
187
wwise.py
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
# wwise riff header parser
|
||||||
|
# thanks to hcs and bnnm work
|
||||||
|
|
||||||
|
def parse_wwise(reader):
|
||||||
|
# default meta config
|
||||||
|
metadata = {
|
||||||
|
"format": 0,
|
||||||
|
"channels": 0,
|
||||||
|
"sampleRate": 0,
|
||||||
|
"avgBitrate": 0,
|
||||||
|
"blockSize": 0,
|
||||||
|
"bitsPerSample": 0,
|
||||||
|
"extraSize": 0,
|
||||||
|
"channelLayout": None,
|
||||||
|
"channelType": None,
|
||||||
|
"codec": None,
|
||||||
|
"codecDisplay": None,
|
||||||
|
"layoutType": None,
|
||||||
|
"interleaveBlockSize": None,
|
||||||
|
"numSamples": None,
|
||||||
|
"duration": 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if reader.GetStreamLength() == 0:
|
||||||
|
print(f"[WARNING] null stream size at {reader.GetName()}, unreadable block")
|
||||||
|
return metadata
|
||||||
|
|
||||||
|
header = reader.ReadBytes(4)
|
||||||
|
|
||||||
|
# endian check header
|
||||||
|
if header == b"RIFX":
|
||||||
|
reader.endianness = "big"
|
||||||
|
elif header == b"RIFF":
|
||||||
|
reader.endianness = "little"
|
||||||
|
else:
|
||||||
|
print(f"[WARNING] invalid header {header} at {reader.GetName()}, assuming unreadable")
|
||||||
|
return metadata
|
||||||
|
|
||||||
|
# additional check
|
||||||
|
reader.SetBufferPos(0x08)
|
||||||
|
check = reader.ReadBytes(4)
|
||||||
|
|
||||||
|
if check != b"WAVE" and check != "XWMA":
|
||||||
|
print(f"[WARNING] invalid check mark {check}, assuming unreadable")
|
||||||
|
return metadata
|
||||||
|
|
||||||
|
# read chunks
|
||||||
|
reader.SetBufferPos(0x0C)
|
||||||
|
|
||||||
|
chunks = {}
|
||||||
|
|
||||||
|
while reader.GetBufferPos() < reader.GetStreamLength():
|
||||||
|
chunk_type = reader.ReadBytes(4)
|
||||||
|
|
||||||
|
if chunk_type not in [b"fmt ", b"JUNK", b"data", b"akd ", b"cue ", b"LIST", b"smpl"]:
|
||||||
|
print(f"[WARNING] unexpected chunk {chunk_type} at {reader.GetName()}")
|
||||||
|
|
||||||
|
formatted_chunk_type = chunk_type.decode("utf-8").replace(" ", "")
|
||||||
|
chunk_length = reader.ReadUInt32()
|
||||||
|
|
||||||
|
if chunk_length > reader.GetRemainingLength():
|
||||||
|
chunk_length = reader.GetRemainingLength()
|
||||||
|
|
||||||
|
chunks[formatted_chunk_type] = {
|
||||||
|
"length": chunk_length,
|
||||||
|
"offset": reader.GetBufferPos(),
|
||||||
|
"data": reader.ReadBytes(chunk_length)
|
||||||
|
}
|
||||||
|
|
||||||
|
# reader fmt header
|
||||||
|
fmt_length = chunks["fmt"]["length"]
|
||||||
|
if fmt_length < 0x10:
|
||||||
|
print(f"[WARNING] invalid fmt chunk length {fmt_length} at {reader.GetName()}, skipping")
|
||||||
|
return metadata
|
||||||
|
|
||||||
|
reader.SetBufferPos(chunks["fmt"]["offset"])
|
||||||
|
|
||||||
|
metadata["format"] = reader.ReadUInt16()
|
||||||
|
metadata["channels"] = reader.ReadUInt16()
|
||||||
|
metadata["sampleRate"] = reader.ReadUInt32()
|
||||||
|
metadata["avgBitrate"] = reader.ReadUInt32()
|
||||||
|
metadata["blockSize"] = reader.ReadUInt16()
|
||||||
|
metadata["bitsPerSample"] = reader.ReadUInt16()
|
||||||
|
|
||||||
|
if chunks["fmt"]["length"] > 0x10 and metadata["format"] != 0x0165 and metadata["format"] != 0x0166:
|
||||||
|
metadata["extraSize"] = reader.ReadUInt16()
|
||||||
|
|
||||||
|
if metadata["extraSize"] >= 0x06:
|
||||||
|
metadata["channelLayout"] = reader.ReadUInt32()
|
||||||
|
|
||||||
|
if metadata["channelLayout"] & 0xFF == metadata["channels"]:
|
||||||
|
metadata["channelType"] = (metadata["channelLayout"] >> 8) & 0x0F
|
||||||
|
metadata["channelLayout"] = metadata["channelLayout"] >> 12
|
||||||
|
|
||||||
|
if metadata["format"] == 0x0166:
|
||||||
|
print(f"[WARNING] XMA2WAVEFORMATEX in fmt at {reader.GetName()}")
|
||||||
|
return metadata
|
||||||
|
|
||||||
|
# parse codec
|
||||||
|
codecs = {
|
||||||
|
0x0001: "PCM",
|
||||||
|
0x0002: "IMA",
|
||||||
|
0x0069: "IMA",
|
||||||
|
0x0161: "XWLA",
|
||||||
|
0x0162: "XWMA",
|
||||||
|
0x0165: "XMA2",
|
||||||
|
0x0166: "XMA2",
|
||||||
|
0xAAC0: "AAC",
|
||||||
|
0xFFF0: "DSP",
|
||||||
|
0xFFFB: "HEVAG",
|
||||||
|
0xFFFC: "ATRAC9",
|
||||||
|
0xFFFE: "PCM",
|
||||||
|
0xFFFF: "VORBIS",
|
||||||
|
0x3039: "OPUSNX",
|
||||||
|
0x3040: "OPUS",
|
||||||
|
0x3041: "OPUSWW",
|
||||||
|
0x8311: "PTADPCM"
|
||||||
|
}
|
||||||
|
|
||||||
|
# genshin should be *mostly* PTADPCM
|
||||||
|
# hsr and zzz should be VORBIS
|
||||||
|
|
||||||
|
if metadata["format"] not in codecs:
|
||||||
|
print(f'[WARNING] unknown codec {metadata["format"]} at {reader.GetName()}')
|
||||||
|
return metadata
|
||||||
|
|
||||||
|
codec = codecs[metadata["format"]]
|
||||||
|
|
||||||
|
if codec not in ["PTADPCM", "VORBIS"]: # Platinum "PtADPCM" custom ADPCM for Wwise
|
||||||
|
print(f"[WARNING] unhandled codec {codec}, need to implement this later")
|
||||||
|
|
||||||
|
metadata["codec"] = codec
|
||||||
|
|
||||||
|
# codec name
|
||||||
|
codecs_names = {
|
||||||
|
"PTADPCM": "Platinum 4-bit ADPCM",
|
||||||
|
"VORBIS": "Custom Vorbis"
|
||||||
|
}
|
||||||
|
|
||||||
|
if codec in codecs_names:
|
||||||
|
metadata["codecDisplay"] = codecs_names[codec]
|
||||||
|
else:
|
||||||
|
metadata["codecDisplay"] = codec
|
||||||
|
|
||||||
|
# parse duration
|
||||||
|
if metadata["codec"] == "PTADPCM":
|
||||||
|
metadata["layoutType"] = "interleave"
|
||||||
|
metadata["interleaveBlockSize"] = metadata["blockSize"] // metadata["channels"]
|
||||||
|
|
||||||
|
metadata["numSamples"] = int((chunks["data"]["length"] / (metadata["channels"] * metadata["interleaveBlockSize"])) * (2 + (metadata["interleaveBlockSize"] - 0x05) * 2))
|
||||||
|
metadata["duration"] = metadata["numSamples"] / metadata["sampleRate"]
|
||||||
|
|
||||||
|
elif metadata["codec"] == "VORBIS":
|
||||||
|
if (metadata["blockSize"] != 0 or metadata["bitsPerSample"] != 0):
|
||||||
|
print(f"[WARNING] worbis type at {reader.GetName()}, skipping")
|
||||||
|
return metadata
|
||||||
|
|
||||||
|
if "vorb" in chunks:
|
||||||
|
# vorb chunk only in wwise earlier to 2012, therefore impossible for mihoyo games
|
||||||
|
print(f"[WARNING] found vorb chunk at {reader.GetName()}, is this the correct game ?")
|
||||||
|
return metadata
|
||||||
|
|
||||||
|
extra_offset = chunks["fmt"]["offset"] + 0x18
|
||||||
|
|
||||||
|
if metadata["extraSize"] != 0x30:
|
||||||
|
print(f"[WARNING] unknown extra wwise size at {reader.GetName()}, skipping")
|
||||||
|
return metadata
|
||||||
|
|
||||||
|
data_offset = 0x10
|
||||||
|
blocks_offset = 0x28
|
||||||
|
# define header to type 2, packet to modified and codebook to aoTuV603, required ?
|
||||||
|
|
||||||
|
metadata["numSamples"] = reader.ReadInt32(extra_offset)
|
||||||
|
setup_offset = reader.ReadUInt32(extra_offset + data_offset)
|
||||||
|
audio_offset = reader.ReadUInt32(extra_offset + data_offset + 0x04)
|
||||||
|
|
||||||
|
block_size_1_exp = reader.ReadUInt8(extra_offset + blocks_offset)
|
||||||
|
block_size_0_exp = reader.ReadUInt8(extra_offset + blocks_offset + 0x01)
|
||||||
|
# if both exp are equals and extra size is 0x30, then reset packet type to standard
|
||||||
|
|
||||||
|
chunks["data"]["offset"] -= audio_offset
|
||||||
|
|
||||||
|
# ignore packets update and codebooks parse attempts, not implemented
|
||||||
|
metadata["layoutType"] = "none"
|
||||||
|
metadata["duration"] = metadata["numSamples"] / metadata["sampleRate"]
|
||||||
|
|
||||||
|
return metadata
|
||||||
Reference in New Issue
Block a user