OpenAudible
This commit is contained in:
commit
82033f9386
704 changed files with 97496 additions and 0 deletions
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
# Auto detect text files and perform LF normalization
|
||||||
|
* text=auto
|
87
.gitignore
vendored
Normal file
87
.gitignore
vendored
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
install4j/
|
||||||
|
installers/
|
||||||
|
*.aax
|
||||||
|
|
||||||
|
#generic
|
||||||
|
tmp/
|
||||||
|
*.log
|
||||||
|
*.swp
|
||||||
|
*.diff
|
||||||
|
*.patch
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# macosx
|
||||||
|
*.DS_Store
|
||||||
|
__MACOSX/
|
||||||
|
unused/
|
||||||
|
|
||||||
|
# windows
|
||||||
|
cachesThumbs.db
|
||||||
|
Desktop.ini
|
||||||
|
|
||||||
|
# java
|
||||||
|
*.class
|
||||||
|
*.war
|
||||||
|
*.ear
|
||||||
|
|
||||||
|
# eclipse
|
||||||
|
.buildpath
|
||||||
|
.classpath
|
||||||
|
.factorypath
|
||||||
|
.project
|
||||||
|
.pmd
|
||||||
|
.checkstyle
|
||||||
|
.ruleset
|
||||||
|
.settings/
|
||||||
|
.worksheet/
|
||||||
|
|
||||||
|
# idea
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# netbeans
|
||||||
|
/nbproject
|
||||||
|
|
||||||
|
# vim
|
||||||
|
.*.sw[a-p]
|
||||||
|
|
||||||
|
# merge tooling
|
||||||
|
*.orig
|
||||||
|
|
||||||
|
# maven
|
||||||
|
target/
|
||||||
|
*.versionsBackup
|
||||||
|
*.releaseBackup
|
||||||
|
|
||||||
|
# sbt
|
||||||
|
dist/*
|
||||||
|
target/
|
||||||
|
lib_managed/
|
||||||
|
src_managed/
|
||||||
|
project/boot/
|
||||||
|
project/plugins/project/
|
||||||
|
|
||||||
|
# scala ide
|
||||||
|
.cache-main
|
||||||
|
.cache-tests
|
||||||
|
.scala_dependencies
|
||||||
|
|
||||||
|
# haskell
|
||||||
|
dist
|
||||||
|
cabal-dev
|
||||||
|
*.o
|
||||||
|
*.hi
|
||||||
|
*.chi
|
||||||
|
*.chs.h
|
||||||
|
.virthualenv
|
||||||
|
|
||||||
|
# android
|
||||||
|
*.apk
|
||||||
|
*.ap_
|
||||||
|
*.dex
|
||||||
|
|
||||||
|
# python
|
||||||
|
*.pyc
|
||||||
|
*.pyo
|
202
LICENSE.md
Normal file
202
LICENSE.md
Normal file
|
@ -0,0 +1,202 @@
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright {yyyy} {name of copyright owner}
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|
122
README.md
Normal file
122
README.md
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
# [OpenAudible](http://openaudible.org)
|
||||||
|
A desktop application for downloading and managing your [audible.com](https://audible.com) content.
|
||||||
|
|
||||||
|
## Latest Release
|
||||||
|
|
||||||
|
A binary installer for Windows, Mac and Linux is to be available, generated with install4j.
|
||||||
|
[Latest Releaes](https://github.com/openaudible/openaudible/releases/latest)
|
||||||
|
|
||||||
|
## Features
|
||||||
|
* Import audible books from your account
|
||||||
|
* Convert to mp3 with all tags
|
||||||
|
* Display all your books in searchable
|
||||||
|
* Export web page/javascript file with all your books
|
||||||
|
|
||||||
|
## Screenshot
|
||||||
|
![Windows Screenshot](http://openaudible.org/images/open_audible_win.png)
|
||||||
|
Windows User Interface
|
||||||
|
|
||||||
|
## Building
|
||||||
|
OpenAudible is a java application that uses Maven for building.
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
Java 8, Maven, and git. Windows, Mac or Linux Desktop.
|
||||||
|
|
||||||
|
Clone the [git repo](https://github.com/openaudible/openaudible)
|
||||||
|
|
||||||
|
```
|
||||||
|
git clone https://github.com/openaudible/openaudible.git
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Build using Intellij
|
||||||
|
Import Project: <br />
|
||||||
|
Select the openaudible/pom.xml file <br />
|
||||||
|
Click through all of the defaults
|
||||||
|
|
||||||
|
#### Build using Eclipse
|
||||||
|
Import... Maven Project<br />
|
||||||
|
Select the openaudible directory<br />
|
||||||
|
|
||||||
|
#### Build from command line (requires maven, java 8 SDK)
|
||||||
|
|
||||||
|
```
|
||||||
|
cd openaudible
|
||||||
|
mvn compile
|
||||||
|
mvn package
|
||||||
|
```
|
||||||
|
## Running/Debugging
|
||||||
|
|
||||||
|
Your IDE should link the platform specific SWT library via the maven profile.
|
||||||
|
|
||||||
|
#### IntelliJ
|
||||||
|
Select menu Run: Debug... <br />
|
||||||
|
Select Edit Configurations... <br />
|
||||||
|
Add Application <br />
|
||||||
|
Name: OpenAudible <br />
|
||||||
|
Main Class: org.openaudible.desktop.Application <br />
|
||||||
|
VM options: -ea <br />
|
||||||
|
Mac VM options: -ea -XstartOnFirstThread <br />
|
||||||
|
Click Debug button <br />
|
||||||
|
|
||||||
|
#### Windows Command Line
|
||||||
|
```
|
||||||
|
java -cp "target\openaudible-jar-with-dependencies.jar;swt\org.eclipse.swt.win32.win32.x86_64-4.6.jar" org.openaudible.desktop.Application
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Mac Command Line
|
||||||
|
```
|
||||||
|
java -XstartOnFirstThread -cp "./target/openaudible-jar-with-dependencies.jar:./swt/org.eclipse.swt.cocoa.macosx.x86_64-4.6.jar" org.openaudible.desktop.Application
|
||||||
|
```
|
||||||
|
Notice on Mac, the -XstartOnFirstThread is required to run SWT apps.
|
||||||
|
|
||||||
|
#### Linux Command Line
|
||||||
|
```
|
||||||
|
java -cp "target/openaudible-jar-with-dependencies.jar:swt/org.eclipse.swt.gtk.linux.x86_64-4.6.jar" org.openaudible.desktop.Application
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Notes
|
||||||
|
|
||||||
|
Running with -ea to alert you of assertion failures is recommended for debugging. We use a lot of "asserts" to help identify problems.
|
||||||
|
|
||||||
|
Enter that into the VM Arguments on your debugger/run dialog if using an IDE.
|
||||||
|
|
||||||
|
You should see the user interface. You may see an error, or a warning about where you can go to preferences and enter your audible account details.
|
||||||
|
|
||||||
|
Open the Preferences from the Edit Menu. <br />
|
||||||
|
Enter your audible user name (email) and password. <br />
|
||||||
|
Before logging in with the application, go to the Controls: Browser menu and log into your audible account. <br />
|
||||||
|
This is only required if logging in fails or if the browser cookies expire. <br />
|
||||||
|
|
||||||
|
The application will use cookies to expedite logging in-- and bypassing some of the "are you a human" checks.
|
||||||
|
|
||||||
|
Errors are logged to an "error.log" file, usually written out to the application directory.
|
||||||
|
|
||||||
|
## Built With
|
||||||
|
|
||||||
|
* [Eclipse SWT](http://www.eclipse.org/swt/) - Standard Widget Toolkit
|
||||||
|
* [HTML Unit](https://htmlunit.sourceforge.net/) - HTML web page scraping
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
This is a work in progress. It needs testing and bug reporting for all platforms.
|
||||||
|
|
||||||
|
* Exporting to a podcast format is planned
|
||||||
|
* Testing with regions is needed
|
||||||
|
* Exporting to a format that supports the best mobile audio book players is the goal.
|
||||||
|
* The UI needs cleaning up, especially for Linux.
|
||||||
|
* Improved "first time setup" and connection needs major improvement
|
||||||
|
* Support for multiple audible accounts would be nice
|
||||||
|
|
||||||
|
Please feel free to submit pull requests.
|
||||||
|
|
||||||
|
## Versioning
|
||||||
|
|
||||||
|
We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/openaudible/openaudible/tags).
|
||||||
|
|
||||||
|
## Authors
|
||||||
|
|
||||||
|
See the list of [contributors](https://github.com/openaudible/openaudible/contributors) who participated in this project.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This project is licensed under the Apache 2.0 License - see the [LICENSE.md](LICENSE.md) file for details, but may uses code licensed by other licenses.
|
BIN
bin/alglib1.dll
Normal file
BIN
bin/alglib1.dll
Normal file
Binary file not shown.
BIN
bin/alglib1.so
Normal file
BIN
bin/alglib1.so
Normal file
Binary file not shown.
15
bin/charset.txt
Normal file
15
bin/charset.txt
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
|
||||||
|
numeric = [0123456789]
|
||||||
|
|
||||||
|
alpha = [ABCDEFGHIJKLMNOPQRSTUVWXYZ]
|
||||||
|
alpha-numeric = [ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789]
|
||||||
|
|
||||||
|
loweralpha = [abcdefghijklmnopqrstuvwxyz]
|
||||||
|
loweralpha-numeric = [abcdefghijklmnopqrstuvwxyz0123456789]
|
||||||
|
|
||||||
|
mixalpha = [abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ]
|
||||||
|
mixalpha-numeric = [abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789]
|
||||||
|
|
||||||
|
ascii-32-95 = [ !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~]
|
||||||
|
ascii-32-65-123-4 = [ !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`{|}~]
|
||||||
|
alpha-numeric-symbol32-space = [ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-_+=~`[]{}|\:;"'<>,.?/ ]
|
509
bin/ffmpeg license.txt
Normal file
509
bin/ffmpeg license.txt
Normal file
|
@ -0,0 +1,509 @@
|
||||||
|
This product uses software developed by ffmpeg.org.
|
||||||
|
|
||||||
|
This build of FFmpeg was created as a LGPL binary.
|
||||||
|
|
||||||
|
This product does not own FFmpeg. The owners can be found at http://www.ffmpeg.org/
|
||||||
|
|
||||||
|
|
||||||
|
GNU LESSER GENERAL PUBLIC LICENSE
|
||||||
|
Version 2.1, February 1999
|
||||||
|
|
||||||
|
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
[This is the first released version of the Lesser GPL. It also counts
|
||||||
|
as the successor of the GNU Library Public License, version 2, hence
|
||||||
|
the version number 2.1.]
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The licenses for most software are designed to take away your
|
||||||
|
freedom to share and change it. By contrast, the GNU General Public
|
||||||
|
Licenses are intended to guarantee your freedom to share and change
|
||||||
|
free software--to make sure the software is free for all its users.
|
||||||
|
|
||||||
|
This license, the Lesser General Public License, applies to some
|
||||||
|
specially designated software packages--typically libraries--of the
|
||||||
|
Free Software Foundation and other authors who decide to use it. You
|
||||||
|
can use it too, but we suggest you first think carefully about whether
|
||||||
|
this license or the ordinary General Public License is the better
|
||||||
|
strategy to use in any particular case, based on the explanations below.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom of use,
|
||||||
|
not price. Our General Public Licenses are designed to make sure that
|
||||||
|
you have the freedom to distribute copies of free software (and charge
|
||||||
|
for this service if you wish); that you receive source code or can get
|
||||||
|
it if you want it; that you can change the software and use pieces of
|
||||||
|
it in new free programs; and that you are informed that you can do
|
||||||
|
these things.
|
||||||
|
|
||||||
|
To protect your rights, we need to make restrictions that forbid
|
||||||
|
distributors to deny you these rights or to ask you to surrender these
|
||||||
|
rights. These restrictions translate to certain responsibilities for
|
||||||
|
you if you distribute copies of the library or if you modify it.
|
||||||
|
|
||||||
|
For example, if you distribute copies of the library, whether gratis
|
||||||
|
or for a fee, you must give the recipients all the rights that we gave
|
||||||
|
you. You must make sure that they, too, receive or can get the source
|
||||||
|
code. If you link other code with the library, you must provide
|
||||||
|
complete object files to the recipients, so that they can relink them
|
||||||
|
with the library after making changes to the library and recompiling
|
||||||
|
it. And you must show them these terms so they know their rights.
|
||||||
|
|
||||||
|
We protect your rights with a two-step method: (1) we copyright the
|
||||||
|
library, and (2) we offer you this license, which gives you legal
|
||||||
|
permission to copy, distribute and/or modify the library.
|
||||||
|
|
||||||
|
To protect each distributor, we want to make it very clear that
|
||||||
|
there is no warranty for the free library. Also, if the library is
|
||||||
|
modified by someone else and passed on, the recipients should know
|
||||||
|
that what they have is not the original version, so that the original
|
||||||
|
author's reputation will not be affected by problems that might be
|
||||||
|
introduced by others.
|
||||||
|
|
||||||
|
Finally, software patents pose a constant threat to the existence of
|
||||||
|
any free program. We wish to make sure that a company cannot
|
||||||
|
effectively restrict the users of a free program by obtaining a
|
||||||
|
restrictive license from a patent holder. Therefore, we insist that
|
||||||
|
any patent license obtained for a version of the library must be
|
||||||
|
consistent with the full freedom of use specified in this license.
|
||||||
|
|
||||||
|
Most GNU software, including some libraries, is covered by the
|
||||||
|
ordinary GNU General Public License. This license, the GNU Lesser
|
||||||
|
General Public License, applies to certain designated libraries, and
|
||||||
|
is quite different from the ordinary General Public License. We use
|
||||||
|
this license for certain libraries in order to permit linking those
|
||||||
|
libraries into non-free programs.
|
||||||
|
|
||||||
|
When a program is linked with a library, whether statically or using
|
||||||
|
a shared library, the combination of the two is legally speaking a
|
||||||
|
combined work, a derivative of the original library. The ordinary
|
||||||
|
General Public License therefore permits such linking only if the
|
||||||
|
entire combination fits its criteria of freedom. The Lesser General
|
||||||
|
Public License permits more lax criteria for linking other code with
|
||||||
|
the library.
|
||||||
|
|
||||||
|
We call this license the "Lesser" General Public License because it
|
||||||
|
does Less to protect the user's freedom than the ordinary General
|
||||||
|
Public License. It also provides other free software developers Less
|
||||||
|
of an advantage over competing non-free programs. These disadvantages
|
||||||
|
are the reason we use the ordinary General Public License for many
|
||||||
|
libraries. However, the Lesser license provides advantages in certain
|
||||||
|
special circumstances.
|
||||||
|
|
||||||
|
For example, on rare occasions, there may be a special need to
|
||||||
|
encourage the widest possible use of a certain library, so that it becomes
|
||||||
|
a de-facto standard. To achieve this, non-free programs must be
|
||||||
|
allowed to use the library. A more frequent case is that a free
|
||||||
|
library does the same job as widely used non-free libraries. In this
|
||||||
|
case, there is little to gain by limiting the free library to free
|
||||||
|
software only, so we use the Lesser General Public License.
|
||||||
|
|
||||||
|
In other cases, permission to use a particular library in non-free
|
||||||
|
programs enables a greater number of people to use a large body of
|
||||||
|
free software. For example, permission to use the GNU C Library in
|
||||||
|
non-free programs enables many more people to use the whole GNU
|
||||||
|
operating system, as well as its variant, the GNU/Linux operating
|
||||||
|
system.
|
||||||
|
|
||||||
|
Although the Lesser General Public License is Less protective of the
|
||||||
|
users' freedom, it does ensure that the user of a program that is
|
||||||
|
linked with the Library has the freedom and the wherewithal to run
|
||||||
|
that program using a modified version of the Library.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow. Pay close attention to the difference between a
|
||||||
|
"work based on the library" and a "work that uses the library". The
|
||||||
|
former contains code derived from the library, whereas the latter must
|
||||||
|
be combined with the library in order to run.
|
||||||
|
|
||||||
|
GNU LESSER GENERAL PUBLIC LICENSE
|
||||||
|
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||||
|
|
||||||
|
0. This License Agreement applies to any software library or other
|
||||||
|
program which contains a notice placed by the copyright holder or
|
||||||
|
other authorized party saying it may be distributed under the terms of
|
||||||
|
this Lesser General Public License (also called "this License").
|
||||||
|
Each licensee is addressed as "you".
|
||||||
|
|
||||||
|
A "library" means a collection of software functions and/or data
|
||||||
|
prepared so as to be conveniently linked with application programs
|
||||||
|
(which use some of those functions and data) to form executables.
|
||||||
|
|
||||||
|
The "Library", below, refers to any such software library or work
|
||||||
|
which has been distributed under these terms. A "work based on the
|
||||||
|
Library" means either the Library or any derivative work under
|
||||||
|
copyright law: that is to say, a work containing the Library or a
|
||||||
|
portion of it, either verbatim or with modifications and/or translated
|
||||||
|
straightforwardly into another language. (Hereinafter, translation is
|
||||||
|
included without limitation in the term "modification".)
|
||||||
|
|
||||||
|
"Source code" for a work means the preferred form of the work for
|
||||||
|
making modifications to it. For a library, complete source code means
|
||||||
|
all the source code for all modules it contains, plus any associated
|
||||||
|
interface definition files, plus the scripts used to control compilation
|
||||||
|
and installation of the library.
|
||||||
|
|
||||||
|
Activities other than copying, distribution and modification are not
|
||||||
|
covered by this License; they are outside its scope. The act of
|
||||||
|
running a program using the Library is not restricted, and output from
|
||||||
|
such a program is covered only if its contents constitute a work based
|
||||||
|
on the Library (independent of the use of the Library in a tool for
|
||||||
|
writing it). Whether that is true depends on what the Library does
|
||||||
|
and what the program that uses the Library does.
|
||||||
|
|
||||||
|
1. You may copy and distribute verbatim copies of the Library's
|
||||||
|
complete source code as you receive it, in any medium, provided that
|
||||||
|
you conspicuously and appropriately publish on each copy an
|
||||||
|
appropriate copyright notice and disclaimer of warranty; keep intact
|
||||||
|
all the notices that refer to this License and to the absence of any
|
||||||
|
warranty; and distribute a copy of this License along with the
|
||||||
|
Library.
|
||||||
|
|
||||||
|
You may charge a fee for the physical act of transferring a copy,
|
||||||
|
and you may at your option offer warranty protection in exchange for a
|
||||||
|
fee.
|
||||||
|
|
||||||
|
2. You may modify your copy or copies of the Library or any portion
|
||||||
|
of it, thus forming a work based on the Library, and copy and
|
||||||
|
distribute such modifications or work under the terms of Section 1
|
||||||
|
above, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) The modified work must itself be a software library.
|
||||||
|
|
||||||
|
b) You must cause the files modified to carry prominent notices
|
||||||
|
stating that you changed the files and the date of any change.
|
||||||
|
|
||||||
|
c) You must cause the whole of the work to be licensed at no
|
||||||
|
charge to all third parties under the terms of this License.
|
||||||
|
|
||||||
|
d) If a facility in the modified Library refers to a function or a
|
||||||
|
table of data to be supplied by an application program that uses
|
||||||
|
the facility, other than as an argument passed when the facility
|
||||||
|
is invoked, then you must make a good faith effort to ensure that,
|
||||||
|
in the event an application does not supply such function or
|
||||||
|
table, the facility still operates, and performs whatever part of
|
||||||
|
its purpose remains meaningful.
|
||||||
|
|
||||||
|
(For example, a function in a library to compute square roots has
|
||||||
|
a purpose that is entirely well-defined independent of the
|
||||||
|
application. Therefore, Subsection 2d requires that any
|
||||||
|
application-supplied function or table used by this function must
|
||||||
|
be optional: if the application does not supply it, the square
|
||||||
|
root function must still compute square roots.)
|
||||||
|
|
||||||
|
These requirements apply to the modified work as a whole. If
|
||||||
|
identifiable sections of that work are not derived from the Library,
|
||||||
|
and can be reasonably considered independent and separate works in
|
||||||
|
themselves, then this License, and its terms, do not apply to those
|
||||||
|
sections when you distribute them as separate works. But when you
|
||||||
|
distribute the same sections as part of a whole which is a work based
|
||||||
|
on the Library, the distribution of the whole must be on the terms of
|
||||||
|
this License, whose permissions for other licensees extend to the
|
||||||
|
entire whole, and thus to each and every part regardless of who wrote
|
||||||
|
it.
|
||||||
|
|
||||||
|
Thus, it is not the intent of this section to claim rights or contest
|
||||||
|
your rights to work written entirely by you; rather, the intent is to
|
||||||
|
exercise the right to control the distribution of derivative or
|
||||||
|
collective works based on the Library.
|
||||||
|
|
||||||
|
In addition, mere aggregation of another work not based on the Library
|
||||||
|
with the Library (or with a work based on the Library) on a volume of
|
||||||
|
a storage or distribution medium does not bring the other work under
|
||||||
|
the scope of this License.
|
||||||
|
|
||||||
|
3. You may opt to apply the terms of the ordinary GNU General Public
|
||||||
|
License instead of this License to a given copy of the Library. To do
|
||||||
|
this, you must alter all the notices that refer to this License, so
|
||||||
|
that they refer to the ordinary GNU General Public License, version 2,
|
||||||
|
instead of to this License. (If a newer version than version 2 of the
|
||||||
|
ordinary GNU General Public License has appeared, then you can specify
|
||||||
|
that version instead if you wish.) Do not make any other change in
|
||||||
|
these notices.
|
||||||
|
|
||||||
|
Once this change is made in a given copy, it is irreversible for
|
||||||
|
that copy, so the ordinary GNU General Public License applies to all
|
||||||
|
subsequent copies and derivative works made from that copy.
|
||||||
|
|
||||||
|
This option is useful when you wish to copy part of the code of
|
||||||
|
the Library into a program that is not a library.
|
||||||
|
|
||||||
|
4. You may copy and distribute the Library (or a portion or
|
||||||
|
derivative of it, under Section 2) in object code or executable form
|
||||||
|
under the terms of Sections 1 and 2 above provided that you accompany
|
||||||
|
it with the complete corresponding machine-readable source code, which
|
||||||
|
must be distributed under the terms of Sections 1 and 2 above on a
|
||||||
|
medium customarily used for software interchange.
|
||||||
|
|
||||||
|
If distribution of object code is made by offering access to copy
|
||||||
|
from a designated place, then offering equivalent access to copy the
|
||||||
|
source code from the same place satisfies the requirement to
|
||||||
|
distribute the source code, even though third parties are not
|
||||||
|
compelled to copy the source along with the object code.
|
||||||
|
|
||||||
|
5. A program that contains no derivative of any portion of the
|
||||||
|
Library, but is designed to work with the Library by being compiled or
|
||||||
|
linked with it, is called a "work that uses the Library". Such a
|
||||||
|
work, in isolation, is not a derivative work of the Library, and
|
||||||
|
therefore falls outside the scope of this License.
|
||||||
|
|
||||||
|
However, linking a "work that uses the Library" with the Library
|
||||||
|
creates an executable that is a derivative of the Library (because it
|
||||||
|
contains portions of the Library), rather than a "work that uses the
|
||||||
|
library". The executable is therefore covered by this License.
|
||||||
|
Section 6 states terms for distribution of such executables.
|
||||||
|
|
||||||
|
When a "work that uses the Library" uses material from a header file
|
||||||
|
that is part of the Library, the object code for the work may be a
|
||||||
|
derivative work of the Library even though the source code is not.
|
||||||
|
Whether this is true is especially significant if the work can be
|
||||||
|
linked without the Library, or if the work is itself a library. The
|
||||||
|
threshold for this to be true is not precisely defined by law.
|
||||||
|
|
||||||
|
If such an object file uses only numerical parameters, data
|
||||||
|
structure layouts and accessors, and small macros and small inline
|
||||||
|
functions (ten lines or less in length), then the use of the object
|
||||||
|
file is unrestricted, regardless of whether it is legally a derivative
|
||||||
|
work. (Executables containing this object code plus portions of the
|
||||||
|
Library will still fall under Section 6.)
|
||||||
|
|
||||||
|
Otherwise, if the work is a derivative of the Library, you may
|
||||||
|
distribute the object code for the work under the terms of Section 6.
|
||||||
|
Any executables containing that work also fall under Section 6,
|
||||||
|
whether or not they are linked directly with the Library itself.
|
||||||
|
|
||||||
|
6. As an exception to the Sections above, you may also combine or
|
||||||
|
link a "work that uses the Library" with the Library to produce a
|
||||||
|
work containing portions of the Library, and distribute that work
|
||||||
|
under terms of your choice, provided that the terms permit
|
||||||
|
modification of the work for the customer's own use and reverse
|
||||||
|
engineering for debugging such modifications.
|
||||||
|
|
||||||
|
You must give prominent notice with each copy of the work that the
|
||||||
|
Library is used in it and that the Library and its use are covered by
|
||||||
|
this License. You must supply a copy of this License. If the work
|
||||||
|
during execution displays copyright notices, you must include the
|
||||||
|
copyright notice for the Library among them, as well as a reference
|
||||||
|
directing the user to the copy of this License. Also, you must do one
|
||||||
|
of these things:
|
||||||
|
|
||||||
|
a) Accompany the work with the complete corresponding
|
||||||
|
machine-readable source code for the Library including whatever
|
||||||
|
changes were used in the work (which must be distributed under
|
||||||
|
Sections 1 and 2 above); and, if the work is an executable linked
|
||||||
|
with the Library, with the complete machine-readable "work that
|
||||||
|
uses the Library", as object code and/or source code, so that the
|
||||||
|
user can modify the Library and then relink to produce a modified
|
||||||
|
executable containing the modified Library. (It is understood
|
||||||
|
that the user who changes the contents of definitions files in the
|
||||||
|
Library will not necessarily be able to recompile the application
|
||||||
|
to use the modified definitions.)
|
||||||
|
|
||||||
|
b) Use a suitable shared library mechanism for linking with the
|
||||||
|
Library. A suitable mechanism is one that (1) uses at run time a
|
||||||
|
copy of the library already present on the user's computer system,
|
||||||
|
rather than copying library functions into the executable, and (2)
|
||||||
|
will operate properly with a modified version of the library, if
|
||||||
|
the user installs one, as long as the modified version is
|
||||||
|
interface-compatible with the version that the work was made with.
|
||||||
|
|
||||||
|
c) Accompany the work with a written offer, valid for at
|
||||||
|
least three years, to give the same user the materials
|
||||||
|
specified in Subsection 6a, above, for a charge no more
|
||||||
|
than the cost of performing this distribution.
|
||||||
|
|
||||||
|
d) If distribution of the work is made by offering access to copy
|
||||||
|
from a designated place, offer equivalent access to copy the above
|
||||||
|
specified materials from the same place.
|
||||||
|
|
||||||
|
e) Verify that the user has already received a copy of these
|
||||||
|
materials or that you have already sent this user a copy.
|
||||||
|
|
||||||
|
For an executable, the required form of the "work that uses the
|
||||||
|
Library" must include any data and utility programs needed for
|
||||||
|
reproducing the executable from it. However, as a special exception,
|
||||||
|
the materials to be distributed need not include anything that is
|
||||||
|
normally distributed (in either source or binary form) with the major
|
||||||
|
components (compiler, kernel, and so on) of the operating system on
|
||||||
|
which the executable runs, unless that component itself accompanies
|
||||||
|
the executable.
|
||||||
|
|
||||||
|
It may happen that this requirement contradicts the license
|
||||||
|
restrictions of other proprietary libraries that do not normally
|
||||||
|
accompany the operating system. Such a contradiction means you cannot
|
||||||
|
use both them and the Library together in an executable that you
|
||||||
|
distribute.
|
||||||
|
|
||||||
|
7. You may place library facilities that are a work based on the
|
||||||
|
Library side-by-side in a single library together with other library
|
||||||
|
facilities not covered by this License, and distribute such a combined
|
||||||
|
library, provided that the separate distribution of the work based on
|
||||||
|
the Library and of the other library facilities is otherwise
|
||||||
|
permitted, and provided that you do these two things:
|
||||||
|
|
||||||
|
a) Accompany the combined library with a copy of the same work
|
||||||
|
based on the Library, uncombined with any other library
|
||||||
|
facilities. This must be distributed under the terms of the
|
||||||
|
Sections above.
|
||||||
|
|
||||||
|
b) Give prominent notice with the combined library of the fact
|
||||||
|
that part of it is a work based on the Library, and explaining
|
||||||
|
where to find the accompanying uncombined form of the same work.
|
||||||
|
|
||||||
|
8. You may not copy, modify, sublicense, link with, or distribute
|
||||||
|
the Library except as expressly provided under this License. Any
|
||||||
|
attempt otherwise to copy, modify, sublicense, link with, or
|
||||||
|
distribute the Library is void, and will automatically terminate your
|
||||||
|
rights under this License. However, parties who have received copies,
|
||||||
|
or rights, from you under this License will not have their licenses
|
||||||
|
terminated so long as such parties remain in full compliance.
|
||||||
|
|
||||||
|
9. You are not required to accept this License, since you have not
|
||||||
|
signed it. However, nothing else grants you permission to modify or
|
||||||
|
distribute the Library or its derivative works. These actions are
|
||||||
|
prohibited by law if you do not accept this License. Therefore, by
|
||||||
|
modifying or distributing the Library (or any work based on the
|
||||||
|
Library), you indicate your acceptance of this License to do so, and
|
||||||
|
all its terms and conditions for copying, distributing or modifying
|
||||||
|
the Library or works based on it.
|
||||||
|
|
||||||
|
10. Each time you redistribute the Library (or any work based on the
|
||||||
|
Library), the recipient automatically receives a license from the
|
||||||
|
original licensor to copy, distribute, link with or modify the Library
|
||||||
|
subject to these terms and conditions. You may not impose any further
|
||||||
|
restrictions on the recipients' exercise of the rights granted herein.
|
||||||
|
You are not responsible for enforcing compliance by third parties with
|
||||||
|
this License.
|
||||||
|
|
||||||
|
11. If, as a consequence of a court judgment or allegation of patent
|
||||||
|
infringement or for any other reason (not limited to patent issues),
|
||||||
|
conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot
|
||||||
|
distribute so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you
|
||||||
|
may not distribute the Library at all. For example, if a patent
|
||||||
|
license would not permit royalty-free redistribution of the Library by
|
||||||
|
all those who receive copies directly or indirectly through you, then
|
||||||
|
the only way you could satisfy both it and this License would be to
|
||||||
|
refrain entirely from distribution of the Library.
|
||||||
|
|
||||||
|
If any portion of this section is held invalid or unenforceable under any
|
||||||
|
particular circumstance, the balance of the section is intended to apply,
|
||||||
|
and the section as a whole is intended to apply in other circumstances.
|
||||||
|
|
||||||
|
It is not the purpose of this section to induce you to infringe any
|
||||||
|
patents or other property right claims or to contest validity of any
|
||||||
|
such claims; this section has the sole purpose of protecting the
|
||||||
|
integrity of the free software distribution system which is
|
||||||
|
implemented by public license practices. Many people have made
|
||||||
|
generous contributions to the wide range of software distributed
|
||||||
|
through that system in reliance on consistent application of that
|
||||||
|
system; it is up to the author/donor to decide if he or she is willing
|
||||||
|
to distribute software through any other system and a licensee cannot
|
||||||
|
impose that choice.
|
||||||
|
|
||||||
|
This section is intended to make thoroughly clear what is believed to
|
||||||
|
be a consequence of the rest of this License.
|
||||||
|
|
||||||
|
12. If the distribution and/or use of the Library is restricted in
|
||||||
|
certain countries either by patents or by copyrighted interfaces, the
|
||||||
|
original copyright holder who places the Library under this License may add
|
||||||
|
an explicit geographical distribution limitation excluding those countries,
|
||||||
|
so that distribution is permitted only in or among countries not thus
|
||||||
|
excluded. In such case, this License incorporates the limitation as if
|
||||||
|
written in the body of this License.
|
||||||
|
|
||||||
|
13. The Free Software Foundation may publish revised and/or new
|
||||||
|
versions of the Lesser General Public License from time to time.
|
||||||
|
Such new versions will be similar in spirit to the present version,
|
||||||
|
but may differ in detail to address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the Library
|
||||||
|
specifies a version number of this License which applies to it and
|
||||||
|
"any later version", you have the option of following the terms and
|
||||||
|
conditions either of that version or of any later version published by
|
||||||
|
the Free Software Foundation. If the Library does not specify a
|
||||||
|
license version number, you may choose any version ever published by
|
||||||
|
the Free Software Foundation.
|
||||||
|
|
||||||
|
14. If you wish to incorporate parts of the Library into other free
|
||||||
|
programs whose distribution conditions are incompatible with these,
|
||||||
|
write to the author to ask for permission. For software which is
|
||||||
|
copyrighted by the Free Software Foundation, write to the Free
|
||||||
|
Software Foundation; we sometimes make exceptions for this. Our
|
||||||
|
decision will be guided by the two goals of preserving the free status
|
||||||
|
of all derivatives of our free software and of promoting the sharing
|
||||||
|
and reuse of software generally.
|
||||||
|
|
||||||
|
NO WARRANTY
|
||||||
|
|
||||||
|
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
|
||||||
|
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
|
||||||
|
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
|
||||||
|
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
|
||||||
|
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
|
||||||
|
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
|
||||||
|
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
|
||||||
|
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
|
||||||
|
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
|
||||||
|
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
|
||||||
|
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
|
||||||
|
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
|
||||||
|
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
|
||||||
|
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
|
||||||
|
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||||
|
DAMAGES.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Libraries
|
||||||
|
|
||||||
|
If you develop a new library, and you want it to be of the greatest
|
||||||
|
possible use to the public, we recommend making it free software that
|
||||||
|
everyone can redistribute and change. You can do so by permitting
|
||||||
|
redistribution under these terms (or, alternatively, under the terms of the
|
||||||
|
ordinary General Public License).
|
||||||
|
|
||||||
|
To apply these terms, attach the following notices to the library. It is
|
||||||
|
safest to attach them to the start of each source file to most effectively
|
||||||
|
convey the exclusion of warranty; and each file should have at least the
|
||||||
|
"copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
<one line to give the library's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or your
|
||||||
|
school, if any, to sign a "copyright disclaimer" for the library, if
|
||||||
|
necessary. Here is a sample; alter the names:
|
||||||
|
|
||||||
|
Yoyodyne, Inc., hereby disclaims all copyright interest in the
|
||||||
|
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
|
||||||
|
|
||||||
|
<signature of Ty Coon>, 1 April 1990
|
||||||
|
Ty Coon, President of Vice
|
||||||
|
|
||||||
|
That's all there is to it!
|
BIN
bin/linux_ffmpeg
Normal file
BIN
bin/linux_ffmpeg
Normal file
Binary file not shown.
BIN
bin/linux_rcrack
Normal file
BIN
bin/linux_rcrack
Normal file
Binary file not shown.
BIN
bin/mac_ffmpeg
Normal file
BIN
bin/mac_ffmpeg
Normal file
Binary file not shown.
BIN
bin/mac_rcrack
Normal file
BIN
bin/mac_rcrack
Normal file
Binary file not shown.
2
bin/rcrack.txt
Normal file
2
bin/rcrack.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Key recovery software by inAudible-NG:
|
||||||
|
https://github.com/inAudible-NG/RainbowCrack-NG
|
1
bin/tables/README.md
Normal file
1
bin/tables/README.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
This folder contains the RainbowTables generated by [inAudible-NG](https://github.com/inAudible-NG/tables)
|
BIN
bin/tables/audible_byte#4-4_0_10000x1362345_0.rt
Normal file
BIN
bin/tables/audible_byte#4-4_0_10000x1362345_0.rt
Normal file
Binary file not shown.
BIN
bin/tables/audible_byte#4-4_1_10000x1362345_0.rt
Normal file
BIN
bin/tables/audible_byte#4-4_1_10000x1362345_0.rt
Normal file
Binary file not shown.
BIN
bin/tables/audible_byte#4-4_2_10000x1362345_0.rt
Normal file
BIN
bin/tables/audible_byte#4-4_2_10000x1362345_0.rt
Normal file
Binary file not shown.
BIN
bin/tables/audible_byte#4-4_3_10000x1362345_0.rt
Normal file
BIN
bin/tables/audible_byte#4-4_3_10000x1362345_0.rt
Normal file
Binary file not shown.
BIN
bin/win_ffmpeg
Normal file
BIN
bin/win_ffmpeg
Normal file
Binary file not shown.
BIN
bin/win_rcrack.exe
Normal file
BIN
bin/win_rcrack.exe
Normal file
Binary file not shown.
222
pom.xml
Normal file
222
pom.xml
Normal file
|
@ -0,0 +1,222 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>org.openaudible</groupId>
|
||||||
|
<artifactId>openaudible</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<name>openaudible</name>
|
||||||
|
<version>0.9</version> <!-- appVersion from org.openaudible.desktop.swt.manager.Version.java -->
|
||||||
|
<description>open source audible library and cross-platform manager application</description>
|
||||||
|
<url>https://github.com/openaudible/openaudible</url>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<maven-compiler-plugin-version>2.3.2</maven-compiler-plugin-version>
|
||||||
|
<openaudible.version>1.0.0</openaudible.version>
|
||||||
|
|
||||||
|
<main.class>org.openaudible.desktop.Application</main.class>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<timestamp>${maven.build.timestamp}</timestamp>
|
||||||
|
<maven.build.timestamp.format>yyyy-MM-dd</maven.build.timestamp.format>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
|
||||||
|
<profiles>
|
||||||
|
<!-- SWT (Eclipse Widgets) have a jar for each platform. Only link with one jar.
|
||||||
|
org.eclipse.swt.gtk.linux.x86-4.6.jar
|
||||||
|
org.eclipse.swt.gtk.linux.x86_64-4.6.jar
|
||||||
|
org.eclipse.swt.win32.win32.x86-4.6.jar
|
||||||
|
org.eclipse.swt.win32.win32.x86_64-4.6.jar
|
||||||
|
org.eclipse.swt.cocoa.macosx.x86_64-4.6.jar
|
||||||
|
-->
|
||||||
|
<profile>
|
||||||
|
<id>Mac</id>
|
||||||
|
<activation>
|
||||||
|
<os><family>mac</family></os>
|
||||||
|
</activation>
|
||||||
|
<properties>
|
||||||
|
<swt_library>org.eclipse.swt.cocoa.macosx.x86_64-4.6.jar</swt_library>
|
||||||
|
</properties>
|
||||||
|
</profile>
|
||||||
|
<profile>
|
||||||
|
<id>Win64</id>
|
||||||
|
<activation>
|
||||||
|
<os><family>windows</family></os>
|
||||||
|
</activation>
|
||||||
|
<properties>
|
||||||
|
<swt_library>org.eclipse.swt.win32.win32.x86_64-4.6.jar</swt_library>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
</profile>
|
||||||
|
<profile>
|
||||||
|
<id>Linux</id>
|
||||||
|
<activation>
|
||||||
|
<os>
|
||||||
|
<family>unix</family>
|
||||||
|
<name>Linux</name>
|
||||||
|
</os>
|
||||||
|
|
||||||
|
</activation>
|
||||||
|
<properties>
|
||||||
|
<swt_library>org.eclipse.swt.gtk.linux.x86_64-4.6.jar</swt_library>
|
||||||
|
</properties>
|
||||||
|
</profile>
|
||||||
|
|
||||||
|
</profiles>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<directory>target</directory>
|
||||||
|
<outputDirectory>target/classes</outputDirectory>
|
||||||
|
<finalName>${project.artifactId}</finalName>
|
||||||
|
<sourceDirectory>${basedir}/src/main/java</sourceDirectory>
|
||||||
|
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.7.0</version>
|
||||||
|
|
||||||
|
<configuration>
|
||||||
|
<source>1.8</source>
|
||||||
|
<target>1.8</target>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-jar-plugin</artifactId>
|
||||||
|
<version>3.0.2</version>
|
||||||
|
<configuration>
|
||||||
|
<archive>
|
||||||
|
<addMavenDescriptor>false</addMavenDescriptor>
|
||||||
|
<manifestEntries>
|
||||||
|
<Main-Class>${main.class}</Main-Class>
|
||||||
|
<Application-Name>OpenAudible</Application-Name>
|
||||||
|
</manifestEntries>
|
||||||
|
</archive>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-assembly-plugin</artifactId>
|
||||||
|
<version>2.6</version>
|
||||||
|
<configuration>
|
||||||
|
<appendAssemblyId>true</appendAssemblyId>
|
||||||
|
<descriptors>
|
||||||
|
<descriptor>src/main/resources/assembly/jar.xml</descriptor>
|
||||||
|
</descriptors>
|
||||||
|
<descriptorRefs>
|
||||||
|
<descriptorRef>jar-with-dependencies</descriptorRef>
|
||||||
|
</descriptorRefs>
|
||||||
|
|
||||||
|
<archive>
|
||||||
|
<addMavenDescriptor>false</addMavenDescriptor>
|
||||||
|
<manifestEntries>
|
||||||
|
<Main-Class>${main.class}</Main-Class>
|
||||||
|
<Application-Name>OpenAudible</Application-Name>
|
||||||
|
<Build-Date>${maven.build.timestamp}</Build-Date>
|
||||||
|
<Built-By>OpenAudible Group</Built-By>
|
||||||
|
</manifestEntries>
|
||||||
|
</archive>
|
||||||
|
</configuration>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>make-assembly</id> <!-- this is used for inheritance merges -->
|
||||||
|
<phase>package</phase> <!-- bind to the packaging phase -->
|
||||||
|
<goals>
|
||||||
|
<goal>single</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
|
||||||
|
<repositories>
|
||||||
|
<!-- There's no official public maven eclipse repository yet. Leon Blakey -->
|
||||||
|
<!-- has created https://github.com/maven-eclipse/maven-eclipse.github.io. -->
|
||||||
|
<repository>
|
||||||
|
<id>maven-eclipse-repo</id>
|
||||||
|
<url>http://maven-eclipse.github.io/maven</url>
|
||||||
|
</repository>
|
||||||
|
</repositories>
|
||||||
|
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<!-- Core dependencies -->
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>net.sourceforge.htmlunit</groupId>
|
||||||
|
<artifactId>htmlunit</artifactId>
|
||||||
|
<version>2.23</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.code.gson</groupId>
|
||||||
|
<artifactId>gson</artifactId>
|
||||||
|
<version>2.2.4</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- desktop dependencies -->
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<!-- SWT library is platform dependent. So we break a few eggs getting it to work with the right profile. -->
|
||||||
|
|
||||||
|
<groupId>org.eclipse.swt</groupId>
|
||||||
|
<artifactId>swt</artifactId>
|
||||||
|
<version>1.0</version>
|
||||||
|
<scope>system</scope>
|
||||||
|
<systemPath>${basedir}/swt/${swt_library}</systemPath>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>commons-logging</groupId>
|
||||||
|
<artifactId>commons-logging</artifactId>
|
||||||
|
<version>1.1.1</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- Internal -->
|
||||||
|
<!-- https://mvnrepository.com/artifact/org.eclipse/jface -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse</groupId>
|
||||||
|
<artifactId>jface</artifactId>
|
||||||
|
<version>3.3.0-I20070606-0010</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</artifactId>
|
||||||
|
<version>4.12</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>xmlunit</groupId>
|
||||||
|
<artifactId>xmlunit</artifactId>
|
||||||
|
<version>1.6</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<!-- Logging -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.logging.log4j</groupId>
|
||||||
|
<artifactId>log4j-api</artifactId>
|
||||||
|
<version>2.3</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.logging.log4j</groupId>
|
||||||
|
<artifactId>log4j-core</artifactId>
|
||||||
|
<version>2.3</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.opencsv</groupId>
|
||||||
|
<artifactId>opencsv</artifactId>
|
||||||
|
<version>3.7</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
60
src/main/java/org/jaudiotagger/FileConstants.java
Normal file
60
src/main/java/org/jaudiotagger/FileConstants.java
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
/**
|
||||||
|
* @author : Paul Taylor
|
||||||
|
* <p/>
|
||||||
|
* Version @version:$Id: FileConstants.java 520 2008-01-01 15:16:38Z paultaylor $
|
||||||
|
* <p/>
|
||||||
|
* Jaudiotagger Copyright (C)2004,2005
|
||||||
|
* <p/>
|
||||||
|
* This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||||
|
* General Public License as published by the Free Software Foundation; either version 2.1 of the License,
|
||||||
|
* or (at your option) any later version.
|
||||||
|
* <p/>
|
||||||
|
* This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||||
|
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the GNU Lesser General Public License for more details.
|
||||||
|
* <p/>
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License along with this library; if not,
|
||||||
|
* you can get a copy from http://www.opensource.org/licenses/lgpl-license.php or write to the Free Software
|
||||||
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
* <p/>
|
||||||
|
* Description:
|
||||||
|
*/
|
||||||
|
package org.jaudiotagger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Definitions of the bit used when reading file format from file
|
||||||
|
*/
|
||||||
|
public interface FileConstants {
|
||||||
|
/**
|
||||||
|
* defined for convenience
|
||||||
|
*/
|
||||||
|
int BIT7 = 0x80;
|
||||||
|
/**
|
||||||
|
* defined for convenience
|
||||||
|
*/
|
||||||
|
int BIT6 = 0x40;
|
||||||
|
/**
|
||||||
|
* defined for convenience
|
||||||
|
*/
|
||||||
|
int BIT5 = 0x20;
|
||||||
|
/**
|
||||||
|
* defined for convenience
|
||||||
|
*/
|
||||||
|
int BIT4 = 0x10;
|
||||||
|
/**
|
||||||
|
* defined for convenience
|
||||||
|
*/
|
||||||
|
int BIT3 = 0x08;
|
||||||
|
/**
|
||||||
|
* defined for convenience
|
||||||
|
*/
|
||||||
|
int BIT2 = 0x04;
|
||||||
|
/**
|
||||||
|
* defined for convenience
|
||||||
|
*/
|
||||||
|
int BIT1 = 0x02;
|
||||||
|
/**
|
||||||
|
* defined for convenience
|
||||||
|
*/
|
||||||
|
int BIT0 = 0x01;
|
||||||
|
}
|
303
src/main/java/org/jaudiotagger/audio/AudioFile.java
Normal file
303
src/main/java/org/jaudiotagger/audio/AudioFile.java
Normal file
|
@ -0,0 +1,303 @@
|
||||||
|
/*
|
||||||
|
* Entagged Audio Tag library
|
||||||
|
* Copyright (c) 2003-2005 Raphaël Slinckx <raphael@slinckx.net>
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this library; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
package org.jaudiotagger.audio;
|
||||||
|
|
||||||
|
import org.jaudiotagger.audio.aiff.AiffTag;
|
||||||
|
import org.jaudiotagger.audio.exceptions.CannotWriteException;
|
||||||
|
import org.jaudiotagger.audio.exceptions.ReadOnlyFileException;
|
||||||
|
import org.jaudiotagger.audio.flac.metadatablock.MetadataBlockDataPicture;
|
||||||
|
import org.jaudiotagger.audio.real.RealTag;
|
||||||
|
import org.jaudiotagger.audio.wav.WavTag;
|
||||||
|
import org.jaudiotagger.logging.ErrorMessage;
|
||||||
|
import org.jaudiotagger.tag.Tag;
|
||||||
|
import org.jaudiotagger.tag.asf.AsfTag;
|
||||||
|
import org.jaudiotagger.tag.flac.FlacTag;
|
||||||
|
import org.jaudiotagger.tag.mp4.Mp4Tag;
|
||||||
|
import org.jaudiotagger.tag.vorbiscomment.VorbisCommentTag;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.RandomAccessFile;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>This is the main object manipulated by the user representing an audiofile, its properties and its tag.</p>
|
||||||
|
* <p>The prefered way to obtain an <code>AudioFile</code> is to use the <code>AudioFileIO.read(File)</code> method.</p>
|
||||||
|
* <p>The <code>AudioFile</code> contains every properties associated with the file itself (no meta-data), like the bitrate, the sampling rate, the encoding audioHeaders, etc.</p>
|
||||||
|
* <p>To get the meta-data contained in this file you have to get the <code>Tag</code> of this <code>AudioFile</code></p>
|
||||||
|
*
|
||||||
|
* @author Raphael Slinckx
|
||||||
|
* @version $Id: AudioFile.java 1067 2012-10-17 18:01:32Z garymcgath $
|
||||||
|
* @see AudioFileIO
|
||||||
|
* @see Tag
|
||||||
|
* @since v0.01
|
||||||
|
*/
|
||||||
|
public class AudioFile {
|
||||||
|
//Logger
|
||||||
|
public static Logger logger = Logger.getLogger("org.jaudiotagger.audio");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The physical file that this instance represents.
|
||||||
|
*/
|
||||||
|
protected File file;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Audio header info
|
||||||
|
*/
|
||||||
|
protected AudioHeader audioHeader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The tag
|
||||||
|
*/
|
||||||
|
protected Tag tag;
|
||||||
|
|
||||||
|
public AudioFile() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>These constructors are used by the different readers, users should not use them, but use the <code>AudioFileIO.read(File)</code> method instead !.</p>
|
||||||
|
* <p>Create the AudioFile representing file f, the encoding audio headers and containing the tag</p>
|
||||||
|
*
|
||||||
|
* @param f The file of the audio file
|
||||||
|
* @param audioHeader the encoding audioHeaders over this file
|
||||||
|
* @param tag the tag contained in this file or null if no tag exists
|
||||||
|
*/
|
||||||
|
public AudioFile(File f, AudioHeader audioHeader, Tag tag) {
|
||||||
|
this.file = f;
|
||||||
|
this.audioHeader = audioHeader;
|
||||||
|
this.tag = tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>These constructors are used by the different readers, users should not use them, but use the <code>AudioFileIO.read(File)</code> method instead !.</p>
|
||||||
|
* <p>Create the AudioFile representing file denoted by pathnames, the encoding audio Headers and containing the tag</p>
|
||||||
|
*
|
||||||
|
* @param s The pathname of the audio file
|
||||||
|
* @param audioHeader the encoding audioHeaders over this file
|
||||||
|
* @param tag the tag contained in this file
|
||||||
|
*/
|
||||||
|
public AudioFile(String s, AudioHeader audioHeader, Tag tag) {
|
||||||
|
this.file = new File(s);
|
||||||
|
this.audioHeader = audioHeader;
|
||||||
|
this.tag = tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param file
|
||||||
|
* @return filename with audioFormat separator stripped off.
|
||||||
|
*/
|
||||||
|
public static String getBaseFilename(File file) {
|
||||||
|
int index = file.getName().toLowerCase().lastIndexOf(".");
|
||||||
|
if (index > 0) {
|
||||||
|
return file.getName().substring(0, index);
|
||||||
|
}
|
||||||
|
return file.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Write the tag contained in this AudioFile in the actual file on the disk, this is the same as calling the <code>AudioFileIO.write(this)</code> method.</p>
|
||||||
|
*
|
||||||
|
* @throws CannotWriteException If the file could not be written/accessed, the extension wasn't recognized, or other IO error occured.
|
||||||
|
* @see AudioFileIO
|
||||||
|
*/
|
||||||
|
public void commit() throws CannotWriteException {
|
||||||
|
AudioFileIO.write(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the physical file
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public File getFile() {
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the file to store the info in
|
||||||
|
*
|
||||||
|
* @param file
|
||||||
|
*/
|
||||||
|
public void setFile(File file) {
|
||||||
|
this.file = file;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return audio header information
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public AudioHeader getAudioHeader() {
|
||||||
|
return audioHeader;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Returns the tag contained in this AudioFile, the <code>Tag</code> contains any useful meta-data, like
|
||||||
|
* artist, album, title, etc. If the file does not contain any tag the null is returned. Some audio formats do
|
||||||
|
* not allow there to be no tag so in this case the reader would return an empty tag whereas for others such
|
||||||
|
* as mp3 it is purely optional.
|
||||||
|
*
|
||||||
|
* @return Returns the tag contained in this AudioFile, or null if no tag exists.
|
||||||
|
*/
|
||||||
|
public Tag getTag() {
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assign a tag to this audio file
|
||||||
|
*
|
||||||
|
* @param tag Tag to be assigned
|
||||||
|
*/
|
||||||
|
public void setTag(Tag tag) {
|
||||||
|
this.tag = tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Returns a multi-line string with the file path, the encoding audioHeader, and the tag contents.</p>
|
||||||
|
*
|
||||||
|
* @return A multi-line string with the file path, the encoding audioHeader, and the tag contents.
|
||||||
|
* TODO Maybe this can be changed ?
|
||||||
|
*/
|
||||||
|
public String toString() {
|
||||||
|
return "AudioFile " + getFile().getAbsolutePath()
|
||||||
|
+ " --------\n" + audioHeader.toString() + "\n" + ((tag == null) ? "" : tag.toString()) + "\n-------------------";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check does file exist
|
||||||
|
*
|
||||||
|
* @param file
|
||||||
|
* @throws FileNotFoundException if file not found
|
||||||
|
*/
|
||||||
|
public void checkFileExists(File file) throws FileNotFoundException {
|
||||||
|
logger.config("Reading file:" + "path" + file.getPath() + ":abs:" + file.getAbsolutePath());
|
||||||
|
if (!file.exists()) {
|
||||||
|
logger.severe("Unable to find:" + file.getPath());
|
||||||
|
throw new FileNotFoundException(ErrorMessage.UNABLE_TO_FIND_FILE.getMsg(file.getPath()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks the file is accessible with the correct permissions, otherwise exception occurs
|
||||||
|
*
|
||||||
|
* @param file
|
||||||
|
* @param readOnly
|
||||||
|
* @return
|
||||||
|
* @throws ReadOnlyFileException
|
||||||
|
* @throws FileNotFoundException
|
||||||
|
*/
|
||||||
|
protected RandomAccessFile checkFilePermissions(File file, boolean readOnly) throws ReadOnlyFileException, FileNotFoundException {
|
||||||
|
RandomAccessFile newFile;
|
||||||
|
|
||||||
|
checkFileExists(file);
|
||||||
|
|
||||||
|
// Unless opened as readonly the file must be writable
|
||||||
|
if (readOnly) {
|
||||||
|
newFile = new RandomAccessFile(file, "r");
|
||||||
|
} else {
|
||||||
|
if (!file.canWrite()) {
|
||||||
|
logger.severe("Unable to write:" + file.getPath());
|
||||||
|
throw new ReadOnlyFileException(ErrorMessage.NO_PERMISSIONS_TO_WRITE_TO_FILE.getMsg(file.getPath()));
|
||||||
|
}
|
||||||
|
newFile = new RandomAccessFile(file, "rws");
|
||||||
|
}
|
||||||
|
return newFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional debugging method. Must override to do anything interesting.
|
||||||
|
*
|
||||||
|
* @return Empty string.
|
||||||
|
*/
|
||||||
|
public String displayStructureAsXML() {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional debugging method. Must override to do anything interesting.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String displayStructureAsPlainText() {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create Default Tag
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Tag createDefaultTag() {
|
||||||
|
if (SupportedFileFormat.FLAC.getFilesuffix().equals(file.getName().substring(file.getName().lastIndexOf('.')))) {
|
||||||
|
return new FlacTag(VorbisCommentTag.createNewTag(), new ArrayList<MetadataBlockDataPicture>());
|
||||||
|
} else if (SupportedFileFormat.OGG.getFilesuffix().equals(file.getName().substring(file.getName().lastIndexOf('.')))) {
|
||||||
|
return VorbisCommentTag.createNewTag();
|
||||||
|
} else if (SupportedFileFormat.MP4.getFilesuffix().equals(file.getName().substring(file.getName().lastIndexOf('.')))) {
|
||||||
|
return new Mp4Tag();
|
||||||
|
} else if (SupportedFileFormat.M4A.getFilesuffix().equals(file.getName().substring(file.getName().lastIndexOf('.')))) {
|
||||||
|
return new Mp4Tag();
|
||||||
|
} else if (SupportedFileFormat.M4P.getFilesuffix().equals(file.getName().substring(file.getName().lastIndexOf('.')))) {
|
||||||
|
return new Mp4Tag();
|
||||||
|
} else if (SupportedFileFormat.WMA.getFilesuffix().equals(file.getName().substring(file.getName().lastIndexOf('.')))) {
|
||||||
|
return new AsfTag();
|
||||||
|
} else if (SupportedFileFormat.WAV.getFilesuffix().equals(file.getName().substring(file.getName().lastIndexOf('.')))) {
|
||||||
|
return new WavTag();
|
||||||
|
} else if (SupportedFileFormat.RA.getFilesuffix().equals(file.getName().substring(file.getName().lastIndexOf('.')))) {
|
||||||
|
return new RealTag();
|
||||||
|
} else if (SupportedFileFormat.RM.getFilesuffix().equals(file.getName().substring(file.getName().lastIndexOf('.')))) {
|
||||||
|
return new RealTag();
|
||||||
|
} else if (SupportedFileFormat.AIF.getFilesuffix().equals(file.getName().substring(file.getName().lastIndexOf('.')))) {
|
||||||
|
return new AiffTag();
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException("Unable to create default tag for this file format");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the tag or if the file doesn't have one at all, create a default tag and return
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Tag getTagOrCreateDefault() {
|
||||||
|
Tag tag = getTag();
|
||||||
|
if (tag == null) {
|
||||||
|
return createDefaultTag();
|
||||||
|
}
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the tag or if the file doesn't have one at all, create a default tag and set it
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Tag getTagOrCreateAndSetDefault() {
|
||||||
|
Tag tag = getTagOrCreateDefault();
|
||||||
|
setTag(tag);
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Tag getTagAndConvertOrCreateAndSetDefault() {
|
||||||
|
return getTagOrCreateAndSetDefault();
|
||||||
|
}
|
||||||
|
}
|
77
src/main/java/org/jaudiotagger/audio/AudioFileFilter.java
Normal file
77
src/main/java/org/jaudiotagger/audio/AudioFileFilter.java
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
/*
|
||||||
|
* Entagged Audio Tag library
|
||||||
|
* Copyright (c) 2003-2005 Raphaël Slinckx <raphael@slinckx.net>
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this library; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
package org.jaudiotagger.audio;
|
||||||
|
|
||||||
|
import org.jaudiotagger.audio.generic.Utils;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileFilter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>This is a simple FileFilter that will only allow the file supported by this library.</p>
|
||||||
|
* <p>It will also accept directories. An additional condition is that file must be readable (read permission) and
|
||||||
|
* are not hidden (dot files, or hidden files)</p>
|
||||||
|
*
|
||||||
|
* @author Raphael Slinckx
|
||||||
|
* @version $Id: AudioFileFilter.java 836 2009-11-12 15:44:07Z paultaylor $
|
||||||
|
* @since v0.01
|
||||||
|
*/
|
||||||
|
public class AudioFileFilter implements FileFilter {
|
||||||
|
/**
|
||||||
|
* allows Directories
|
||||||
|
*/
|
||||||
|
private final boolean allowDirectories;
|
||||||
|
|
||||||
|
public AudioFileFilter(boolean allowDirectories) {
|
||||||
|
this.allowDirectories = allowDirectories;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AudioFileFilter() {
|
||||||
|
this(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Check whether the given file meet the required conditions (supported by the library OR directory).
|
||||||
|
* The File must also be readable and not hidden.</p>
|
||||||
|
*
|
||||||
|
* @param f The file to test
|
||||||
|
* @return a boolean indicating if the file is accepted or not
|
||||||
|
*/
|
||||||
|
public boolean accept(File f) {
|
||||||
|
if (f.isHidden() || !f.canRead()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (f.isDirectory()) {
|
||||||
|
return allowDirectories;
|
||||||
|
}
|
||||||
|
|
||||||
|
String ext = Utils.getExtension(f);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (SupportedFileFormat.valueOf(ext.toUpperCase()) != null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (IllegalArgumentException iae) {
|
||||||
|
//Not known enum value
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
317
src/main/java/org/jaudiotagger/audio/AudioFileIO.java
Normal file
317
src/main/java/org/jaudiotagger/audio/AudioFileIO.java
Normal file
|
@ -0,0 +1,317 @@
|
||||||
|
/*
|
||||||
|
* Entagged Audio Tag library
|
||||||
|
* Copyright (c) 2003-2005 Raphaël Slinckx <raphael@slinckx.net>
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this library; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
package org.jaudiotagger.audio;
|
||||||
|
|
||||||
|
import org.jaudiotagger.audio.aiff.AiffFileReader;
|
||||||
|
import org.jaudiotagger.audio.asf.AsfFileReader;
|
||||||
|
import org.jaudiotagger.audio.asf.AsfFileWriter;
|
||||||
|
import org.jaudiotagger.audio.exceptions.CannotReadException;
|
||||||
|
import org.jaudiotagger.audio.exceptions.CannotWriteException;
|
||||||
|
import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException;
|
||||||
|
import org.jaudiotagger.audio.exceptions.ReadOnlyFileException;
|
||||||
|
import org.jaudiotagger.audio.flac.FlacFileReader;
|
||||||
|
import org.jaudiotagger.audio.flac.FlacFileWriter;
|
||||||
|
import org.jaudiotagger.audio.generic.*;
|
||||||
|
import org.jaudiotagger.audio.mp3.MP3FileReader;
|
||||||
|
import org.jaudiotagger.audio.mp3.MP3FileWriter;
|
||||||
|
import org.jaudiotagger.audio.mp4.Mp4FileReader;
|
||||||
|
import org.jaudiotagger.audio.mp4.Mp4FileWriter;
|
||||||
|
import org.jaudiotagger.audio.ogg.OggFileReader;
|
||||||
|
import org.jaudiotagger.audio.ogg.OggFileWriter;
|
||||||
|
import org.jaudiotagger.audio.real.RealFileReader;
|
||||||
|
import org.jaudiotagger.audio.wav.WavFileReader;
|
||||||
|
import org.jaudiotagger.audio.wav.WavFileWriter;
|
||||||
|
import org.jaudiotagger.logging.ErrorMessage;
|
||||||
|
import org.jaudiotagger.tag.TagException;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p/>
|
||||||
|
* The main entry point for the Tag Reading/Writing operations, this class will
|
||||||
|
* select the appropriate reader/writer for the given file.
|
||||||
|
* </p>
|
||||||
|
* <p/>
|
||||||
|
* It selects the appropriate reader/writer based on the file extension (case
|
||||||
|
* ignored).
|
||||||
|
* </p>
|
||||||
|
* <p/>
|
||||||
|
* Here is an simple example of use:
|
||||||
|
* </p>
|
||||||
|
* <p/>
|
||||||
|
* <code>
|
||||||
|
* AudioFile audioFile = AudioFileIO.read(new File("audiofile.mp3")); //Reads the given file.<br/>
|
||||||
|
* int bitrate = audioFile.getBitrate(); //Retreives the bitrate of the file.<br/>
|
||||||
|
* String artist = audioFile.getTag().getFirst(TagFieldKey.ARTIST); //Retreive the artist name.<br/>
|
||||||
|
* audioFile.getTag().setGenre("Progressive Rock"); //Sets the genre to Prog. Rock, note the file on disk is still unmodified.<br/>
|
||||||
|
* AudioFileIO.write(audioFile); //Write the modifications in the file on disk.
|
||||||
|
* </code>
|
||||||
|
* </p>
|
||||||
|
* <p/>
|
||||||
|
* You can also use the <code>commit()</code> method defined for
|
||||||
|
* <code>AudioFile</code>s to achieve the same goal as
|
||||||
|
* <code>AudioFileIO.write(File)</code>, like this:
|
||||||
|
* </p>
|
||||||
|
* <p/>
|
||||||
|
* <code>
|
||||||
|
* AudioFile audioFile = AudioFileIO.read(new File("audiofile.mp3"));<br/>
|
||||||
|
* audioFile.getTag().setGenre("Progressive Rock");<br/>
|
||||||
|
* audioFile.commit(); //Write the modifications in the file on disk.<br/>
|
||||||
|
* </code>
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author Raphael Slinckx
|
||||||
|
* @version $Id: AudioFileIO.java 1067 2012-10-17 18:01:32Z garymcgath $
|
||||||
|
* @see AudioFile
|
||||||
|
* @see org.jaudiotagger.tag.Tag
|
||||||
|
* @since v0.01
|
||||||
|
*/
|
||||||
|
public class AudioFileIO {
|
||||||
|
|
||||||
|
//Logger
|
||||||
|
public static Logger logger = Logger.getLogger("org.jaudiotagger.audio");
|
||||||
|
|
||||||
|
// !! Do not forget to also add new supported extensions to AudioFileFilter
|
||||||
|
// !!
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This field contains the default instance for static use.
|
||||||
|
*/
|
||||||
|
private static AudioFileIO defaultInstance;
|
||||||
|
/**
|
||||||
|
* This member is used to broadcast modification events to registered
|
||||||
|
*/
|
||||||
|
private final ModificationHandler modificationHandler;
|
||||||
|
// These tables contains all the readers/writers associated with extension
|
||||||
|
// as a key
|
||||||
|
private Map<String, AudioFileReader> readers = new HashMap<String, AudioFileReader>();
|
||||||
|
private Map<String, AudioFileWriter> writers = new HashMap<String, AudioFileWriter>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance.
|
||||||
|
*/
|
||||||
|
public AudioFileIO() {
|
||||||
|
this.modificationHandler = new ModificationHandler();
|
||||||
|
prepareReadersAndWriters();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p/>
|
||||||
|
* Delete the tag, if any, contained in the given file.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param f The file where the tag will be deleted
|
||||||
|
* @throws org.jaudiotagger.audio.exceptions.CannotWriteException If the file could not be written/accessed, the extension
|
||||||
|
* wasn't recognized, or other IO error occurred.
|
||||||
|
* @throws org.jaudiotagger.audio.exceptions.CannotReadException
|
||||||
|
*/
|
||||||
|
public static void delete(AudioFile f) throws CannotReadException, CannotWriteException {
|
||||||
|
getDefaultAudioFileIO().deleteTag(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method returns the default instance for static use.<br>
|
||||||
|
*
|
||||||
|
* @return The default instance.
|
||||||
|
*/
|
||||||
|
public static AudioFileIO getDefaultAudioFileIO() {
|
||||||
|
if (defaultInstance == null) {
|
||||||
|
defaultInstance = new AudioFileIO();
|
||||||
|
}
|
||||||
|
return defaultInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p/>
|
||||||
|
* Read the tag contained in the given file.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param f The file to read.
|
||||||
|
* @return The AudioFile with the file tag and the file encoding info.
|
||||||
|
* @throws org.jaudiotagger.audio.exceptions.CannotReadException If the file could not be read, the extension wasn't
|
||||||
|
* recognized, or an IO error occurred during the read.
|
||||||
|
* @throws org.jaudiotagger.tag.TagException
|
||||||
|
* @throws org.jaudiotagger.audio.exceptions.ReadOnlyFileException
|
||||||
|
* @throws java.io.IOException
|
||||||
|
* @throws org.jaudiotagger.audio.exceptions.InvalidAudioFrameException
|
||||||
|
*/
|
||||||
|
public static AudioFile read(File f)
|
||||||
|
throws CannotReadException, IOException, TagException, ReadOnlyFileException, InvalidAudioFrameException {
|
||||||
|
return getDefaultAudioFileIO().readFile(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p/>
|
||||||
|
* Write the tag contained in the audioFile in the actual file on the disk.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param f The AudioFile to be written
|
||||||
|
* @throws CannotWriteException If the file could not be written/accessed, the extension
|
||||||
|
* wasn't recognized, or other IO error occurred.
|
||||||
|
*/
|
||||||
|
public static void write(AudioFile f) throws CannotWriteException {
|
||||||
|
getDefaultAudioFileIO().writeFile(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an listener for all file formats.
|
||||||
|
*
|
||||||
|
* @param listener listener
|
||||||
|
*/
|
||||||
|
public void addAudioFileModificationListener(
|
||||||
|
AudioFileModificationListener listener) {
|
||||||
|
this.modificationHandler.addAudioFileModificationListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p/>
|
||||||
|
* Delete the tag, if any, contained in the given file.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param f The file where the tag will be deleted
|
||||||
|
* @throws org.jaudiotagger.audio.exceptions.CannotWriteException If the file could not be written/accessed, the extension
|
||||||
|
* wasn't recognized, or other IO error occurred.
|
||||||
|
* @throws org.jaudiotagger.audio.exceptions.CannotReadException
|
||||||
|
*/
|
||||||
|
public void deleteTag(AudioFile f) throws CannotReadException, CannotWriteException {
|
||||||
|
String ext = Utils.getExtension(f.getFile());
|
||||||
|
|
||||||
|
Object afw = writers.get(ext);
|
||||||
|
if (afw == null) {
|
||||||
|
throw new CannotWriteException(ErrorMessage.NO_DELETER_FOR_THIS_FORMAT.getMsg(ext));
|
||||||
|
}
|
||||||
|
|
||||||
|
((AudioFileWriter) afw).delete(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the readers and writers.
|
||||||
|
*/
|
||||||
|
private void prepareReadersAndWriters() {
|
||||||
|
|
||||||
|
// Tag Readers
|
||||||
|
readers.put(SupportedFileFormat.OGG.getFilesuffix(), new OggFileReader());
|
||||||
|
readers.put(SupportedFileFormat.FLAC.getFilesuffix(), new FlacFileReader());
|
||||||
|
readers.put(SupportedFileFormat.MP3.getFilesuffix(), new MP3FileReader());
|
||||||
|
readers.put(SupportedFileFormat.MP4.getFilesuffix(), new Mp4FileReader());
|
||||||
|
readers.put(SupportedFileFormat.M4A.getFilesuffix(), new Mp4FileReader());
|
||||||
|
readers.put(SupportedFileFormat.M4P.getFilesuffix(), new Mp4FileReader());
|
||||||
|
readers.put(SupportedFileFormat.M4B.getFilesuffix(), new Mp4FileReader());
|
||||||
|
readers.put(SupportedFileFormat.WAV.getFilesuffix(), new WavFileReader());
|
||||||
|
readers.put(SupportedFileFormat.WMA.getFilesuffix(), new AsfFileReader());
|
||||||
|
readers.put(SupportedFileFormat.AIF.getFilesuffix(), new AiffFileReader());
|
||||||
|
final RealFileReader realReader = new RealFileReader();
|
||||||
|
readers.put(SupportedFileFormat.RA.getFilesuffix(), realReader);
|
||||||
|
readers.put(SupportedFileFormat.RM.getFilesuffix(), realReader);
|
||||||
|
|
||||||
|
// Tag Writers
|
||||||
|
writers.put(SupportedFileFormat.OGG.getFilesuffix(), new OggFileWriter());
|
||||||
|
writers.put(SupportedFileFormat.FLAC.getFilesuffix(), new FlacFileWriter());
|
||||||
|
writers.put(SupportedFileFormat.MP3.getFilesuffix(), new MP3FileWriter());
|
||||||
|
writers.put(SupportedFileFormat.MP4.getFilesuffix(), new Mp4FileWriter());
|
||||||
|
writers.put(SupportedFileFormat.M4A.getFilesuffix(), new Mp4FileWriter());
|
||||||
|
writers.put(SupportedFileFormat.M4P.getFilesuffix(), new Mp4FileWriter());
|
||||||
|
writers.put(SupportedFileFormat.M4B.getFilesuffix(), new Mp4FileWriter());
|
||||||
|
writers.put(SupportedFileFormat.WAV.getFilesuffix(), new WavFileWriter());
|
||||||
|
writers.put(SupportedFileFormat.WMA.getFilesuffix(), new AsfFileWriter());
|
||||||
|
|
||||||
|
// Register modificationHandler
|
||||||
|
Iterator<AudioFileWriter> it = writers.values().iterator();
|
||||||
|
for (AudioFileWriter curr : writers.values()) {
|
||||||
|
curr.setAudioFileModificationListener(this.modificationHandler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p/>
|
||||||
|
* Read the tag contained in the given file.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param f The file to read.
|
||||||
|
* @return The AudioFile with the file tag and the file encoding info.
|
||||||
|
* @throws org.jaudiotagger.audio.exceptions.CannotReadException If the file could not be read, the extension wasn't
|
||||||
|
* recognized, or an IO error occurred during the read.
|
||||||
|
* @throws org.jaudiotagger.tag.TagException
|
||||||
|
* @throws org.jaudiotagger.audio.exceptions.ReadOnlyFileException
|
||||||
|
* @throws java.io.IOException
|
||||||
|
* @throws org.jaudiotagger.audio.exceptions.InvalidAudioFrameException
|
||||||
|
*/
|
||||||
|
public AudioFile readFile(File f)
|
||||||
|
throws CannotReadException, IOException, TagException, ReadOnlyFileException, InvalidAudioFrameException {
|
||||||
|
checkFileExists(f);
|
||||||
|
String ext = Utils.getExtension(f);
|
||||||
|
|
||||||
|
AudioFileReader afr = readers.get(ext);
|
||||||
|
if (afr == null) {
|
||||||
|
throw new CannotReadException(ErrorMessage.NO_READER_FOR_THIS_FORMAT.getMsg(ext));
|
||||||
|
}
|
||||||
|
|
||||||
|
return afr.read(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check does file exist
|
||||||
|
*
|
||||||
|
* @param file
|
||||||
|
* @throws java.io.FileNotFoundException
|
||||||
|
*/
|
||||||
|
public void checkFileExists(File file) throws FileNotFoundException {
|
||||||
|
logger.config("Reading file:" + "path" + file.getPath() + ":abs:" + file.getAbsolutePath());
|
||||||
|
if (!file.exists()) {
|
||||||
|
logger.severe("Unable to find:" + file.getPath());
|
||||||
|
throw new FileNotFoundException(ErrorMessage.UNABLE_TO_FIND_FILE.getMsg(file.getPath()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a listener for all file formats.
|
||||||
|
*
|
||||||
|
* @param listener listener
|
||||||
|
*/
|
||||||
|
public void removeAudioFileModificationListener(
|
||||||
|
AudioFileModificationListener listener) {
|
||||||
|
this.modificationHandler.removeAudioFileModificationListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p/>
|
||||||
|
* Write the tag contained in the audioFile in the actual file on the disk.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param f The AudioFile to be written
|
||||||
|
* @throws CannotWriteException If the file could not be written/accessed, the extension
|
||||||
|
* wasn't recognized, or other IO error occurred.
|
||||||
|
*/
|
||||||
|
public void writeFile(AudioFile f) throws CannotWriteException {
|
||||||
|
String ext = Utils.getExtension(f.getFile());
|
||||||
|
|
||||||
|
AudioFileWriter afw = writers.get(ext);
|
||||||
|
if (afw == null) {
|
||||||
|
throw new CannotWriteException(ErrorMessage.NO_WRITER_FOR_THIS_FORMAT.getMsg(ext));
|
||||||
|
}
|
||||||
|
|
||||||
|
afw.write(f);
|
||||||
|
}
|
||||||
|
}
|
64
src/main/java/org/jaudiotagger/audio/AudioHeader.java
Normal file
64
src/main/java/org/jaudiotagger/audio/AudioHeader.java
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
package org.jaudiotagger.audio;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Representation of AudioHeader
|
||||||
|
* <p/>
|
||||||
|
* <p>Contains info about the Audio Header
|
||||||
|
*/
|
||||||
|
public interface AudioHeader {
|
||||||
|
/**
|
||||||
|
* @return the audio file type
|
||||||
|
*/
|
||||||
|
String getEncodingType();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the BitRate of the Audio
|
||||||
|
*/
|
||||||
|
String getBitRate();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return birate as a number
|
||||||
|
*/
|
||||||
|
long getBitRateAsNumber();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the Sampling rate
|
||||||
|
*/
|
||||||
|
String getSampleRate();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
int getSampleRateAsNumber();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the format
|
||||||
|
*/
|
||||||
|
String getFormat();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the Channel Mode such as Stereo or Mono
|
||||||
|
*/
|
||||||
|
String getChannels();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return if the bitRate is variable
|
||||||
|
*/
|
||||||
|
boolean isVariableBitRate();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return track length
|
||||||
|
*/
|
||||||
|
int getTrackLength();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the number of bits per sample
|
||||||
|
*/
|
||||||
|
int getBitsPerSample();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
boolean isLossless();
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
package org.jaudiotagger.audio;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Files formats currently supported by Library.
|
||||||
|
* Each enum value is associated with a file suffix (extension).
|
||||||
|
*/
|
||||||
|
public enum SupportedFileFormat {
|
||||||
|
OGG("ogg"),
|
||||||
|
MP3("mp3"),
|
||||||
|
FLAC("flac"),
|
||||||
|
MP4("mp4"),
|
||||||
|
M4A("m4a"),
|
||||||
|
M4P("m4p"),
|
||||||
|
WMA("wma"),
|
||||||
|
WAV("wav"),
|
||||||
|
RA("ra"),
|
||||||
|
RM("rm"),
|
||||||
|
M4B("m4b"),
|
||||||
|
AIF("aif");
|
||||||
|
|
||||||
|
private String filesuffix;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for internal use by this enum.
|
||||||
|
*/
|
||||||
|
SupportedFileFormat(String filesuffix) {
|
||||||
|
this.filesuffix = filesuffix;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the file suffix (lower case without initial .) associated with the format.
|
||||||
|
*/
|
||||||
|
public String getFilesuffix() {
|
||||||
|
return filesuffix;
|
||||||
|
}
|
||||||
|
}
|
179
src/main/java/org/jaudiotagger/audio/aiff/AiffAudioHeader.java
Normal file
179
src/main/java/org/jaudiotagger/audio/aiff/AiffAudioHeader.java
Normal file
|
@ -0,0 +1,179 @@
|
||||||
|
package org.jaudiotagger.audio.aiff;
|
||||||
|
|
||||||
|
import org.jaudiotagger.audio.generic.GenericAudioHeader;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Non-"tag" metadata from the AIFF file. In general, read-only.
|
||||||
|
*/
|
||||||
|
public class AiffAudioHeader extends GenericAudioHeader {
|
||||||
|
|
||||||
|
private FileType fileType;
|
||||||
|
private Date timestamp;
|
||||||
|
private Endian endian;
|
||||||
|
private String audioEncoding;
|
||||||
|
private String name;
|
||||||
|
private String author;
|
||||||
|
private String copyright;
|
||||||
|
private List<String> applicationIdentifiers;
|
||||||
|
private List<String> comments;
|
||||||
|
|
||||||
|
public AiffAudioHeader() {
|
||||||
|
applicationIdentifiers = new ArrayList<String>();
|
||||||
|
comments = new ArrayList<String>();
|
||||||
|
endian = Endian.BIG_ENDIAN;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the timestamp of the file.
|
||||||
|
*/
|
||||||
|
public Date getTimestamp() {
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the timestamp.
|
||||||
|
*/
|
||||||
|
public void setTimestamp(Date d) {
|
||||||
|
timestamp = d;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the file type (AIFF or AIFC)
|
||||||
|
*/
|
||||||
|
public FileType getFileType() {
|
||||||
|
return fileType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the file type (AIFF or AIFC)
|
||||||
|
*/
|
||||||
|
public void setFileType(FileType typ) {
|
||||||
|
fileType = typ;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the author
|
||||||
|
*/
|
||||||
|
public String getAuthor() {
|
||||||
|
return author;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the author
|
||||||
|
*/
|
||||||
|
public void setAuthor(String a) {
|
||||||
|
author = a;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the name. May be null.
|
||||||
|
*/
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the name
|
||||||
|
*/
|
||||||
|
public void setName(String n) {
|
||||||
|
name = n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the copyright. May be null.
|
||||||
|
*/
|
||||||
|
public String getCopyright() {
|
||||||
|
return copyright;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the copyright
|
||||||
|
*/
|
||||||
|
public void setCopyright(String c) {
|
||||||
|
copyright = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return endian status (big or little)
|
||||||
|
*/
|
||||||
|
public Endian getEndian() {
|
||||||
|
return endian;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set endian status (big or little)
|
||||||
|
*/
|
||||||
|
public void setEndian(Endian e) {
|
||||||
|
endian = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return list of all application identifiers
|
||||||
|
*/
|
||||||
|
public List<String> getApplicationIdentifiers() {
|
||||||
|
return applicationIdentifiers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an application identifier. There can be any number of these.
|
||||||
|
*/
|
||||||
|
public void addApplicationIdentifier(String id) {
|
||||||
|
applicationIdentifiers.add(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return list of all annotations
|
||||||
|
*/
|
||||||
|
public List<String> getAnnotations() {
|
||||||
|
return applicationIdentifiers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an annotation. There can be any number of these.
|
||||||
|
*/
|
||||||
|
public void addAnnotation(String a) {
|
||||||
|
applicationIdentifiers.add(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return list of all comments
|
||||||
|
*/
|
||||||
|
public List<String> getComments() {
|
||||||
|
return comments;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a comment. There can be any number of these.
|
||||||
|
*/
|
||||||
|
public void addComment(String c) {
|
||||||
|
comments.add(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the audio encoding as a descriptive string
|
||||||
|
*/
|
||||||
|
public String getAudioEncoding() {
|
||||||
|
return audioEncoding;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the audio encoding as a descriptive string
|
||||||
|
*/
|
||||||
|
public void setAudioEncoding(String s) {
|
||||||
|
audioEncoding = s;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum FileType {
|
||||||
|
AIFFTYPE,
|
||||||
|
AIFCTYPE
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Endian {
|
||||||
|
BIG_ENDIAN,
|
||||||
|
LITTLE_ENDIAN
|
||||||
|
}
|
||||||
|
}
|
83
src/main/java/org/jaudiotagger/audio/aiff/AiffFile.java
Normal file
83
src/main/java/org/jaudiotagger/audio/aiff/AiffFile.java
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
package org.jaudiotagger.audio.aiff;
|
||||||
|
|
||||||
|
import org.jaudiotagger.audio.AudioFile;
|
||||||
|
import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException;
|
||||||
|
import org.jaudiotagger.audio.exceptions.ReadOnlyFileException;
|
||||||
|
import org.jaudiotagger.tag.TagException;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.RandomAccessFile;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
public class AiffFile extends AudioFile {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A static DateFormat object for generating ISO date strings
|
||||||
|
*/
|
||||||
|
public final static SimpleDateFormat ISO_DATE_FORMATTER = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new empty AiffFile that is not associated with a
|
||||||
|
* specific file.
|
||||||
|
*/
|
||||||
|
public AiffFile() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new MP3File datatype and parse the tag from the given filename.
|
||||||
|
*
|
||||||
|
* @param filename AIFF file
|
||||||
|
* @throws IOException on any I/O error
|
||||||
|
* @throws TagException on any exception generated by this library.
|
||||||
|
* @throws org.jaudiotagger.audio.exceptions.ReadOnlyFileException
|
||||||
|
* @throws org.jaudiotagger.audio.exceptions.InvalidAudioFrameException
|
||||||
|
*/
|
||||||
|
public AiffFile(String filename) throws
|
||||||
|
IOException, TagException, ReadOnlyFileException, InvalidAudioFrameException {
|
||||||
|
this(new File(filename));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new MP3File datatype and parse the tag from the given file
|
||||||
|
* Object.
|
||||||
|
*
|
||||||
|
* @param file MP3 file
|
||||||
|
* @throws IOException on any I/O error
|
||||||
|
* @throws TagException on any exception generated by this library.
|
||||||
|
* @throws org.jaudiotagger.audio.exceptions.ReadOnlyFileException
|
||||||
|
* @throws org.jaudiotagger.audio.exceptions.InvalidAudioFrameException
|
||||||
|
*/
|
||||||
|
public AiffFile(File file)
|
||||||
|
throws IOException, TagException, ReadOnlyFileException, InvalidAudioFrameException {
|
||||||
|
this(file, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AiffFile(File file, boolean readOnly)
|
||||||
|
throws IOException, TagException, ReadOnlyFileException, InvalidAudioFrameException {
|
||||||
|
RandomAccessFile newFile = null;
|
||||||
|
try {
|
||||||
|
logger.setLevel(Level.FINEST);
|
||||||
|
logger.fine("Called AiffFile constructor on " + file.getAbsolutePath());
|
||||||
|
this.file = file;
|
||||||
|
|
||||||
|
//Check File accessibility
|
||||||
|
newFile = checkFilePermissions(file, readOnly);
|
||||||
|
audioHeader = new AiffAudioHeader();
|
||||||
|
//readTag();
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
if (newFile != null) {
|
||||||
|
newFile.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public AiffAudioHeader getAiffAudioHeader() {
|
||||||
|
return (AiffAudioHeader) audioHeader;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
150
src/main/java/org/jaudiotagger/audio/aiff/AiffFileReader.java
Normal file
150
src/main/java/org/jaudiotagger/audio/aiff/AiffFileReader.java
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
package org.jaudiotagger.audio.aiff;
|
||||||
|
|
||||||
|
import org.jaudiotagger.audio.exceptions.CannotReadException;
|
||||||
|
import org.jaudiotagger.audio.generic.AudioFileReader;
|
||||||
|
import org.jaudiotagger.audio.generic.GenericAudioHeader;
|
||||||
|
import org.jaudiotagger.tag.Tag;
|
||||||
|
import org.jaudiotagger.tag.aiff.AiffTag;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.RandomAccessFile;
|
||||||
|
|
||||||
|
public class AiffFileReader extends AudioFileReader {
|
||||||
|
|
||||||
|
/* Fixed value for first 4 bytes */
|
||||||
|
private static final int[] sigByte =
|
||||||
|
{0X46, 0X4F, 0X52, 0X4D};
|
||||||
|
|
||||||
|
/* AIFF-specific information which isn't "tag" information */
|
||||||
|
private AiffAudioHeader aiffHeader;
|
||||||
|
|
||||||
|
/* "Tag" information */
|
||||||
|
private AiffTag aiffTag;
|
||||||
|
|
||||||
|
/* InputStream that reads the file sequentially */
|
||||||
|
// private DataInputStream inStream;
|
||||||
|
|
||||||
|
public AiffFileReader() {
|
||||||
|
aiffHeader = new AiffAudioHeader();
|
||||||
|
aiffTag = new AiffTag();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public AiffFileReader(RandomAccessFile raf) {
|
||||||
|
aiffHeader = new AiffAudioHeader();
|
||||||
|
aiffTag = new AiffTag();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the file and fills in the audio header and tag information.
|
||||||
|
* Holds the tag information for later and returns the audio header.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected GenericAudioHeader getEncodingInfo(RandomAccessFile raf)
|
||||||
|
throws CannotReadException, IOException {
|
||||||
|
logger.finest("Reading AIFF file ");
|
||||||
|
byte sigBuf[] = new byte[4];
|
||||||
|
raf.read(sigBuf);
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
if (sigBuf[i] != sigByte[i]) {
|
||||||
|
logger.finest("AIFF file has incorrect signature");
|
||||||
|
throw new CannotReadException("Not an AIFF file: incorrect signature");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
long bytesRemaining = AiffUtil.readUINT32(raf);
|
||||||
|
|
||||||
|
// Read the file type.
|
||||||
|
if (!readFileType(raf)) {
|
||||||
|
throw new CannotReadException("Invalid AIFF file: Incorrect file type info");
|
||||||
|
}
|
||||||
|
bytesRemaining -= 4;
|
||||||
|
while (bytesRemaining > 0) {
|
||||||
|
if (!readChunk(raf, bytesRemaining)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return aiffHeader;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Tag getTag(RandomAccessFile raf) throws CannotReadException,
|
||||||
|
IOException {
|
||||||
|
logger.info("getTag called");
|
||||||
|
|
||||||
|
// TODO fill out stub code
|
||||||
|
return aiffTag;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reads the file type.
|
||||||
|
* Broken out from parse().
|
||||||
|
* If it is not a valid file type, returns false.
|
||||||
|
*/
|
||||||
|
private boolean readFileType(RandomAccessFile raf) throws IOException {
|
||||||
|
String typ = AiffUtil.read4Chars(raf);
|
||||||
|
if ("AIFF".equals(typ)) {
|
||||||
|
aiffHeader.setFileType(AiffAudioHeader.FileType.AIFFTYPE);
|
||||||
|
return true;
|
||||||
|
} else if ("AIFC".equals(typ)) {
|
||||||
|
aiffHeader.setFileType(AiffAudioHeader.FileType.AIFCTYPE);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads an AIFF Chunk.
|
||||||
|
*/
|
||||||
|
protected boolean readChunk
|
||||||
|
(RandomAccessFile raf, long bytesRemaining)
|
||||||
|
throws IOException {
|
||||||
|
Chunk chunk = null;
|
||||||
|
ChunkHeader chunkh = new ChunkHeader();
|
||||||
|
if (!chunkh.readHeader(raf)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int chunkSize = (int) chunkh.getSize();
|
||||||
|
bytesRemaining -= chunkSize + 8;
|
||||||
|
|
||||||
|
String id = chunkh.getID();
|
||||||
|
if ("FVER".equals(id)) {
|
||||||
|
chunk = new FormatVersionChunk(chunkh, raf, aiffHeader);
|
||||||
|
} else if ("APPL".equals(id)) {
|
||||||
|
chunk = new ApplicationChunk(chunkh, raf, aiffHeader);
|
||||||
|
// Any number of application chunks is ok
|
||||||
|
} else if ("COMM".equals(id)) {
|
||||||
|
// There should be no more than one of these
|
||||||
|
chunk = new CommonChunk(chunkh, raf, aiffHeader);
|
||||||
|
} else if ("COMT".equals(id)) {
|
||||||
|
chunk = new CommentsChunk(chunkh, raf, aiffHeader);
|
||||||
|
} else if ("NAME".equals(id)) {
|
||||||
|
chunk = new NameChunk(chunkh, raf, aiffHeader);
|
||||||
|
} else if ("AUTH".equals(id)) {
|
||||||
|
chunk = new AuthorChunk(chunkh, raf, aiffHeader);
|
||||||
|
} else if ("(c) ".equals(id)) {
|
||||||
|
chunk = new CopyrightChunk(chunkh, raf, aiffHeader);
|
||||||
|
} else if ("ANNO".equals(id)) {
|
||||||
|
chunk = new AnnotationChunk(chunkh, raf, aiffHeader);
|
||||||
|
} else if ("ID3 ".equals(id)) {
|
||||||
|
chunk = new ID3Chunk(chunkh, raf, aiffTag);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chunk != null) {
|
||||||
|
if (!chunk.readChunk()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Other chunk types are legal, just skip over them
|
||||||
|
raf.skipBytes(chunkSize);
|
||||||
|
}
|
||||||
|
if ((chunkSize & 1) != 0) {
|
||||||
|
// Must come out to an even byte boundary
|
||||||
|
raf.skipBytes(1);
|
||||||
|
--bytesRemaining;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package org.jaudiotagger.audio.aiff;
|
||||||
|
|
||||||
|
import org.jaudiotagger.audio.exceptions.CannotReadException;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.RandomAccessFile;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Functions for reading an AIFF file
|
||||||
|
*/
|
||||||
|
public class AiffInfoReader {
|
||||||
|
|
||||||
|
public AiffAudioHeader read(RandomAccessFile raf) throws CannotReadException, IOException {
|
||||||
|
if (raf.length() < 4) {
|
||||||
|
//Empty File
|
||||||
|
throw new CannotReadException("Not an AIFF file; too short");
|
||||||
|
}
|
||||||
|
return null; // TODO stub
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
package org.jaudiotagger.audio.aiff;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.RandomAccessFile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class implements an InputStream over a RandomAccessFile for the
|
||||||
|
* sake of efficiency. The AIFF reader uses a RandomAccessFile for
|
||||||
|
* consistency with the other modules, but really just reads the file
|
||||||
|
* sequentially. This class permits reasonable buffering.
|
||||||
|
*/
|
||||||
|
public class AiffInputStream extends InputStream {
|
||||||
|
|
||||||
|
private final static int BUFSIZE = 2048;
|
||||||
|
/**
|
||||||
|
* The underlying file
|
||||||
|
*/
|
||||||
|
private RandomAccessFile raf;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The input buffer
|
||||||
|
*/
|
||||||
|
private byte[] fileBuf;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of valid bytes in the input buffer
|
||||||
|
*/
|
||||||
|
private int fileBufSize;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current position in the input buffer
|
||||||
|
*/
|
||||||
|
private int fileBufOffset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* End of file flag
|
||||||
|
*/
|
||||||
|
private boolean eof;
|
||||||
|
|
||||||
|
public AiffInputStream(RandomAccessFile raf) {
|
||||||
|
this.raf = raf;
|
||||||
|
eof = false;
|
||||||
|
fileBuf = new byte[BUFSIZE];
|
||||||
|
fileBufSize = 0;
|
||||||
|
fileBufOffset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read() throws IOException {
|
||||||
|
for (; ; ) {
|
||||||
|
if (eof) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (fileBufOffset < fileBufSize) {
|
||||||
|
return ((int) fileBuf[fileBufOffset++]) & 0XFF;
|
||||||
|
} else {
|
||||||
|
fillBuf();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Refill the buffer */
|
||||||
|
private void fillBuf() throws IOException {
|
||||||
|
int bytesRead = raf.read(fileBuf, 0, BUFSIZE);
|
||||||
|
fileBufOffset = 0;
|
||||||
|
fileBufSize = bytesRead;
|
||||||
|
if (fileBufSize == 0) {
|
||||||
|
eof = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
166
src/main/java/org/jaudiotagger/audio/aiff/AiffTag.java
Normal file
166
src/main/java/org/jaudiotagger/audio/aiff/AiffTag.java
Normal file
|
@ -0,0 +1,166 @@
|
||||||
|
package org.jaudiotagger.audio.aiff;
|
||||||
|
|
||||||
|
import org.jaudiotagger.audio.generic.GenericTag;
|
||||||
|
import org.jaudiotagger.audio.generic.Utils;
|
||||||
|
import org.jaudiotagger.tag.FieldDataInvalidException;
|
||||||
|
import org.jaudiotagger.tag.KeyNotFoundException;
|
||||||
|
import org.jaudiotagger.tag.TagField;
|
||||||
|
import org.jaudiotagger.tag.TagTextField;
|
||||||
|
|
||||||
|
public class AiffTag extends GenericTag {
|
||||||
|
|
||||||
|
public boolean hasField(AiffTagFieldKey fieldKey) {
|
||||||
|
return hasField(fieldKey.name());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create new AIFF-specific field and set it in the tag
|
||||||
|
*
|
||||||
|
* @param genericKey
|
||||||
|
* @param value
|
||||||
|
* @throws KeyNotFoundException
|
||||||
|
* @throws FieldDataInvalidException
|
||||||
|
*/
|
||||||
|
public void setField(AiffTagFieldKey genericKey, String value) throws KeyNotFoundException, FieldDataInvalidException {
|
||||||
|
TagField tagfield = createField(genericKey, value);
|
||||||
|
setField(tagfield);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TagField createField(AiffTagFieldKey genericKey, String value) throws KeyNotFoundException, FieldDataInvalidException {
|
||||||
|
return new AiffTagTextField(genericKey.name(), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class AiffTagTextField implements TagTextField {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores the identifier.
|
||||||
|
*/
|
||||||
|
private final String id;
|
||||||
|
/**
|
||||||
|
* Stores the string.
|
||||||
|
*/
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance.
|
||||||
|
*
|
||||||
|
* @param fieldId The identifier.
|
||||||
|
* @param initialContent The string.
|
||||||
|
*/
|
||||||
|
public AiffTagTextField(String fieldId, String initialContent) {
|
||||||
|
this.id = fieldId;
|
||||||
|
this.content = initialContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (overridden)
|
||||||
|
*
|
||||||
|
* @see org.jaudiotagger.tag.TagField#copyContent(org.jaudiotagger.tag.TagField)
|
||||||
|
*/
|
||||||
|
public void copyContent(TagField field) {
|
||||||
|
if (field instanceof TagTextField) {
|
||||||
|
this.content = ((TagTextField) field).getContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (overridden)
|
||||||
|
*
|
||||||
|
* @see org.jaudiotagger.tag.TagTextField#getContent()
|
||||||
|
*/
|
||||||
|
public String getContent() {
|
||||||
|
return this.content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (overridden)
|
||||||
|
*
|
||||||
|
* @see org.jaudiotagger.tag.TagTextField#setContent(java.lang.String)
|
||||||
|
*/
|
||||||
|
public void setContent(String s) {
|
||||||
|
this.content = s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (overridden)
|
||||||
|
*
|
||||||
|
* @see org.jaudiotagger.tag.TagTextField#getEncoding()
|
||||||
|
*/
|
||||||
|
public String getEncoding() {
|
||||||
|
return "ISO-8859-1";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (overridden)
|
||||||
|
*
|
||||||
|
* @see org.jaudiotagger.tag.TagTextField#setEncoding(java.lang.String)
|
||||||
|
*/
|
||||||
|
public void setEncoding(String s) {
|
||||||
|
/* Not allowed */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (overridden)
|
||||||
|
*
|
||||||
|
* @see org.jaudiotagger.tag.TagField#getId()
|
||||||
|
*/
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (overridden)
|
||||||
|
*
|
||||||
|
* @see org.jaudiotagger.tag.TagField#getRawContent()
|
||||||
|
*/
|
||||||
|
public byte[] getRawContent() {
|
||||||
|
return this.content == null ? new byte[]{} : Utils.getDefaultBytes(this.content, getEncoding());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (overridden)
|
||||||
|
*
|
||||||
|
* @see org.jaudiotagger.tag.TagField#isBinary()
|
||||||
|
*/
|
||||||
|
public boolean isBinary() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (overridden)
|
||||||
|
*
|
||||||
|
* @see org.jaudiotagger.tag.TagField#isBinary(boolean)
|
||||||
|
*/
|
||||||
|
public void isBinary(boolean b) {
|
||||||
|
/* not supported */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (overridden)
|
||||||
|
*
|
||||||
|
* @see org.jaudiotagger.tag.TagField#isCommon()
|
||||||
|
*/
|
||||||
|
public boolean isCommon() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (overridden)
|
||||||
|
*
|
||||||
|
* @see org.jaudiotagger.tag.TagField#isEmpty()
|
||||||
|
*/
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return this.content.equals("");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (overridden)
|
||||||
|
*
|
||||||
|
* @see java.lang.Object#toString()
|
||||||
|
*/
|
||||||
|
public String toString() {
|
||||||
|
return getContent();
|
||||||
|
}
|
||||||
|
} // End of AiffTagTextField
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package org.jaudiotagger.audio.aiff;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum for AIFF fields that don't have obvious matches in FieldKey
|
||||||
|
*/
|
||||||
|
public enum AiffTagFieldKey {
|
||||||
|
TIMESTAMP("TIMESTAMP");
|
||||||
|
|
||||||
|
private String fieldName;
|
||||||
|
|
||||||
|
AiffTagFieldKey(String fieldName) {
|
||||||
|
this.fieldName = fieldName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFieldName() {
|
||||||
|
return fieldName;
|
||||||
|
}
|
||||||
|
}
|
111
src/main/java/org/jaudiotagger/audio/aiff/AiffUtil.java
Normal file
111
src/main/java/org/jaudiotagger/audio/aiff/AiffUtil.java
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
package org.jaudiotagger.audio.aiff;
|
||||||
|
|
||||||
|
//import java.io.EOFException;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.RandomAccessFile;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
//import java.io.InputStream;
|
||||||
|
|
||||||
|
public class AiffUtil {
|
||||||
|
|
||||||
|
private final static SimpleDateFormat dateFmt =
|
||||||
|
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
|
||||||
|
|
||||||
|
private final static Charset LATIN1 = Charset.availableCharsets().get("ISO-8859-1");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads 4 bytes from file and interprets them as UINT32.<br>
|
||||||
|
*
|
||||||
|
* @param raf file to read from.
|
||||||
|
* @return UINT32 value
|
||||||
|
* @throws IOException on I/O Errors.
|
||||||
|
*/
|
||||||
|
public static long readUINT32(RandomAccessFile raf) throws IOException {
|
||||||
|
long result = 0;
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
// Warning, always cast to long here. Otherwise it will be
|
||||||
|
// shifted as int, which may produce a negative value, which will
|
||||||
|
// then be extended to long and assign the long variable a negative
|
||||||
|
// value.
|
||||||
|
result <<= 8;
|
||||||
|
result |= (long) raf.read();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads 4 bytes and concatenates them into a String.
|
||||||
|
* This pattern is used for ID's of various kinds.
|
||||||
|
*/
|
||||||
|
public static String read4Chars(RandomAccessFile raf) throws IOException {
|
||||||
|
StringBuffer sbuf = new StringBuffer(4);
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
char ch = (char) raf.read();
|
||||||
|
sbuf.append(ch);
|
||||||
|
}
|
||||||
|
return sbuf.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double read80BitDouble(RandomAccessFile raf)
|
||||||
|
throws IOException {
|
||||||
|
byte[] buf = new byte[10];
|
||||||
|
raf.readFully(buf);
|
||||||
|
ExtDouble xd = new ExtDouble(buf);
|
||||||
|
return xd.toDouble();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a Macintosh-style timestamp (seconds since
|
||||||
|
* January 1, 1904) into a Java date. The timestamp is
|
||||||
|
* treated as a time in the default localization.
|
||||||
|
* Depending on that localization,
|
||||||
|
* there may be some variation in the exact hour of the date
|
||||||
|
* returned, e.g., due to daylight savings time.
|
||||||
|
*/
|
||||||
|
public static Date timestampToDate(long timestamp) {
|
||||||
|
Calendar cal = Calendar.getInstance();
|
||||||
|
cal.set(1904, 0, 1, 0, 0, 0);
|
||||||
|
|
||||||
|
// If we add the seconds directly, we'll truncate the long
|
||||||
|
// value when converting to int. So convert to hours plus
|
||||||
|
// residual seconds.
|
||||||
|
int hours = (int) (timestamp / 3600);
|
||||||
|
int seconds = (int) (timestamp - (long) hours * 3600L);
|
||||||
|
cal.add(Calendar.HOUR_OF_DAY, hours);
|
||||||
|
cal.add(Calendar.SECOND, seconds);
|
||||||
|
Date dat = cal.getTime();
|
||||||
|
return dat;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format a date as text
|
||||||
|
*/
|
||||||
|
public static String formatDate(Date dat) {
|
||||||
|
return dateFmt.format(dat);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a byte array to a Pascal string. The first byte is the byte count,
|
||||||
|
* followed by that many active characters.
|
||||||
|
*/
|
||||||
|
public static String bytesToPascalString(byte[] data) {
|
||||||
|
int len = (int) data[0];
|
||||||
|
return new String(data, 0, len, LATIN1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a Pascal string from the file.
|
||||||
|
*/
|
||||||
|
public static String readPascalString(RandomAccessFile raf) throws IOException {
|
||||||
|
int len = raf.read();
|
||||||
|
byte[] buf = new byte[len + 1];
|
||||||
|
raf.read(buf, 1, len);
|
||||||
|
buf[0] = (byte) len;
|
||||||
|
return bytesToPascalString(buf);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
package org.jaudiotagger.audio.aiff;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.RandomAccessFile;
|
||||||
|
|
||||||
|
public class AnnotationChunk extends TextChunk {
|
||||||
|
|
||||||
|
private AiffAudioHeader aiffHeader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param hdr The header for this chunk
|
||||||
|
* @param raf The file from which the AIFF data are being read
|
||||||
|
* @param aHdr The AiffAudioHeader into which information is stored
|
||||||
|
*/
|
||||||
|
public AnnotationChunk(
|
||||||
|
ChunkHeader hdr,
|
||||||
|
RandomAccessFile raf,
|
||||||
|
AiffAudioHeader aHdr) {
|
||||||
|
super(hdr, raf);
|
||||||
|
aiffHeader = aHdr;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean readChunk() throws IOException {
|
||||||
|
if (!super.readChunk()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
aiffHeader.addAnnotation(chunkText);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
package org.jaudiotagger.audio.aiff;
|
||||||
|
|
||||||
|
import org.jaudiotagger.audio.generic.Utils;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.RandomAccessFile;
|
||||||
|
|
||||||
|
public class ApplicationChunk extends Chunk {
|
||||||
|
|
||||||
|
// private AiffTag aiffTag;
|
||||||
|
private AiffAudioHeader aiffHeader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param hdr The header for this chunk
|
||||||
|
* @param raf The file from which the AIFF data are being read
|
||||||
|
* @param tag The AiffTag into which information is stored
|
||||||
|
*/
|
||||||
|
public ApplicationChunk(
|
||||||
|
ChunkHeader hdr,
|
||||||
|
RandomAccessFile raf,
|
||||||
|
AiffAudioHeader aHdr) {
|
||||||
|
super(raf, hdr);
|
||||||
|
aiffHeader = aHdr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads a chunk and puts an Application property into
|
||||||
|
* the RepInfo object.
|
||||||
|
*
|
||||||
|
* @return <code>false</code> if the chunk is structurally
|
||||||
|
* invalid, otherwise <code>true</code>
|
||||||
|
*/
|
||||||
|
public boolean readChunk() throws IOException {
|
||||||
|
String applicationSignature = Utils.readString(raf, 4);
|
||||||
|
String applicationName = null;
|
||||||
|
byte[] data = new byte[(int) (bytesLeft - 4)];
|
||||||
|
raf.readFully(data);
|
||||||
|
// If the application signature is 'pdos' or 'stoc',
|
||||||
|
// then the beginning of the data area is a Pascal
|
||||||
|
// string naming the application. Otherwise, we
|
||||||
|
// ignore the data. ('pdos' is for Apple II
|
||||||
|
// applications, 'stoc' for the entire non-Apple world.)
|
||||||
|
if ("stoc".equals(applicationSignature) ||
|
||||||
|
"pdos".equals(applicationSignature)) {
|
||||||
|
applicationName = AiffUtil.bytesToPascalString(data);
|
||||||
|
}
|
||||||
|
aiffHeader.addApplicationIdentifier
|
||||||
|
(applicationSignature + ": " + applicationName);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
33
src/main/java/org/jaudiotagger/audio/aiff/AuthorChunk.java
Normal file
33
src/main/java/org/jaudiotagger/audio/aiff/AuthorChunk.java
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
package org.jaudiotagger.audio.aiff;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.RandomAccessFile;
|
||||||
|
|
||||||
|
public class AuthorChunk extends TextChunk {
|
||||||
|
|
||||||
|
private AiffAudioHeader aiffHeader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param hdr The header for this chunk
|
||||||
|
* @param raf The file from which the AIFF data are being read
|
||||||
|
* @param aHdr The AiffAudioHeader into which information is stored
|
||||||
|
*/
|
||||||
|
public AuthorChunk(
|
||||||
|
ChunkHeader hdr,
|
||||||
|
RandomAccessFile raf,
|
||||||
|
AiffAudioHeader aHdr) {
|
||||||
|
super(hdr, raf);
|
||||||
|
aiffHeader = aHdr;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean readChunk() throws IOException {
|
||||||
|
if (!super.readChunk()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
aiffHeader.setAuthor(chunkText);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
61
src/main/java/org/jaudiotagger/audio/aiff/Chunk.java
Normal file
61
src/main/java/org/jaudiotagger/audio/aiff/Chunk.java
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
package org.jaudiotagger.audio.aiff;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.RandomAccessFile;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract superclass for IFF/AIFF chunks.
|
||||||
|
*
|
||||||
|
* @author Gary McGath
|
||||||
|
*/
|
||||||
|
public abstract class Chunk {
|
||||||
|
|
||||||
|
protected long bytesLeft;
|
||||||
|
protected RandomAccessFile raf;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param module The Module under which this was called
|
||||||
|
* @param hdr The header for this chunk
|
||||||
|
* @param dstrm The stream from which the data are being read
|
||||||
|
*/
|
||||||
|
public Chunk(RandomAccessFile raf, ChunkHeader hdr) {
|
||||||
|
this.raf = raf;
|
||||||
|
bytesLeft = hdr.getSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads a chunk and puts appropriate information into
|
||||||
|
* the RepInfo object.
|
||||||
|
*
|
||||||
|
* @param info RepInfo object to receive information
|
||||||
|
* @return <code>false</code> if the chunk is structurally
|
||||||
|
* invalid, otherwise <code>true</code>
|
||||||
|
*/
|
||||||
|
public abstract boolean readChunk() throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a byte buffer cleanly to an ASCII string.
|
||||||
|
* This is used for fixed-allocation strings in Broadcast
|
||||||
|
* WAVE chunks, and might have uses elsewhere.
|
||||||
|
* If a string is shorter than its fixed allocation, we're
|
||||||
|
* guaranteed only that there is a null terminating the string,
|
||||||
|
* and noise could follow it. So we can't use the byte buffer
|
||||||
|
* constructor for a string.
|
||||||
|
*/
|
||||||
|
protected String byteBufString(byte[] b) {
|
||||||
|
StringBuffer sb = new StringBuffer(b.length);
|
||||||
|
for (int i = 0; i < b.length; i++) {
|
||||||
|
byte c = b[i];
|
||||||
|
if (c == 0) {
|
||||||
|
// Terminate when we see a null
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
sb.append((char) c);
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
63
src/main/java/org/jaudiotagger/audio/aiff/ChunkHeader.java
Normal file
63
src/main/java/org/jaudiotagger/audio/aiff/ChunkHeader.java
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
package org.jaudiotagger.audio.aiff;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.RandomAccessFile;
|
||||||
|
|
||||||
|
public class ChunkHeader {
|
||||||
|
|
||||||
|
private long _size; // This does not include the 8 bytes of header
|
||||||
|
private String _chunkID; // 4-character ID of the chunk
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param module The module under which the chunk is being read
|
||||||
|
* @param info The RepInfo object being used by the module
|
||||||
|
*/
|
||||||
|
public ChunkHeader() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the header of a chunk. If _chunkID is non-null,
|
||||||
|
* it's assumed to have already been read.
|
||||||
|
*/
|
||||||
|
public boolean readHeader(RandomAccessFile raf) throws IOException {
|
||||||
|
StringBuffer id = new StringBuffer(4);
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
int ch = raf.read();
|
||||||
|
if (ch < 32) {
|
||||||
|
String hx = Integer.toHexString(ch);
|
||||||
|
if (hx.length() < 2) {
|
||||||
|
hx = "0" + hx;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
id.append((char) ch);
|
||||||
|
}
|
||||||
|
_chunkID = id.toString();
|
||||||
|
_size = AiffUtil.readUINT32(raf);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the chunk type, which is a 4-character code
|
||||||
|
*/
|
||||||
|
public String getID() {
|
||||||
|
return _chunkID;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the chunk type, which is a 4-character code, directly.
|
||||||
|
*/
|
||||||
|
public void setID(String id) {
|
||||||
|
_chunkID = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the chunk size (excluding the first 8 bytes)
|
||||||
|
*/
|
||||||
|
public long getSize() {
|
||||||
|
return _size;
|
||||||
|
}
|
||||||
|
}
|
55
src/main/java/org/jaudiotagger/audio/aiff/CommentsChunk.java
Normal file
55
src/main/java/org/jaudiotagger/audio/aiff/CommentsChunk.java
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
package org.jaudiotagger.audio.aiff;
|
||||||
|
|
||||||
|
import org.jaudiotagger.audio.generic.Utils;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.RandomAccessFile;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
public class CommentsChunk extends Chunk {
|
||||||
|
|
||||||
|
private AiffAudioHeader aiffHeader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param hdr The header for this chunk
|
||||||
|
* @param raf The file from which the AIFF data are being read
|
||||||
|
* @param tag The AiffTag into which information is stored
|
||||||
|
*/
|
||||||
|
public CommentsChunk(
|
||||||
|
ChunkHeader hdr,
|
||||||
|
RandomAccessFile raf,
|
||||||
|
AiffAudioHeader aHdr) {
|
||||||
|
super(raf, hdr);
|
||||||
|
aiffHeader = aHdr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads a chunk and extracts information.
|
||||||
|
*
|
||||||
|
* @return <code>false</code> if the chunk is structurally
|
||||||
|
* invalid, otherwise <code>true</code>
|
||||||
|
*/
|
||||||
|
public boolean readChunk() throws IOException {
|
||||||
|
int numComments = Utils.readUint16(raf);
|
||||||
|
// Create a List of comments
|
||||||
|
for (int i = 0; i < numComments; i++) {
|
||||||
|
long timestamp = Utils.readUint32(raf);
|
||||||
|
Date jTimestamp = AiffUtil.timestampToDate(timestamp);
|
||||||
|
int marker = Utils.readInt16(raf);
|
||||||
|
int count = Utils.readUint16(raf);
|
||||||
|
bytesLeft -= 8;
|
||||||
|
byte[] buf = new byte[count];
|
||||||
|
raf.read(buf);
|
||||||
|
bytesLeft -= count;
|
||||||
|
String cmt = new String(buf);
|
||||||
|
|
||||||
|
// Append a timestamp to the comment
|
||||||
|
cmt += " " + AiffUtil.formatDate(jTimestamp);
|
||||||
|
aiffHeader.addComment(cmt);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
101
src/main/java/org/jaudiotagger/audio/aiff/CommonChunk.java
Normal file
101
src/main/java/org/jaudiotagger/audio/aiff/CommonChunk.java
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
package org.jaudiotagger.audio.aiff;
|
||||||
|
|
||||||
|
import org.jaudiotagger.audio.generic.Utils;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.RandomAccessFile;
|
||||||
|
|
||||||
|
public class CommonChunk extends Chunk {
|
||||||
|
|
||||||
|
private AiffAudioHeader aiffHeader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param hdr The header for this chunk
|
||||||
|
* @param raf The file from which the AIFF data are being read
|
||||||
|
* @param tag The AiffTag into which information is stored
|
||||||
|
*/
|
||||||
|
public CommonChunk(
|
||||||
|
ChunkHeader hdr,
|
||||||
|
RandomAccessFile raf,
|
||||||
|
AiffAudioHeader aHdr) {
|
||||||
|
super(raf, hdr);
|
||||||
|
aiffHeader = aHdr;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean readChunk() throws IOException {
|
||||||
|
int numChannels = Utils.readUint16(raf);
|
||||||
|
long numSampleFrames = Utils.readUint32(raf);
|
||||||
|
int sampleSize = Utils.readUint16(raf);
|
||||||
|
bytesLeft -= 8;
|
||||||
|
|
||||||
|
String compressionType = null;
|
||||||
|
String compressionName = null;
|
||||||
|
|
||||||
|
double sampleRate = AiffUtil.read80BitDouble(raf);
|
||||||
|
bytesLeft -= 10;
|
||||||
|
|
||||||
|
if (aiffHeader.getFileType() == AiffAudioHeader.FileType.AIFCTYPE) {
|
||||||
|
if (bytesLeft == 0) {
|
||||||
|
// This is a rather special case, but testing did turn up
|
||||||
|
// a file that misbehaved in this way.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
compressionType = AiffUtil.read4Chars(raf);
|
||||||
|
// According to David Ackerman, the compression type can
|
||||||
|
// change the endianness of the document.
|
||||||
|
if (compressionType.equals("sowt")) {
|
||||||
|
aiffHeader.setEndian(AiffAudioHeader.Endian.LITTLE_ENDIAN);
|
||||||
|
}
|
||||||
|
bytesLeft -= 4;
|
||||||
|
compressionName = AiffUtil.readPascalString(raf);
|
||||||
|
bytesLeft -= compressionName.length() + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
aiffHeader.setBitsPerSample(sampleSize);
|
||||||
|
aiffHeader.setSamplingRate((int) sampleRate);
|
||||||
|
aiffHeader.setChannelNumber(numChannels);
|
||||||
|
aiffHeader.setLength((int) (numSampleFrames / sampleRate));
|
||||||
|
aiffHeader.setPreciseLength((float) (numSampleFrames / sampleRate));
|
||||||
|
aiffHeader.setLossless(true); // for all known compression types
|
||||||
|
// Proper handling of compression type should depend
|
||||||
|
// on whether raw output is set
|
||||||
|
if (compressionType != null) {
|
||||||
|
if (compressionType.equals("NONE")) {
|
||||||
|
} else if (compressionType.equals("raw ")) {
|
||||||
|
compressionName = "PCM 8-bit offset-binary";
|
||||||
|
} else if (compressionType.equals("twos")) {
|
||||||
|
compressionName = "PCM 16-bit twos-complement big-endian";
|
||||||
|
} else if (compressionType.equals("sowt")) {
|
||||||
|
compressionName = "PCM 16-bit twos-complement little-endian";
|
||||||
|
} else if (compressionType.equals("fl32")) {
|
||||||
|
compressionName = "PCM 32-bit integer";
|
||||||
|
} else if (compressionType.equals("fl64")) {
|
||||||
|
compressionName = "PCM 64-bit floating point";
|
||||||
|
} else if (compressionType.equals("in24")) {
|
||||||
|
compressionName = "PCM 24-bit integer";
|
||||||
|
} else if (compressionType.equals("in32")) {
|
||||||
|
compressionName = "PCM 32-bit integer";
|
||||||
|
} else {
|
||||||
|
aiffHeader.setLossless(false); // We don't know, so we have to assume lossy
|
||||||
|
}
|
||||||
|
aiffHeader.setAudioEncoding(compressionName);
|
||||||
|
|
||||||
|
// The size of the data after compression isn't available
|
||||||
|
// from the Common chunk, so we mark it as "unknown."
|
||||||
|
// With a bit more sophistication, we could combine the
|
||||||
|
// information from here and the Sound Data chunk to get
|
||||||
|
// the effective byte rate, but we're about to release.
|
||||||
|
String name = compressionName;
|
||||||
|
if (name == null || name.length() == 0) {
|
||||||
|
name = compressionType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
package org.jaudiotagger.audio.aiff;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.RandomAccessFile;
|
||||||
|
|
||||||
|
public class CopyrightChunk extends TextChunk {
|
||||||
|
|
||||||
|
private AiffAudioHeader aiffHeader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param hdr The header for this chunk
|
||||||
|
* @param raf The file from which the AIFF data are being read
|
||||||
|
* @param aHdr The AiffAudioHeader into which information is stored
|
||||||
|
*/
|
||||||
|
public CopyrightChunk(
|
||||||
|
ChunkHeader hdr,
|
||||||
|
RandomAccessFile raf,
|
||||||
|
AiffAudioHeader aHdr) {
|
||||||
|
super(hdr, raf);
|
||||||
|
aiffHeader = aHdr;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean readChunk() throws IOException {
|
||||||
|
if (!super.readChunk()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
aiffHeader.setCopyright(chunkText);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
78
src/main/java/org/jaudiotagger/audio/aiff/ExtDouble.java
Normal file
78
src/main/java/org/jaudiotagger/audio/aiff/ExtDouble.java
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
package org.jaudiotagger.audio.aiff;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Code to deal with the 80-bit floating point (extended double)
|
||||||
|
* numbers which occur in AIFF files. Should also be applicable
|
||||||
|
* in general.
|
||||||
|
* <p>
|
||||||
|
* Java has no built-in support for IEEE 754 extended double numbers.
|
||||||
|
* Thus, we have to unpack the number and convert it to a double by
|
||||||
|
* hand. There is, of course, loss of precision.
|
||||||
|
* <p>
|
||||||
|
* This isn't designed for high-precision work; as the standard
|
||||||
|
* disclaimer says, don't use it for life support systems or nuclear
|
||||||
|
* power plants.
|
||||||
|
* <p>
|
||||||
|
* Lifted bodily from JHOVE.
|
||||||
|
*
|
||||||
|
* @author Gary McGath
|
||||||
|
*/
|
||||||
|
public class ExtDouble {
|
||||||
|
|
||||||
|
byte[] _rawData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param rawData A 10-byte array representing the number
|
||||||
|
* in the sequence in which it was stored.
|
||||||
|
*/
|
||||||
|
public ExtDouble(byte[] rawData) {
|
||||||
|
_rawData = rawData;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert the value to a Java double. This results in
|
||||||
|
* loss of precision. If the number is out of range,
|
||||||
|
* results aren't guaranteed.
|
||||||
|
*/
|
||||||
|
public double toDouble() {
|
||||||
|
int sign;
|
||||||
|
int exponent;
|
||||||
|
long mantissa = 0;
|
||||||
|
|
||||||
|
// Extract the sign bit.
|
||||||
|
sign = _rawData[0] >> 7;
|
||||||
|
|
||||||
|
// Extract the exponent. It's stored with a
|
||||||
|
// bias of 16383, so subtract that off.
|
||||||
|
// Also, the mantissa is between 1 and 2 (i.e.,
|
||||||
|
// all but 1 digits are to the right of the binary point, so
|
||||||
|
// we take 62 (not 63: see below) off the exponent for that.
|
||||||
|
exponent = (_rawData[0] << 8) | _rawData[1];
|
||||||
|
exponent &= 0X7FFF; // strip off sign bit
|
||||||
|
exponent -= (16383 + 62); // 1 is added to the "real" exponent
|
||||||
|
|
||||||
|
// Extract the mantissa. It's 64 bits of unsigned
|
||||||
|
// data, but a long is a signed number, so we have to
|
||||||
|
// discard the LSB. We'll lose more than that converting
|
||||||
|
// to double anyway. This division by 2 is the reason for
|
||||||
|
// adding an extra 1 to the exponent above.
|
||||||
|
int shifter = 55;
|
||||||
|
for (int i = 2; i < 9; i++) {
|
||||||
|
mantissa |= ((long) _rawData[i] & 0XFFL) << shifter;
|
||||||
|
shifter -= 8;
|
||||||
|
}
|
||||||
|
mantissa |= _rawData[9] >>> 1;
|
||||||
|
|
||||||
|
// Now put it together in a floating point number.
|
||||||
|
double val = Math.pow(2, exponent);
|
||||||
|
val *= mantissa;
|
||||||
|
if (sign != 0) {
|
||||||
|
val = -val;
|
||||||
|
}
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
package org.jaudiotagger.audio.aiff;
|
||||||
|
|
||||||
|
import org.jaudiotagger.audio.generic.Utils;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.RandomAccessFile;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
public class FormatVersionChunk extends Chunk {
|
||||||
|
|
||||||
|
private AiffAudioHeader aiffHeader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param hdr The header for this chunk
|
||||||
|
* @param raf The file from which the AIFF data are being read
|
||||||
|
* @param tag The AiffTag into which information is stored
|
||||||
|
*/
|
||||||
|
public FormatVersionChunk(
|
||||||
|
ChunkHeader hdr,
|
||||||
|
RandomAccessFile raf,
|
||||||
|
AiffAudioHeader aHdr) {
|
||||||
|
super(raf, hdr);
|
||||||
|
aiffHeader = aHdr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads a chunk and extracts information.
|
||||||
|
*
|
||||||
|
* @return <code>false</code> if the chunk is structurally
|
||||||
|
* invalid, otherwise <code>true</code>
|
||||||
|
*/
|
||||||
|
public boolean readChunk() throws IOException {
|
||||||
|
long rawTimestamp = Utils.readUint32(raf);
|
||||||
|
// The timestamp is in seconds since January 1, 1904.
|
||||||
|
// We must convert to Java time.
|
||||||
|
Date timestamp = AiffUtil.timestampToDate(rawTimestamp);
|
||||||
|
aiffHeader.setTimestamp(timestamp);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
98
src/main/java/org/jaudiotagger/audio/aiff/ID3Chunk.java
Normal file
98
src/main/java/org/jaudiotagger/audio/aiff/ID3Chunk.java
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
package org.jaudiotagger.audio.aiff;
|
||||||
|
|
||||||
|
import org.jaudiotagger.audio.AudioFile;
|
||||||
|
import org.jaudiotagger.audio.exceptions.CannotReadException;
|
||||||
|
import org.jaudiotagger.tag.TagException;
|
||||||
|
import org.jaudiotagger.tag.aiff.AiffTag;
|
||||||
|
import org.jaudiotagger.tag.id3.AbstractID3v2Tag;
|
||||||
|
import org.jaudiotagger.tag.id3.ID3v22Tag;
|
||||||
|
import org.jaudiotagger.tag.id3.ID3v23Tag;
|
||||||
|
import org.jaudiotagger.tag.id3.ID3v24Tag;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.RandomAccessFile;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
public class ID3Chunk extends Chunk {
|
||||||
|
|
||||||
|
private AiffTag aiffTag;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param hdr The header for this chunk
|
||||||
|
* @param raf The file from which the AIFF data are being read
|
||||||
|
* @param tag The AiffTag into which information is stored
|
||||||
|
*/
|
||||||
|
public ID3Chunk(
|
||||||
|
ChunkHeader hdr,
|
||||||
|
RandomAccessFile raf,
|
||||||
|
AiffTag tag) {
|
||||||
|
super(raf, hdr);
|
||||||
|
aiffTag = tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean readChunk() throws IOException {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
if (!isId3v2Tag()) {
|
||||||
|
return false; // Bad ID3V2 tag
|
||||||
|
}
|
||||||
|
int version = raf.read();
|
||||||
|
AbstractID3v2Tag id3Tag;
|
||||||
|
switch (version) {
|
||||||
|
case 2:
|
||||||
|
id3Tag = new ID3v22Tag();
|
||||||
|
AudioFile.logger.finest("Reading ID3V2.2 tag");
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
id3Tag = new ID3v23Tag();
|
||||||
|
AudioFile.logger.finest("Reading ID3V2.3 tag");
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
id3Tag = new ID3v24Tag();
|
||||||
|
AudioFile.logger.finest("Reading ID3V2.4 tag");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false; // bad or unknown version
|
||||||
|
}
|
||||||
|
aiffTag.setID3Tag(id3Tag);
|
||||||
|
raf.seek(raf.getFilePointer() - 4); // back up to start of tag
|
||||||
|
byte[] buf = new byte[(int) bytesLeft];
|
||||||
|
raf.read(buf);
|
||||||
|
ByteBuffer bb = ByteBuffer.allocate((int) bytesLeft);
|
||||||
|
bb.put(buf);
|
||||||
|
try {
|
||||||
|
id3Tag.read(bb);
|
||||||
|
} catch (TagException e) {
|
||||||
|
AudioFile.logger.info("Exception reading ID3 tag: " + e.getClass().getName()
|
||||||
|
+ ": " + e.getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param rawdata
|
||||||
|
* @param isFramingBit
|
||||||
|
* @return logical representation of VorbisCommentTag
|
||||||
|
* @throws IOException
|
||||||
|
* @throws CannotReadException
|
||||||
|
*/
|
||||||
|
public void parse(byte[] rawdata, AiffTag aiffTag) throws IOException, CannotReadException {
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads 3 bytes to determine if the tag really looks like ID3 data.
|
||||||
|
*/
|
||||||
|
private boolean isId3v2Tag() throws IOException {
|
||||||
|
byte buf[] = new byte[3];
|
||||||
|
raf.read(buf);
|
||||||
|
String id = new String(buf, "ASCII");
|
||||||
|
return "ID3".equals(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
33
src/main/java/org/jaudiotagger/audio/aiff/NameChunk.java
Normal file
33
src/main/java/org/jaudiotagger/audio/aiff/NameChunk.java
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
package org.jaudiotagger.audio.aiff;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.RandomAccessFile;
|
||||||
|
|
||||||
|
public class NameChunk extends TextChunk {
|
||||||
|
|
||||||
|
private AiffAudioHeader aiffHeader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param hdr The header for this chunk
|
||||||
|
* @param raf The file from which the AIFF data are being read
|
||||||
|
* @param aHdr The AiffAudioHeader into which information is stored
|
||||||
|
*/
|
||||||
|
public NameChunk(
|
||||||
|
ChunkHeader hdr,
|
||||||
|
RandomAccessFile raf,
|
||||||
|
AiffAudioHeader aHdr) {
|
||||||
|
super(hdr, raf);
|
||||||
|
aiffHeader = aHdr;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean readChunk() throws IOException {
|
||||||
|
if (!super.readChunk()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
aiffHeader.setName(chunkText);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
40
src/main/java/org/jaudiotagger/audio/aiff/TextChunk.java
Normal file
40
src/main/java/org/jaudiotagger/audio/aiff/TextChunk.java
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
package org.jaudiotagger.audio.aiff;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.RandomAccessFile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class provides common functionality for NameChunk, AuthorChunk,
|
||||||
|
* and CopyrightChunk
|
||||||
|
*/
|
||||||
|
public abstract class TextChunk extends Chunk {
|
||||||
|
|
||||||
|
protected String chunkText;
|
||||||
|
private AiffAudioHeader aiffHeader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param hdr The header for this chunk
|
||||||
|
* @param raf The file from which the AIFF data are being read
|
||||||
|
*/
|
||||||
|
public TextChunk(
|
||||||
|
ChunkHeader hdr,
|
||||||
|
RandomAccessFile raf) {
|
||||||
|
super(raf, hdr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the chunk. The subclasses need to take the value of
|
||||||
|
* chunkText and use it appropriately.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean readChunk() throws IOException {
|
||||||
|
|
||||||
|
byte[] buf = new byte[(int) bytesLeft];
|
||||||
|
raf.read(buf);
|
||||||
|
chunkText = new String(buf, "ISO-8859-1");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
20
src/main/java/org/jaudiotagger/audio/aiff/package.html
Normal file
20
src/main/java/org/jaudiotagger/audio/aiff/package.html
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
|
||||||
|
"http://www.w3.org/TR/1999/REC-html401-19991224/loose.dtd">
|
||||||
|
|
||||||
|
<html long="en">
|
||||||
|
<head>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body bgcolor="white">
|
||||||
|
|
||||||
|
<b>Very preliminary</b> code for AIFF files.
|
||||||
|
<b>Not even remotely working yet.</b> Uploaded for review.
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- package.html by Gary McGath -->
|
||||||
|
<!-- Put @see and @since tags down here. -->
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
267
src/main/java/org/jaudiotagger/audio/asf/AsfFileReader.java
Normal file
267
src/main/java/org/jaudiotagger/audio/asf/AsfFileReader.java
Normal file
|
@ -0,0 +1,267 @@
|
||||||
|
/*
|
||||||
|
* Entagged Audio Tag library
|
||||||
|
* Copyright (c) 2004-2005 Christian Laireiter <liree@web.de>
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this library; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
package org.jaudiotagger.audio.asf;
|
||||||
|
|
||||||
|
import org.jaudiotagger.audio.AudioFile;
|
||||||
|
import org.jaudiotagger.audio.asf.data.AsfHeader;
|
||||||
|
import org.jaudiotagger.audio.asf.data.AudioStreamChunk;
|
||||||
|
import org.jaudiotagger.audio.asf.data.MetadataContainer;
|
||||||
|
import org.jaudiotagger.audio.asf.data.MetadataDescriptor;
|
||||||
|
import org.jaudiotagger.audio.asf.io.*;
|
||||||
|
import org.jaudiotagger.audio.asf.util.TagConverter;
|
||||||
|
import org.jaudiotagger.audio.asf.util.Utils;
|
||||||
|
import org.jaudiotagger.audio.exceptions.CannotReadException;
|
||||||
|
import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException;
|
||||||
|
import org.jaudiotagger.audio.exceptions.ReadOnlyFileException;
|
||||||
|
import org.jaudiotagger.audio.generic.AudioFileReader;
|
||||||
|
import org.jaudiotagger.audio.generic.GenericAudioHeader;
|
||||||
|
import org.jaudiotagger.logging.ErrorMessage;
|
||||||
|
import org.jaudiotagger.tag.TagException;
|
||||||
|
import org.jaudiotagger.tag.asf.AsfTag;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This reader can read ASF files containing any content (stream type). <br>
|
||||||
|
*
|
||||||
|
* @author Christian Laireiter
|
||||||
|
*/
|
||||||
|
public class AsfFileReader extends AudioFileReader {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logger instance
|
||||||
|
*/
|
||||||
|
private final static Logger LOGGER = Logger
|
||||||
|
.getLogger("org.jaudiotagger.audio.asf");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This reader will be configured to read tag and audio header information.<br>
|
||||||
|
*/
|
||||||
|
private final static AsfHeaderReader HEADER_READER;
|
||||||
|
|
||||||
|
static {
|
||||||
|
final List<Class<? extends ChunkReader>> readers = new ArrayList<Class<? extends ChunkReader>>();
|
||||||
|
readers.add(ContentDescriptionReader.class);
|
||||||
|
readers.add(ContentBrandingReader.class);
|
||||||
|
readers.add(MetadataReader.class);
|
||||||
|
readers.add(LanguageListReader.class);
|
||||||
|
|
||||||
|
// Create the header extension object reader with just content
|
||||||
|
// description reader as well
|
||||||
|
// as extended content description reader.
|
||||||
|
final AsfExtHeaderReader extReader = new AsfExtHeaderReader(readers,
|
||||||
|
true);
|
||||||
|
readers.add(FileHeaderReader.class);
|
||||||
|
readers.add(StreamChunkReader.class);
|
||||||
|
HEADER_READER = new AsfHeaderReader(readers, true);
|
||||||
|
HEADER_READER.setExtendedHeaderReader(extReader);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if the "isVbr" field is set in the extended content
|
||||||
|
* description.<br>
|
||||||
|
*
|
||||||
|
* @param header the header to look up.
|
||||||
|
* @return <code>true</code> if "isVbr" is present with a
|
||||||
|
* <code>true</code> value.
|
||||||
|
*/
|
||||||
|
private boolean determineVariableBitrate(final AsfHeader header) {
|
||||||
|
assert header != null;
|
||||||
|
boolean result = false;
|
||||||
|
final MetadataContainer extDesc = header
|
||||||
|
.findExtendedContentDescription();
|
||||||
|
if (extDesc != null) {
|
||||||
|
final List<MetadataDescriptor> descriptors = extDesc
|
||||||
|
.getDescriptorsByName("IsVBR");
|
||||||
|
if (descriptors != null && !descriptors.isEmpty()) {
|
||||||
|
result = Boolean.TRUE.toString().equals(
|
||||||
|
descriptors.get(0).getString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a generic audio header instance with provided data from header.
|
||||||
|
*
|
||||||
|
* @param header ASF header which contains the information.
|
||||||
|
* @return generic audio header representation.
|
||||||
|
* @throws CannotReadException If header does not contain mandatory information. (Audio
|
||||||
|
* stream chunk and file header chunk)
|
||||||
|
*/
|
||||||
|
private GenericAudioHeader getAudioHeader(final AsfHeader header)
|
||||||
|
throws CannotReadException {
|
||||||
|
final GenericAudioHeader info = new GenericAudioHeader();
|
||||||
|
if (header.getFileHeader() == null) {
|
||||||
|
throw new CannotReadException(
|
||||||
|
"Invalid ASF/WMA file. File header object not available.");
|
||||||
|
}
|
||||||
|
if (header.getAudioStreamChunk() == null) {
|
||||||
|
throw new CannotReadException(
|
||||||
|
"Invalid ASF/WMA file. No audio stream contained.");
|
||||||
|
}
|
||||||
|
info.setBitrate(header.getAudioStreamChunk().getKbps());
|
||||||
|
info.setChannelNumber((int) header.getAudioStreamChunk()
|
||||||
|
.getChannelCount());
|
||||||
|
info.setEncodingType("ASF (audio): "
|
||||||
|
+ header.getAudioStreamChunk().getCodecDescription());
|
||||||
|
info
|
||||||
|
.setLossless(header.getAudioStreamChunk()
|
||||||
|
.getCompressionFormat() == AudioStreamChunk.WMA_LOSSLESS);
|
||||||
|
info.setPreciseLength(header.getFileHeader().getPreciseDuration());
|
||||||
|
info.setSamplingRate((int) header.getAudioStreamChunk()
|
||||||
|
.getSamplingRate());
|
||||||
|
info.setVariableBitRate(determineVariableBitrate(header));
|
||||||
|
info.setBitsPerSample(header.getAudioStreamChunk().getBitsPerSample());
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (overridden)
|
||||||
|
*
|
||||||
|
* @see org.jaudiotagger.audio.generic.AudioFileReader#getEncodingInfo(java.io.RandomAccessFile)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected GenericAudioHeader getEncodingInfo(final RandomAccessFile raf)
|
||||||
|
throws CannotReadException, IOException {
|
||||||
|
raf.seek(0);
|
||||||
|
GenericAudioHeader info;
|
||||||
|
try {
|
||||||
|
final AsfHeader header = AsfHeaderReader.readInfoHeader(raf);
|
||||||
|
if (header == null) {
|
||||||
|
throw new CannotReadException(
|
||||||
|
"Some values must have been "
|
||||||
|
+ "incorrect for interpretation as asf with wma content.");
|
||||||
|
}
|
||||||
|
info = getAudioHeader(header);
|
||||||
|
} catch (final Exception e) {
|
||||||
|
if (e instanceof IOException) {
|
||||||
|
throw (IOException) e;
|
||||||
|
} else if (e instanceof CannotReadException) {
|
||||||
|
throw (CannotReadException) e;
|
||||||
|
} else {
|
||||||
|
throw new CannotReadException("Failed to read. Cause: "
|
||||||
|
+ e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a tag instance with provided data from header.
|
||||||
|
*
|
||||||
|
* @param header ASF header which contains the information.
|
||||||
|
* @return generic audio header representation.
|
||||||
|
*/
|
||||||
|
private AsfTag getTag(final AsfHeader header) {
|
||||||
|
return TagConverter.createTagOf(header);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (overridden)
|
||||||
|
*
|
||||||
|
* @see org.jaudiotagger.audio.generic.AudioFileReader#getTag(java.io.RandomAccessFile)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected AsfTag getTag(final RandomAccessFile raf)
|
||||||
|
throws CannotReadException, IOException {
|
||||||
|
raf.seek(0);
|
||||||
|
AsfTag tag;
|
||||||
|
try {
|
||||||
|
final AsfHeader header = AsfHeaderReader.readTagHeader(raf);
|
||||||
|
if (header == null) {
|
||||||
|
throw new CannotReadException(
|
||||||
|
"Some values must have been "
|
||||||
|
+ "incorrect for interpretation as asf with wma content.");
|
||||||
|
}
|
||||||
|
|
||||||
|
tag = TagConverter.createTagOf(header);
|
||||||
|
|
||||||
|
} catch (final Exception e) {
|
||||||
|
logger.severe(e.getMessage());
|
||||||
|
if (e instanceof IOException) {
|
||||||
|
throw (IOException) e;
|
||||||
|
} else if (e instanceof CannotReadException) {
|
||||||
|
throw (CannotReadException) e;
|
||||||
|
} else {
|
||||||
|
throw new CannotReadException("Failed to read. Cause: "
|
||||||
|
+ e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public AudioFile read(final File f) throws CannotReadException,
|
||||||
|
IOException, TagException, ReadOnlyFileException,
|
||||||
|
InvalidAudioFrameException {
|
||||||
|
if (!f.canRead()) {
|
||||||
|
throw new CannotReadException(
|
||||||
|
ErrorMessage.GENERAL_READ_FAILED_DO_NOT_HAVE_PERMISSION_TO_READ_FILE
|
||||||
|
.getMsg(f.getAbsolutePath()));
|
||||||
|
}
|
||||||
|
InputStream stream = null;
|
||||||
|
try {
|
||||||
|
stream = new FullRequestInputStream(new BufferedInputStream(
|
||||||
|
new FileInputStream(f)));
|
||||||
|
final AsfHeader header = HEADER_READER.read(Utils.readGUID(stream),
|
||||||
|
stream, 0);
|
||||||
|
if (header == null) {
|
||||||
|
throw new CannotReadException(ErrorMessage.ASF_HEADER_MISSING
|
||||||
|
.getMsg(f.getAbsolutePath()));
|
||||||
|
}
|
||||||
|
if (header.getFileHeader() == null) {
|
||||||
|
throw new CannotReadException(
|
||||||
|
ErrorMessage.ASF_FILE_HEADER_MISSING.getMsg(f
|
||||||
|
.getAbsolutePath()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Just log a warning because file seems to play okay
|
||||||
|
if (header.getFileHeader().getFileSize().longValue() != f.length()) {
|
||||||
|
logger
|
||||||
|
.warning(ErrorMessage.ASF_FILE_HEADER_SIZE_DOES_NOT_MATCH_FILE_SIZE
|
||||||
|
.getMsg(f.getAbsolutePath(), header
|
||||||
|
.getFileHeader().getFileSize()
|
||||||
|
.longValue(), f.length()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AudioFile(f, getAudioHeader(header), getTag(header));
|
||||||
|
|
||||||
|
} catch (final CannotReadException e) {
|
||||||
|
throw e;
|
||||||
|
} catch (final Exception e) {
|
||||||
|
throw new CannotReadException("\"" + f + "\" :" + e, e);
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
if (stream != null) {
|
||||||
|
stream.close();
|
||||||
|
}
|
||||||
|
} catch (final Exception ex) {
|
||||||
|
LOGGER.severe("\"" + f + "\" :" + ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
141
src/main/java/org/jaudiotagger/audio/asf/AsfFileWriter.java
Normal file
141
src/main/java/org/jaudiotagger/audio/asf/AsfFileWriter.java
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
/*
|
||||||
|
* Entagged Audio Tag library
|
||||||
|
* Copyright (c) 2004-2005 Christian Laireiter <liree@web.de>
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this library; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
package org.jaudiotagger.audio.asf;
|
||||||
|
|
||||||
|
import org.jaudiotagger.audio.asf.data.AsfHeader;
|
||||||
|
import org.jaudiotagger.audio.asf.data.ChunkContainer;
|
||||||
|
import org.jaudiotagger.audio.asf.data.MetadataContainer;
|
||||||
|
import org.jaudiotagger.audio.asf.io.*;
|
||||||
|
import org.jaudiotagger.audio.asf.util.TagConverter;
|
||||||
|
import org.jaudiotagger.audio.exceptions.CannotWriteException;
|
||||||
|
import org.jaudiotagger.audio.generic.AudioFileWriter;
|
||||||
|
import org.jaudiotagger.tag.Tag;
|
||||||
|
import org.jaudiotagger.tag.asf.AsfTag;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.RandomAccessFile;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class writes given tags to ASF files containing WMA content. <br>
|
||||||
|
* <br>
|
||||||
|
*
|
||||||
|
* @author Christian Laireiter
|
||||||
|
*/
|
||||||
|
public class AsfFileWriter extends AudioFileWriter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void deleteTag(final RandomAccessFile raf,
|
||||||
|
final RandomAccessFile tempRaf) throws CannotWriteException,
|
||||||
|
IOException {
|
||||||
|
writeTag(new AsfTag(true), raf, tempRaf);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean[] searchExistence(final ChunkContainer container,
|
||||||
|
final MetadataContainer[] metaContainers) {
|
||||||
|
assert container != null;
|
||||||
|
assert metaContainers != null;
|
||||||
|
final boolean[] result = new boolean[metaContainers.length];
|
||||||
|
for (int i = 0; i < result.length; i++) {
|
||||||
|
result[i] = container.hasChunkByGUID(metaContainers[i]
|
||||||
|
.getContainerType().getContainerGUID());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void writeTag(final Tag tag, final RandomAccessFile raf,
|
||||||
|
final RandomAccessFile rafTemp) throws CannotWriteException,
|
||||||
|
IOException {
|
||||||
|
/*
|
||||||
|
* Since this implementation should not change the structure of the ASF
|
||||||
|
* file (locations of content description chunks), we need to read the
|
||||||
|
* content description chunk and the extended content description chunk
|
||||||
|
* from the source file. In the second step we need to determine which
|
||||||
|
* modifier (asf header or asf extended header) gets the appropriate
|
||||||
|
* modifiers. The following policies are applied: if the source does not
|
||||||
|
* contain any descriptor, the necessary descriptors are appended to the
|
||||||
|
* header object.
|
||||||
|
*
|
||||||
|
* if the source contains only one descriptor in the header extension
|
||||||
|
* object, and the other type is needed as well, the other one will be
|
||||||
|
* put into the header extension object.
|
||||||
|
*
|
||||||
|
* for each descriptor type, if an object is found, an updater will be
|
||||||
|
* configured.
|
||||||
|
*/
|
||||||
|
final AsfHeader sourceHeader = AsfHeaderReader.readTagHeader(raf);
|
||||||
|
raf.seek(0); // Reset for the streamer
|
||||||
|
/*
|
||||||
|
* Now createField modifiers for metadata descriptor and extended content
|
||||||
|
* descriptor as implied by the given Tag.
|
||||||
|
*/
|
||||||
|
// TODO not convinced that we need to copy fields here
|
||||||
|
final AsfTag copy = new AsfTag(tag, true);
|
||||||
|
final MetadataContainer[] distribution = TagConverter
|
||||||
|
.distributeMetadata(copy);
|
||||||
|
final boolean[] existHeader = searchExistence(sourceHeader,
|
||||||
|
distribution);
|
||||||
|
final boolean[] existExtHeader = searchExistence(sourceHeader
|
||||||
|
.getExtendedHeader(), distribution);
|
||||||
|
// Modifiers for the asf header object
|
||||||
|
final List<ChunkModifier> headerModifier = new ArrayList<ChunkModifier>();
|
||||||
|
// Modifiers for the asf header extension object
|
||||||
|
final List<ChunkModifier> extHeaderModifier = new ArrayList<ChunkModifier>();
|
||||||
|
for (int i = 0; i < distribution.length; i++) {
|
||||||
|
final WriteableChunkModifer modifier = new WriteableChunkModifer(
|
||||||
|
distribution[i]);
|
||||||
|
if (existHeader[i]) {
|
||||||
|
// Will remove or modify chunks in ASF header
|
||||||
|
headerModifier.add(modifier);
|
||||||
|
} else if (existExtHeader[i]) {
|
||||||
|
// Will remove or modify chunks in extended header
|
||||||
|
extHeaderModifier.add(modifier);
|
||||||
|
} else {
|
||||||
|
// Objects (chunks) will be added here.
|
||||||
|
if (i == 0 || i == 2 || i == 1) {
|
||||||
|
// Add content description and extended content description
|
||||||
|
// at header for maximum compatibility
|
||||||
|
headerModifier.add(modifier);
|
||||||
|
} else {
|
||||||
|
// For now, the rest should be created at extended header
|
||||||
|
// since other positions aren't known.
|
||||||
|
extHeaderModifier.add(modifier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// only addField an AsfExtHeaderModifier, if there is actually something to
|
||||||
|
// change (performance)
|
||||||
|
if (!extHeaderModifier.isEmpty()) {
|
||||||
|
headerModifier.add(new AsfExtHeaderModifier(extHeaderModifier));
|
||||||
|
}
|
||||||
|
new AsfStreamer()
|
||||||
|
.createModifiedCopy(new RandomAccessFileInputstream(raf),
|
||||||
|
new RandomAccessFileOutputStream(rafTemp),
|
||||||
|
headerModifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
package org.jaudiotagger.audio.asf.data;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class represents the ASF extended header object (chunk).<br>
|
||||||
|
* Like {@link AsfHeader} it contains multiple other ASF objects (chunks).<br>
|
||||||
|
*
|
||||||
|
* @author Christian Laireiter
|
||||||
|
*/
|
||||||
|
public final class AsfExtendedHeader extends ChunkContainer {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance.<br>
|
||||||
|
*
|
||||||
|
* @param pos Position within the stream.<br>
|
||||||
|
* @param length the length of the extended header object.
|
||||||
|
*/
|
||||||
|
public AsfExtendedHeader(final long pos, final BigInteger length) {
|
||||||
|
super(GUID.GUID_HEADER_EXTENSION, pos, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Returns the contentDescription.
|
||||||
|
*/
|
||||||
|
public ContentDescription getContentDescription() {
|
||||||
|
return (ContentDescription) getFirst(GUID.GUID_CONTENTDESCRIPTION,
|
||||||
|
ContentDescription.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Returns the tagHeader.
|
||||||
|
*/
|
||||||
|
public MetadataContainer getExtendedContentDescription() {
|
||||||
|
return (MetadataContainer) getFirst(
|
||||||
|
GUID.GUID_EXTENDED_CONTENT_DESCRIPTION, MetadataContainer.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a language list object if present.
|
||||||
|
*
|
||||||
|
* @return a language list object.
|
||||||
|
*/
|
||||||
|
public LanguageList getLanguageList() {
|
||||||
|
return (LanguageList) getFirst(GUID.GUID_LANGUAGE_LIST,
|
||||||
|
LanguageList.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a metadata library object if present.
|
||||||
|
*
|
||||||
|
* @return metadata library objet
|
||||||
|
*/
|
||||||
|
public MetadataContainer getMetadataLibraryObject() {
|
||||||
|
return (MetadataContainer) getFirst(GUID.GUID_METADATA_LIBRARY,
|
||||||
|
MetadataContainer.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a metadata object if present.
|
||||||
|
*
|
||||||
|
* @return metadata object
|
||||||
|
*/
|
||||||
|
public MetadataContainer getMetadataObject() {
|
||||||
|
return (MetadataContainer) getFirst(GUID.GUID_METADATA,
|
||||||
|
MetadataContainer.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
213
src/main/java/org/jaudiotagger/audio/asf/data/AsfHeader.java
Normal file
213
src/main/java/org/jaudiotagger/audio/asf/data/AsfHeader.java
Normal file
|
@ -0,0 +1,213 @@
|
||||||
|
/*
|
||||||
|
* Entagged Audio Tag library
|
||||||
|
* Copyright (c) 2004-2005 Christian Laireiter <liree@web.de>
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this library; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
package org.jaudiotagger.audio.asf.data;
|
||||||
|
|
||||||
|
import org.jaudiotagger.audio.asf.util.Utils;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Each ASF file starts with a so called header. <br>
|
||||||
|
* This header contains other chunks. Each chunk starts with a 16 byte GUID
|
||||||
|
* followed by the length (in bytes) of the chunk (including GUID). The length
|
||||||
|
* number takes 8 bytes and is unsigned. Finally the chunk's data appears. <br>
|
||||||
|
*
|
||||||
|
* @author Christian Laireiter
|
||||||
|
*/
|
||||||
|
public final class AsfHeader extends ChunkContainer {
|
||||||
|
/**
|
||||||
|
* The charset "UTF-16LE" is mandatory for ASF handling.
|
||||||
|
*/
|
||||||
|
public final static Charset ASF_CHARSET = Charset.forName("UTF-16LE"); //$NON-NLS-1$
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Byte sequence representing the zero term character.
|
||||||
|
*/
|
||||||
|
public final static byte[] ZERO_TERM = {0, 0};
|
||||||
|
|
||||||
|
static {
|
||||||
|
Set<GUID> MULTI_CHUNKS = new HashSet<GUID>();
|
||||||
|
MULTI_CHUNKS.add(GUID.GUID_STREAM);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An ASF header contains multiple chunks. <br>
|
||||||
|
* The count of those is stored here.
|
||||||
|
*/
|
||||||
|
private final long chunkCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance.
|
||||||
|
*
|
||||||
|
* @param pos see {@link Chunk#position}
|
||||||
|
* @param chunkLen see {@link Chunk#chunkLength}
|
||||||
|
* @param chunkCnt
|
||||||
|
*/
|
||||||
|
public AsfHeader(final long pos, final BigInteger chunkLen,
|
||||||
|
final long chunkCnt) {
|
||||||
|
super(GUID.GUID_HEADER, pos, chunkLen);
|
||||||
|
this.chunkCount = chunkCnt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method looks for an content description object in this header
|
||||||
|
* instance, if not found there, it tries to get one from a contained ASF
|
||||||
|
* header extension object.
|
||||||
|
*
|
||||||
|
* @return content description if found, <code>null</code> otherwise.
|
||||||
|
*/
|
||||||
|
public ContentDescription findContentDescription() {
|
||||||
|
ContentDescription result = getContentDescription();
|
||||||
|
if (result == null && getExtendedHeader() != null) {
|
||||||
|
result = getExtendedHeader().getContentDescription();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method looks for an extended content description object in this
|
||||||
|
* header instance, if not found there, it tries to get one from a contained
|
||||||
|
* ASF header extension object.
|
||||||
|
*
|
||||||
|
* @return extended content description if found, <code>null</code>
|
||||||
|
* otherwise.
|
||||||
|
*/
|
||||||
|
public MetadataContainer findExtendedContentDescription() {
|
||||||
|
MetadataContainer result = getExtendedContentDescription();
|
||||||
|
if (result == null && getExtendedHeader() != null) {
|
||||||
|
result = getExtendedHeader().getExtendedContentDescription();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method searches for a metadata container of the given type.<br>
|
||||||
|
*
|
||||||
|
* @param type the type of the container to look up.
|
||||||
|
* @return a container of specified type, of <code>null</code> if not
|
||||||
|
* contained.
|
||||||
|
*/
|
||||||
|
public MetadataContainer findMetadataContainer(final ContainerType type) {
|
||||||
|
MetadataContainer result = (MetadataContainer) getFirst(type
|
||||||
|
.getContainerGUID(), MetadataContainer.class);
|
||||||
|
if (result == null) {
|
||||||
|
result = (MetadataContainer) getExtendedHeader().getFirst(
|
||||||
|
type.getContainerGUID(), MetadataContainer.class);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method returns the first audio stream chunk found in the asf file or
|
||||||
|
* stream.
|
||||||
|
*
|
||||||
|
* @return Returns the audioStreamChunk.
|
||||||
|
*/
|
||||||
|
public AudioStreamChunk getAudioStreamChunk() {
|
||||||
|
AudioStreamChunk result = null;
|
||||||
|
final List<Chunk> streamChunks = assertChunkList(GUID.GUID_STREAM);
|
||||||
|
for (int i = 0; i < streamChunks.size() && result == null; i++) {
|
||||||
|
if (streamChunks.get(i) instanceof AudioStreamChunk) {
|
||||||
|
result = (AudioStreamChunk) streamChunks.get(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the amount of chunks, when this instance was created.<br>
|
||||||
|
* If chunks have been added, this won't be reflected with this call.<br>
|
||||||
|
* For that use {@link #getChunks()}.
|
||||||
|
*
|
||||||
|
* @return Chunkcount at instance creation.
|
||||||
|
*/
|
||||||
|
public long getChunkCount() {
|
||||||
|
return this.chunkCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Returns the contentDescription.
|
||||||
|
*/
|
||||||
|
public ContentDescription getContentDescription() {
|
||||||
|
return (ContentDescription) getFirst(GUID.GUID_CONTENTDESCRIPTION,
|
||||||
|
ContentDescription.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Returns the encodingChunk.
|
||||||
|
*/
|
||||||
|
public EncodingChunk getEncodingChunk() {
|
||||||
|
return (EncodingChunk) getFirst(GUID.GUID_ENCODING, EncodingChunk.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Returns the encodingChunk.
|
||||||
|
*/
|
||||||
|
public EncryptionChunk getEncryptionChunk() {
|
||||||
|
return (EncryptionChunk) getFirst(GUID.GUID_CONTENT_ENCRYPTION,
|
||||||
|
EncryptionChunk.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Returns the tagHeader.
|
||||||
|
*/
|
||||||
|
public MetadataContainer getExtendedContentDescription() {
|
||||||
|
return (MetadataContainer) getFirst(
|
||||||
|
GUID.GUID_EXTENDED_CONTENT_DESCRIPTION, MetadataContainer.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Returns the extended header.
|
||||||
|
*/
|
||||||
|
public AsfExtendedHeader getExtendedHeader() {
|
||||||
|
return (AsfExtendedHeader) getFirst(GUID.GUID_HEADER_EXTENSION,
|
||||||
|
AsfExtendedHeader.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Returns the fileHeader.
|
||||||
|
*/
|
||||||
|
public FileHeader getFileHeader() {
|
||||||
|
return (FileHeader) getFirst(GUID.GUID_FILE, FileHeader.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Returns the streamBitratePropertiesChunk.
|
||||||
|
*/
|
||||||
|
public StreamBitratePropertiesChunk getStreamBitratePropertiesChunk() {
|
||||||
|
return (StreamBitratePropertiesChunk) getFirst(
|
||||||
|
GUID.GUID_STREAM_BITRATE_PROPERTIES,
|
||||||
|
StreamBitratePropertiesChunk.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String prettyPrint(final String prefix) {
|
||||||
|
final StringBuilder result = new StringBuilder(super.prettyPrint(prefix,
|
||||||
|
prefix + " | : Contains: \"" + getChunkCount() + "\" chunks"
|
||||||
|
+ Utils.LINE_SEPARATOR));
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,302 @@
|
||||||
|
/*
|
||||||
|
* Entagged Audio Tag library
|
||||||
|
* Copyright (c) 2004-2005 Christian Laireiter <liree@web.de>
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this library; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
package org.jaudiotagger.audio.asf.data;
|
||||||
|
|
||||||
|
import org.jaudiotagger.audio.asf.util.Utils;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class represents the stream chunk describing an audio stream. <br>
|
||||||
|
*
|
||||||
|
* @author Christian Laireiter
|
||||||
|
*/
|
||||||
|
public final class AudioStreamChunk extends StreamChunk {
|
||||||
|
/**
|
||||||
|
* Stores the hex values of codec identifiers to their descriptions. <br>
|
||||||
|
*/
|
||||||
|
public final static String[][] CODEC_DESCRIPTIONS = {
|
||||||
|
{"161", " (Windows Media Audio (ver 7,8,9))"},
|
||||||
|
{"162", " (Windows Media Audio 9 series (Professional))"},
|
||||||
|
{"163", "(Windows Media Audio 9 series (Lossless))"},
|
||||||
|
{"7A21", " (GSM-AMR (CBR))"}, {"7A22", " (GSM-AMR (VBR))"}};
|
||||||
|
/**
|
||||||
|
* Stores the audio codec number for WMA
|
||||||
|
*/
|
||||||
|
public final static long WMA = 0x161;
|
||||||
|
/**
|
||||||
|
* Stores the audio codec number for WMA (CBR)
|
||||||
|
*/
|
||||||
|
public final static long WMA_CBR = 0x7A21;
|
||||||
|
/**
|
||||||
|
* Stores the audio codec number for WMA_LOSSLESS
|
||||||
|
*/
|
||||||
|
public final static long WMA_LOSSLESS = 0x163;
|
||||||
|
/**
|
||||||
|
* Stores the audio codec number for WMA_PRO
|
||||||
|
*/
|
||||||
|
public final static long WMA_PRO = 0x162;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores the audio codec number for WMA (VBR)
|
||||||
|
*/
|
||||||
|
public final static long WMA_VBR = 0x7A22;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores the average amount of bytes used by audio stream. <br>
|
||||||
|
* This value is a field within type specific data of audio stream. Maybe it
|
||||||
|
* could be used to calculate the KBPs.
|
||||||
|
*/
|
||||||
|
private long averageBytesPerSec;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Amount of bits used per sample. <br>
|
||||||
|
*/
|
||||||
|
private int bitsPerSample;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The block alignment of the audio data.
|
||||||
|
*/
|
||||||
|
private long blockAlignment;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of channels.
|
||||||
|
*/
|
||||||
|
private long channelCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Some data which needs to be interpreted if the codec is handled.
|
||||||
|
*/
|
||||||
|
private byte[] codecData = new byte[0];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The audio compression format code.
|
||||||
|
*/
|
||||||
|
private long compressionFormat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* this field stores the error concealment type.
|
||||||
|
*/
|
||||||
|
private GUID errorConcealment;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sampling rate of audio stream.
|
||||||
|
*/
|
||||||
|
private long samplingRate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance.
|
||||||
|
*
|
||||||
|
* @param chunkLen Length of the entire chunk (including guid and size)
|
||||||
|
*/
|
||||||
|
public AudioStreamChunk(final BigInteger chunkLen) {
|
||||||
|
super(GUID.GUID_AUDIOSTREAM, chunkLen);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Returns the averageBytesPerSec.
|
||||||
|
*/
|
||||||
|
public long getAverageBytesPerSec() {
|
||||||
|
return this.averageBytesPerSec;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param avgeBytesPerSec The averageBytesPerSec to set.
|
||||||
|
*/
|
||||||
|
public void setAverageBytesPerSec(final long avgeBytesPerSec) {
|
||||||
|
this.averageBytesPerSec = avgeBytesPerSec;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Returns the bitsPerSample.
|
||||||
|
*/
|
||||||
|
public int getBitsPerSample() {
|
||||||
|
return this.bitsPerSample;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the bitsPerSample
|
||||||
|
*
|
||||||
|
* @param bps
|
||||||
|
*/
|
||||||
|
public void setBitsPerSample(final int bps) {
|
||||||
|
this.bitsPerSample = bps;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Returns the blockAlignment.
|
||||||
|
*/
|
||||||
|
public long getBlockAlignment() {
|
||||||
|
return this.blockAlignment;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the blockAlignment.
|
||||||
|
*
|
||||||
|
* @param align
|
||||||
|
*/
|
||||||
|
public void setBlockAlignment(final long align) {
|
||||||
|
this.blockAlignment = align;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Returns the channelCount.
|
||||||
|
*/
|
||||||
|
public long getChannelCount() {
|
||||||
|
return this.channelCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param channels The channelCount to set.
|
||||||
|
*/
|
||||||
|
public void setChannelCount(final long channels) {
|
||||||
|
this.channelCount = channels;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Returns the codecData.
|
||||||
|
*/
|
||||||
|
public byte[] getCodecData() {
|
||||||
|
return this.codecData.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the codecData
|
||||||
|
*
|
||||||
|
* @param codecSpecificData
|
||||||
|
*/
|
||||||
|
public void setCodecData(final byte[] codecSpecificData) {
|
||||||
|
if (codecSpecificData == null) {
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
this.codecData = codecSpecificData.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method will take a look at {@link #compressionFormat}and returns a
|
||||||
|
* String with its hex value and if known a textual note on what coded it
|
||||||
|
* represents. <br>
|
||||||
|
*
|
||||||
|
* @return A description for the used codec.
|
||||||
|
*/
|
||||||
|
public String getCodecDescription() {
|
||||||
|
final StringBuilder result = new StringBuilder(Long
|
||||||
|
.toHexString(getCompressionFormat()));
|
||||||
|
String furtherDesc = " (Unknown)";
|
||||||
|
for (final String[] aCODEC_DESCRIPTIONS : CODEC_DESCRIPTIONS) {
|
||||||
|
if (aCODEC_DESCRIPTIONS[0].equalsIgnoreCase(result.toString())) {
|
||||||
|
furtherDesc = aCODEC_DESCRIPTIONS[1];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (result.length() % 2 == 0) {
|
||||||
|
result.insert(0, "0x");
|
||||||
|
} else {
|
||||||
|
result.insert(0, "0x0");
|
||||||
|
}
|
||||||
|
result.append(furtherDesc);
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Returns the compressionFormat.
|
||||||
|
*/
|
||||||
|
public long getCompressionFormat() {
|
||||||
|
return this.compressionFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param cFormatCode The compressionFormat to set.
|
||||||
|
*/
|
||||||
|
public void setCompressionFormat(final long cFormatCode) {
|
||||||
|
this.compressionFormat = cFormatCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Returns the errorConcealment.
|
||||||
|
*/
|
||||||
|
public GUID getErrorConcealment() {
|
||||||
|
return this.errorConcealment;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method sets the error concealment type which is given by two GUIDs. <br>
|
||||||
|
*
|
||||||
|
* @param errConc the type of error concealment the audio stream is stored as.
|
||||||
|
*/
|
||||||
|
public void setErrorConcealment(final GUID errConc) {
|
||||||
|
this.errorConcealment = errConc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method takes the value of {@link #getAverageBytesPerSec()}and
|
||||||
|
* calculates the kbps out of it, by simply multiplying by 8 and dividing by
|
||||||
|
* 1000. <br>
|
||||||
|
*
|
||||||
|
* @return amount of bits per second in kilo bits.
|
||||||
|
*/
|
||||||
|
public int getKbps() {
|
||||||
|
return (int) getAverageBytesPerSec() * 8 / 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Returns the samplingRate.
|
||||||
|
*/
|
||||||
|
public long getSamplingRate() {
|
||||||
|
return this.samplingRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param sampRate The samplingRate to set.
|
||||||
|
*/
|
||||||
|
public void setSamplingRate(final long sampRate) {
|
||||||
|
this.samplingRate = sampRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This mehtod returns whether the audio stream data is error concealed. <br>
|
||||||
|
* For now only interleaved concealment is known. <br>
|
||||||
|
*
|
||||||
|
* @return <code>true</code> if error concealment is used.
|
||||||
|
*/
|
||||||
|
public boolean isErrorConcealed() {
|
||||||
|
return getErrorConcealment().equals(
|
||||||
|
GUID.GUID_AUDIO_ERROR_CONCEALEMENT_INTERLEAVED);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String prettyPrint(final String prefix) {
|
||||||
|
final StringBuilder result = new StringBuilder(super.prettyPrint(prefix));
|
||||||
|
result.append(prefix).append(" |-> Audio info:").append(
|
||||||
|
Utils.LINE_SEPARATOR);
|
||||||
|
result.append(prefix).append(" | : Bitrate : ").append(getKbps())
|
||||||
|
.append(Utils.LINE_SEPARATOR);
|
||||||
|
result.append(prefix).append(" | : Channels : ").append(
|
||||||
|
getChannelCount()).append(" at ").append(getSamplingRate())
|
||||||
|
.append(" Hz").append(Utils.LINE_SEPARATOR);
|
||||||
|
result.append(prefix).append(" | : Bits per Sample: ").append(
|
||||||
|
getBitsPerSample()).append(Utils.LINE_SEPARATOR);
|
||||||
|
result.append(prefix).append(" | : Formatcode: ").append(
|
||||||
|
getCodecDescription()).append(Utils.LINE_SEPARATOR);
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
}
|
175
src/main/java/org/jaudiotagger/audio/asf/data/Chunk.java
Normal file
175
src/main/java/org/jaudiotagger/audio/asf/data/Chunk.java
Normal file
|
@ -0,0 +1,175 @@
|
||||||
|
/*
|
||||||
|
* Entagged Audio Tag library
|
||||||
|
* Copyright (c) 2004-2005 Christian Laireiter <liree@web.de>
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this library; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
package org.jaudiotagger.audio.asf.data;
|
||||||
|
|
||||||
|
import org.jaudiotagger.audio.asf.util.Utils;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class represents a chunk within ASF streams. <br>
|
||||||
|
* Each chunk starts with a 16byte {@linkplain GUID GUID} identifying the type.
|
||||||
|
* After that a number (represented by 8 bytes) follows which shows the size in
|
||||||
|
* bytes of the chunk. Finally there is the data of the chunk.
|
||||||
|
*
|
||||||
|
* @author Christian Laireiter
|
||||||
|
*/
|
||||||
|
public class Chunk {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The length of current chunk. <br>
|
||||||
|
*/
|
||||||
|
protected final BigInteger chunkLength;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The GUID of represented chunk header.
|
||||||
|
*/
|
||||||
|
protected final GUID guid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The position of current header object within file or stream.
|
||||||
|
*/
|
||||||
|
protected long position;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance
|
||||||
|
*
|
||||||
|
* @param headerGuid The GUID of header object.
|
||||||
|
* @param chunkLen Length of current chunk.
|
||||||
|
*/
|
||||||
|
public Chunk(final GUID headerGuid, final BigInteger chunkLen) {
|
||||||
|
if (headerGuid == null) {
|
||||||
|
throw new IllegalArgumentException("GUID must not be null.");
|
||||||
|
}
|
||||||
|
if (chunkLen == null || chunkLen.compareTo(BigInteger.ZERO) < 0) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"chunkLen must not be null nor negative.");
|
||||||
|
}
|
||||||
|
this.guid = headerGuid;
|
||||||
|
this.chunkLength = chunkLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance
|
||||||
|
*
|
||||||
|
* @param headerGuid The GUID of header object.
|
||||||
|
* @param pos Position of header object within stream or file.
|
||||||
|
* @param chunkLen Length of current chunk.
|
||||||
|
*/
|
||||||
|
public Chunk(final GUID headerGuid, final long pos,
|
||||||
|
final BigInteger chunkLen) {
|
||||||
|
if (headerGuid == null) {
|
||||||
|
throw new IllegalArgumentException("GUID must not be null");
|
||||||
|
}
|
||||||
|
if (pos < 0) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Position of header can't be negative.");
|
||||||
|
}
|
||||||
|
if (chunkLen == null || chunkLen.compareTo(BigInteger.ZERO) < 0) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"chunkLen must not be null nor negative.");
|
||||||
|
}
|
||||||
|
this.guid = headerGuid;
|
||||||
|
this.position = pos;
|
||||||
|
this.chunkLength = chunkLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method returns the End of the current chunk introduced by current
|
||||||
|
* header object.
|
||||||
|
*
|
||||||
|
* @return Position after current chunk.
|
||||||
|
* @deprecated typo, use {@link #getChunkEnd()} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public long getChunckEnd() {
|
||||||
|
return this.position + this.chunkLength.longValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method returns the End of the current chunk introduced by current
|
||||||
|
* header object.
|
||||||
|
*
|
||||||
|
* @return Position after current chunk.
|
||||||
|
*/
|
||||||
|
public long getChunkEnd() {
|
||||||
|
return this.position + this.chunkLength.longValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Returns the chunkLength.
|
||||||
|
*/
|
||||||
|
public BigInteger getChunkLength() {
|
||||||
|
return this.chunkLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Returns the guid.
|
||||||
|
*/
|
||||||
|
public GUID getGuid() {
|
||||||
|
return this.guid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Returns the position.
|
||||||
|
*/
|
||||||
|
public long getPosition() {
|
||||||
|
return this.position;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the position.
|
||||||
|
*
|
||||||
|
* @param pos position to set.
|
||||||
|
*/
|
||||||
|
public void setPosition(final long pos) {
|
||||||
|
this.position = pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method creates a String containing useful information prepared to be
|
||||||
|
* printed on STD-OUT. <br>
|
||||||
|
* This method is intended to be overwritten by inheriting classes.
|
||||||
|
*
|
||||||
|
* @param prefix each line gets this string prepended.
|
||||||
|
* @return Information of current Chunk Object.
|
||||||
|
*/
|
||||||
|
public String prettyPrint(final String prefix) {
|
||||||
|
final StringBuilder result = new StringBuilder();
|
||||||
|
result.append(prefix).append("-> GUID: ").append(
|
||||||
|
GUID.getGuidDescription(this.guid))
|
||||||
|
.append(Utils.LINE_SEPARATOR);
|
||||||
|
result.append(prefix).append(" | : Starts at position: ").append(
|
||||||
|
getPosition()).append(Utils.LINE_SEPARATOR);
|
||||||
|
result.append(prefix).append(" | : Last byte at: ").append(
|
||||||
|
getChunkEnd() - 1).append(Utils.LINE_SEPARATOR);
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (overridden)
|
||||||
|
*
|
||||||
|
* @see java.lang.Object#toString()
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return prettyPrint("");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,176 @@
|
||||||
|
package org.jaudiotagger.audio.asf.data;
|
||||||
|
|
||||||
|
import org.jaudiotagger.audio.asf.util.ChunkPositionComparator;
|
||||||
|
import org.jaudiotagger.audio.asf.util.Utils;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores multiple ASF objects (chunks) in form of {@link Chunk} objects, and is
|
||||||
|
* itself an ASF object (chunk).<br>
|
||||||
|
* <br>
|
||||||
|
* Because current implementation is solely used for ASF metadata, all chunks
|
||||||
|
* (except for {@link StreamChunk}) may only be {@linkplain #addChunk(Chunk)
|
||||||
|
* inserted} once.
|
||||||
|
*
|
||||||
|
* @author Christian Laireiter
|
||||||
|
*/
|
||||||
|
public class ChunkContainer extends Chunk {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores the {@link GUID} instances, which are allowed multiple times
|
||||||
|
* within an ASF header.
|
||||||
|
*/
|
||||||
|
private final static Set<GUID> MULTI_CHUNKS;
|
||||||
|
|
||||||
|
static {
|
||||||
|
MULTI_CHUNKS = new HashSet<GUID>();
|
||||||
|
MULTI_CHUNKS.add(GUID.GUID_STREAM);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores the {@link Chunk} objects to their {@link GUID}.
|
||||||
|
*/
|
||||||
|
private final Map<GUID, List<Chunk>> chunkTable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance.
|
||||||
|
*
|
||||||
|
* @param chunkGUID the GUID which identifies the chunk.
|
||||||
|
* @param pos the position of the chunk within the stream.
|
||||||
|
* @param length the length of the chunk.
|
||||||
|
*/
|
||||||
|
public ChunkContainer(final GUID chunkGUID, final long pos,
|
||||||
|
final BigInteger length) {
|
||||||
|
super(chunkGUID, pos, length);
|
||||||
|
this.chunkTable = new Hashtable<GUID, List<Chunk>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests whether all stored chunks have a unique starting position among
|
||||||
|
* their brothers.
|
||||||
|
*
|
||||||
|
* @param container the container to test.
|
||||||
|
* @return <code>true</code> if all chunks are located at an unique
|
||||||
|
* position. However, no intersection is tested.
|
||||||
|
*/
|
||||||
|
protected static boolean chunkstartsUnique(final ChunkContainer container) {
|
||||||
|
boolean result = true;
|
||||||
|
final Set<Long> chunkStarts = new HashSet<Long>();
|
||||||
|
final Collection<Chunk> chunks = container.getChunks();
|
||||||
|
for (final Chunk curr : chunks) {
|
||||||
|
result &= chunkStarts.add(curr.getPosition());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a chunk to the container.<br>
|
||||||
|
*
|
||||||
|
* @param toAdd The chunk which is to be added.
|
||||||
|
* @throws IllegalArgumentException If a chunk of same type is already added, except for
|
||||||
|
* {@link StreamChunk}.
|
||||||
|
*/
|
||||||
|
public void addChunk(final Chunk toAdd) {
|
||||||
|
final List<Chunk> list = assertChunkList(toAdd.getGuid());
|
||||||
|
if (!list.isEmpty() && !MULTI_CHUNKS.contains(toAdd.getGuid())) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"The GUID of the given chunk indicates, that there is no more instance allowed."); //$NON-NLS-1$
|
||||||
|
}
|
||||||
|
list.add(toAdd);
|
||||||
|
assert chunkstartsUnique(this) : "Chunk has equal start position like an already inserted one."; //$NON-NLS-1$
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method asserts that a {@link List} exists for the given {@link GUID}
|
||||||
|
* , in {@link #chunkTable}.<br>
|
||||||
|
*
|
||||||
|
* @param lookFor The GUID to get list for.
|
||||||
|
* @return an already existing, or newly created list.
|
||||||
|
*/
|
||||||
|
protected List<Chunk> assertChunkList(final GUID lookFor) {
|
||||||
|
List<Chunk> result = this.chunkTable.get(lookFor);
|
||||||
|
if (result == null) {
|
||||||
|
result = new ArrayList<Chunk>();
|
||||||
|
this.chunkTable.put(lookFor, result);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a collection of all contained chunks.<br>
|
||||||
|
*
|
||||||
|
* @return all contained chunks
|
||||||
|
*/
|
||||||
|
public Collection<Chunk> getChunks() {
|
||||||
|
final List<Chunk> result = new ArrayList<Chunk>();
|
||||||
|
for (final List<Chunk> curr : this.chunkTable.values()) {
|
||||||
|
result.addAll(curr);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Looks for the first stored chunk which has the given GUID.
|
||||||
|
*
|
||||||
|
* @param lookFor GUID to look up.
|
||||||
|
* @param instanceOf The class which must additionally be matched.
|
||||||
|
* @return <code>null</code> if no chunk was found, or the stored instance
|
||||||
|
* doesn't match.
|
||||||
|
*/
|
||||||
|
protected Chunk getFirst(final GUID lookFor,
|
||||||
|
final Class<? extends Chunk> instanceOf) {
|
||||||
|
Chunk result = null;
|
||||||
|
final List<Chunk> list = this.chunkTable.get(lookFor);
|
||||||
|
if (list != null && !list.isEmpty()) {
|
||||||
|
final Chunk chunk = list.get(0);
|
||||||
|
if (instanceOf.isAssignableFrom(chunk.getClass())) {
|
||||||
|
result = chunk;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method checks if a chunk has been {@linkplain #addChunk(Chunk)
|
||||||
|
* added} with specified {@linkplain Chunk#getGuid() GUID}.<br>
|
||||||
|
*
|
||||||
|
* @param lookFor GUID to look up.
|
||||||
|
* @return <code>true</code> if chunk with specified GUID has been added.
|
||||||
|
*/
|
||||||
|
public boolean hasChunkByGUID(final GUID lookFor) {
|
||||||
|
return this.chunkTable.containsKey(lookFor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String prettyPrint(final String prefix) {
|
||||||
|
return prettyPrint(prefix, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Nearly the same as {@link #prettyPrint(String)} however, additional
|
||||||
|
* information can be injected below the {@link Chunk#prettyPrint(String)}
|
||||||
|
* output and the listing of the contained chunks.<br>
|
||||||
|
*
|
||||||
|
* @param prefix The prefix to prepend.
|
||||||
|
* @param containerInfo Information to inject.
|
||||||
|
* @return Information of current Chunk Object.
|
||||||
|
*/
|
||||||
|
public String prettyPrint(final String prefix, final String containerInfo) {
|
||||||
|
final StringBuilder result = new StringBuilder(super.prettyPrint(prefix));
|
||||||
|
result.append(containerInfo);
|
||||||
|
result.append(prefix).append(" |").append(Utils.LINE_SEPARATOR);
|
||||||
|
final ArrayList<Chunk> list = new ArrayList<Chunk>(getChunks());
|
||||||
|
Collections.sort(list, new ChunkPositionComparator());
|
||||||
|
|
||||||
|
for (Chunk curr : list) {
|
||||||
|
result.append(curr.prettyPrint(prefix + " |"));
|
||||||
|
result.append(prefix).append(" |").append(Utils.LINE_SEPARATOR);
|
||||||
|
}
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
}
|
283
src/main/java/org/jaudiotagger/audio/asf/data/ContainerType.java
Normal file
283
src/main/java/org/jaudiotagger/audio/asf/data/ContainerType.java
Normal file
|
@ -0,0 +1,283 @@
|
||||||
|
package org.jaudiotagger.audio.asf.data;
|
||||||
|
|
||||||
|
import org.jaudiotagger.audio.asf.util.Utils;
|
||||||
|
import org.jaudiotagger.logging.ErrorMessage;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enumerates capabilities, respectively uses, of metadata descriptors.<br>
|
||||||
|
* <br>
|
||||||
|
* The {@link #METADATA_LIBRARY_OBJECT} allows the most variations of data, as
|
||||||
|
* well as no size limitation (if it can be stored within a DWORD amount of
|
||||||
|
* bytes).<br>
|
||||||
|
*
|
||||||
|
* @author Christian Laireiter
|
||||||
|
*/
|
||||||
|
public enum ContainerType {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The descriptor is used in the content branding object (chunk)
|
||||||
|
*/
|
||||||
|
CONTENT_BRANDING(GUID.GUID_CONTENT_BRANDING, 32, false, false, false, false),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The descriptor is used in the content description object (chunk), so
|
||||||
|
* {@linkplain MetadataDescriptor#DWORD_MAXVALUE maximum data length}
|
||||||
|
* applies, no language index and stream number are allowed, as well as no
|
||||||
|
* multiple values.
|
||||||
|
*/
|
||||||
|
CONTENT_DESCRIPTION(GUID.GUID_CONTENTDESCRIPTION, 16, false, false, false,
|
||||||
|
false),
|
||||||
|
/**
|
||||||
|
* The descriptor is used in an extended content description object, so the
|
||||||
|
* {@linkplain MetadataDescriptor#DWORD_MAXVALUE maximum data size} applies,
|
||||||
|
* and no language index and stream number other than "0" is
|
||||||
|
* allowed. Additionally no multiple values are permitted.
|
||||||
|
*/
|
||||||
|
EXTENDED_CONTENT(GUID.GUID_EXTENDED_CONTENT_DESCRIPTION, 16, false, false,
|
||||||
|
false, false),
|
||||||
|
/**
|
||||||
|
* The descriptor is used in a metadata library object. No real size limit
|
||||||
|
* (except DWORD range) applies. Stream numbers and language indexes can be
|
||||||
|
* specified.
|
||||||
|
*/
|
||||||
|
METADATA_LIBRARY_OBJECT(GUID.GUID_METADATA_LIBRARY, 32, true, true, true,
|
||||||
|
true),
|
||||||
|
/**
|
||||||
|
* The descriptor is used in a metadata object. The
|
||||||
|
* {@linkplain MetadataDescriptor#DWORD_MAXVALUE maximum data size} applies.
|
||||||
|
* Stream numbers can be specified. But no language index (always
|
||||||
|
* "0").
|
||||||
|
*/
|
||||||
|
METADATA_OBJECT(GUID.GUID_METADATA, 16, false, true, false, true);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores the guid that identifies ASF chunks which store metadata of the
|
||||||
|
* current type.
|
||||||
|
*/
|
||||||
|
private final GUID containerGUID;
|
||||||
|
/**
|
||||||
|
* <code>true</code> if the descriptor field can store {@link GUID} values.
|
||||||
|
*/
|
||||||
|
private final boolean guidEnabled;
|
||||||
|
/**
|
||||||
|
* <code>true</code> if descriptor field can refer to a language.
|
||||||
|
*/
|
||||||
|
private final boolean languageEnabled;
|
||||||
|
/**
|
||||||
|
* The maximum amount of bytes the descriptor data may consume.<br>
|
||||||
|
*/
|
||||||
|
private final BigInteger maximumDataLength;
|
||||||
|
/**
|
||||||
|
* <code>true</code> if the container may store multiple values of the same
|
||||||
|
* metadata descriptor specification (equality on name, language, and
|
||||||
|
* stream).<br>
|
||||||
|
* WindowsMedia players advanced tag editor for example stores the
|
||||||
|
* WM/Picture attribute once in the extended content description, and all
|
||||||
|
* others in the metadata library object.
|
||||||
|
*/
|
||||||
|
private final boolean multiValued;
|
||||||
|
/**
|
||||||
|
* if <code>-1</code> a size value has to be compared against
|
||||||
|
* {@link #maximumDataLength} because {@link Long#MAX_VALUE} is exceeded.<br>
|
||||||
|
* Otherwise this is the {@link BigInteger#longValue()} representation.
|
||||||
|
*/
|
||||||
|
private final long perfMaxDataLen;
|
||||||
|
/**
|
||||||
|
* <code>true</code> if descriptor field can refer to specific streams.
|
||||||
|
*/
|
||||||
|
private final boolean streamEnabled;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance
|
||||||
|
*
|
||||||
|
* @param guid see {@link #containerGUID}
|
||||||
|
* @param maxDataLenBits The amount of bits that is used to represent an unsigned value
|
||||||
|
* for the containers size descriptors. Will create a maximum
|
||||||
|
* value for {@link #maximumDataLength}. (2 ^ maxDataLenBits -1)
|
||||||
|
* @param guidAllowed see {@link #guidEnabled}
|
||||||
|
* @param stream see {@link #streamEnabled}
|
||||||
|
* @param language see {@link #languageEnabled}
|
||||||
|
* @param multiValue see {@link #multiValued}
|
||||||
|
*/
|
||||||
|
ContainerType(final GUID guid, final int maxDataLenBits,
|
||||||
|
final boolean guidAllowed, final boolean stream,
|
||||||
|
final boolean language, final boolean multiValue) {
|
||||||
|
this.containerGUID = guid;
|
||||||
|
this.maximumDataLength = BigInteger.valueOf(2).pow(maxDataLenBits)
|
||||||
|
.subtract(BigInteger.ONE);
|
||||||
|
if (this.maximumDataLength
|
||||||
|
.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) <= 0) {
|
||||||
|
this.perfMaxDataLen = this.maximumDataLength.longValue();
|
||||||
|
} else {
|
||||||
|
this.perfMaxDataLen = -1;
|
||||||
|
}
|
||||||
|
this.guidEnabled = guidAllowed;
|
||||||
|
this.streamEnabled = stream;
|
||||||
|
this.languageEnabled = language;
|
||||||
|
this.multiValued = multiValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if low has <= index as high, in respect to
|
||||||
|
* {@link #getOrdered()}
|
||||||
|
*
|
||||||
|
* @param low
|
||||||
|
* @param high
|
||||||
|
* @return <code>true</code> if in correct order.
|
||||||
|
*/
|
||||||
|
public static boolean areInCorrectOrder(final ContainerType low,
|
||||||
|
final ContainerType high) {
|
||||||
|
final List<ContainerType> asList = Arrays.asList(getOrdered());
|
||||||
|
return asList.indexOf(low) <= asList.indexOf(high);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the elements in an order, that indicates more capabilities
|
||||||
|
* (ascending).<br>
|
||||||
|
*
|
||||||
|
* @return capability ordered types
|
||||||
|
*/
|
||||||
|
public static ContainerType[] getOrdered() {
|
||||||
|
return new ContainerType[]{CONTENT_DESCRIPTION, CONTENT_BRANDING, EXTENDED_CONTENT, METADATA_OBJECT, METADATA_LIBRARY_OBJECT};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls {@link #checkConstraints(String, byte[], int, int, int)} and
|
||||||
|
* actually throws the exception if there is one.
|
||||||
|
*
|
||||||
|
* @param name name of the descriptor
|
||||||
|
* @param data content
|
||||||
|
* @param type data type
|
||||||
|
* @param stream stream number
|
||||||
|
* @param language language index
|
||||||
|
*/
|
||||||
|
public void assertConstraints(final String name, final byte[] data,
|
||||||
|
final int type, final int stream, final int language) {
|
||||||
|
final RuntimeException result = checkConstraints(name, data, type,
|
||||||
|
stream, language);
|
||||||
|
if (result != null) {
|
||||||
|
throw result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the values for a {@linkplain MetadataDescriptor content
|
||||||
|
* descriptor} match the contraints of the container type, and returns a
|
||||||
|
* {@link RuntimeException} if the requirements aren't met.
|
||||||
|
*
|
||||||
|
* @param name name of the descriptor
|
||||||
|
* @param data content
|
||||||
|
* @param type data type
|
||||||
|
* @param stream stream number
|
||||||
|
* @param language language index
|
||||||
|
* @return <code>null</code> if everything is fine.
|
||||||
|
*/
|
||||||
|
public RuntimeException checkConstraints(final String name,
|
||||||
|
final byte[] data, final int type, final int stream,
|
||||||
|
final int language) {
|
||||||
|
RuntimeException result = null;
|
||||||
|
// TODO generate tests
|
||||||
|
if (name == null || data == null) {
|
||||||
|
result = new IllegalArgumentException("Arguments must not be null.");
|
||||||
|
} else {
|
||||||
|
if (!Utils.isStringLengthValidNullSafe(name)) {
|
||||||
|
result = new IllegalArgumentException(
|
||||||
|
ErrorMessage.WMA_LENGTH_OF_STRING_IS_TOO_LARGE
|
||||||
|
.getMsg(name.length()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (result == null && !isWithinValueRange(data.length)) {
|
||||||
|
result = new IllegalArgumentException(
|
||||||
|
ErrorMessage.WMA_LENGTH_OF_DATA_IS_TOO_LARGE.getMsg(
|
||||||
|
data.length, getMaximumDataLength(),
|
||||||
|
getContainerGUID().getDescription()));
|
||||||
|
}
|
||||||
|
if (result == null
|
||||||
|
&& (stream < 0 || stream > MetadataDescriptor.MAX_STREAM_NUMBER || (!isStreamNumberEnabled() && stream != 0))) {
|
||||||
|
final String streamAllowed = isStreamNumberEnabled() ? "0 to 127"
|
||||||
|
: "0";
|
||||||
|
result = new IllegalArgumentException(
|
||||||
|
ErrorMessage.WMA_INVALID_STREAM_REFERNCE.getMsg(stream,
|
||||||
|
streamAllowed, getContainerGUID().getDescription()));
|
||||||
|
}
|
||||||
|
if (result == null && type == MetadataDescriptor.TYPE_GUID
|
||||||
|
&& !isGuidEnabled()) {
|
||||||
|
result = new IllegalArgumentException(
|
||||||
|
ErrorMessage.WMA_INVALID_GUID_USE.getMsg(getContainerGUID()
|
||||||
|
.getDescription()));
|
||||||
|
}
|
||||||
|
if (result == null
|
||||||
|
&& ((language != 0 && !isLanguageEnabled()) || (language < 0 || language >= MetadataDescriptor.MAX_LANG_INDEX))) {
|
||||||
|
final String langAllowed = isStreamNumberEnabled() ? "0 to 126"
|
||||||
|
: "0";
|
||||||
|
result = new IllegalArgumentException(
|
||||||
|
ErrorMessage.WMA_INVALID_LANGUAGE_USE.getMsg(language,
|
||||||
|
getContainerGUID().getDescription(), langAllowed));
|
||||||
|
}
|
||||||
|
if (result == null && this == CONTENT_DESCRIPTION
|
||||||
|
&& type != MetadataDescriptor.TYPE_STRING) {
|
||||||
|
result = new IllegalArgumentException(
|
||||||
|
ErrorMessage.WMA_ONLY_STRING_IN_CD.getMsg());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the containerGUID
|
||||||
|
*/
|
||||||
|
public GUID getContainerGUID() {
|
||||||
|
return this.containerGUID;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the maximumDataLength
|
||||||
|
*/
|
||||||
|
public BigInteger getMaximumDataLength() {
|
||||||
|
return this.maximumDataLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the guidEnabled
|
||||||
|
*/
|
||||||
|
public boolean isGuidEnabled() {
|
||||||
|
return this.guidEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the languageEnabled
|
||||||
|
*/
|
||||||
|
public boolean isLanguageEnabled() {
|
||||||
|
return this.languageEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests if the given value is less than or equal to
|
||||||
|
* {@link #getMaximumDataLength()}, and greater or equal to zero.<br>
|
||||||
|
*
|
||||||
|
* @param value The value to test
|
||||||
|
* @return <code>true</code> if size restrictions for binary data are met
|
||||||
|
* with this container type.
|
||||||
|
*/
|
||||||
|
public boolean isWithinValueRange(final long value) {
|
||||||
|
return (this.perfMaxDataLen == -1 || this.perfMaxDataLen >= value)
|
||||||
|
&& value >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the multiValued
|
||||||
|
*/
|
||||||
|
public boolean isMultiValued() {
|
||||||
|
return this.multiValued;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the streamEnabled
|
||||||
|
*/
|
||||||
|
public boolean isStreamNumberEnabled() {
|
||||||
|
return this.streamEnabled;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,209 @@
|
||||||
|
package org.jaudiotagger.audio.asf.data;
|
||||||
|
|
||||||
|
import org.jaudiotagger.audio.asf.util.Utils;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This structure represents the value of the content branding object, which
|
||||||
|
* stores the banner image, the banner image URL and the copyright URL.<br>
|
||||||
|
*
|
||||||
|
* @author Christian Laireiter
|
||||||
|
*/
|
||||||
|
public final class ContentBranding extends MetadataContainer {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores the allowed {@linkplain MetadataDescriptor#getName() descriptor
|
||||||
|
* keys}.
|
||||||
|
*/
|
||||||
|
public final static Set<String> ALLOWED;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Descriptor key representing the banner image.
|
||||||
|
*/
|
||||||
|
public final static String KEY_BANNER_IMAGE = "BANNER_IMAGE";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Descriptor key representing the banner image type.<br>
|
||||||
|
* <br>
|
||||||
|
* <b>Known/valid values are:</b>
|
||||||
|
* <ol>
|
||||||
|
* <li>0: there is no image present</li>
|
||||||
|
* <li>1: there is a BMP image</li>
|
||||||
|
* <li>2: there is a JPEG image</li>
|
||||||
|
* <li>3: there is a GIF image</li>
|
||||||
|
* </ol>
|
||||||
|
*/
|
||||||
|
public final static String KEY_BANNER_TYPE = "BANNER_IMAGE_TYPE";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Descriptor key representing the banner image URL.
|
||||||
|
*/
|
||||||
|
public final static String KEY_BANNER_URL = "BANNER_IMAGE_URL";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Descriptor key representing the copyright URL.
|
||||||
|
*/
|
||||||
|
public final static String KEY_COPYRIGHT_URL = "COPYRIGHT_URL";
|
||||||
|
|
||||||
|
static {
|
||||||
|
ALLOWED = new HashSet<String>();
|
||||||
|
ALLOWED.add(KEY_BANNER_IMAGE);
|
||||||
|
ALLOWED.add(KEY_BANNER_TYPE);
|
||||||
|
ALLOWED.add(KEY_BANNER_URL);
|
||||||
|
ALLOWED.add(KEY_COPYRIGHT_URL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance.
|
||||||
|
*/
|
||||||
|
public ContentBranding() {
|
||||||
|
this(0, BigInteger.ZERO);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance.
|
||||||
|
*
|
||||||
|
* @param pos Position of content description within file or stream
|
||||||
|
* @param size Length of content description.
|
||||||
|
*/
|
||||||
|
public ContentBranding(final long pos, final BigInteger size) {
|
||||||
|
super(ContainerType.CONTENT_BRANDING, pos, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the banner image URL.
|
||||||
|
*
|
||||||
|
* @return the banner image URL.
|
||||||
|
*/
|
||||||
|
public String getBannerImageURL() {
|
||||||
|
return getValueFor(KEY_BANNER_URL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method sets the banner image URL, if <code>imageURL</code> is not
|
||||||
|
* blank.<br>
|
||||||
|
*
|
||||||
|
* @param imageURL image URL to set.
|
||||||
|
*/
|
||||||
|
public void setBannerImageURL(final String imageURL) {
|
||||||
|
if (Utils.isBlank(imageURL)) {
|
||||||
|
removeDescriptorsByName(KEY_BANNER_URL);
|
||||||
|
} else {
|
||||||
|
assertDescriptor(KEY_BANNER_URL).setStringValue(imageURL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the copyright URL.
|
||||||
|
*
|
||||||
|
* @return the banner image URL.
|
||||||
|
*/
|
||||||
|
public String getCopyRightURL() {
|
||||||
|
return getValueFor(KEY_COPYRIGHT_URL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method sets the copyright URL, if <code>copyRight</code> is not
|
||||||
|
* blank.<br>
|
||||||
|
*
|
||||||
|
* @param copyRight copyright URL to set.
|
||||||
|
*/
|
||||||
|
public void setCopyRightURL(final String copyRight) {
|
||||||
|
if (Utils.isBlank(copyRight)) {
|
||||||
|
removeDescriptorsByName(KEY_COPYRIGHT_URL);
|
||||||
|
} else {
|
||||||
|
assertDescriptor(KEY_COPYRIGHT_URL).setStringValue(copyRight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public long getCurrentAsfChunkSize() {
|
||||||
|
// GUID, size, image type, image data size, image url data size,
|
||||||
|
// copyright data size
|
||||||
|
long result = 40;
|
||||||
|
result += assertDescriptor(KEY_BANNER_IMAGE,
|
||||||
|
MetadataDescriptor.TYPE_BINARY).getRawDataSize();
|
||||||
|
result += getBannerImageURL().length();
|
||||||
|
result += getCopyRightURL().length();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the binary image data.
|
||||||
|
*
|
||||||
|
* @return binary image data.
|
||||||
|
*/
|
||||||
|
public byte[] getImageData() {
|
||||||
|
return assertDescriptor(KEY_BANNER_IMAGE,
|
||||||
|
MetadataDescriptor.TYPE_BINARY).getRawData();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the image type.<br>
|
||||||
|
*
|
||||||
|
* @return image type
|
||||||
|
* @see #KEY_BANNER_TYPE for known/valid values.
|
||||||
|
*/
|
||||||
|
public long getImageType() {
|
||||||
|
if (!hasDescriptor(KEY_BANNER_TYPE)) {
|
||||||
|
final MetadataDescriptor descriptor = new MetadataDescriptor(
|
||||||
|
ContainerType.CONTENT_BRANDING, KEY_BANNER_TYPE,
|
||||||
|
MetadataDescriptor.TYPE_DWORD);
|
||||||
|
descriptor.setDWordValue(0);
|
||||||
|
addDescriptor(descriptor);
|
||||||
|
}
|
||||||
|
return assertDescriptor(KEY_BANNER_TYPE).getNumber();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean isAddSupported(final MetadataDescriptor descriptor) {
|
||||||
|
return ALLOWED.contains(descriptor.getName())
|
||||||
|
&& super.isAddSupported(descriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param imageType
|
||||||
|
* @param imageData
|
||||||
|
*/
|
||||||
|
public void setImage(final long imageType, final byte[] imageData) {
|
||||||
|
assert imageType >= 0 && imageType <= 3;
|
||||||
|
assert imageType > 0 || imageData.length == 0;
|
||||||
|
assertDescriptor(KEY_BANNER_TYPE, MetadataDescriptor.TYPE_DWORD)
|
||||||
|
.setDWordValue(imageType);
|
||||||
|
assertDescriptor(KEY_BANNER_IMAGE, MetadataDescriptor.TYPE_BINARY)
|
||||||
|
.setBinaryValue(imageData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public long writeInto(final OutputStream out) throws IOException {
|
||||||
|
final long chunkSize = getCurrentAsfChunkSize();
|
||||||
|
out.write(getGuid().getBytes());
|
||||||
|
Utils.writeUINT64(chunkSize, out);
|
||||||
|
Utils.writeUINT32(getImageType(), out);
|
||||||
|
assert getImageType() >= 0 && getImageType() <= 3;
|
||||||
|
final byte[] imageData = getImageData();
|
||||||
|
assert getImageType() > 0 || imageData.length == 0;
|
||||||
|
Utils.writeUINT32(imageData.length, out);
|
||||||
|
out.write(imageData);
|
||||||
|
Utils.writeUINT32(getBannerImageURL().length(), out);
|
||||||
|
out.write(getBannerImageURL().getBytes("ASCII"));
|
||||||
|
Utils.writeUINT32(getCopyRightURL().length(), out);
|
||||||
|
out.write(getCopyRightURL().getBytes("ASCII"));
|
||||||
|
return chunkSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,243 @@
|
||||||
|
/*
|
||||||
|
* Entagged Audio Tag library
|
||||||
|
* Copyright (c) 2004-2005 Christian Laireiter <liree@web.de>
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this library; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
package org.jaudiotagger.audio.asf.data;
|
||||||
|
|
||||||
|
import org.jaudiotagger.audio.asf.util.Utils;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class represents the data of a chunk which contains title, author,
|
||||||
|
* copyright, description and the rating of the file. <br>
|
||||||
|
* It is optional within ASF files. But if, exists only once.
|
||||||
|
*
|
||||||
|
* @author Christian Laireiter
|
||||||
|
*/
|
||||||
|
public final class ContentDescription extends MetadataContainer {
|
||||||
|
/**
|
||||||
|
* Stores the only allowed keys of this metadata container.
|
||||||
|
*/
|
||||||
|
public final static Set<String> ALLOWED;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Field key for author.
|
||||||
|
*/
|
||||||
|
public final static String KEY_AUTHOR = "AUTHOR";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Field key for copyright.
|
||||||
|
*/
|
||||||
|
public final static String KEY_COPYRIGHT = "COPYRIGHT";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Field key for description.
|
||||||
|
*/
|
||||||
|
public final static String KEY_DESCRIPTION = "DESCRIPTION";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Field key for rating.
|
||||||
|
*/
|
||||||
|
public final static String KEY_RATING = "RATING";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Field key for title.
|
||||||
|
*/
|
||||||
|
public final static String KEY_TITLE = "TITLE";
|
||||||
|
|
||||||
|
static {
|
||||||
|
ALLOWED = new HashSet<String>(Arrays.asList(KEY_AUTHOR,
|
||||||
|
KEY_COPYRIGHT, KEY_DESCRIPTION, KEY_RATING, KEY_TITLE));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance. <br>
|
||||||
|
*/
|
||||||
|
public ContentDescription() {
|
||||||
|
this(0, BigInteger.ZERO);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance.
|
||||||
|
*
|
||||||
|
* @param pos Position of content description within file or stream
|
||||||
|
* @param chunkLen Length of content description.
|
||||||
|
*/
|
||||||
|
public ContentDescription(final long pos, final BigInteger chunkLen) {
|
||||||
|
super(ContainerType.CONTENT_DESCRIPTION, pos, chunkLen);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Returns the author.
|
||||||
|
*/
|
||||||
|
public String getAuthor() {
|
||||||
|
return getValueFor(KEY_AUTHOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param fileAuthor The author to set.
|
||||||
|
* @throws IllegalArgumentException If "UTF-16LE"-byte-representation would take more than 65535
|
||||||
|
* bytes.
|
||||||
|
*/
|
||||||
|
public void setAuthor(final String fileAuthor) throws IllegalArgumentException {
|
||||||
|
setStringValue(KEY_AUTHOR, fileAuthor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Returns the comment.
|
||||||
|
*/
|
||||||
|
public String getComment() {
|
||||||
|
return getValueFor(KEY_DESCRIPTION);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param tagComment The comment to set.
|
||||||
|
* @throws IllegalArgumentException If "UTF-16LE"-byte-representation would take more than 65535
|
||||||
|
* bytes.
|
||||||
|
*/
|
||||||
|
public void setComment(final String tagComment) throws IllegalArgumentException {
|
||||||
|
setStringValue(KEY_DESCRIPTION, tagComment);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Returns the copyRight.
|
||||||
|
*/
|
||||||
|
public String getCopyRight() {
|
||||||
|
return getValueFor(KEY_COPYRIGHT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public long getCurrentAsfChunkSize() {
|
||||||
|
long result = 44; // GUID + UINT64 for size + 5 times string length
|
||||||
|
// (each
|
||||||
|
// 2 bytes) + 5 times zero term char (2 bytes each).
|
||||||
|
result += getAuthor().length() * 2; // UTF-16LE
|
||||||
|
result += getComment().length() * 2;
|
||||||
|
result += getRating().length() * 2;
|
||||||
|
result += getTitle().length() * 2;
|
||||||
|
result += getCopyRight().length() * 2;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return returns the rating.
|
||||||
|
*/
|
||||||
|
public String getRating() {
|
||||||
|
return getValueFor(KEY_RATING);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param ratingText The rating to be set.
|
||||||
|
* @throws IllegalArgumentException If "UTF-16LE"-byte-representation would take more than 65535
|
||||||
|
* bytes.
|
||||||
|
*/
|
||||||
|
public void setRating(final String ratingText) throws IllegalArgumentException {
|
||||||
|
setStringValue(KEY_RATING, ratingText);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Returns the title.
|
||||||
|
*/
|
||||||
|
public String getTitle() {
|
||||||
|
return getValueFor(KEY_TITLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param songTitle The title to set.
|
||||||
|
* @throws IllegalArgumentException If "UTF-16LE"-byte-representation would take more than 65535
|
||||||
|
* bytes.
|
||||||
|
*/
|
||||||
|
public void setTitle(final String songTitle) throws IllegalArgumentException {
|
||||||
|
setStringValue(KEY_TITLE, songTitle);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean isAddSupported(final MetadataDescriptor descriptor) {
|
||||||
|
return ALLOWED.contains(descriptor.getName())
|
||||||
|
&& super.isAddSupported(descriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String prettyPrint(final String prefix) {
|
||||||
|
final StringBuilder result = new StringBuilder(super.prettyPrint(prefix));
|
||||||
|
result.append(prefix).append(" |->Title : ").append(getTitle())
|
||||||
|
.append(Utils.LINE_SEPARATOR);
|
||||||
|
result.append(prefix).append(" |->Author : ").append(getAuthor())
|
||||||
|
.append(Utils.LINE_SEPARATOR);
|
||||||
|
result.append(prefix).append(" |->Copyright : ").append(
|
||||||
|
getCopyRight()).append(Utils.LINE_SEPARATOR);
|
||||||
|
result.append(prefix).append(" |->Description: ").append(getComment())
|
||||||
|
.append(Utils.LINE_SEPARATOR);
|
||||||
|
result.append(prefix).append(" |->Rating :").append(getRating())
|
||||||
|
.append(Utils.LINE_SEPARATOR);
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param cpright The copyRight to set.
|
||||||
|
* @throws IllegalArgumentException If "UTF-16LE"-byte-representation would take more than 65535
|
||||||
|
* bytes.
|
||||||
|
*/
|
||||||
|
public void setCopyright(final String cpright) throws IllegalArgumentException {
|
||||||
|
setStringValue(KEY_COPYRIGHT, cpright);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public long writeInto(final OutputStream out) throws IOException {
|
||||||
|
final long chunkSize = getCurrentAsfChunkSize();
|
||||||
|
|
||||||
|
out.write(this.getGuid().getBytes());
|
||||||
|
Utils.writeUINT64(getCurrentAsfChunkSize(), out);
|
||||||
|
// write the sizes of the string representations plus 2 bytes zero term
|
||||||
|
// character
|
||||||
|
Utils.writeUINT16(getTitle().length() * 2 + 2, out);
|
||||||
|
Utils.writeUINT16(getAuthor().length() * 2 + 2, out);
|
||||||
|
Utils.writeUINT16(getCopyRight().length() * 2 + 2, out);
|
||||||
|
Utils.writeUINT16(getComment().length() * 2 + 2, out);
|
||||||
|
Utils.writeUINT16(getRating().length() * 2 + 2, out);
|
||||||
|
// write the Strings
|
||||||
|
out.write(Utils.getBytes(getTitle(), AsfHeader.ASF_CHARSET));
|
||||||
|
out.write(AsfHeader.ZERO_TERM);
|
||||||
|
out.write(Utils.getBytes(getAuthor(), AsfHeader.ASF_CHARSET));
|
||||||
|
out.write(AsfHeader.ZERO_TERM);
|
||||||
|
out.write(Utils.getBytes(getCopyRight(), AsfHeader.ASF_CHARSET));
|
||||||
|
out.write(AsfHeader.ZERO_TERM);
|
||||||
|
out.write(Utils.getBytes(getComment(), AsfHeader.ASF_CHARSET));
|
||||||
|
out.write(AsfHeader.ZERO_TERM);
|
||||||
|
out.write(Utils.getBytes(getRating(), AsfHeader.ASF_CHARSET));
|
||||||
|
out.write(AsfHeader.ZERO_TERM);
|
||||||
|
return chunkSize;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
/*
|
||||||
|
* Entagged Audio Tag library
|
||||||
|
* Copyright (c) 2004-2005 Christian Laireiter <liree@web.de>
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this library; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
package org.jaudiotagger.audio.asf.data;
|
||||||
|
|
||||||
|
import org.jaudiotagger.audio.asf.util.Utils;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class was intended to store the data of a chunk which contained the
|
||||||
|
* encoding parameters in textual form. <br>
|
||||||
|
* Since the needed parameters were found in other chunks the implementation of
|
||||||
|
* this class was paused. <br>
|
||||||
|
* TODO complete analysis.
|
||||||
|
*
|
||||||
|
* @author Christian Laireiter
|
||||||
|
*/
|
||||||
|
public class EncodingChunk extends Chunk {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The read strings.
|
||||||
|
*/
|
||||||
|
private final List<String> strings;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance.
|
||||||
|
*
|
||||||
|
* @param chunkLen Length of current chunk.
|
||||||
|
*/
|
||||||
|
public EncodingChunk(final BigInteger chunkLen) {
|
||||||
|
super(GUID.GUID_ENCODING, chunkLen);
|
||||||
|
this.strings = new ArrayList<String>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method appends a String.
|
||||||
|
*
|
||||||
|
* @param toAdd String to add.
|
||||||
|
*/
|
||||||
|
public void addString(final String toAdd) {
|
||||||
|
this.strings.add(toAdd);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method returns a collection of all {@linkplain String Strings} which
|
||||||
|
* were added due {@link #addString(String)}.
|
||||||
|
*
|
||||||
|
* @return Inserted Strings.
|
||||||
|
*/
|
||||||
|
public Collection<String> getStrings() {
|
||||||
|
return new ArrayList<String>(this.strings);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String prettyPrint(final String prefix) {
|
||||||
|
final StringBuilder result = new StringBuilder(super
|
||||||
|
.prettyPrint(prefix));
|
||||||
|
this.strings.iterator();
|
||||||
|
for (final String string : this.strings) {
|
||||||
|
result.append(prefix).append(" | : ").append(string).append(
|
||||||
|
Utils.LINE_SEPARATOR);
|
||||||
|
}
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,149 @@
|
||||||
|
package org.jaudiotagger.audio.asf.data;
|
||||||
|
|
||||||
|
import org.jaudiotagger.audio.asf.util.Utils;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author eric
|
||||||
|
*/
|
||||||
|
public class EncryptionChunk extends Chunk {
|
||||||
|
/**
|
||||||
|
* The read strings.
|
||||||
|
*/
|
||||||
|
private final ArrayList<String> strings;
|
||||||
|
private String keyID;
|
||||||
|
private String licenseURL;
|
||||||
|
private String protectionType;
|
||||||
|
private String secretData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance.
|
||||||
|
*
|
||||||
|
* @param chunkLen Length of current chunk.
|
||||||
|
*/
|
||||||
|
public EncryptionChunk(final BigInteger chunkLen) {
|
||||||
|
super(GUID.GUID_CONTENT_ENCRYPTION, chunkLen);
|
||||||
|
this.strings = new ArrayList<String>();
|
||||||
|
this.secretData = "";
|
||||||
|
this.protectionType = "";
|
||||||
|
this.keyID = "";
|
||||||
|
this.licenseURL = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method appends a String.
|
||||||
|
*
|
||||||
|
* @param toAdd String to add.
|
||||||
|
*/
|
||||||
|
public void addString(final String toAdd) {
|
||||||
|
this.strings.add(toAdd);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method gets the keyID.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String getKeyID() {
|
||||||
|
return this.keyID;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method appends a String.
|
||||||
|
*
|
||||||
|
* @param toAdd String to add.
|
||||||
|
*/
|
||||||
|
public void setKeyID(final String toAdd) {
|
||||||
|
this.keyID = toAdd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method gets the license URL.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String getLicenseURL() {
|
||||||
|
return this.licenseURL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method appends a String.
|
||||||
|
*
|
||||||
|
* @param toAdd String to add.
|
||||||
|
*/
|
||||||
|
public void setLicenseURL(final String toAdd) {
|
||||||
|
this.licenseURL = toAdd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method gets the secret data.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String getProtectionType() {
|
||||||
|
return this.protectionType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method appends a String.
|
||||||
|
*
|
||||||
|
* @param toAdd String to add.
|
||||||
|
*/
|
||||||
|
public void setProtectionType(final String toAdd) {
|
||||||
|
this.protectionType = toAdd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method gets the secret data.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String getSecretData() {
|
||||||
|
return this.secretData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method adds the secret data.
|
||||||
|
*
|
||||||
|
* @param toAdd String to add.
|
||||||
|
*/
|
||||||
|
public void setSecretData(final String toAdd) {
|
||||||
|
this.secretData = toAdd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method returns a collection of all {@link String}s which were addid
|
||||||
|
* due {@link #addString(String)}.
|
||||||
|
*
|
||||||
|
* @return Inserted Strings.
|
||||||
|
*/
|
||||||
|
public Collection<String> getStrings() {
|
||||||
|
return new ArrayList<String>(this.strings);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String prettyPrint(final String prefix) {
|
||||||
|
final StringBuilder result = new StringBuilder(super.prettyPrint(prefix));
|
||||||
|
result.insert(0, Utils.LINE_SEPARATOR + prefix + " Encryption:"
|
||||||
|
+ Utils.LINE_SEPARATOR);
|
||||||
|
result.append(prefix).append(" |->keyID ").append(this.keyID).append(
|
||||||
|
Utils.LINE_SEPARATOR);
|
||||||
|
result.append(prefix).append(" |->secretData ").append(this.secretData)
|
||||||
|
.append(Utils.LINE_SEPARATOR);
|
||||||
|
result.append(prefix).append(" |->protectionType ").append(
|
||||||
|
this.protectionType).append(Utils.LINE_SEPARATOR);
|
||||||
|
result.append(prefix).append(" |->licenseURL ").append(this.licenseURL)
|
||||||
|
.append(Utils.LINE_SEPARATOR);
|
||||||
|
this.strings.iterator();
|
||||||
|
for (final String string : this.strings) {
|
||||||
|
result.append(prefix).append(" |->").append(string).append(Utils.LINE_SEPARATOR);
|
||||||
|
}
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
}
|
233
src/main/java/org/jaudiotagger/audio/asf/data/FileHeader.java
Normal file
233
src/main/java/org/jaudiotagger/audio/asf/data/FileHeader.java
Normal file
|
@ -0,0 +1,233 @@
|
||||||
|
/*
|
||||||
|
* Entagged Audio Tag library
|
||||||
|
* Copyright (c) 2004-2005 Christian Laireiter <liree@web.de>
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this library; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
package org.jaudiotagger.audio.asf.data;
|
||||||
|
|
||||||
|
import org.jaudiotagger.audio.asf.util.Utils;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class stores the information about the file, which is contained within a
|
||||||
|
* special chunk of ASF files.<br>
|
||||||
|
*
|
||||||
|
* @author Christian Laireiter
|
||||||
|
*/
|
||||||
|
public class FileHeader extends Chunk {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Duration of the media content in 100ns steps.
|
||||||
|
*/
|
||||||
|
private final BigInteger duration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The time the file was created.
|
||||||
|
*/
|
||||||
|
private final Date fileCreationTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Size of the file or stream.
|
||||||
|
*/
|
||||||
|
private final BigInteger fileSize;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Usually contains value of 2.
|
||||||
|
*/
|
||||||
|
private final long flags;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum size of stream packages. <br>
|
||||||
|
* <b>Warning: </b> must be same size as {@link #minPackageSize}. Its not
|
||||||
|
* known how to handle deviating values.
|
||||||
|
*/
|
||||||
|
private final long maxPackageSize;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minimun size of stream packages. <br>
|
||||||
|
* <b>Warning: </b> must be same size as {@link #maxPackageSize}. Its not
|
||||||
|
* known how to handle deviating values.
|
||||||
|
*/
|
||||||
|
private final long minPackageSize;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of stream packages within the File.
|
||||||
|
*/
|
||||||
|
private final BigInteger packageCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No Idea of the Meaning, but stored anyway. <br>
|
||||||
|
* Source documentation says it is: "Timestamp of end position"
|
||||||
|
*/
|
||||||
|
private final BigInteger timeEndPos;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Like {@link #timeEndPos}no Idea.
|
||||||
|
*/
|
||||||
|
private final BigInteger timeStartPos;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Size of an uncompressed video frame.
|
||||||
|
*/
|
||||||
|
private final long uncompressedFrameSize;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance.
|
||||||
|
*
|
||||||
|
* @param chunckLen Length of the file header (chunk)
|
||||||
|
* @param size Size of file or stream
|
||||||
|
* @param fileTime Time file or stream was created. Time is calculated since 1st
|
||||||
|
* january of 1601 in 100ns steps.
|
||||||
|
* @param pkgCount Number of stream packages.
|
||||||
|
* @param dur Duration of media clip in 100ns steps
|
||||||
|
* @param timestampStart Timestamp of start {@link #timeStartPos}
|
||||||
|
* @param timestampEnd Timestamp of end {@link #timeEndPos}
|
||||||
|
* @param headerFlags some stream related flags.
|
||||||
|
* @param minPkgSize minimum size of packages
|
||||||
|
* @param maxPkgSize maximum size of packages
|
||||||
|
* @param uncmpVideoFrameSize Size of an uncompressed Video Frame.
|
||||||
|
*/
|
||||||
|
public FileHeader(final BigInteger chunckLen, final BigInteger size,
|
||||||
|
final BigInteger fileTime, final BigInteger pkgCount,
|
||||||
|
final BigInteger dur, final BigInteger timestampStart,
|
||||||
|
final BigInteger timestampEnd, final long headerFlags,
|
||||||
|
final long minPkgSize, final long maxPkgSize,
|
||||||
|
final long uncmpVideoFrameSize) {
|
||||||
|
super(GUID.GUID_FILE, chunckLen);
|
||||||
|
this.fileSize = size;
|
||||||
|
this.packageCount = pkgCount;
|
||||||
|
this.duration = dur;
|
||||||
|
this.timeStartPos = timestampStart;
|
||||||
|
this.timeEndPos = timestampEnd;
|
||||||
|
this.flags = headerFlags;
|
||||||
|
this.minPackageSize = minPkgSize;
|
||||||
|
this.maxPackageSize = maxPkgSize;
|
||||||
|
this.uncompressedFrameSize = uncmpVideoFrameSize;
|
||||||
|
this.fileCreationTime = Utils.getDateOf(fileTime).getTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Returns the duration.
|
||||||
|
*/
|
||||||
|
public BigInteger getDuration() {
|
||||||
|
return this.duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method converts {@link #getDuration()}from 100ns steps to normal
|
||||||
|
* seconds.
|
||||||
|
*
|
||||||
|
* @return Duration of the media in seconds.
|
||||||
|
*/
|
||||||
|
public int getDurationInSeconds() {
|
||||||
|
return this.duration.divide(new BigInteger("10000000")).intValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Returns the fileCreationTime.
|
||||||
|
*/
|
||||||
|
public Date getFileCreationTime() {
|
||||||
|
return new Date(this.fileCreationTime.getTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Returns the fileSize.
|
||||||
|
*/
|
||||||
|
public BigInteger getFileSize() {
|
||||||
|
return this.fileSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Returns the flags.
|
||||||
|
*/
|
||||||
|
public long getFlags() {
|
||||||
|
return this.flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Returns the maxPackageSize.
|
||||||
|
*/
|
||||||
|
public long getMaxPackageSize() {
|
||||||
|
return this.maxPackageSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Returns the minPackageSize.
|
||||||
|
*/
|
||||||
|
public long getMinPackageSize() {
|
||||||
|
return this.minPackageSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Returns the packageCount.
|
||||||
|
*/
|
||||||
|
public BigInteger getPackageCount() {
|
||||||
|
return this.packageCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method converts {@link #getDuration()} from 100ns steps to normal
|
||||||
|
* seconds with a fractional part taking milliseconds.<br>
|
||||||
|
*
|
||||||
|
* @return The duration of the media in seconds (with a precision of
|
||||||
|
* milliseconds)
|
||||||
|
*/
|
||||||
|
public float getPreciseDuration() {
|
||||||
|
return (float) (getDuration().doubleValue() / 10000000d);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Returns the timeEndPos.
|
||||||
|
*/
|
||||||
|
public BigInteger getTimeEndPos() {
|
||||||
|
return this.timeEndPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Returns the timeStartPos.
|
||||||
|
*/
|
||||||
|
public BigInteger getTimeStartPos() {
|
||||||
|
return this.timeStartPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Returns the uncompressedFrameSize.
|
||||||
|
*/
|
||||||
|
public long getUncompressedFrameSize() {
|
||||||
|
return this.uncompressedFrameSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (overridden)
|
||||||
|
*
|
||||||
|
* @see org.jaudiotagger.audio.asf.data.Chunk#prettyPrint(String)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String prettyPrint(final String prefix) {
|
||||||
|
final StringBuilder result = new StringBuilder(super.prettyPrint(prefix));
|
||||||
|
result.append(prefix).append(" |-> Filesize = ").append(
|
||||||
|
getFileSize().toString()).append(" Bytes").append(
|
||||||
|
Utils.LINE_SEPARATOR);
|
||||||
|
result.append(prefix).append(" |-> Media duration= ").append(
|
||||||
|
getDuration().divide(new BigInteger("10000")).toString())
|
||||||
|
.append(" ms").append(Utils.LINE_SEPARATOR);
|
||||||
|
result.append(prefix).append(" |-> Created at = ").append(
|
||||||
|
getFileCreationTime()).append(Utils.LINE_SEPARATOR);
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
}
|
527
src/main/java/org/jaudiotagger/audio/asf/data/GUID.java
Normal file
527
src/main/java/org/jaudiotagger/audio/asf/data/GUID.java
Normal file
|
@ -0,0 +1,527 @@
|
||||||
|
/*
|
||||||
|
* Entagged Audio Tag library
|
||||||
|
* Copyright (c) 2004-2005 Christian Laireiter <liree@web.de>
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this library; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
package org.jaudiotagger.audio.asf.data;
|
||||||
|
|
||||||
|
import org.jaudiotagger.audio.asf.util.Utils;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is used for representation of GUIDs and as a reference list of all
|
||||||
|
* Known GUIDs. <br>
|
||||||
|
*
|
||||||
|
* @author Christian Laireiter
|
||||||
|
*/
|
||||||
|
public final class GUID {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This constant defines the GUID for stream chunks describing audio
|
||||||
|
* streams, indicating the the audio stream has no error concealment. <br>
|
||||||
|
*/
|
||||||
|
public final static GUID GUID_AUDIO_ERROR_CONCEALEMENT_ABSENT = new GUID(
|
||||||
|
new int[]{0x40, 0xA4, 0xF1, 0x49, 0xCE, 0x4E, 0xD0, 0x11, 0xA3,
|
||||||
|
0xAC, 0x00, 0xA0, 0xC9, 0x03, 0x48, 0xF6},
|
||||||
|
"Audio error concealment absent.");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This constant defines the GUID for stream chunks describing audio
|
||||||
|
* streams, indicating the the audio stream has interleaved error
|
||||||
|
* concealment. <br>
|
||||||
|
*/
|
||||||
|
public final static GUID GUID_AUDIO_ERROR_CONCEALEMENT_INTERLEAVED = new GUID(
|
||||||
|
new int[]{0x40, 0xA4, 0xF1, 0x49, 0xCE, 0x4E, 0xD0, 0x11, 0xA3,
|
||||||
|
0xAC, 0x00, 0xA0, 0xC9, 0x03, 0x48, 0xF6},
|
||||||
|
"Interleaved audio error concealment.");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This constant stores the GUID indicating that stream type is audio.
|
||||||
|
*/
|
||||||
|
public final static GUID GUID_AUDIOSTREAM = new GUID(new int[]{0x40,
|
||||||
|
0x9E, 0x69, 0xF8, 0x4D, 0x5B, 0xCF, 0x11, 0xA8, 0xFD, 0x00, 0x80,
|
||||||
|
0x5F, 0x5C, 0x44, 0x2B}, " Audio stream");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This constant stores the GUID indicating a content branding object.
|
||||||
|
*/
|
||||||
|
public final static GUID GUID_CONTENT_BRANDING = new GUID(new int[]{0xFA,
|
||||||
|
0xB3, 0x11, 0x22, 0x23, 0xBD, 0xD2, 0x11, 0xB4, 0xB7, 0x00, 0xA0,
|
||||||
|
0xC9, 0x55, 0xFC, 0x6E}, "Content Branding");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is for the Content Encryption Object
|
||||||
|
* 2211B3FB-BD23-11D2-B4B7-00A0C955FC6E, needs to be little-endian.
|
||||||
|
*/
|
||||||
|
public final static GUID GUID_CONTENT_ENCRYPTION = new GUID(new int[]{
|
||||||
|
0xfb, 0xb3, 0x11, 0x22, 0x23, 0xbd, 0xd2, 0x11, 0xb4, 0xb7, 0x00,
|
||||||
|
0xa0, 0xc9, 0x55, 0xfc, 0x6e}, "Content Encryption Object");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This constant represents the guidData for a chunk which contains Title,
|
||||||
|
* author, copyright, description and rating.
|
||||||
|
*/
|
||||||
|
public final static GUID GUID_CONTENTDESCRIPTION = new GUID(new int[]{
|
||||||
|
0x33, 0x26, 0xB2, 0x75, 0x8E, 0x66, 0xCF, 0x11, 0xA6, 0xD9, 0x00,
|
||||||
|
0xAA, 0x00, 0x62, 0xCE, 0x6C}, "Content Description");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This constant stores the GUID for Encoding-Info chunks.
|
||||||
|
*/
|
||||||
|
public final static GUID GUID_ENCODING = new GUID(new int[]{0x40, 0x52,
|
||||||
|
0xD1, 0x86, 0x1D, 0x31, 0xD0, 0x11, 0xA3, 0xA4, 0x00, 0xA0, 0xC9,
|
||||||
|
0x03, 0x48, 0xF6}, "Encoding description");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This constant defines the GUID for a WMA "Extended Content Description"
|
||||||
|
* chunk. <br>
|
||||||
|
*/
|
||||||
|
public final static GUID GUID_EXTENDED_CONTENT_DESCRIPTION = new GUID(
|
||||||
|
new int[]{0x40, 0xA4, 0xD0, 0xD2, 0x07, 0xE3, 0xD2, 0x11, 0x97,
|
||||||
|
0xF0, 0x00, 0xA0, 0xC9, 0x5E, 0xA8, 0x50},
|
||||||
|
"Extended Content Description");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GUID of ASF file header.
|
||||||
|
*/
|
||||||
|
public final static GUID GUID_FILE = new GUID(new int[]{0xA1, 0xDC, 0xAB,
|
||||||
|
0x8C, 0x47, 0xA9, 0xCF, 0x11, 0x8E, 0xE4, 0x00, 0xC0, 0x0C, 0x20,
|
||||||
|
0x53, 0x65}, "File header");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This constant defines the GUID of a asf header chunk.
|
||||||
|
*/
|
||||||
|
public final static GUID GUID_HEADER = new GUID(new int[]{0x30, 0x26,
|
||||||
|
0xb2, 0x75, 0x8e, 0x66, 0xcf, 0x11, 0xa6, 0xd9, 0x00, 0xaa, 0x00,
|
||||||
|
0x62, 0xce, 0x6c}, "Asf header");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This constant stores a GUID whose functionality is unknown.
|
||||||
|
*/
|
||||||
|
public final static GUID GUID_HEADER_EXTENSION = new GUID(new int[]{0xB5,
|
||||||
|
0x03, 0xBF, 0x5F, 0x2E, 0xA9, 0xCF, 0x11, 0x8E, 0xE3, 0x00, 0xC0,
|
||||||
|
0x0C, 0x20, 0x53, 0x65}, "Header Extension");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This constant stores the GUID indicating the asf language list object.<br>
|
||||||
|
*/
|
||||||
|
public final static GUID GUID_LANGUAGE_LIST = new GUID(new int[]{0xa9,
|
||||||
|
0x46, 0x43, 0x7c, 0xe0, 0xef, 0xfc, 0x4b, 0xb2, 0x29, 0x39, 0x3e,
|
||||||
|
0xde, 0x41, 0x5c, 0x85}, "Language List");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This constant stores the length of GUIDs used with ASF streams. <br>
|
||||||
|
*/
|
||||||
|
public final static int GUID_LENGTH = 16;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This constant stores the GUID indicating the asf metadata object.<br>
|
||||||
|
*/
|
||||||
|
public final static GUID GUID_METADATA = new GUID(new int[]{0xea, 0xcb,
|
||||||
|
0xf8, 0xc5, 0xaf, 0x5b, 0x77, 0x48, 0x84, 0x67, 0xaa, 0x8c, 0x44,
|
||||||
|
0xfa, 0x4c, 0xca}, "Metadata");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This constant stores the GUID indicating the asf metadata library object.<br>
|
||||||
|
*/
|
||||||
|
public final static GUID GUID_METADATA_LIBRARY = new GUID(new int[]{0x94,
|
||||||
|
0x1c, 0x23, 0x44, 0x98, 0x94, 0xd1, 0x49, 0xa1, 0x41, 0x1d, 0x13,
|
||||||
|
0x4e, 0x45, 0x70, 0x54}, "Metadata Library");
|
||||||
|
/**
|
||||||
|
* This constant stores the GUID indicating a stream object.
|
||||||
|
*/
|
||||||
|
public final static GUID GUID_STREAM = new GUID(new int[]{0x91, 0x07,
|
||||||
|
0xDC, 0xB7, 0xB7, 0xA9, 0xCF, 0x11, 0x8E, 0xE6, 0x00, 0xC0, 0x0C,
|
||||||
|
0x20, 0x53, 0x65}, "Stream");
|
||||||
|
/**
|
||||||
|
* This constant stores a GUID indicating a "stream bitrate properties"
|
||||||
|
* chunk.
|
||||||
|
*/
|
||||||
|
public final static GUID GUID_STREAM_BITRATE_PROPERTIES = new GUID(
|
||||||
|
new int[]{0xCE, 0x75, 0xF8, 0x7B, 0x8D, 0x46, 0xD1, 0x11, 0x8D,
|
||||||
|
0x82, 0x00, 0x60, 0x97, 0xC9, 0xA2, 0xB2},
|
||||||
|
"Stream bitrate properties");
|
||||||
|
/**
|
||||||
|
* This constant represents a GUID implementation which can be used for
|
||||||
|
* generic implementations, which have to provide a GUID, but do not really
|
||||||
|
* require a specific GUID to work.
|
||||||
|
*/
|
||||||
|
public final static GUID GUID_UNSPECIFIED = new GUID(new int[]{0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00}, "Unspecified");
|
||||||
|
/**
|
||||||
|
* This constant stores the GUID indicating that stream type is video.
|
||||||
|
*/
|
||||||
|
public final static GUID GUID_VIDEOSTREAM = new GUID(new int[]{0xC0,
|
||||||
|
0xEF, 0x19, 0xBC, 0x4D, 0x5B, 0xCF, 0x11, 0xA8, 0xFD, 0x00, 0x80,
|
||||||
|
0x5F, 0x5C, 0x44, 0x2B}, "Video stream");
|
||||||
|
/**
|
||||||
|
* This field stores all known GUIDs.
|
||||||
|
*/
|
||||||
|
public final static GUID[] KNOWN_GUIDS;
|
||||||
|
/**
|
||||||
|
* This constant stores the GUID for a "script command object".<br>
|
||||||
|
*/
|
||||||
|
public final static GUID SCRIPT_COMMAND_OBJECT = new GUID(new int[]{0x30,
|
||||||
|
0x1a, 0xfb, 0x1e, 0x62, 0x0b, 0xd0, 0x11, 0xa3, 0x9b, 0x00, 0xa0,
|
||||||
|
0xc9, 0x03, 0x48, 0xf6}, "Script Command Object");
|
||||||
|
/**
|
||||||
|
* The GUID String values format.<br>
|
||||||
|
*/
|
||||||
|
private final static Pattern GUID_PATTERN = Pattern
|
||||||
|
.compile(
|
||||||
|
"[a-f0-9]{8}\\-[a-f0-9]{4}\\-[a-f0-9]{4}\\-[a-f0-9]{4}\\-[a-f0-9]{12}",
|
||||||
|
Pattern.CASE_INSENSITIVE);
|
||||||
|
/**
|
||||||
|
* This map is used, to get the description of a GUID instance, which has
|
||||||
|
* been created by reading.<br>
|
||||||
|
* The map comparison is done against the {@link GUID#guidData} field. But
|
||||||
|
* only the {@link #KNOWN_GUIDS} have a description set.
|
||||||
|
*/
|
||||||
|
private final static Map<GUID, GUID> GUID_TO_CONFIGURED;
|
||||||
|
|
||||||
|
static {
|
||||||
|
KNOWN_GUIDS = new GUID[]{GUID_AUDIO_ERROR_CONCEALEMENT_ABSENT,
|
||||||
|
GUID_CONTENTDESCRIPTION, GUID_AUDIOSTREAM, GUID_ENCODING,
|
||||||
|
GUID_FILE, GUID_HEADER, GUID_STREAM,
|
||||||
|
GUID_EXTENDED_CONTENT_DESCRIPTION, GUID_VIDEOSTREAM,
|
||||||
|
GUID_HEADER_EXTENSION, GUID_STREAM_BITRATE_PROPERTIES,
|
||||||
|
SCRIPT_COMMAND_OBJECT, GUID_CONTENT_ENCRYPTION,
|
||||||
|
GUID_CONTENT_BRANDING, GUID_UNSPECIFIED, GUID_METADATA_LIBRARY,
|
||||||
|
GUID_METADATA, GUID_LANGUAGE_LIST};
|
||||||
|
GUID_TO_CONFIGURED = new HashMap<GUID, GUID>(KNOWN_GUIDS.length);
|
||||||
|
for (final GUID curr : KNOWN_GUIDS) {
|
||||||
|
assert !GUID_TO_CONFIGURED.containsKey(curr) : "Double definition: \""
|
||||||
|
+ GUID_TO_CONFIGURED.get(curr).getDescription()
|
||||||
|
+ "\" <-> \"" + curr.getDescription() + "\"";
|
||||||
|
GUID_TO_CONFIGURED.put(curr, curr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores an optionally description of the GUID.
|
||||||
|
*/
|
||||||
|
private String description = "";
|
||||||
|
/**
|
||||||
|
* An instance of this class stores the value of the wrapped GUID in this
|
||||||
|
* field. <br>
|
||||||
|
*/
|
||||||
|
private int[] guidData = null;
|
||||||
|
/**
|
||||||
|
* Stores the hash code of the object.<br>
|
||||||
|
* <code>"-1"</code> if not determined yet.
|
||||||
|
*/
|
||||||
|
private int hash;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance and assigns given <code>value</code>.<br>
|
||||||
|
*
|
||||||
|
* @param value GUID, which should be assigned. (will be converted to int[])
|
||||||
|
*/
|
||||||
|
public GUID(final byte[] value) {
|
||||||
|
assert value != null;
|
||||||
|
final int[] tmp = new int[value.length];
|
||||||
|
for (int i = 0; i < value.length; i++) {
|
||||||
|
tmp[i] = (0xFF & value[i]);
|
||||||
|
}
|
||||||
|
setGUID(tmp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance and assigns given <code>value</code>.<br>
|
||||||
|
*
|
||||||
|
* @param value GUID, which should be assigned.
|
||||||
|
*/
|
||||||
|
public GUID(final int[] value) {
|
||||||
|
setGUID(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance like {@link #GUID(int[])}and sets the optional
|
||||||
|
* description. <br>
|
||||||
|
*
|
||||||
|
* @param value GUID, which should be assigned.
|
||||||
|
* @param desc Description for the GUID.
|
||||||
|
*/
|
||||||
|
public GUID(final int[] value, final String desc) {
|
||||||
|
this(value);
|
||||||
|
if (desc == null) {
|
||||||
|
throw new IllegalArgumentException("Argument must not be null.");
|
||||||
|
}
|
||||||
|
this.description = desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance like {@link #GUID(int[])} and sets the optional
|
||||||
|
* description. (the int[] is obtained by {@link GUID#parseGUID(String)}) <br>
|
||||||
|
*
|
||||||
|
* @param guidString GUID, which should be assigned.
|
||||||
|
* @param desc Description for the GUID.
|
||||||
|
*/
|
||||||
|
public GUID(final String guidString, final String desc) {
|
||||||
|
this(parseGUID(guidString).getGUID());
|
||||||
|
if (desc == null) {
|
||||||
|
throw new IllegalArgumentException("Argument must not be null.");
|
||||||
|
}
|
||||||
|
this.description = desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method checks if the given <code>value</code> is matching the GUID
|
||||||
|
* specification of ASF streams. <br>
|
||||||
|
*
|
||||||
|
* @param value possible GUID.
|
||||||
|
* @return <code>true</code> if <code>value</code> matches the specification
|
||||||
|
* of a GUID.
|
||||||
|
*/
|
||||||
|
public static boolean assertGUID(final int[] value) {
|
||||||
|
return value != null && value.length == GUID.GUID_LENGTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method looks up a GUID instance from {@link #KNOWN_GUIDS} which
|
||||||
|
* matches the value of the given GUID.
|
||||||
|
*
|
||||||
|
* @param orig GUID to look up.
|
||||||
|
* @return a GUID instance from {@link #KNOWN_GUIDS} if available.
|
||||||
|
* <code>null</code> else.
|
||||||
|
*/
|
||||||
|
public static GUID getConfigured(final GUID orig) {
|
||||||
|
// safe against null
|
||||||
|
return GUID_TO_CONFIGURED.get(orig);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method searches a GUID in {@link #KNOWN_GUIDS}which is equal to the
|
||||||
|
* given <code>guidData</code> and returns its description. <br>
|
||||||
|
* This method is useful if a GUID was read out of a file and no
|
||||||
|
* identification has been done yet.
|
||||||
|
*
|
||||||
|
* @param guid GUID, which description is needed.
|
||||||
|
* @return description of the GUID if found. Else <code>null</code>
|
||||||
|
*/
|
||||||
|
public static String getGuidDescription(final GUID guid) {
|
||||||
|
String result = null;
|
||||||
|
if (guid == null) {
|
||||||
|
throw new IllegalArgumentException("Argument must not be null.");
|
||||||
|
}
|
||||||
|
if (getConfigured(guid) != null) {
|
||||||
|
result = getConfigured(guid).getDescription();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method parses a String as GUID.<br>
|
||||||
|
* The format is like the one in the ASF specification.<br>
|
||||||
|
* An Example: <code>C5F8CBEA-5BAF-4877-8467-AA8C44FA4CCA</code><br>
|
||||||
|
*
|
||||||
|
* @param guid the string to parse.
|
||||||
|
* @return the GUID.
|
||||||
|
* @throws GUIDFormatException If the GUID has an invalid format.
|
||||||
|
*/
|
||||||
|
public static GUID parseGUID(final String guid) throws GUIDFormatException {
|
||||||
|
if (guid == null) {
|
||||||
|
throw new GUIDFormatException("null");
|
||||||
|
}
|
||||||
|
if (!GUID_PATTERN.matcher(guid).matches()) {
|
||||||
|
throw new GUIDFormatException("Invalid guidData format.");
|
||||||
|
}
|
||||||
|
final int[] bytes = new int[GUID_LENGTH];
|
||||||
|
/*
|
||||||
|
* Don't laugh, but did not really come up with a nicer solution today
|
||||||
|
*/
|
||||||
|
final int[] arrayIndices = {3, 2, 1, 0, 5, 4, 7, 6, 8, 9, 10, 11, 12,
|
||||||
|
13, 14, 15};
|
||||||
|
int arrayPointer = 0;
|
||||||
|
for (int i = 0; i < guid.length(); i++) {
|
||||||
|
if (guid.charAt(i) == '-') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
bytes[arrayIndices[arrayPointer++]] = Integer.parseInt(guid
|
||||||
|
.substring(i, i + 2), 16);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return new GUID(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method compares two objects. If the given Object is a {@link GUID},
|
||||||
|
* the stored GUID values are compared. <br>
|
||||||
|
*
|
||||||
|
* @see java.lang.Object#equals(java.lang.Object)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean equals(final Object obj) {
|
||||||
|
boolean result = false;
|
||||||
|
if (obj instanceof GUID) {
|
||||||
|
final GUID other = (GUID) obj;
|
||||||
|
result = Arrays.equals(this.getGUID(), other.getGUID());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method returns the GUID as an array of bytes. <br>
|
||||||
|
*
|
||||||
|
* @return The GUID as a byte array.
|
||||||
|
* @see #getGUID()
|
||||||
|
*/
|
||||||
|
public byte[] getBytes() {
|
||||||
|
final byte[] result = new byte[this.guidData.length];
|
||||||
|
for (int i = 0; i < result.length; i++) {
|
||||||
|
result[i] = (byte) (this.guidData[i] & 0xFF);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Returns the description.
|
||||||
|
*/
|
||||||
|
public String getDescription() {
|
||||||
|
return this.description;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method returns the GUID of this object. <br>
|
||||||
|
*
|
||||||
|
* @return stored GUID.
|
||||||
|
*/
|
||||||
|
public int[] getGUID() {
|
||||||
|
final int[] copy = new int[this.guidData.length];
|
||||||
|
System.arraycopy(this.guidData, 0, copy, 0, this.guidData.length);
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method saves a copy of the given <code>value</code> as the
|
||||||
|
* represented value of this object. <br>
|
||||||
|
* The given value is checked with {@link #assertGUID(int[])}.<br>
|
||||||
|
*
|
||||||
|
* @param value GUID to assign.
|
||||||
|
*/
|
||||||
|
private void setGUID(final int[] value) {
|
||||||
|
if (assertGUID(value)) {
|
||||||
|
this.guidData = new int[GUID_LENGTH];
|
||||||
|
System.arraycopy(value, 0, this.guidData, 0, GUID_LENGTH);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"The given guidData doesn't match the GUID specification.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience method to get 2digit hex values of each byte.
|
||||||
|
*
|
||||||
|
* @param bytes bytes to convert.
|
||||||
|
* @return each byte as 2 digit hex.
|
||||||
|
*/
|
||||||
|
private String[] getHex(final byte[] bytes) {
|
||||||
|
final String[] result = new String[bytes.length];
|
||||||
|
final StringBuilder tmp = new StringBuilder();
|
||||||
|
for (int i = 0; i < bytes.length; i++) {
|
||||||
|
tmp.delete(0, tmp.length());
|
||||||
|
tmp.append(Integer.toHexString(0xFF & bytes[i]));
|
||||||
|
if (tmp.length() == 1) {
|
||||||
|
tmp.insert(0, "0");
|
||||||
|
}
|
||||||
|
result[i] = tmp.toString();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
if (this.hash == -1) {
|
||||||
|
int tmp = 0;
|
||||||
|
for (final int curr : getGUID()) {
|
||||||
|
tmp = tmp * 31 + curr;
|
||||||
|
}
|
||||||
|
this.hash = tmp;
|
||||||
|
}
|
||||||
|
return this.hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method checks if the currently stored GUID ({@link #guidData}) is
|
||||||
|
* correctly filled. <br>
|
||||||
|
*
|
||||||
|
* @return <code>true</code> if it is.
|
||||||
|
*/
|
||||||
|
public boolean isValid() {
|
||||||
|
return assertGUID(getGUID());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method gives a hex formatted representation of {@link #getGUID()}
|
||||||
|
*
|
||||||
|
* @return hex formatted representation.
|
||||||
|
*/
|
||||||
|
public String prettyPrint() {
|
||||||
|
final StringBuilder result = new StringBuilder();
|
||||||
|
String descr = getDescription();
|
||||||
|
if (Utils.isBlank(descr)) {
|
||||||
|
descr = getGuidDescription(this);
|
||||||
|
}
|
||||||
|
if (!Utils.isBlank(descr)) {
|
||||||
|
result.append("Description: ").append(descr).append(
|
||||||
|
Utils.LINE_SEPARATOR).append(" ");
|
||||||
|
}
|
||||||
|
result.append(this.toString());
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
// C5F8CBEA-5BAF-4877-8467-AA8C44FA4CCA
|
||||||
|
// 0xea, 0xcb,0xf8, 0xc5, 0xaf, 0x5b, 0x77, 0x48, 0x84, 0x67, 0xaa,
|
||||||
|
// 0x8c, 0x44,0xfa, 0x4c, 0xca
|
||||||
|
final StringBuilder result = new StringBuilder();
|
||||||
|
final String[] bytes = getHex(getBytes());
|
||||||
|
result.append(bytes[3]);
|
||||||
|
result.append(bytes[2]);
|
||||||
|
result.append(bytes[1]);
|
||||||
|
result.append(bytes[0]);
|
||||||
|
result.append('-');
|
||||||
|
result.append(bytes[5]);
|
||||||
|
result.append(bytes[4]);
|
||||||
|
result.append('-');
|
||||||
|
result.append(bytes[7]);
|
||||||
|
result.append(bytes[6]);
|
||||||
|
result.append('-');
|
||||||
|
result.append(bytes[8]);
|
||||||
|
result.append(bytes[9]);
|
||||||
|
result.append('-');
|
||||||
|
result.append(bytes[10]);
|
||||||
|
result.append(bytes[11]);
|
||||||
|
result.append(bytes[12]);
|
||||||
|
result.append(bytes[13]);
|
||||||
|
result.append(bytes[14]);
|
||||||
|
result.append(bytes[15]);
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package org.jaudiotagger.audio.asf.data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This exception is used when a string was about to be interpreted as a GUID,
|
||||||
|
* but did not match the format.<br>
|
||||||
|
*
|
||||||
|
* @author Christian Laireiter
|
||||||
|
*/
|
||||||
|
public class GUIDFormatException extends IllegalArgumentException {
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private static final long serialVersionUID = 6035645678612384953L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance.
|
||||||
|
*
|
||||||
|
* @param detail detail message.
|
||||||
|
*/
|
||||||
|
public GUIDFormatException(final String detail) {
|
||||||
|
super(detail);
|
||||||
|
}
|
||||||
|
}
|
113
src/main/java/org/jaudiotagger/audio/asf/data/LanguageList.java
Normal file
113
src/main/java/org/jaudiotagger/audio/asf/data/LanguageList.java
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
package org.jaudiotagger.audio.asf.data;
|
||||||
|
|
||||||
|
import org.jaudiotagger.audio.asf.util.Utils;
|
||||||
|
import org.jaudiotagger.logging.ErrorMessage;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This structure represents the data of the ASF language object.<br>
|
||||||
|
* The language list is simply a listing of language codes which should comply
|
||||||
|
* to RFC-1766.<br>
|
||||||
|
* <b>Consider:</b> the index of a language is used by other entries in the ASF
|
||||||
|
* metadata.
|
||||||
|
*
|
||||||
|
* @author Christian Laireiter
|
||||||
|
*/
|
||||||
|
public class LanguageList extends Chunk {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of language codes, complying RFC-1766
|
||||||
|
*/
|
||||||
|
private final List<String> languages = new ArrayList<String>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance.<br>
|
||||||
|
*/
|
||||||
|
public LanguageList() {
|
||||||
|
super(GUID.GUID_LANGUAGE_LIST, 0, BigInteger.ZERO);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance.
|
||||||
|
*
|
||||||
|
* @param pos position within the ASF file.
|
||||||
|
* @param size size of the chunk
|
||||||
|
*/
|
||||||
|
public LanguageList(final long pos, final BigInteger size) {
|
||||||
|
super(GUID.GUID_LANGUAGE_LIST, pos, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method adds a language.<br>
|
||||||
|
*
|
||||||
|
* @param language language code
|
||||||
|
*/
|
||||||
|
public void addLanguage(final String language) {
|
||||||
|
if (language.length() < MetadataDescriptor.MAX_LANG_INDEX) {
|
||||||
|
if (!this.languages.contains(language)) {
|
||||||
|
this.languages.add(language);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
ErrorMessage.WMA_LENGTH_OF_LANGUAGE_IS_TOO_LARGE
|
||||||
|
.getMsg(language.length() * 2 + 2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the language code at the specified index.
|
||||||
|
*
|
||||||
|
* @param index the index of the language code to get.
|
||||||
|
* @return the language code at given index.
|
||||||
|
*/
|
||||||
|
public String getLanguage(final int index) {
|
||||||
|
return this.languages.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the amount of stored language codes.
|
||||||
|
*
|
||||||
|
* @return number of stored language codes.
|
||||||
|
*/
|
||||||
|
public int getLanguageCount() {
|
||||||
|
return this.languages.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all language codes in list.
|
||||||
|
*
|
||||||
|
* @return list of language codes.
|
||||||
|
*/
|
||||||
|
public List<String> getLanguages() {
|
||||||
|
return new ArrayList<String>(this.languages);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String prettyPrint(final String prefix) {
|
||||||
|
final StringBuilder result = new StringBuilder(super.prettyPrint(prefix));
|
||||||
|
for (int i = 0; i < getLanguageCount(); i++) {
|
||||||
|
result.append(prefix);
|
||||||
|
result.append(" |-> ");
|
||||||
|
result.append(i);
|
||||||
|
result.append(" : ");
|
||||||
|
result.append(getLanguage(i));
|
||||||
|
result.append(Utils.LINE_SEPARATOR);
|
||||||
|
}
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the language entry at specified index.
|
||||||
|
*
|
||||||
|
* @param index index of language to remove.
|
||||||
|
*/
|
||||||
|
public void removeLanguage(final int index) {
|
||||||
|
this.languages.remove(index);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,443 @@
|
||||||
|
package org.jaudiotagger.audio.asf.data;
|
||||||
|
|
||||||
|
import org.jaudiotagger.audio.asf.io.WriteableChunk;
|
||||||
|
import org.jaudiotagger.audio.asf.util.Utils;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This structure represents the "Metadata Object","Metadata
|
||||||
|
* Library Object" and "Extended Content Description".<br>
|
||||||
|
*
|
||||||
|
* @author Christian Laireiter
|
||||||
|
*/
|
||||||
|
public class MetadataContainer extends Chunk implements WriteableChunk {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* stores the represented container type.<br>
|
||||||
|
*/
|
||||||
|
private final ContainerType containerType;
|
||||||
|
/**
|
||||||
|
* Stores the descriptors.
|
||||||
|
*/
|
||||||
|
private final Map<DescriptorPointer, List<MetadataDescriptor>> descriptors = new Hashtable<DescriptorPointer, List<MetadataDescriptor>>();
|
||||||
|
/**
|
||||||
|
* for performance reasons this instance is used to look up existing
|
||||||
|
* descriptors in {@link #descriptors}.<br>
|
||||||
|
*/
|
||||||
|
private final DescriptorPointer perfPoint = new DescriptorPointer(
|
||||||
|
new MetadataDescriptor(""));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance.
|
||||||
|
*
|
||||||
|
* @param type determines the type of the container
|
||||||
|
*/
|
||||||
|
public MetadataContainer(final ContainerType type) {
|
||||||
|
this(type, 0, BigInteger.ZERO);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance.
|
||||||
|
*
|
||||||
|
* @param type determines the type of the container
|
||||||
|
* @param pos location in the ASF file
|
||||||
|
* @param size size of the chunk.
|
||||||
|
*/
|
||||||
|
public MetadataContainer(final ContainerType type, final long pos,
|
||||||
|
final BigInteger size) {
|
||||||
|
super(type.getContainerGUID(), pos, size);
|
||||||
|
this.containerType = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance.
|
||||||
|
*
|
||||||
|
* @param containerGUID the containers GUID
|
||||||
|
* @param pos location in the ASF file
|
||||||
|
* @param size size of the chunk.
|
||||||
|
*/
|
||||||
|
public MetadataContainer(final GUID containerGUID, final long pos,
|
||||||
|
final BigInteger size) {
|
||||||
|
this(determineType(containerGUID), pos, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Looks up all {@linkplain ContainerType#getContainerGUID() guids} and
|
||||||
|
* returns the matching type.
|
||||||
|
*
|
||||||
|
* @param guid GUID to look up
|
||||||
|
* @return matching container type.
|
||||||
|
* @throws IllegalArgumentException if no container type matches
|
||||||
|
*/
|
||||||
|
private static ContainerType determineType(final GUID guid)
|
||||||
|
throws IllegalArgumentException {
|
||||||
|
assert guid != null;
|
||||||
|
ContainerType result = null;
|
||||||
|
for (final ContainerType curr : ContainerType.values()) {
|
||||||
|
if (curr.getContainerGUID().equals(guid)) {
|
||||||
|
result = curr;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (result == null) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Unknown metadata container specified by GUID ("
|
||||||
|
+ guid.toString() + ")");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a metadata descriptor.
|
||||||
|
*
|
||||||
|
* @param toAdd the descriptor to add.
|
||||||
|
* @throws IllegalArgumentException if descriptor does not meet container requirements, or
|
||||||
|
* already exist.
|
||||||
|
*/
|
||||||
|
public final void addDescriptor(final MetadataDescriptor toAdd)
|
||||||
|
throws IllegalArgumentException {
|
||||||
|
// check with throwing exceptions
|
||||||
|
this.containerType.assertConstraints(toAdd.getName(), toAdd
|
||||||
|
.getRawData(), toAdd.getType(), toAdd.getStreamNumber(), toAdd
|
||||||
|
.getLanguageIndex());
|
||||||
|
// validate containers capabilities
|
||||||
|
if (!isAddSupported(toAdd)) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Descriptor cannot be added, see isAddSupported(...)");
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* Check for containers types capabilities.
|
||||||
|
*/
|
||||||
|
// Search for descriptor list by name, language and stream.
|
||||||
|
List<MetadataDescriptor> list;
|
||||||
|
synchronized (this.perfPoint) {
|
||||||
|
list = this.descriptors.get(this.perfPoint.setDescriptor(toAdd));
|
||||||
|
}
|
||||||
|
if (list == null) {
|
||||||
|
list = new ArrayList<MetadataDescriptor>();
|
||||||
|
this.descriptors.put(new DescriptorPointer(toAdd), list);
|
||||||
|
} else {
|
||||||
|
if (!list.isEmpty() && !this.containerType.isMultiValued()) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Container does not allow multiple values of descriptors with same name, language index and stream number");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
list.add(toAdd);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method asserts that this container has a descriptor with the
|
||||||
|
* specified key, means returns an existing or creates a new descriptor.
|
||||||
|
*
|
||||||
|
* @param key the descriptor name to look up (or create)
|
||||||
|
* @return the/a descriptor with the specified name (and initial type of
|
||||||
|
* {@link MetadataDescriptor#TYPE_STRING}.
|
||||||
|
*/
|
||||||
|
protected final MetadataDescriptor assertDescriptor(final String key) {
|
||||||
|
return assertDescriptor(key, MetadataDescriptor.TYPE_STRING);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method asserts that this container has a descriptor with the
|
||||||
|
* specified key, means returns an existing or creates a new descriptor.
|
||||||
|
*
|
||||||
|
* @param key the descriptor name to look up (or create)
|
||||||
|
* @param type if the descriptor is created, this data type is applied.
|
||||||
|
* @return the/a descriptor with the specified name.
|
||||||
|
*/
|
||||||
|
protected final MetadataDescriptor assertDescriptor(final String key,
|
||||||
|
final int type) {
|
||||||
|
MetadataDescriptor desc;
|
||||||
|
final List<MetadataDescriptor> descriptorsByName = getDescriptorsByName(key);
|
||||||
|
if (descriptorsByName == null || descriptorsByName.isEmpty()) {
|
||||||
|
desc = new MetadataDescriptor(getContainerType(), key, type);
|
||||||
|
addDescriptor(desc);
|
||||||
|
} else {
|
||||||
|
desc = descriptorsByName.get(0);
|
||||||
|
}
|
||||||
|
return desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether a descriptor already exists.<br>
|
||||||
|
* Name, stream number and language index are compared. Data and data type
|
||||||
|
* are ignored.
|
||||||
|
*
|
||||||
|
* @param lookup descriptor to look up.
|
||||||
|
* @return <code>true</code> if such a descriptor already exists.
|
||||||
|
*/
|
||||||
|
public final boolean containsDescriptor(final MetadataDescriptor lookup) {
|
||||||
|
assert lookup != null;
|
||||||
|
return this.descriptors.containsKey(this.perfPoint
|
||||||
|
.setDescriptor(lookup));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the type of container this instance represents.<br>
|
||||||
|
*
|
||||||
|
* @return represented container type.
|
||||||
|
*/
|
||||||
|
public final ContainerType getContainerType() {
|
||||||
|
return this.containerType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public long getCurrentAsfChunkSize() {
|
||||||
|
/*
|
||||||
|
* 16 bytes GUID, 8 bytes chunk size, 2 bytes descriptor count
|
||||||
|
*/
|
||||||
|
long result = 26;
|
||||||
|
for (final MetadataDescriptor curr : getDescriptors()) {
|
||||||
|
result += curr.getCurrentAsfSize(this.containerType);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of contained descriptors.
|
||||||
|
*
|
||||||
|
* @return number of descriptors.
|
||||||
|
*/
|
||||||
|
public final int getDescriptorCount() {
|
||||||
|
return this.getDescriptors().size();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all stored descriptors.
|
||||||
|
*
|
||||||
|
* @return stored descriptors.
|
||||||
|
*/
|
||||||
|
public final List<MetadataDescriptor> getDescriptors() {
|
||||||
|
final List<MetadataDescriptor> result = new ArrayList<MetadataDescriptor>();
|
||||||
|
for (final List<MetadataDescriptor> curr : this.descriptors.values()) {
|
||||||
|
result.addAll(curr);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of descriptors with the given
|
||||||
|
* {@linkplain MetadataDescriptor#getName() name}.<br>
|
||||||
|
*
|
||||||
|
* @param name name of the descriptors to return
|
||||||
|
* @return list of descriptors with given name.
|
||||||
|
*/
|
||||||
|
public final List<MetadataDescriptor> getDescriptorsByName(final String name) {
|
||||||
|
assert name != null;
|
||||||
|
final List<MetadataDescriptor> result = new ArrayList<MetadataDescriptor>();
|
||||||
|
final Collection<List<MetadataDescriptor>> values = this.descriptors
|
||||||
|
.values();
|
||||||
|
for (final List<MetadataDescriptor> currList : values) {
|
||||||
|
if (!currList.isEmpty() && currList.get(0).getName().equals(name)) {
|
||||||
|
result.addAll(currList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method looks up a descriptor with given name and returns its value
|
||||||
|
* as string.<br>
|
||||||
|
*
|
||||||
|
* @param name the name of the descriptor to look up.
|
||||||
|
* @return the string representation of a found descriptors value. Even an
|
||||||
|
* empty string if no descriptor has been found.
|
||||||
|
*/
|
||||||
|
protected final String getValueFor(final String name) {
|
||||||
|
String result = "";
|
||||||
|
final List<MetadataDescriptor> descs = getDescriptorsByName(name);
|
||||||
|
if (descs != null) {
|
||||||
|
assert descs.size() <= 1;
|
||||||
|
if (!descs.isEmpty()) {
|
||||||
|
result = descs.get(0).getString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if this container contains a descriptor with given
|
||||||
|
* {@linkplain MetadataDescriptor#getName() name}.<br>
|
||||||
|
*
|
||||||
|
* @param name Name of the descriptor to look for.
|
||||||
|
* @return <code>true</code> if descriptor has been found.
|
||||||
|
*/
|
||||||
|
public final boolean hasDescriptor(final String name) {
|
||||||
|
return !getDescriptorsByName(name).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines/checks if the given descriptor may be added to the container.<br>
|
||||||
|
* This implies a check for the capabilities of the container specified by
|
||||||
|
* its {@linkplain #getContainerType() container type}.<br>
|
||||||
|
*
|
||||||
|
* @param descriptor the descriptor to test.
|
||||||
|
* @return <code>true</code> if {@link #addDescriptor(MetadataDescriptor)}
|
||||||
|
* can be called with given descriptor.
|
||||||
|
*/
|
||||||
|
public boolean isAddSupported(final MetadataDescriptor descriptor) {
|
||||||
|
boolean result = getContainerType().checkConstraints(
|
||||||
|
descriptor.getName(), descriptor.getRawData(),
|
||||||
|
descriptor.getType(), descriptor.getStreamNumber(),
|
||||||
|
descriptor.getLanguageIndex()) == null;
|
||||||
|
// Now check if there is already a value contained.
|
||||||
|
if (result && !getContainerType().isMultiValued()) {
|
||||||
|
synchronized (this.perfPoint) {
|
||||||
|
final List<MetadataDescriptor> list = this.descriptors
|
||||||
|
.get(this.perfPoint.setDescriptor(descriptor));
|
||||||
|
if (list != null) {
|
||||||
|
result = list.isEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public final boolean isEmpty() {
|
||||||
|
boolean result = true;
|
||||||
|
if (getDescriptorCount() != 0) {
|
||||||
|
final Iterator<MetadataDescriptor> iterator = getDescriptors()
|
||||||
|
.iterator();
|
||||||
|
while (result && iterator.hasNext()) {
|
||||||
|
result &= iterator.next().isEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String prettyPrint(final String prefix) {
|
||||||
|
final StringBuilder result = new StringBuilder(super.prettyPrint(prefix));
|
||||||
|
for (final MetadataDescriptor curr : getDescriptors()) {
|
||||||
|
result.append(prefix).append(" |-> ");
|
||||||
|
result.append(curr);
|
||||||
|
result.append(Utils.LINE_SEPARATOR);
|
||||||
|
}
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all stored descriptors with the given
|
||||||
|
* {@linkplain MetadataDescriptor#getName() name}.<br>
|
||||||
|
*
|
||||||
|
* @param name the name to remove.
|
||||||
|
*/
|
||||||
|
public final void removeDescriptorsByName(final String name) {
|
||||||
|
assert name != null;
|
||||||
|
final Iterator<List<MetadataDescriptor>> iterator = this.descriptors
|
||||||
|
.values().iterator();
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
final List<MetadataDescriptor> curr = iterator.next();
|
||||||
|
if (!curr.isEmpty() && curr.get(0).getName().equals(name)) {
|
||||||
|
iterator.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@linkplain #assertDescriptor(String) asserts} the existence of a
|
||||||
|
* descriptor with given <code>name</code> and
|
||||||
|
* {@linkplain MetadataDescriptor#setStringValue(String) assings} the string
|
||||||
|
* value.
|
||||||
|
*
|
||||||
|
* @param name the name of the descriptor to set the value for.
|
||||||
|
* @param value the string value.
|
||||||
|
*/
|
||||||
|
protected final void setStringValue(final String name, final String value) {
|
||||||
|
assertDescriptor(name).setStringValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public long writeInto(final OutputStream out) throws IOException {
|
||||||
|
final long chunkSize = getCurrentAsfChunkSize();
|
||||||
|
final List<MetadataDescriptor> descriptorList = getDescriptors();
|
||||||
|
out.write(getGuid().getBytes());
|
||||||
|
Utils.writeUINT64(chunkSize, out);
|
||||||
|
Utils.writeUINT16(descriptorList.size(), out);
|
||||||
|
for (final MetadataDescriptor curr : descriptorList) {
|
||||||
|
curr.writeInto(out, this.containerType);
|
||||||
|
}
|
||||||
|
return chunkSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is used to uniquely identify an enclosed descriptor by its
|
||||||
|
* name, language index and stream number.<br>
|
||||||
|
* The type of the descriptor is ignored, since it just specifies the data
|
||||||
|
* content.
|
||||||
|
*
|
||||||
|
* @author Christian Laireiter
|
||||||
|
*/
|
||||||
|
private final static class DescriptorPointer {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The represented descriptor.
|
||||||
|
*/
|
||||||
|
private MetadataDescriptor desc;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance.
|
||||||
|
*
|
||||||
|
* @param descriptor the metadata descriptor to identify.
|
||||||
|
*/
|
||||||
|
public DescriptorPointer(final MetadataDescriptor descriptor) {
|
||||||
|
setDescriptor(descriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean equals(final Object obj) {
|
||||||
|
boolean result = obj == this;
|
||||||
|
if (obj instanceof DescriptorPointer && !result) {
|
||||||
|
final MetadataDescriptor other = ((DescriptorPointer) obj).desc;
|
||||||
|
result = this.desc.getName().equals(other.getName());
|
||||||
|
result &= this.desc.getLanguageIndex() == other
|
||||||
|
.getLanguageIndex();
|
||||||
|
result &= this.desc.getStreamNumber() == other
|
||||||
|
.getStreamNumber();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int hashCode;
|
||||||
|
hashCode = this.desc.getName().hashCode();
|
||||||
|
hashCode = hashCode * 31 + this.desc.getLanguageIndex();
|
||||||
|
hashCode = hashCode * 31 + this.desc.getStreamNumber();
|
||||||
|
return hashCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the descriptor to identify.
|
||||||
|
*
|
||||||
|
* @param descriptor the descriptor to identify.
|
||||||
|
* @return this instance.
|
||||||
|
*/
|
||||||
|
protected DescriptorPointer setDescriptor(
|
||||||
|
final MetadataDescriptor descriptor) {
|
||||||
|
assert descriptor != null;
|
||||||
|
this.desc = descriptor;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
package org.jaudiotagger.audio.asf.data;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A factory for creating appropriate {@link MetadataContainer} objects upon
|
||||||
|
* specified {@linkplain ContainerType container types}.<br>
|
||||||
|
*
|
||||||
|
* @author Christian Laireiter
|
||||||
|
*/
|
||||||
|
public final class MetadataContainerFactory {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory instance.
|
||||||
|
*/
|
||||||
|
private final static MetadataContainerFactory INSTANCE = new MetadataContainerFactory();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hidden utility class constructor.
|
||||||
|
*/
|
||||||
|
private MetadataContainerFactory() {
|
||||||
|
// Hidden
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an instance.
|
||||||
|
*
|
||||||
|
* @return an instance.
|
||||||
|
*/
|
||||||
|
public static MetadataContainerFactory getInstance() {
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an appropriate {@linkplain MetadataContainer container
|
||||||
|
* implementation} for the given container type.
|
||||||
|
*
|
||||||
|
* @param type the type of container to get a container instance for.
|
||||||
|
* @return appropriate container implementation.
|
||||||
|
*/
|
||||||
|
public MetadataContainer createContainer(final ContainerType type) {
|
||||||
|
return createContainer(type, 0, BigInteger.ZERO);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience Method for I/O. Same as
|
||||||
|
* {@link #createContainer(ContainerType)}, but additionally assigns
|
||||||
|
* position and size. (since a {@link MetadataContainer} is actually a
|
||||||
|
* {@link Chunk}).
|
||||||
|
*
|
||||||
|
* @param type The containers type.
|
||||||
|
* @param pos the position within the stream.
|
||||||
|
* @param chunkSize the size of the container.
|
||||||
|
* @return an appropriate container implementation with assigned size and
|
||||||
|
* position.
|
||||||
|
*/
|
||||||
|
public MetadataContainer createContainer(final ContainerType type,
|
||||||
|
final long pos, final BigInteger chunkSize) {
|
||||||
|
MetadataContainer result;
|
||||||
|
if (type == ContainerType.CONTENT_DESCRIPTION) {
|
||||||
|
result = new ContentDescription(pos, chunkSize);
|
||||||
|
} else if (type == ContainerType.CONTENT_BRANDING) {
|
||||||
|
result = new ContentBranding(pos, chunkSize);
|
||||||
|
} else {
|
||||||
|
result = new MetadataContainer(type, pos, chunkSize);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience method which calls {@link #createContainer(ContainerType)}
|
||||||
|
* for each given container type.
|
||||||
|
*
|
||||||
|
* @param types types of the container which are to be created.
|
||||||
|
* @return appropriate container implementations.
|
||||||
|
*/
|
||||||
|
public MetadataContainer[] createContainers(final ContainerType[] types) {
|
||||||
|
assert types != null;
|
||||||
|
final MetadataContainer[] result = new MetadataContainer[types.length];
|
||||||
|
for (int i = 0; i < result.length; i++) {
|
||||||
|
result[i] = createContainer(types[i]);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,877 @@
|
||||||
|
/*
|
||||||
|
* Entagged Audio Tag library
|
||||||
|
* Copyright (c) 2004-2005 Christian Laireiter <liree@web.de>
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this library; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
package org.jaudiotagger.audio.asf.data;
|
||||||
|
|
||||||
|
import org.jaudiotagger.audio.asf.util.Utils;
|
||||||
|
import org.jaudiotagger.logging.ErrorMessage;
|
||||||
|
import org.jaudiotagger.tag.TagOptionSingleton;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This structure represents metadata objects in ASF {@link MetadataContainer}.<br>
|
||||||
|
* The values are
|
||||||
|
* {@linkplain ContainerType#assertConstraints(String, byte[], int, int, int)
|
||||||
|
* checked} against the capability introduced by the given
|
||||||
|
* {@link ContainerType} at construction.<br>
|
||||||
|
* <br>
|
||||||
|
* <b>Limitation</b>: Even though some container types do not restrict the data
|
||||||
|
* size to {@link Integer#MAX_VALUE}, this implementation does it (due to java
|
||||||
|
* nature).<br>
|
||||||
|
* 2 GiB of data should suffice, and even be to large for normal java heap.
|
||||||
|
*
|
||||||
|
* @author Christian Laireiter
|
||||||
|
*/
|
||||||
|
public class MetadataDescriptor implements Comparable<MetadataDescriptor>,
|
||||||
|
Cloneable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum value for WORD.
|
||||||
|
*/
|
||||||
|
public static final long DWORD_MAXVALUE = new BigInteger("FFFFFFFF", 16)
|
||||||
|
.longValue();
|
||||||
|
/**
|
||||||
|
* The maximum language index allowed. (exclusive)
|
||||||
|
*/
|
||||||
|
public static final int MAX_LANG_INDEX = 127;
|
||||||
|
/**
|
||||||
|
* Maximum stream number. (inclusive)
|
||||||
|
*/
|
||||||
|
public static final int MAX_STREAM_NUMBER = 127;
|
||||||
|
/**
|
||||||
|
* Maximum value for a QWORD value (64 bit unsigned).<br>
|
||||||
|
*/
|
||||||
|
public static final BigInteger QWORD_MAXVALUE = new BigInteger(
|
||||||
|
"FFFFFFFFFFFFFFFF", 16);
|
||||||
|
/**
|
||||||
|
* Constant for the metadata descriptor-type for binary data.
|
||||||
|
*/
|
||||||
|
public final static int TYPE_BINARY = 1;
|
||||||
|
/**
|
||||||
|
* Constant for the metadata descriptor-type for booleans.
|
||||||
|
*/
|
||||||
|
public final static int TYPE_BOOLEAN = 2;
|
||||||
|
/**
|
||||||
|
* Constant for the metadata descriptor-type for DWORD (32-bit unsigned). <br>
|
||||||
|
*/
|
||||||
|
public final static int TYPE_DWORD = 3;
|
||||||
|
/**
|
||||||
|
* Constant for the metadata descriptor-type for GUIDs (128-bit).<br>
|
||||||
|
*/
|
||||||
|
public final static int TYPE_GUID = 6;
|
||||||
|
/**
|
||||||
|
* Constant for the metadata descriptor-type for QWORD (64-bit unsinged). <br>
|
||||||
|
*/
|
||||||
|
public final static int TYPE_QWORD = 4;
|
||||||
|
/**
|
||||||
|
* Constant for the metadata descriptor-type for Strings.
|
||||||
|
*/
|
||||||
|
public final static int TYPE_STRING = 0;
|
||||||
|
/**
|
||||||
|
* Constant for the metadata descriptor-type for WORD (16-bit unsigned). <br>
|
||||||
|
*/
|
||||||
|
public final static int TYPE_WORD = 5;
|
||||||
|
/**
|
||||||
|
* Maximum value for WORD.
|
||||||
|
*/
|
||||||
|
public static final int WORD_MAXVALUE = 65535;
|
||||||
|
/**
|
||||||
|
* Logger instance.
|
||||||
|
*/
|
||||||
|
private static final Logger LOGGER = Logger
|
||||||
|
.getLogger("org.jaudiotagger.audio.asf.data");
|
||||||
|
/**
|
||||||
|
* Stores the containerType of the descriptor.
|
||||||
|
*/
|
||||||
|
private final ContainerType containerType;
|
||||||
|
/**
|
||||||
|
* The name of the metadata descriptor.
|
||||||
|
*/
|
||||||
|
private final String name;
|
||||||
|
/**
|
||||||
|
* The binary representation of the value.
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
* Note: The maximum data length could be up to a 64-Bit number (unsigned),
|
||||||
|
* but java for now handles just int sized byte[]. Since this class stores
|
||||||
|
* all data in primitive byte[] this size restriction is cascaded to all
|
||||||
|
* dependent implementations.
|
||||||
|
*/
|
||||||
|
private byte[] content = new byte[0];
|
||||||
|
/**
|
||||||
|
* This field shows the type of the metadata descriptor. <br>
|
||||||
|
*
|
||||||
|
* @see #TYPE_BINARY
|
||||||
|
* @see #TYPE_BOOLEAN
|
||||||
|
* @see #TYPE_DWORD
|
||||||
|
* @see #TYPE_GUID
|
||||||
|
* @see #TYPE_QWORD
|
||||||
|
* @see #TYPE_STRING
|
||||||
|
* @see #TYPE_WORD
|
||||||
|
*/
|
||||||
|
private int descriptorType;
|
||||||
|
/**
|
||||||
|
* the index of the language in the {@linkplain LanguageList language list}
|
||||||
|
* this descriptor applies to.<br>
|
||||||
|
*/
|
||||||
|
private int languageIndex = 0;
|
||||||
|
/**
|
||||||
|
* The number of the stream, this descriptor applies to.<br>
|
||||||
|
*/
|
||||||
|
private int streamNumber = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an Instance.<br>
|
||||||
|
*
|
||||||
|
* @param type the container type, this descriptor is resctricted to.
|
||||||
|
* @param propName Name of the MetadataDescriptor.
|
||||||
|
* @param propType Type of the metadata descriptor. See {@link #descriptorType}
|
||||||
|
*/
|
||||||
|
public MetadataDescriptor(final ContainerType type, final String propName,
|
||||||
|
final int propType) {
|
||||||
|
this(type, propName, propType, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an Instance.
|
||||||
|
*
|
||||||
|
* @param type The container type the values (the whole descriptor) is
|
||||||
|
* restricted to.
|
||||||
|
* @param propName Name of the MetadataDescriptor.
|
||||||
|
* @param propType Type of the metadata descriptor. See {@link #descriptorType}
|
||||||
|
* @param stream the number of the stream the descriptor refers to.
|
||||||
|
* @param language the index of the language entry in a {@link LanguageList} this
|
||||||
|
* descriptor refers to.<br>
|
||||||
|
* <b>Consider</b>: No checks performed if language entry exists.
|
||||||
|
*/
|
||||||
|
public MetadataDescriptor(final ContainerType type, final String propName,
|
||||||
|
final int propType, final int stream, final int language) {
|
||||||
|
assert type != null;
|
||||||
|
type.assertConstraints(propName, new byte[0], propType, stream,
|
||||||
|
language);
|
||||||
|
this.containerType = type;
|
||||||
|
this.name = propName;
|
||||||
|
this.descriptorType = propType;
|
||||||
|
this.streamNumber = stream;
|
||||||
|
this.languageIndex = language;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance.<br>
|
||||||
|
* Capabilities are set to {@link ContainerType#METADATA_LIBRARY_OBJECT}.<br>
|
||||||
|
*
|
||||||
|
* @param propName name of the metadata descriptor.
|
||||||
|
*/
|
||||||
|
public MetadataDescriptor(final String propName) {
|
||||||
|
this(propName, TYPE_STRING);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an Instance.<br>
|
||||||
|
* Capabilities are set to {@link ContainerType#METADATA_LIBRARY_OBJECT}.<br>
|
||||||
|
*
|
||||||
|
* @param propName Name of the MetadataDescriptor.
|
||||||
|
* @param propType Type of the metadata descriptor. See {@link #descriptorType}
|
||||||
|
*/
|
||||||
|
public MetadataDescriptor(final String propName, final int propType) {
|
||||||
|
this(ContainerType.METADATA_LIBRARY_OBJECT, propName, propType, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the descriptors value into a number if possible.<br>
|
||||||
|
* A boolean will be converted to "1" if <code>true</code>,
|
||||||
|
* otherwise "0".<br>
|
||||||
|
* String will be interpreted as number with radix "10".<br>
|
||||||
|
* Binary data will be interpreted as the default WORD,DWORD or QWORD binary
|
||||||
|
* representation, but only if the data does not exceed 8 bytes. This
|
||||||
|
* precaution is done to prevent creating a number of a multi kilobyte
|
||||||
|
* image.<br>
|
||||||
|
* A GUID cannot be converted in any case.
|
||||||
|
*
|
||||||
|
* @return number representation.
|
||||||
|
* @throws NumberFormatException If no conversion is supported.
|
||||||
|
*/
|
||||||
|
public BigInteger asNumber() {
|
||||||
|
BigInteger result = null;
|
||||||
|
switch (this.descriptorType) {
|
||||||
|
case TYPE_BOOLEAN:
|
||||||
|
case TYPE_WORD:
|
||||||
|
case TYPE_DWORD:
|
||||||
|
case TYPE_QWORD:
|
||||||
|
case TYPE_BINARY:
|
||||||
|
if (this.content.length > 8) {
|
||||||
|
throw new NumberFormatException(
|
||||||
|
"Binary data would exceed QWORD");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TYPE_GUID:
|
||||||
|
throw new NumberFormatException(
|
||||||
|
"GUID cannot be converted to a number.");
|
||||||
|
case TYPE_STRING:
|
||||||
|
result = new BigInteger(getString(), 10);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
if (result == null) {
|
||||||
|
final byte[] copy = new byte[this.content.length];
|
||||||
|
for (int i = 0; i < copy.length; i++) {
|
||||||
|
copy[i] = this.content[this.content.length - (i + 1)];
|
||||||
|
}
|
||||||
|
result = new BigInteger(1, copy);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (overridden)
|
||||||
|
*
|
||||||
|
* @see java.lang.Object#clone()
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Object clone() throws CloneNotSupportedException {
|
||||||
|
return super.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public int compareTo(final MetadataDescriptor other) {
|
||||||
|
return getName().compareTo(other.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method creates a copy of the current object. <br>
|
||||||
|
* All data will be copied, too. <br>
|
||||||
|
*
|
||||||
|
* @return A new metadata descriptor containing the same values as the
|
||||||
|
* current one.
|
||||||
|
*/
|
||||||
|
public MetadataDescriptor createCopy() {
|
||||||
|
final MetadataDescriptor result = new MetadataDescriptor(
|
||||||
|
this.containerType, this.name, this.descriptorType,
|
||||||
|
this.streamNumber, this.languageIndex);
|
||||||
|
result.content = getRawData();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (overridden)
|
||||||
|
*
|
||||||
|
* @see java.lang.Object#equals(java.lang.Object)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean equals(final Object obj) {
|
||||||
|
boolean result = false;
|
||||||
|
if (obj instanceof MetadataDescriptor) {
|
||||||
|
if (obj == this) {
|
||||||
|
result = true;
|
||||||
|
} else {
|
||||||
|
final MetadataDescriptor other = (MetadataDescriptor) obj;
|
||||||
|
result = other.getName().equals(getName())
|
||||||
|
&& other.descriptorType == this.descriptorType
|
||||||
|
&& other.languageIndex == this.languageIndex
|
||||||
|
&& other.streamNumber == this.streamNumber
|
||||||
|
&& Arrays.equals(this.content, other.content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value of the MetadataDescriptor as a Boolean. <br>
|
||||||
|
* If no Conversion is Possible false is returned. <br>
|
||||||
|
* <code>true</code> if first byte of {@link #content}is not zero.
|
||||||
|
*
|
||||||
|
* @return boolean representation of the current value.
|
||||||
|
*/
|
||||||
|
public boolean getBoolean() {
|
||||||
|
return this.content.length > 0 && this.content[0] != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method will return a byte array, which can directly be written into
|
||||||
|
* an "Extended Content Description"-chunk. <br>
|
||||||
|
*
|
||||||
|
* @return byte[] with the data, that occurs in ASF files.
|
||||||
|
* @deprecated {@link #writeInto(OutputStream, ContainerType)} is used
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public byte[] getBytes() {
|
||||||
|
final ByteArrayOutputStream result = new ByteArrayOutputStream();
|
||||||
|
try {
|
||||||
|
writeInto(result, this.containerType);
|
||||||
|
} catch (final IOException e) {
|
||||||
|
LOGGER.warning(e.getMessage());
|
||||||
|
}
|
||||||
|
return result.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the container type this descriptor ist restricted to.
|
||||||
|
*
|
||||||
|
* @return the container type
|
||||||
|
*/
|
||||||
|
public ContainerType getContainerType() {
|
||||||
|
return this.containerType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the size (in bytes) this descriptor will take when written to an
|
||||||
|
* ASF file.<br>
|
||||||
|
*
|
||||||
|
* @param type the container type for which the size is calculated.
|
||||||
|
* @return size of the descriptor in an ASF file.
|
||||||
|
*/
|
||||||
|
public int getCurrentAsfSize(final ContainerType type) {
|
||||||
|
/*
|
||||||
|
* 2 bytes name length, 2 bytes name zero term, 2 bytes type, 2 bytes
|
||||||
|
* content length
|
||||||
|
*/
|
||||||
|
int result = 8;
|
||||||
|
|
||||||
|
if (type != ContainerType.EXTENDED_CONTENT) {
|
||||||
|
// Stream number and language index (respectively reserved field).
|
||||||
|
// And +2 bytes, because data type is 32 bit, not 16
|
||||||
|
result += 6;
|
||||||
|
}
|
||||||
|
result += getName().length() * 2;
|
||||||
|
|
||||||
|
if (this.getType() == TYPE_BOOLEAN) {
|
||||||
|
result += 2;
|
||||||
|
if (type == ContainerType.EXTENDED_CONTENT) {
|
||||||
|
// Extended content description boolean values are stored with
|
||||||
|
// 32-bit
|
||||||
|
result += 2;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
|
result += this.content.length;
|
||||||
|
if (TYPE_STRING == this.getType()) {
|
||||||
|
result += 2; // zero term of content string.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the GUID value, if content could represent one.
|
||||||
|
*
|
||||||
|
* @return GUID value
|
||||||
|
*/
|
||||||
|
public GUID getGuid() {
|
||||||
|
GUID result = null;
|
||||||
|
if (getType() == TYPE_GUID && this.content.length == GUID.GUID_LENGTH) {
|
||||||
|
result = new GUID(this.content);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the index of the language that is referred (see
|
||||||
|
* {@link LanguageList}):
|
||||||
|
*
|
||||||
|
* @return the language index
|
||||||
|
*/
|
||||||
|
public int getLanguageIndex() {
|
||||||
|
return this.languageIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the index of the referred language (see {@link LanguageList}).<br>
|
||||||
|
* <b>Consider</b>: The {@linkplain #containerType requirements} must be
|
||||||
|
* held.
|
||||||
|
*
|
||||||
|
* @param language the language index to set
|
||||||
|
*/
|
||||||
|
public void setLanguageIndex(final int language) {
|
||||||
|
this.containerType.assertConstraints(this.name, this.content,
|
||||||
|
this.descriptorType, this.streamNumber, language);
|
||||||
|
this.languageIndex = language;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method returns the name of the metadata descriptor.
|
||||||
|
*
|
||||||
|
* @return Name.
|
||||||
|
*/
|
||||||
|
public String getName() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method returns the value of the metadata descriptor as a long. <br>
|
||||||
|
* Converts the needed amount of byte out of {@link #content}to a number. <br>
|
||||||
|
* Only possible if {@link #getType()}equals on of the following: <br>
|
||||||
|
* <li>
|
||||||
|
*
|
||||||
|
* @return integer value.
|
||||||
|
* @see #TYPE_BOOLEAN </li> <li>
|
||||||
|
* @see #TYPE_DWORD </li> <li>
|
||||||
|
* @see #TYPE_QWORD </li> <li>
|
||||||
|
* @see #TYPE_WORD </li>
|
||||||
|
*/
|
||||||
|
public long getNumber() {
|
||||||
|
int bytesNeeded;
|
||||||
|
switch (getType()) {
|
||||||
|
case TYPE_BOOLEAN:
|
||||||
|
bytesNeeded = 1;
|
||||||
|
break;
|
||||||
|
case TYPE_DWORD:
|
||||||
|
bytesNeeded = 4;
|
||||||
|
break;
|
||||||
|
case TYPE_QWORD:
|
||||||
|
bytesNeeded = 8;
|
||||||
|
break;
|
||||||
|
case TYPE_WORD:
|
||||||
|
bytesNeeded = 2;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new UnsupportedOperationException(
|
||||||
|
"The current type doesn't allow an interpretation as a number. ("
|
||||||
|
+ getType() + ")");
|
||||||
|
}
|
||||||
|
if (bytesNeeded > this.content.length) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"The stored data cannot represent the type of current object.");
|
||||||
|
}
|
||||||
|
long result = 0;
|
||||||
|
for (int i = 0; i < bytesNeeded; i++) {
|
||||||
|
result |= (((long) this.content[i] & 0xFF) << (i * 8));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method returns a copy of the content of the descriptor. <br>
|
||||||
|
*
|
||||||
|
* @return The content in binary representation, as it would be written to
|
||||||
|
* asf file. <br>
|
||||||
|
*/
|
||||||
|
public byte[] getRawData() {
|
||||||
|
final byte[] copy = new byte[this.content.length];
|
||||||
|
System.arraycopy(this.content, 0, copy, 0, this.content.length);
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the size (in bytes) the binary representation of the content
|
||||||
|
* uses. (length of {@link #getRawData()})<br>
|
||||||
|
*
|
||||||
|
* @return size of binary representation of the content.
|
||||||
|
*/
|
||||||
|
public int getRawDataSize() {
|
||||||
|
return this.content.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the stream number this descriptor applies to.<br>
|
||||||
|
*
|
||||||
|
* @return the stream number.
|
||||||
|
*/
|
||||||
|
public int getStreamNumber() {
|
||||||
|
return this.streamNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the stream number the descriptor applies to.<br>
|
||||||
|
* <b>Consider</b>: The {@linkplain #containerType requirements} must be
|
||||||
|
* held.
|
||||||
|
*
|
||||||
|
* @param stream the stream number to set
|
||||||
|
*/
|
||||||
|
public void setStreamNumber(final int stream) {
|
||||||
|
this.containerType.assertConstraints(this.name, this.content,
|
||||||
|
this.descriptorType, stream, this.languageIndex);
|
||||||
|
this.streamNumber = stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value of the MetadataDescriptor as a String. <br>
|
||||||
|
*
|
||||||
|
* @return String - Representation Value
|
||||||
|
*/
|
||||||
|
public String getString() {
|
||||||
|
String result = null;
|
||||||
|
switch (getType()) {
|
||||||
|
case TYPE_BINARY:
|
||||||
|
result = "binary data";
|
||||||
|
break;
|
||||||
|
case TYPE_BOOLEAN:
|
||||||
|
result = String.valueOf(getBoolean());
|
||||||
|
break;
|
||||||
|
case TYPE_GUID:
|
||||||
|
result = getGuid() == null ? "Invalid GUID" : getGuid().toString();
|
||||||
|
break;
|
||||||
|
case TYPE_QWORD:
|
||||||
|
case TYPE_DWORD:
|
||||||
|
case TYPE_WORD:
|
||||||
|
result = String.valueOf(getNumber());
|
||||||
|
break;
|
||||||
|
case TYPE_STRING:
|
||||||
|
try {
|
||||||
|
result = new String(this.content, "UTF-16LE");
|
||||||
|
} catch (final UnsupportedEncodingException e) {
|
||||||
|
LOGGER.warning(e.getMessage());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException("Current type is not known.");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method converts the given string value into the current
|
||||||
|
* {@linkplain #getType() data type}.
|
||||||
|
*
|
||||||
|
* @param value value to set.
|
||||||
|
* @throws IllegalArgumentException If conversion was impossible.
|
||||||
|
*/
|
||||||
|
public void setString(final String value)
|
||||||
|
throws IllegalArgumentException {
|
||||||
|
try {
|
||||||
|
switch (getType()) {
|
||||||
|
case TYPE_BINARY:
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Cannot interpret binary as string.");
|
||||||
|
case TYPE_BOOLEAN:
|
||||||
|
setBooleanValue(Boolean.parseBoolean(value));
|
||||||
|
break;
|
||||||
|
case TYPE_DWORD:
|
||||||
|
setDWordValue(Long.parseLong(value));
|
||||||
|
break;
|
||||||
|
case TYPE_QWORD:
|
||||||
|
setQWordValue(new BigInteger(value, 10));
|
||||||
|
break;
|
||||||
|
case TYPE_WORD:
|
||||||
|
setWordValue(Integer.parseInt(value));
|
||||||
|
break;
|
||||||
|
case TYPE_GUID:
|
||||||
|
setGUIDValue(GUID.parseGUID(value));
|
||||||
|
break;
|
||||||
|
case TYPE_STRING:
|
||||||
|
setStringValue(value);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// new Type added but not handled.
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
} catch (final NumberFormatException nfe) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Value cannot be parsed as Number or is out of range (\""
|
||||||
|
+ value + "\")", nfe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the type of the metadata descriptor. <br>
|
||||||
|
*
|
||||||
|
* @return the value of {@link #descriptorType}
|
||||||
|
* @see #TYPE_BINARY
|
||||||
|
* @see #TYPE_BOOLEAN
|
||||||
|
* @see #TYPE_DWORD
|
||||||
|
* @see #TYPE_GUID
|
||||||
|
* @see #TYPE_QWORD
|
||||||
|
* @see #TYPE_STRING
|
||||||
|
* @see #TYPE_WORD
|
||||||
|
*/
|
||||||
|
public int getType() {
|
||||||
|
return this.descriptorType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return this.name.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method checks if the binary data is empty. <br>
|
||||||
|
* Disregarding the type of the descriptor its content is stored as a byte
|
||||||
|
* array.
|
||||||
|
*
|
||||||
|
* @return <code>true</code> if no value is set.
|
||||||
|
*/
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return this.content.length == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the Value of the current metadata descriptor. <br>
|
||||||
|
* Using this method will change {@link #descriptorType}to
|
||||||
|
* {@link #TYPE_BINARY}.<br>
|
||||||
|
*
|
||||||
|
* @param data Value to set.
|
||||||
|
* @throws IllegalArgumentException if data is invalid for {@linkplain #getContainerType()
|
||||||
|
* container}.
|
||||||
|
*/
|
||||||
|
public void setBinaryValue(final byte[] data)
|
||||||
|
throws IllegalArgumentException {
|
||||||
|
this.containerType.assertConstraints(this.name, data,
|
||||||
|
this.descriptorType, this.streamNumber, this.languageIndex);
|
||||||
|
this.content = data.clone();
|
||||||
|
this.descriptorType = TYPE_BINARY;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the Value of the current metadata descriptor. <br>
|
||||||
|
* Using this method will change {@link #descriptorType}to
|
||||||
|
* {@link #TYPE_BOOLEAN}.<br>
|
||||||
|
*
|
||||||
|
* @param value Value to set.
|
||||||
|
*/
|
||||||
|
public void setBooleanValue(final boolean value) {
|
||||||
|
this.content = new byte[]{value ? (byte) 1 : 0};
|
||||||
|
this.descriptorType = TYPE_BOOLEAN;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the Value of the current metadata descriptor. <br>
|
||||||
|
* Using this method will change {@link #descriptorType}to
|
||||||
|
* {@link #TYPE_DWORD}.
|
||||||
|
*
|
||||||
|
* @param value Value to set.
|
||||||
|
*/
|
||||||
|
public void setDWordValue(final long value) {
|
||||||
|
if (value < 0 || value > DWORD_MAXVALUE) {
|
||||||
|
throw new IllegalArgumentException("value out of range (0-"
|
||||||
|
+ DWORD_MAXVALUE + ")");
|
||||||
|
}
|
||||||
|
this.content = Utils.getBytes(value, 4);
|
||||||
|
this.descriptorType = TYPE_DWORD;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the value of the metadata descriptor.<br>
|
||||||
|
* Using this method will change {@link #descriptorType} to
|
||||||
|
* {@link #TYPE_GUID}
|
||||||
|
*
|
||||||
|
* @param value value to set.
|
||||||
|
*/
|
||||||
|
public void setGUIDValue(final GUID value) {
|
||||||
|
this.containerType.assertConstraints(this.name, value.getBytes(),
|
||||||
|
TYPE_GUID, this.streamNumber, this.languageIndex);
|
||||||
|
this.content = value.getBytes();
|
||||||
|
this.descriptorType = TYPE_GUID;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the Value of the current metadata descriptor. <br>
|
||||||
|
* Using this method will change {@link #descriptorType}to
|
||||||
|
* {@link #TYPE_QWORD}
|
||||||
|
*
|
||||||
|
* @param value Value to set.
|
||||||
|
* @throws NumberFormatException on <code>null</code> values.
|
||||||
|
* @throws IllegalArgumentException on illegal values or values exceeding range.
|
||||||
|
*/
|
||||||
|
public void setQWordValue(final BigInteger value)
|
||||||
|
throws IllegalArgumentException {
|
||||||
|
if (value == null) {
|
||||||
|
throw new NumberFormatException("null");
|
||||||
|
}
|
||||||
|
if (BigInteger.ZERO.compareTo(value) > 0) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Only unsigned values allowed (no negative)");
|
||||||
|
}
|
||||||
|
if (MetadataDescriptor.QWORD_MAXVALUE.compareTo(value) < 0) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Value exceeds QWORD (64 bit unsigned)");
|
||||||
|
}
|
||||||
|
this.content = new byte[8];
|
||||||
|
final byte[] valuesBytes = value.toByteArray();
|
||||||
|
if (valuesBytes.length <= 8) {
|
||||||
|
for (int i = valuesBytes.length - 1; i >= 0; i--) {
|
||||||
|
this.content[valuesBytes.length - (i + 1)] = valuesBytes[i];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* In case of 64-Bit set
|
||||||
|
*/
|
||||||
|
Arrays.fill(this.content, (byte) 0xFF);
|
||||||
|
}
|
||||||
|
this.descriptorType = TYPE_QWORD;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the Value of the current metadata descriptor. <br>
|
||||||
|
* Using this method will change {@link #descriptorType}to
|
||||||
|
* {@link #TYPE_QWORD}
|
||||||
|
*
|
||||||
|
* @param value Value to set.
|
||||||
|
*/
|
||||||
|
public void setQWordValue(final long value) {
|
||||||
|
if (value < 0) {
|
||||||
|
throw new IllegalArgumentException("value out of range (0-"
|
||||||
|
+ MetadataDescriptor.QWORD_MAXVALUE.toString() + ")");
|
||||||
|
}
|
||||||
|
this.content = Utils.getBytes(value, 8);
|
||||||
|
this.descriptorType = TYPE_QWORD;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the Value of the current metadata descriptor. <br>
|
||||||
|
* Using this method will change {@link #descriptorType}to
|
||||||
|
* {@link #TYPE_STRING}.
|
||||||
|
*
|
||||||
|
* @param value Value to set.
|
||||||
|
* @throws IllegalArgumentException If byte representation would take more than 65535 Bytes.
|
||||||
|
*/
|
||||||
|
// TODO Test
|
||||||
|
public void setStringValue(final String value)
|
||||||
|
throws IllegalArgumentException {
|
||||||
|
if (value == null) {
|
||||||
|
this.content = new byte[0];
|
||||||
|
} else {
|
||||||
|
final byte[] tmp = Utils.getBytes(value, AsfHeader.ASF_CHARSET);
|
||||||
|
if (getContainerType().isWithinValueRange(tmp.length)) {
|
||||||
|
// Everything is fine here, data can be stored.
|
||||||
|
this.content = tmp;
|
||||||
|
} else {
|
||||||
|
// Normally a size violation, check if JAudiotagger my truncate
|
||||||
|
// the string
|
||||||
|
if (TagOptionSingleton.getInstance()
|
||||||
|
.isTruncateTextWithoutErrors()) {
|
||||||
|
// truncate the string
|
||||||
|
final int copyBytes = (int) getContainerType()
|
||||||
|
.getMaximumDataLength().longValue();
|
||||||
|
this.content = new byte[copyBytes % 2 == 0 ? copyBytes
|
||||||
|
: copyBytes - 1];
|
||||||
|
System.arraycopy(tmp, 0, this.content, 0,
|
||||||
|
this.content.length);
|
||||||
|
} else {
|
||||||
|
// We may not truncate, so its an error
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
ErrorMessage.WMA_LENGTH_OF_DATA_IS_TOO_LARGE
|
||||||
|
.getMsg(tmp.length, getContainerType()
|
||||||
|
.getMaximumDataLength(),
|
||||||
|
getContainerType()
|
||||||
|
.getContainerGUID()
|
||||||
|
.getDescription()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.descriptorType = TYPE_STRING;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the Value of the current metadata descriptor. <br>
|
||||||
|
* Using this method will change {@link #descriptorType}to
|
||||||
|
* {@link #TYPE_WORD}
|
||||||
|
*
|
||||||
|
* @param value Value to set.
|
||||||
|
* @throws IllegalArgumentException on negative values. ASF just supports unsigned values.
|
||||||
|
*/
|
||||||
|
public void setWordValue(final int value)
|
||||||
|
throws IllegalArgumentException {
|
||||||
|
if (value < 0 || value > WORD_MAXVALUE) {
|
||||||
|
throw new IllegalArgumentException("value out of range (0-"
|
||||||
|
+ WORD_MAXVALUE + ")");
|
||||||
|
}
|
||||||
|
this.content = Utils.getBytes(value, 2);
|
||||||
|
this.descriptorType = TYPE_WORD;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (overridden)
|
||||||
|
*
|
||||||
|
* @see java.lang.Object#toString()
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return getName()
|
||||||
|
+ " : "
|
||||||
|
+ new String[]{"String: ", "Binary: ", "Boolean: ",
|
||||||
|
"DWORD: ", "QWORD:", "WORD:", "GUID:"}[this.descriptorType]
|
||||||
|
+ getString() + " (language: " + this.languageIndex
|
||||||
|
+ " / stream: " + this.streamNumber + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes this descriptor into the specified output stream.<br>
|
||||||
|
*
|
||||||
|
* @param out stream to write into.
|
||||||
|
* @param contType the container type this descriptor is written to.
|
||||||
|
* @return amount of bytes written.
|
||||||
|
* @throws IOException on I/O Errors
|
||||||
|
*/
|
||||||
|
public int writeInto(final OutputStream out,
|
||||||
|
final ContainerType contType) throws IOException {
|
||||||
|
final int size = getCurrentAsfSize(contType);
|
||||||
|
/*
|
||||||
|
* Booleans are stored as one byte, if a boolean is written, the data
|
||||||
|
* must be converted according to the container type.
|
||||||
|
*/
|
||||||
|
byte[] binaryData;
|
||||||
|
if (this.descriptorType == TYPE_BOOLEAN) {
|
||||||
|
binaryData = new byte[contType == ContainerType.EXTENDED_CONTENT ? 4
|
||||||
|
: 2];
|
||||||
|
binaryData[0] = (byte) (getBoolean() ? 1 : 0);
|
||||||
|
} else {
|
||||||
|
binaryData = this.content;
|
||||||
|
}
|
||||||
|
// for Metadata objects the stream number and language index
|
||||||
|
if (contType != ContainerType.EXTENDED_CONTENT) {
|
||||||
|
Utils.writeUINT16(getLanguageIndex(), out);
|
||||||
|
Utils.writeUINT16(getStreamNumber(), out);
|
||||||
|
}
|
||||||
|
Utils.writeUINT16(getName().length() * 2 + 2, out);
|
||||||
|
|
||||||
|
// The name for the metadata objects come later
|
||||||
|
if (contType == ContainerType.EXTENDED_CONTENT) {
|
||||||
|
out.write(Utils.getBytes(getName(), AsfHeader.ASF_CHARSET));
|
||||||
|
out.write(AsfHeader.ZERO_TERM);
|
||||||
|
}
|
||||||
|
|
||||||
|
// type and content len follow up are identical
|
||||||
|
final int type = getType();
|
||||||
|
Utils.writeUINT16(type, out);
|
||||||
|
int contentLen = binaryData.length;
|
||||||
|
if (TYPE_STRING == type) {
|
||||||
|
contentLen += 2; // Zero Term
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contType == ContainerType.EXTENDED_CONTENT) {
|
||||||
|
Utils.writeUINT16(contentLen, out);
|
||||||
|
} else {
|
||||||
|
Utils.writeUINT32(contentLen, out);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metadata objects now write their descriptor name
|
||||||
|
if (contType != ContainerType.EXTENDED_CONTENT) {
|
||||||
|
out.write(Utils.getBytes(getName(), AsfHeader.ASF_CHARSET));
|
||||||
|
out.write(AsfHeader.ZERO_TERM);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The content.
|
||||||
|
out.write(binaryData);
|
||||||
|
if (TYPE_STRING == type) {
|
||||||
|
out.write(AsfHeader.ZERO_TERM);
|
||||||
|
}
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,107 @@
|
||||||
|
/*
|
||||||
|
* Entagged Audio Tag library
|
||||||
|
* Copyright (c) 2004-2005 Christian Laireiter <liree@web.de>
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this library; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
package org.jaudiotagger.audio.asf.data;
|
||||||
|
|
||||||
|
import org.jaudiotagger.audio.asf.util.Utils;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class represents the "Stream Bitrate Properties" chunk of an ASF media
|
||||||
|
* file. <br>
|
||||||
|
* It is optional, but contains useful information about the streams bitrate.<br>
|
||||||
|
*
|
||||||
|
* @author Christian Laireiter
|
||||||
|
*/
|
||||||
|
public class StreamBitratePropertiesChunk extends Chunk {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For each call of {@link #addBitrateRecord(int, long)} an {@link Long}
|
||||||
|
* object is appended, which represents the average bitrate.
|
||||||
|
*/
|
||||||
|
private final List<Long> bitRates;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For each call of {@link #addBitrateRecord(int, long)} an {@link Integer}
|
||||||
|
* object is appended, which represents the stream-number.
|
||||||
|
*/
|
||||||
|
private final List<Integer> streamNumbers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance.
|
||||||
|
*
|
||||||
|
* @param chunkLen Length of current chunk.
|
||||||
|
*/
|
||||||
|
public StreamBitratePropertiesChunk(final BigInteger chunkLen) {
|
||||||
|
super(GUID.GUID_STREAM_BITRATE_PROPERTIES, chunkLen);
|
||||||
|
this.bitRates = new ArrayList<Long>();
|
||||||
|
this.streamNumbers = new ArrayList<Integer>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the public values of a stream-record.
|
||||||
|
*
|
||||||
|
* @param streamNum The number of the referred stream.
|
||||||
|
* @param averageBitrate Its average bitrate.
|
||||||
|
*/
|
||||||
|
public void addBitrateRecord(final int streamNum, final long averageBitrate) {
|
||||||
|
this.streamNumbers.add(streamNum);
|
||||||
|
this.bitRates.add(averageBitrate);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the average bitrate of the given stream.<br>
|
||||||
|
*
|
||||||
|
* @param streamNumber Number of the stream whose bitrate to determine.
|
||||||
|
* @return The average bitrate of the numbered stream. <code>-1</code> if no
|
||||||
|
* information was given.
|
||||||
|
*/
|
||||||
|
public long getAvgBitrate(final int streamNumber) {
|
||||||
|
final Integer seach = streamNumber;
|
||||||
|
final int index = this.streamNumbers.indexOf(seach);
|
||||||
|
long result;
|
||||||
|
if (index == -1) {
|
||||||
|
result = -1;
|
||||||
|
} else {
|
||||||
|
result = this.bitRates.get(index);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (overridden)
|
||||||
|
*
|
||||||
|
* @see org.jaudiotagger.audio.asf.data.Chunk#prettyPrint(String)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String prettyPrint(final String prefix) {
|
||||||
|
final StringBuilder result = new StringBuilder(super.prettyPrint(prefix));
|
||||||
|
for (int i = 0; i < this.bitRates.size(); i++) {
|
||||||
|
result.append(prefix).append(" |-> Stream no. \"").append(
|
||||||
|
this.streamNumbers.get(i)).append(
|
||||||
|
"\" has an average bitrate of \"").append(
|
||||||
|
this.bitRates.get(i)).append('"').append(
|
||||||
|
Utils.LINE_SEPARATOR);
|
||||||
|
}
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
180
src/main/java/org/jaudiotagger/audio/asf/data/StreamChunk.java
Normal file
180
src/main/java/org/jaudiotagger/audio/asf/data/StreamChunk.java
Normal file
|
@ -0,0 +1,180 @@
|
||||||
|
/*
|
||||||
|
* Entagged Audio Tag library
|
||||||
|
* Copyright (c) 2004-2005 Christian Laireiter <liree@web.de>
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this library; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
package org.jaudiotagger.audio.asf.data;
|
||||||
|
|
||||||
|
import org.jaudiotagger.audio.asf.util.Utils;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is the base for all handled stream contents. <br>
|
||||||
|
* A Stream chunk delivers information about a audio or video stream. Because of
|
||||||
|
* this the stream chunk identifies in one field what type of stream it is
|
||||||
|
* describing and so other data is provided. However some information is common
|
||||||
|
* to all stream chunks which are stored in this hierarchy of the class tree.
|
||||||
|
*
|
||||||
|
* @author Christian Laireiter
|
||||||
|
*/
|
||||||
|
public abstract class StreamChunk extends Chunk {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores the stream type.<br>
|
||||||
|
*
|
||||||
|
* @see GUID#GUID_AUDIOSTREAM
|
||||||
|
* @see GUID#GUID_VIDEOSTREAM
|
||||||
|
*/
|
||||||
|
private final GUID type;
|
||||||
|
/**
|
||||||
|
* If <code>true</code>, the stream data is encrypted.
|
||||||
|
*/
|
||||||
|
private boolean contentEncrypted;
|
||||||
|
/**
|
||||||
|
* This field stores the number of the current stream. <br>
|
||||||
|
*/
|
||||||
|
private int streamNumber;
|
||||||
|
/**
|
||||||
|
* @see #typeSpecificDataSize
|
||||||
|
*/
|
||||||
|
private long streamSpecificDataSize;
|
||||||
|
/**
|
||||||
|
* Something technical. <br>
|
||||||
|
* Format time in 100-ns steps.
|
||||||
|
*/
|
||||||
|
private long timeOffset;
|
||||||
|
/**
|
||||||
|
* Stores the size of type specific data structure within chunk.
|
||||||
|
*/
|
||||||
|
private long typeSpecificDataSize;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance
|
||||||
|
*
|
||||||
|
* @param streamType The GUID which tells the stream type represented (
|
||||||
|
* {@link GUID#GUID_AUDIOSTREAM} or {@link GUID#GUID_VIDEOSTREAM}
|
||||||
|
* ):
|
||||||
|
* @param chunkLen length of chunk
|
||||||
|
*/
|
||||||
|
public StreamChunk(final GUID streamType, final BigInteger chunkLen) {
|
||||||
|
super(GUID.GUID_STREAM, chunkLen);
|
||||||
|
assert GUID.GUID_AUDIOSTREAM.equals(streamType)
|
||||||
|
|| GUID.GUID_VIDEOSTREAM.equals(streamType);
|
||||||
|
this.type = streamType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Returns the streamNumber.
|
||||||
|
*/
|
||||||
|
public int getStreamNumber() {
|
||||||
|
return this.streamNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param streamNum The streamNumber to set.
|
||||||
|
*/
|
||||||
|
public void setStreamNumber(final int streamNum) {
|
||||||
|
this.streamNumber = streamNum;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Returns the streamSpecificDataSize.
|
||||||
|
*/
|
||||||
|
public long getStreamSpecificDataSize() {
|
||||||
|
return this.streamSpecificDataSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param strSpecDataSize The streamSpecificDataSize to set.
|
||||||
|
*/
|
||||||
|
public void setStreamSpecificDataSize(final long strSpecDataSize) {
|
||||||
|
this.streamSpecificDataSize = strSpecDataSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the stream type of the stream chunk.<br>
|
||||||
|
*
|
||||||
|
* @return {@link GUID#GUID_AUDIOSTREAM} or {@link GUID#GUID_VIDEOSTREAM}.
|
||||||
|
*/
|
||||||
|
public GUID getStreamType() {
|
||||||
|
return this.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Returns the timeOffset.
|
||||||
|
*/
|
||||||
|
public long getTimeOffset() {
|
||||||
|
return this.timeOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param timeOffs sets the time offset
|
||||||
|
*/
|
||||||
|
public void setTimeOffset(final long timeOffs) {
|
||||||
|
this.timeOffset = timeOffs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Returns the typeSpecificDataSize.
|
||||||
|
*/
|
||||||
|
public long getTypeSpecificDataSize() {
|
||||||
|
return this.typeSpecificDataSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param typeSpecDataSize The typeSpecificDataSize to set.
|
||||||
|
*/
|
||||||
|
public void setTypeSpecificDataSize(final long typeSpecDataSize) {
|
||||||
|
this.typeSpecificDataSize = typeSpecDataSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Returns the contentEncrypted.
|
||||||
|
*/
|
||||||
|
public boolean isContentEncrypted() {
|
||||||
|
return this.contentEncrypted;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param cntEnc The contentEncrypted to set.
|
||||||
|
*/
|
||||||
|
public void setContentEncrypted(final boolean cntEnc) {
|
||||||
|
this.contentEncrypted = cntEnc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (overridden)
|
||||||
|
*
|
||||||
|
* @see org.jaudiotagger.audio.asf.data.Chunk#prettyPrint(String)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String prettyPrint(final String prefix) {
|
||||||
|
final StringBuilder result = new StringBuilder(super.prettyPrint(prefix));
|
||||||
|
result.append(prefix).append(" |-> Stream number: ").append(
|
||||||
|
getStreamNumber()).append(Utils.LINE_SEPARATOR);
|
||||||
|
result.append(prefix).append(" |-> Type specific data size : ")
|
||||||
|
.append(getTypeSpecificDataSize()).append(Utils.LINE_SEPARATOR);
|
||||||
|
result.append(prefix).append(" |-> Stream specific data size: ")
|
||||||
|
.append(getStreamSpecificDataSize()).append(
|
||||||
|
Utils.LINE_SEPARATOR);
|
||||||
|
result.append(prefix).append(" |-> Time Offset : ")
|
||||||
|
.append(getTimeOffset()).append(Utils.LINE_SEPARATOR);
|
||||||
|
result.append(prefix).append(" |-> Content Encryption : ")
|
||||||
|
.append(isContentEncrypted()).append(Utils.LINE_SEPARATOR);
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,131 @@
|
||||||
|
/*
|
||||||
|
* Entagged Audio Tag library
|
||||||
|
* Copyright (c) 2004-2005 Christian Laireiter <liree@web.de>
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this library; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
package org.jaudiotagger.audio.asf.data;
|
||||||
|
|
||||||
|
import org.jaudiotagger.audio.asf.util.Utils;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Christian Laireiter
|
||||||
|
*/
|
||||||
|
public class VideoStreamChunk extends StreamChunk {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores the codecs id. Normally the Four-CC (4-Bytes).
|
||||||
|
*/
|
||||||
|
private byte[] codecId = new byte[0];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This field stores the height of the video stream.
|
||||||
|
*/
|
||||||
|
private long pictureHeight;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This field stores the width of the video stream.
|
||||||
|
*/
|
||||||
|
private long pictureWidth;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance.
|
||||||
|
*
|
||||||
|
* @param chunkLen Length of the entire chunk (including guid and size)
|
||||||
|
*/
|
||||||
|
public VideoStreamChunk(final BigInteger chunkLen) {
|
||||||
|
super(GUID.GUID_VIDEOSTREAM, chunkLen);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Returns the codecId.
|
||||||
|
*/
|
||||||
|
public byte[] getCodecId() {
|
||||||
|
return this.codecId.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param codecIdentifier The codecId to set.
|
||||||
|
*/
|
||||||
|
public void setCodecId(final byte[] codecIdentifier) {
|
||||||
|
this.codecId = codecIdentifier.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link #getCodecId()}, as a String, where each byte has been
|
||||||
|
* converted to a <code>char</code>.
|
||||||
|
*
|
||||||
|
* @return Codec Id as String.
|
||||||
|
*/
|
||||||
|
public String getCodecIdAsString() {
|
||||||
|
String result;
|
||||||
|
if (this.codecId == null) {
|
||||||
|
result = "Unknown";
|
||||||
|
} else {
|
||||||
|
result = new String(getCodecId());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Returns the pictureHeight.
|
||||||
|
*/
|
||||||
|
public long getPictureHeight() {
|
||||||
|
return this.pictureHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param picHeight
|
||||||
|
*/
|
||||||
|
public void setPictureHeight(final long picHeight) {
|
||||||
|
this.pictureHeight = picHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Returns the pictureWidth.
|
||||||
|
*/
|
||||||
|
public long getPictureWidth() {
|
||||||
|
return this.pictureWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param picWidth
|
||||||
|
*/
|
||||||
|
public void setPictureWidth(final long picWidth) {
|
||||||
|
this.pictureWidth = picWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (overridden)
|
||||||
|
*
|
||||||
|
* @see org.jaudiotagger.audio.asf.data.StreamChunk#prettyPrint(String)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String prettyPrint(final String prefix) {
|
||||||
|
final StringBuilder result = new StringBuilder(super.prettyPrint(prefix));
|
||||||
|
result.insert(0, Utils.LINE_SEPARATOR + prefix + "|->VideoStream");
|
||||||
|
result.append(prefix).append("Video info:")
|
||||||
|
.append(Utils.LINE_SEPARATOR);
|
||||||
|
result.append(prefix).append(" |->Width : ").append(
|
||||||
|
getPictureWidth()).append(Utils.LINE_SEPARATOR);
|
||||||
|
result.append(prefix).append(" |->Heigth : ").append(
|
||||||
|
getPictureHeight()).append(Utils.LINE_SEPARATOR);
|
||||||
|
result.append(prefix).append(" |->Codec : ").append(
|
||||||
|
getCodecIdAsString()).append(Utils.LINE_SEPARATOR);
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
}
|
19
src/main/java/org/jaudiotagger/audio/asf/data/package.html
Normal file
19
src/main/java/org/jaudiotagger/audio/asf/data/package.html
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
|
||||||
|
"http://www.w3.org/TR/1999/REC-html401-19991224/loose.dtd">
|
||||||
|
|
||||||
|
<html long="en">
|
||||||
|
<head>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body bgcolor="white">
|
||||||
|
|
||||||
|
Classes for data components of the Microsoft Advanced Systems Format header.
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- package.html by Gary McGath -->
|
||||||
|
<!-- Put @see and @since tags down here. -->
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,135 @@
|
||||||
|
package org.jaudiotagger.audio.asf.io;
|
||||||
|
|
||||||
|
import org.jaudiotagger.audio.asf.data.GUID;
|
||||||
|
import org.jaudiotagger.audio.asf.util.Utils;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This modifier manipulates an ASF header extension object.
|
||||||
|
*
|
||||||
|
* @author Christian Laireiter
|
||||||
|
*/
|
||||||
|
public class AsfExtHeaderModifier implements ChunkModifier {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of modifiers which are to be applied to contained chunks.
|
||||||
|
*/
|
||||||
|
private final List<ChunkModifier> modifierList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance.<br>
|
||||||
|
*
|
||||||
|
* @param modifiers modifiers to apply.
|
||||||
|
*/
|
||||||
|
public AsfExtHeaderModifier(final List<ChunkModifier> modifiers) {
|
||||||
|
assert modifiers != null;
|
||||||
|
this.modifierList = new ArrayList<ChunkModifier>(modifiers);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simply copies a chunk from <code>source</code> to
|
||||||
|
* <code>destination</code>.<br>
|
||||||
|
* The method assumes, that the GUID has already been read and will write
|
||||||
|
* the provided one to the destination.<br>
|
||||||
|
* The chunk length however will be read and used to determine the amount of
|
||||||
|
* bytes to copy.
|
||||||
|
*
|
||||||
|
* @param guid GUID of the current CHUNK.
|
||||||
|
* @param source source of an ASF chunk, which is to be located at the chunk
|
||||||
|
* length field.
|
||||||
|
* @param destination the destination to copy the chunk to.
|
||||||
|
* @throws IOException on I/O errors.
|
||||||
|
*/
|
||||||
|
private void copyChunk(final GUID guid, final InputStream source,
|
||||||
|
final OutputStream destination) throws IOException {
|
||||||
|
final long chunkSize = Utils.readUINT64(source);
|
||||||
|
destination.write(guid.getBytes());
|
||||||
|
Utils.writeUINT64(chunkSize, destination);
|
||||||
|
Utils.copy(source, destination, chunkSize - 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public boolean isApplicable(final GUID guid) {
|
||||||
|
return GUID.GUID_HEADER_EXTENSION.equals(guid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public ModificationResult modify(final GUID guid, final InputStream source,
|
||||||
|
final OutputStream destination) throws IOException {
|
||||||
|
assert GUID.GUID_HEADER_EXTENSION.equals(guid);
|
||||||
|
|
||||||
|
long difference = 0;
|
||||||
|
final List<ChunkModifier> modders = new ArrayList<ChunkModifier>(
|
||||||
|
this.modifierList);
|
||||||
|
final Set<GUID> occuredGuids = new HashSet<GUID>();
|
||||||
|
occuredGuids.add(guid);
|
||||||
|
|
||||||
|
final BigInteger chunkLen = Utils.readBig64(source);
|
||||||
|
final GUID reserved1 = Utils.readGUID(source);
|
||||||
|
final int reserved2 = Utils.readUINT16(source);
|
||||||
|
final long dataSize = Utils.readUINT32(source);
|
||||||
|
|
||||||
|
assert dataSize == 0 || dataSize >= 24;
|
||||||
|
assert chunkLen.subtract(BigInteger.valueOf(46)).longValue() == dataSize;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Stream buffer for the chunk list
|
||||||
|
*/
|
||||||
|
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
|
/*
|
||||||
|
* Stream which counts read bytes. Dirty but quick way of implementing
|
||||||
|
* this.
|
||||||
|
*/
|
||||||
|
final CountingInputStream cis = new CountingInputStream(source);
|
||||||
|
|
||||||
|
while (cis.getReadCount() < dataSize) {
|
||||||
|
// read GUID
|
||||||
|
final GUID curr = Utils.readGUID(cis);
|
||||||
|
boolean handled = false;
|
||||||
|
for (int i = 0; i < modders.size() && !handled; i++) {
|
||||||
|
if (modders.get(i).isApplicable(curr)) {
|
||||||
|
final ModificationResult modRes = modders.get(i).modify(
|
||||||
|
curr, cis, bos);
|
||||||
|
difference += modRes.getByteDifference();
|
||||||
|
occuredGuids.addAll(modRes.getOccuredGUIDs());
|
||||||
|
modders.remove(i);
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!handled) {
|
||||||
|
occuredGuids.add(curr);
|
||||||
|
copyChunk(curr, cis, bos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Now apply the left modifiers.
|
||||||
|
for (final ChunkModifier curr : modders) {
|
||||||
|
// chunks, which were not in the source file, will be added to the
|
||||||
|
// destination
|
||||||
|
final ModificationResult result = curr.modify(null, null, bos);
|
||||||
|
difference += result.getByteDifference();
|
||||||
|
occuredGuids.addAll(result.getOccuredGUIDs());
|
||||||
|
}
|
||||||
|
destination.write(GUID.GUID_HEADER_EXTENSION.getBytes());
|
||||||
|
Utils.writeUINT64(chunkLen.add(BigInteger.valueOf(difference))
|
||||||
|
.longValue(), destination);
|
||||||
|
destination.write(reserved1.getBytes());
|
||||||
|
Utils.writeUINT16(reserved2, destination);
|
||||||
|
Utils.writeUINT32(dataSize + difference, destination);
|
||||||
|
destination.write(bos.toByteArray());
|
||||||
|
return new ModificationResult(0, difference, occuredGuids);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
package org.jaudiotagger.audio.asf.io;
|
||||||
|
|
||||||
|
import org.jaudiotagger.audio.asf.data.AsfExtendedHeader;
|
||||||
|
import org.jaudiotagger.audio.asf.data.GUID;
|
||||||
|
import org.jaudiotagger.audio.asf.util.Utils;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This reader reads an ASF header extension object from an {@link InputStream}
|
||||||
|
* and creates an {@link AsfExtendedHeader} object.<br>
|
||||||
|
*
|
||||||
|
* @author Christian Laireiter
|
||||||
|
*/
|
||||||
|
public class AsfExtHeaderReader extends ChunkContainerReader<AsfExtendedHeader> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The GUID this reader {@linkplain #getApplyingIds() applies to}
|
||||||
|
*/
|
||||||
|
private final static GUID[] APPLYING = {GUID.GUID_HEADER_EXTENSION};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a reader instance, which only utilizes the given list of chunk
|
||||||
|
* readers.<br>
|
||||||
|
*
|
||||||
|
* @param toRegister List of {@link ChunkReader} class instances, which are to be
|
||||||
|
* utilized by the instance.
|
||||||
|
* @param readChunkOnce if <code>true</code>, each chunk type (identified by chunk
|
||||||
|
* GUID) will handled only once, if a reader is available, other
|
||||||
|
* chunks will be discarded.
|
||||||
|
*/
|
||||||
|
public AsfExtHeaderReader(
|
||||||
|
final List<Class<? extends ChunkReader>> toRegister,
|
||||||
|
final boolean readChunkOnce) {
|
||||||
|
super(toRegister, readChunkOnce);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public boolean canFail() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected AsfExtendedHeader createContainer(final long streamPosition,
|
||||||
|
final BigInteger chunkLength, final InputStream stream)
|
||||||
|
throws IOException {
|
||||||
|
Utils.readGUID(stream); // First reserved field (should be a specific
|
||||||
|
// GUID.
|
||||||
|
Utils.readUINT16(stream); // Second reserved field (should always be 6)
|
||||||
|
final long extensionSize = Utils.readUINT32(stream);
|
||||||
|
assert extensionSize == 0 || extensionSize >= 24;
|
||||||
|
assert chunkLength.subtract(BigInteger.valueOf(46)).longValue() == extensionSize;
|
||||||
|
return new AsfExtendedHeader(streamPosition, chunkLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public GUID[] getApplyingIds() {
|
||||||
|
return APPLYING.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
231
src/main/java/org/jaudiotagger/audio/asf/io/AsfHeaderReader.java
Normal file
231
src/main/java/org/jaudiotagger/audio/asf/io/AsfHeaderReader.java
Normal file
|
@ -0,0 +1,231 @@
|
||||||
|
/*
|
||||||
|
* Entagged Audio Tag library
|
||||||
|
* Copyright (c) 2004-2005 Christian Laireiter <liree@web.de>
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this library; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
package org.jaudiotagger.audio.asf.io;
|
||||||
|
|
||||||
|
import org.jaudiotagger.audio.asf.data.AsfHeader;
|
||||||
|
import org.jaudiotagger.audio.asf.data.GUID;
|
||||||
|
import org.jaudiotagger.audio.asf.util.Utils;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This <i>class </i> reads an ASF header out of an input stream an creates an
|
||||||
|
* {@link org.jaudiotagger.audio.asf.data.AsfHeader} object if successful. <br>
|
||||||
|
* For now only ASF ver 1.0 is supported, because ver 2.0 seems not to be used
|
||||||
|
* anywhere. <br>
|
||||||
|
* ASF headers contains other chunks. As of this other readers of current
|
||||||
|
* <b>package </b> are called from within.
|
||||||
|
*
|
||||||
|
* @author Christian Laireiter
|
||||||
|
*/
|
||||||
|
public class AsfHeaderReader extends ChunkContainerReader<AsfHeader> {
|
||||||
|
/**
|
||||||
|
* The GUID this reader {@linkplain #getApplyingIds() applies to}
|
||||||
|
*/
|
||||||
|
private final static GUID[] APPLYING = {GUID.GUID_HEADER};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ASF reader configured to extract all information.
|
||||||
|
*/
|
||||||
|
private final static AsfHeaderReader FULL_READER;
|
||||||
|
/**
|
||||||
|
* ASF reader configured to just extract information about audio streams.<br>
|
||||||
|
* If the ASF file only contains one audio stream it works fine.<br>
|
||||||
|
*/
|
||||||
|
private final static AsfHeaderReader INFO_READER;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ASF reader configured to just extract metadata information.<br>
|
||||||
|
*/
|
||||||
|
private final static AsfHeaderReader TAG_READER;
|
||||||
|
|
||||||
|
static {
|
||||||
|
final List<Class<? extends ChunkReader>> readers = new ArrayList<Class<? extends ChunkReader>>();
|
||||||
|
readers.add(FileHeaderReader.class);
|
||||||
|
readers.add(StreamChunkReader.class);
|
||||||
|
INFO_READER = new AsfHeaderReader(readers, true);
|
||||||
|
readers.clear();
|
||||||
|
readers.add(ContentDescriptionReader.class);
|
||||||
|
readers.add(ContentBrandingReader.class);
|
||||||
|
readers.add(LanguageListReader.class);
|
||||||
|
readers.add(MetadataReader.class);
|
||||||
|
/*
|
||||||
|
* Create the header extension object readers with just content
|
||||||
|
* description reader, extended content description reader, language
|
||||||
|
* list reader and both metadata object readers.
|
||||||
|
*/
|
||||||
|
final AsfExtHeaderReader extReader = new AsfExtHeaderReader(readers,
|
||||||
|
true);
|
||||||
|
final AsfExtHeaderReader extReader2 = new AsfExtHeaderReader(readers,
|
||||||
|
true);
|
||||||
|
TAG_READER = new AsfHeaderReader(readers, true);
|
||||||
|
TAG_READER.setExtendedHeaderReader(extReader);
|
||||||
|
readers.add(FileHeaderReader.class);
|
||||||
|
readers.add(StreamChunkReader.class);
|
||||||
|
readers.add(EncodingChunkReader.class);
|
||||||
|
readers.add(EncryptionChunkReader.class);
|
||||||
|
readers.add(StreamBitratePropertiesReader.class);
|
||||||
|
FULL_READER = new AsfHeaderReader(readers, false);
|
||||||
|
FULL_READER.setExtendedHeaderReader(extReader2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance of this reader.
|
||||||
|
*
|
||||||
|
* @param toRegister The chunk readers to utilize.
|
||||||
|
* @param readChunkOnce if <code>true</code>, each chunk type (identified by chunk
|
||||||
|
* GUID) will handled only once, if a reader is available, other
|
||||||
|
* chunks will be discarded.
|
||||||
|
*/
|
||||||
|
public AsfHeaderReader(final List<Class<? extends ChunkReader>> toRegister,
|
||||||
|
final boolean readChunkOnce) {
|
||||||
|
super(toRegister, readChunkOnce);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a Stream that will read from the specified
|
||||||
|
* {@link RandomAccessFile};<br>
|
||||||
|
*
|
||||||
|
* @param raf data source to read from.
|
||||||
|
* @return a stream which accesses the source.
|
||||||
|
*/
|
||||||
|
private static InputStream createStream(final RandomAccessFile raf) {
|
||||||
|
return new FullRequestInputStream(new BufferedInputStream(
|
||||||
|
new RandomAccessFileInputstream(raf)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method extracts the full ASF-Header from the given file.<br>
|
||||||
|
* If no header could be extracted <code>null</code> is returned. <br>
|
||||||
|
*
|
||||||
|
* @param file the ASF file to read.<br>
|
||||||
|
* @return AsfHeader-Wrapper, or <code>null</code> if no supported ASF
|
||||||
|
* header was found.
|
||||||
|
* @throws IOException on I/O Errors.
|
||||||
|
*/
|
||||||
|
public static AsfHeader readHeader(final File file) throws IOException {
|
||||||
|
final InputStream stream = new FileInputStream(file);
|
||||||
|
final AsfHeader result = FULL_READER.read(Utils.readGUID(stream),
|
||||||
|
stream, 0);
|
||||||
|
stream.close();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method tries to extract a full ASF-header out of the given stream. <br>
|
||||||
|
* If no header could be extracted <code>null</code> is returned. <br>
|
||||||
|
*
|
||||||
|
* @param file File which contains the ASF header.
|
||||||
|
* @return AsfHeader-Wrapper, or <code>null</code> if no supported ASF
|
||||||
|
* header was found.
|
||||||
|
* @throws IOException Read errors
|
||||||
|
*/
|
||||||
|
public static AsfHeader readHeader(final RandomAccessFile file)
|
||||||
|
throws IOException {
|
||||||
|
final InputStream stream = createStream(file);
|
||||||
|
return FULL_READER.read(Utils.readGUID(stream), stream, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method tries to extract an ASF-header out of the given stream, which
|
||||||
|
* only contains information about the audio stream.<br>
|
||||||
|
* If no header could be extracted <code>null</code> is returned. <br>
|
||||||
|
*
|
||||||
|
* @param file File which contains the ASF header.
|
||||||
|
* @return AsfHeader-Wrapper, or <code>null</code> if no supported ASF
|
||||||
|
* header was found.
|
||||||
|
* @throws IOException Read errors
|
||||||
|
*/
|
||||||
|
public static AsfHeader readInfoHeader(final RandomAccessFile file)
|
||||||
|
throws IOException {
|
||||||
|
final InputStream stream = createStream(file);
|
||||||
|
return INFO_READER.read(Utils.readGUID(stream), stream, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method tries to extract an ASF-header out of the given stream, which
|
||||||
|
* only contains metadata.<br>
|
||||||
|
* If no header could be extracted <code>null</code> is returned. <br>
|
||||||
|
*
|
||||||
|
* @param file File which contains the ASF header.
|
||||||
|
* @return AsfHeader-Wrapper, or <code>null</code> if no supported ASF
|
||||||
|
* header was found.
|
||||||
|
* @throws IOException Read errors
|
||||||
|
*/
|
||||||
|
public static AsfHeader readTagHeader(final RandomAccessFile file)
|
||||||
|
throws IOException {
|
||||||
|
final InputStream stream = createStream(file);
|
||||||
|
return TAG_READER.read(Utils.readGUID(stream), stream, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public boolean canFail() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected AsfHeader createContainer(final long streamPosition,
|
||||||
|
final BigInteger chunkLength, final InputStream stream)
|
||||||
|
throws IOException {
|
||||||
|
final long chunkCount = Utils.readUINT32(stream);
|
||||||
|
/*
|
||||||
|
* 2 reserved bytes. first should be equal to 0x01 and second 0x02. ASF
|
||||||
|
* specification suggests to not read the content if second byte is not
|
||||||
|
* 0x02.
|
||||||
|
*/
|
||||||
|
if (stream.read() != 1) {
|
||||||
|
throw new IOException("No ASF"); //$NON-NLS-1$
|
||||||
|
}
|
||||||
|
if (stream.read() != 2) {
|
||||||
|
throw new IOException("No ASF"); //$NON-NLS-1$
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* Creating the resulting object
|
||||||
|
*/
|
||||||
|
return new AsfHeader(streamPosition, chunkLength, chunkCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public GUID[] getApplyingIds() {
|
||||||
|
return APPLYING.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the {@link AsfExtHeaderReader}, which is to be used, when an header
|
||||||
|
* extension object is found.
|
||||||
|
*
|
||||||
|
* @param extReader header extension object reader.
|
||||||
|
*/
|
||||||
|
public void setExtendedHeaderReader(final AsfExtHeaderReader extReader) {
|
||||||
|
for (final GUID curr : extReader.getApplyingIds()) {
|
||||||
|
this.readerMap.put(curr, extReader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
176
src/main/java/org/jaudiotagger/audio/asf/io/AsfStreamer.java
Normal file
176
src/main/java/org/jaudiotagger/audio/asf/io/AsfStreamer.java
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
package org.jaudiotagger.audio.asf.io;
|
||||||
|
|
||||||
|
import org.jaudiotagger.audio.asf.data.GUID;
|
||||||
|
import org.jaudiotagger.audio.asf.util.Utils;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class creates a modified copy of an ASF file.<br>
|
||||||
|
*
|
||||||
|
* @author Christian Laireiter
|
||||||
|
*/
|
||||||
|
public class AsfStreamer {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simply copies a chunk from <code>source</code> to
|
||||||
|
* <code>destination</code>.<br>
|
||||||
|
* The method assumes, that the GUID has already been read and will write
|
||||||
|
* the provided one to the destination.<br>
|
||||||
|
* The chunk length however will be read and used to determine the amount of
|
||||||
|
* bytes to copy.
|
||||||
|
*
|
||||||
|
* @param guid GUID of the current chunk.
|
||||||
|
* @param source source of an ASF chunk, which is to be located at the chunk
|
||||||
|
* length field.
|
||||||
|
* @param destination the destination to copy the chunk to.
|
||||||
|
* @throws IOException on I/O errors.
|
||||||
|
*/
|
||||||
|
private void copyChunk(final GUID guid, final InputStream source,
|
||||||
|
final OutputStream destination) throws IOException {
|
||||||
|
final long chunkSize = Utils.readUINT64(source);
|
||||||
|
destination.write(guid.getBytes());
|
||||||
|
Utils.writeUINT64(chunkSize, destination);
|
||||||
|
Utils.copy(source, destination, chunkSize - 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the <code>source</code> and applies the modifications provided by
|
||||||
|
* the given <code>modifiers</code>, and puts it to <code>dest</code>.<br>
|
||||||
|
* Each {@linkplain ChunkModifier modifier} is used only once, so if one
|
||||||
|
* should be used multiple times, it should be added multiple times into the
|
||||||
|
* list.<br>
|
||||||
|
*
|
||||||
|
* @param source the source ASF file
|
||||||
|
* @param dest the destination to write the modified version to.
|
||||||
|
* @param modifiers list of chunk modifiers to apply.
|
||||||
|
* @throws IOException on I/O errors.
|
||||||
|
*/
|
||||||
|
public void createModifiedCopy(final InputStream source,
|
||||||
|
final OutputStream dest, final List<ChunkModifier> modifiers)
|
||||||
|
throws IOException {
|
||||||
|
final List<ChunkModifier> modders = new ArrayList<ChunkModifier>();
|
||||||
|
if (modifiers != null) {
|
||||||
|
modders.addAll(modifiers);
|
||||||
|
}
|
||||||
|
// Read and check ASF GUID
|
||||||
|
final GUID readGUID = Utils.readGUID(source);
|
||||||
|
if (GUID.GUID_HEADER.equals(readGUID)) {
|
||||||
|
// used to calculate differences
|
||||||
|
long totalDiff = 0;
|
||||||
|
long chunkDiff = 0;
|
||||||
|
|
||||||
|
// read header information
|
||||||
|
final long headerSize = Utils.readUINT64(source);
|
||||||
|
final long chunkCount = Utils.readUINT32(source);
|
||||||
|
final byte[] reserved = new byte[2];
|
||||||
|
reserved[0] = (byte) (source.read() & 0xFF);
|
||||||
|
reserved[1] = (byte) (source.read() & 0xFF);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* bos will get all unmodified and modified header chunks. This is
|
||||||
|
* necessary, because the header chunk (and file properties chunk)
|
||||||
|
* need to be adjusted but are written in front of the others.
|
||||||
|
*/
|
||||||
|
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
|
// fileHeader will get the binary representation of the file
|
||||||
|
// properties chunk, without GUID
|
||||||
|
byte[] fileHeader = null;
|
||||||
|
|
||||||
|
// Iterate through all chunks
|
||||||
|
for (long i = 0; i < chunkCount; i++) {
|
||||||
|
// Read GUID
|
||||||
|
final GUID curr = Utils.readGUID(source);
|
||||||
|
// special case for file properties chunk
|
||||||
|
if (GUID.GUID_FILE.equals(curr)) {
|
||||||
|
final ByteArrayOutputStream tmp = new ByteArrayOutputStream();
|
||||||
|
final long size = Utils.readUINT64(source);
|
||||||
|
Utils.writeUINT64(size, tmp);
|
||||||
|
Utils.copy(source, tmp, size - 24);
|
||||||
|
fileHeader = tmp.toByteArray();
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* Now look for ChunkModifier objects which modify the
|
||||||
|
* current chunk
|
||||||
|
*/
|
||||||
|
boolean handled = false;
|
||||||
|
for (int j = 0; j < modders.size() && !handled; j++) {
|
||||||
|
if (modders.get(j).isApplicable(curr)) {
|
||||||
|
// alter current chunk
|
||||||
|
final ModificationResult result = modders.get(j)
|
||||||
|
.modify(curr, source, bos);
|
||||||
|
// remember size differences.
|
||||||
|
chunkDiff += result.getChunkCountDifference();
|
||||||
|
totalDiff += result.getByteDifference();
|
||||||
|
// remove current modifier from index.
|
||||||
|
modders.remove(j);
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!handled) {
|
||||||
|
// copy chunks which are not modified.
|
||||||
|
copyChunk(curr, source, bos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Now apply the left modifiers.
|
||||||
|
for (final ChunkModifier curr : modders) {
|
||||||
|
// chunks, which were not in the source file, will be added to
|
||||||
|
// the destination
|
||||||
|
final ModificationResult result = curr.modify(null, null, bos);
|
||||||
|
chunkDiff += result.getChunkCountDifference();
|
||||||
|
totalDiff += result.getByteDifference();
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* Now all header objects have been read or manipulated and stored
|
||||||
|
* in the internal buffer (bos).
|
||||||
|
*/
|
||||||
|
// write ASF GUID
|
||||||
|
dest.write(readGUID.getBytes());
|
||||||
|
// write altered header object size
|
||||||
|
Utils.writeUINT64(headerSize + totalDiff, dest);
|
||||||
|
// write altered number of chunks
|
||||||
|
Utils.writeUINT32(chunkCount + chunkDiff, dest);
|
||||||
|
// write the reserved 2 bytes (0x01,0x02).
|
||||||
|
dest.write(reserved);
|
||||||
|
// write the new file header
|
||||||
|
modifyFileHeader(new ByteArrayInputStream(fileHeader), dest,
|
||||||
|
totalDiff);
|
||||||
|
// write the header objects (chunks)
|
||||||
|
dest.write(bos.toByteArray());
|
||||||
|
// copy the rest of the file (data and index)
|
||||||
|
Utils.flush(source, dest);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("No ASF header object.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a slight variation of
|
||||||
|
* {@link #copyChunk(GUID, InputStream, OutputStream)}, it only handles file
|
||||||
|
* property chunks correctly.<br>
|
||||||
|
* The copied chunk will have the file size field modified by the given
|
||||||
|
* <code>fileSizeDiff</code> value.
|
||||||
|
*
|
||||||
|
* @param source source of file properties chunk, located at its chunk length
|
||||||
|
* field.
|
||||||
|
* @param destination the destination to copy the chunk to.
|
||||||
|
* @param fileSizeDiff the difference which should be applied. (negative values would
|
||||||
|
* subtract the stored file size)
|
||||||
|
* @throws IOException on I/O errors.
|
||||||
|
*/
|
||||||
|
private void modifyFileHeader(final InputStream source,
|
||||||
|
final OutputStream destination, final long fileSizeDiff)
|
||||||
|
throws IOException {
|
||||||
|
destination.write(GUID.GUID_FILE.getBytes());
|
||||||
|
final long chunkSize = Utils.readUINT64(source);
|
||||||
|
Utils.writeUINT64(chunkSize, destination);
|
||||||
|
destination.write(Utils.readGUID(source).getBytes());
|
||||||
|
final long fileSize = Utils.readUINT64(source);
|
||||||
|
Utils.writeUINT64(fileSize + fileSizeDiff, destination);
|
||||||
|
Utils.copy(source, destination, chunkSize - 48);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,215 @@
|
||||||
|
package org.jaudiotagger.audio.asf.io;
|
||||||
|
|
||||||
|
import org.jaudiotagger.audio.asf.data.Chunk;
|
||||||
|
import org.jaudiotagger.audio.asf.data.ChunkContainer;
|
||||||
|
import org.jaudiotagger.audio.asf.data.GUID;
|
||||||
|
import org.jaudiotagger.audio.asf.util.Utils;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class represents a reader implementation, which is able to read ASF
|
||||||
|
* objects (chunks) which store other objects (chunks) within them.<br>
|
||||||
|
*
|
||||||
|
* @param <ChunkType> The {@link ChunkContainer} instance, the implementation will
|
||||||
|
* create.
|
||||||
|
* @author Christian Laireiter
|
||||||
|
*/
|
||||||
|
abstract class ChunkContainerReader<ChunkType extends ChunkContainer>
|
||||||
|
implements ChunkReader {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Within this range, a {@link ChunkReader} should be aware if it fails.
|
||||||
|
*/
|
||||||
|
public final static int READ_LIMIT = 8192;
|
||||||
|
/**
|
||||||
|
* Logger
|
||||||
|
*/
|
||||||
|
protected static final Logger LOGGER = Logger
|
||||||
|
.getLogger("org.jaudiotabgger.audio"); //$NON-NLS-1$
|
||||||
|
/**
|
||||||
|
* If <code>true</code> each chunk type will only be read once.<br>
|
||||||
|
*/
|
||||||
|
protected final boolean eachChunkOnce;
|
||||||
|
/**
|
||||||
|
* Registers GUIDs to their reader classes.<br>
|
||||||
|
*/
|
||||||
|
protected final Map<GUID, ChunkReader> readerMap = new HashMap<GUID, ChunkReader>();
|
||||||
|
/**
|
||||||
|
* If <code>true</code> due to a {@linkplain #register(Class) registered}
|
||||||
|
* chunk reader, all {@link InputStream} objects passed to
|
||||||
|
* {@link #read(GUID, InputStream, long)} must support mark/reset.
|
||||||
|
*/
|
||||||
|
protected boolean hasFailingReaders = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a reader instance, which only utilizes the given list of chunk
|
||||||
|
* readers.<br>
|
||||||
|
*
|
||||||
|
* @param toRegister List of {@link ChunkReader} class instances, which are to be
|
||||||
|
* utilized by the instance.
|
||||||
|
* @param readChunkOnce if <code>true</code>, each chunk type (identified by chunk
|
||||||
|
* GUID) will handled only once, if a reader is available, other
|
||||||
|
* chunks will be discarded.
|
||||||
|
*/
|
||||||
|
protected ChunkContainerReader(
|
||||||
|
final List<Class<? extends ChunkReader>> toRegister,
|
||||||
|
final boolean readChunkOnce) {
|
||||||
|
this.eachChunkOnce = readChunkOnce;
|
||||||
|
for (final Class<? extends ChunkReader> curr : toRegister) {
|
||||||
|
register(curr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks for the constraints of this class.
|
||||||
|
*
|
||||||
|
* @param stream stream to test.
|
||||||
|
* @throws IllegalArgumentException If stream does not meet the requirements.
|
||||||
|
*/
|
||||||
|
protected void checkStream(final InputStream stream)
|
||||||
|
throws IllegalArgumentException {
|
||||||
|
if (this.hasFailingReaders && !stream.markSupported()) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Stream has to support mark/reset.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called by {@link #read(GUID, InputStream, long)} in order
|
||||||
|
* to create the resulting object. Implementations of this class should now
|
||||||
|
* return a new instance of their implementation specific result <b>AND</b>
|
||||||
|
* all data should be read, until the list of chunks starts. (The
|
||||||
|
* {@link ChunkContainer#getChunkEnd()} must return a sane result, too)<br>
|
||||||
|
*
|
||||||
|
* @param streamPosition position of the stream, the chunk starts.
|
||||||
|
* @param chunkLength the length of the chunk (from chunk header)
|
||||||
|
* @param stream to read the implementation specific information.
|
||||||
|
* @return instance of the implementations result.
|
||||||
|
* @throws IOException On I/O Errors and Invalid data.
|
||||||
|
*/
|
||||||
|
abstract protected ChunkType createContainer(long streamPosition,
|
||||||
|
BigInteger chunkLength, InputStream stream) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a configured {@linkplain ChunkReader reader} instance for ASF
|
||||||
|
* objects (chunks) with the specified <code>guid</code>.
|
||||||
|
*
|
||||||
|
* @param guid GUID which identifies the chunk to be read.
|
||||||
|
* @return an appropriate reader implementation, <code>null</code> if not
|
||||||
|
* {@linkplain #register(Class) registered}.
|
||||||
|
*/
|
||||||
|
protected ChunkReader getReader(final GUID guid) {
|
||||||
|
return this.readerMap.get(guid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests whether {@link #getReader(GUID)} won't return <code>null</code>.<br>
|
||||||
|
*
|
||||||
|
* @param guid GUID which identifies the chunk to be read.
|
||||||
|
* @return <code>true</code> if a reader is available.
|
||||||
|
*/
|
||||||
|
protected boolean isReaderAvailable(final GUID guid) {
|
||||||
|
return this.readerMap.containsKey(guid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This Method implements the reading of a chunk container.<br>
|
||||||
|
*
|
||||||
|
* @param guid GUID of the currently read container.
|
||||||
|
* @param stream Stream which contains the chunk container.
|
||||||
|
* @param chunkStart The start of the chunk container from stream start.<br>
|
||||||
|
* For direct file streams one can assume <code>0</code> here.
|
||||||
|
* @return <code>null</code> if no valid data found, else a Wrapper
|
||||||
|
* containing all supported data.
|
||||||
|
* @throws IOException Read errors.
|
||||||
|
* @throws IllegalArgumentException If one used {@link ChunkReader} could
|
||||||
|
* {@linkplain ChunkReader#canFail() fail} and the stream source
|
||||||
|
* doesn't support mark/reset.
|
||||||
|
*/
|
||||||
|
public ChunkType read(final GUID guid, final InputStream stream,
|
||||||
|
final long chunkStart) throws IOException, IllegalArgumentException {
|
||||||
|
checkStream(stream);
|
||||||
|
final CountingInputStream cis = new CountingInputStream(stream);
|
||||||
|
if (!Arrays.asList(getApplyingIds()).contains(guid)) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"provided GUID is not supported by this reader.");
|
||||||
|
}
|
||||||
|
// For Know the file pointer pointed to an ASF header chunk.
|
||||||
|
final BigInteger chunkLen = Utils.readBig64(cis);
|
||||||
|
/*
|
||||||
|
* now read implementation specific information until the chunk
|
||||||
|
* collection starts and create the resulting object.
|
||||||
|
*/
|
||||||
|
final ChunkType result = createContainer(chunkStart, chunkLen, cis);
|
||||||
|
// 16 bytes have already been for providing the GUID
|
||||||
|
long currentPosition = chunkStart + cis.getReadCount() + 16;
|
||||||
|
|
||||||
|
final HashSet<GUID> alreadyRead = new HashSet<GUID>();
|
||||||
|
/*
|
||||||
|
* Now reading header of chuncks.
|
||||||
|
*/
|
||||||
|
while (currentPosition < result.getChunkEnd()) {
|
||||||
|
final GUID currentGUID = Utils.readGUID(cis);
|
||||||
|
final boolean skip = this.eachChunkOnce
|
||||||
|
&& (!isReaderAvailable(currentGUID) || !alreadyRead
|
||||||
|
.add(currentGUID));
|
||||||
|
Chunk chunk;
|
||||||
|
/*
|
||||||
|
* If one reader tells it could fail (new method), then check the
|
||||||
|
* input stream for mark/reset. And use it if failed.
|
||||||
|
*/
|
||||||
|
if (!skip && isReaderAvailable(currentGUID)) {
|
||||||
|
final ChunkReader reader = getReader(currentGUID);
|
||||||
|
if (reader.canFail()) {
|
||||||
|
cis.mark(READ_LIMIT);
|
||||||
|
}
|
||||||
|
chunk = getReader(currentGUID).read(currentGUID, cis,
|
||||||
|
currentPosition);
|
||||||
|
} else {
|
||||||
|
chunk = ChunkHeaderReader.getInstance().read(currentGUID, cis,
|
||||||
|
currentPosition);
|
||||||
|
}
|
||||||
|
if (chunk == null) {
|
||||||
|
/*
|
||||||
|
* Reader failed
|
||||||
|
*/
|
||||||
|
cis.reset();
|
||||||
|
} else {
|
||||||
|
if (!skip) {
|
||||||
|
result.addChunk(chunk);
|
||||||
|
}
|
||||||
|
currentPosition = chunk.getChunkEnd();
|
||||||
|
// Always take into account, that 16 bytes have been read prior
|
||||||
|
// to calling this method
|
||||||
|
assert cis.getReadCount() + chunkStart + 16 == currentPosition;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers the given reader.<br>
|
||||||
|
*
|
||||||
|
* @param <T> The actual reader implementation.
|
||||||
|
* @param toRegister chunk reader which is to be registered.
|
||||||
|
*/
|
||||||
|
private <T extends ChunkReader> void register(final Class<T> toRegister) {
|
||||||
|
try {
|
||||||
|
final T reader = toRegister.newInstance();
|
||||||
|
for (final GUID curr : reader.getApplyingIds()) {
|
||||||
|
this.readerMap.put(curr, reader);
|
||||||
|
}
|
||||||
|
} catch (InstantiationException e) {
|
||||||
|
LOGGER.severe(e.getMessage());
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
LOGGER.severe(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
/*
|
||||||
|
* Entagged Audio Tag library
|
||||||
|
* Copyright (c) 2004-2005 Christian Laireiter <liree@web.de>
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this library; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
package org.jaudiotagger.audio.asf.io;
|
||||||
|
|
||||||
|
import org.jaudiotagger.audio.asf.data.Chunk;
|
||||||
|
import org.jaudiotagger.audio.asf.data.GUID;
|
||||||
|
import org.jaudiotagger.audio.asf.util.Utils;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default reader, Reads GUID and size out of an input stream and creates a
|
||||||
|
* {@link org.jaudiotagger.audio.asf.data.Chunk}object, finally skips the
|
||||||
|
* remaining chunk bytes.
|
||||||
|
*
|
||||||
|
* @author Christian Laireiter
|
||||||
|
*/
|
||||||
|
final class ChunkHeaderReader implements ChunkReader {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The GUID this reader {@linkplain #getApplyingIds() applies to}
|
||||||
|
*/
|
||||||
|
private final static GUID[] APPLYING = {GUID.GUID_UNSPECIFIED};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default instance.
|
||||||
|
*/
|
||||||
|
private static final ChunkHeaderReader INSTANCE = new ChunkHeaderReader();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hidden Utility class constructor.
|
||||||
|
*/
|
||||||
|
private ChunkHeaderReader() {
|
||||||
|
// Hidden
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an instance of the reader.
|
||||||
|
*
|
||||||
|
* @return instance.
|
||||||
|
*/
|
||||||
|
public static ChunkHeaderReader getInstance() {
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public boolean canFail() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public GUID[] getApplyingIds() {
|
||||||
|
return APPLYING.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public Chunk read(final GUID guid, final InputStream stream,
|
||||||
|
final long chunkStart) throws IOException {
|
||||||
|
final BigInteger chunkLen = Utils.readBig64(stream);
|
||||||
|
stream.skip(chunkLen.longValue() - 24);
|
||||||
|
return new Chunk(guid, chunkStart, chunkLen);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package org.jaudiotagger.audio.asf.io;
|
||||||
|
|
||||||
|
import org.jaudiotagger.audio.asf.data.GUID;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads an ASF chunk and writes a modified copy.<br>
|
||||||
|
*
|
||||||
|
* @author Christian Laireiter
|
||||||
|
*/
|
||||||
|
public interface ChunkModifier {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines, whether the modifier handles chunks identified by given
|
||||||
|
* <code>guid</code>.
|
||||||
|
*
|
||||||
|
* @param guid GUID to test.
|
||||||
|
* @return <code>true</code>, if this modifier can be used to modify the
|
||||||
|
* chunk.
|
||||||
|
*/
|
||||||
|
boolean isApplicable(GUID guid);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes a modified copy of the chunk into the <code>destination.</code>.<br>
|
||||||
|
*
|
||||||
|
* @param guid GUID of the chunk to modify.
|
||||||
|
* @param source a stream providing the chunk, starting at the chunks length
|
||||||
|
* field.
|
||||||
|
* @param destination destination for the modified chunk.
|
||||||
|
* @return the differences between source and destination.
|
||||||
|
* @throws IOException on I/O errors.
|
||||||
|
*/
|
||||||
|
ModificationResult modify(GUID guid, InputStream source,
|
||||||
|
OutputStream destination) throws IOException;
|
||||||
|
|
||||||
|
}
|
50
src/main/java/org/jaudiotagger/audio/asf/io/ChunkReader.java
Normal file
50
src/main/java/org/jaudiotagger/audio/asf/io/ChunkReader.java
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
package org.jaudiotagger.audio.asf.io;
|
||||||
|
|
||||||
|
import org.jaudiotagger.audio.asf.data.Chunk;
|
||||||
|
import org.jaudiotagger.audio.asf.data.GUID;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A ChunkReader provides methods for reading an ASF chunk.<br>
|
||||||
|
*
|
||||||
|
* @author Christian Laireiter
|
||||||
|
*/
|
||||||
|
public interface ChunkReader {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells whether the reader can fail to return a valid chunk.<br>
|
||||||
|
* The current Use would be a modified version of {@link StreamChunkReader},
|
||||||
|
* which is configured to only manage audio streams. However, the primary
|
||||||
|
* GUID for audio and video streams is the same. So if a stream shows itself
|
||||||
|
* to be a video stream, the reader would return <code>null</code>.<br>
|
||||||
|
*
|
||||||
|
* @return <code>true</code>, if further analysis of the chunk can show,
|
||||||
|
* that the reader is not applicable, despite the header GUID
|
||||||
|
* {@linkplain #getApplyingIds() identification} told it can handle
|
||||||
|
* the chunk.
|
||||||
|
*/
|
||||||
|
boolean canFail();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the GUIDs identifying the types of chunk, this reader will parse.<br>
|
||||||
|
*
|
||||||
|
* @return the GUIDs identifying the types of chunk, this reader will parse.<br>
|
||||||
|
*/
|
||||||
|
GUID[] getApplyingIds();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the chunk.
|
||||||
|
*
|
||||||
|
* @param guid the GUID of the chunks header, which is about to be read.
|
||||||
|
* @param stream source to read chunk from.<br>
|
||||||
|
* No {@link GUID} is expected at the currents stream position.
|
||||||
|
* The length of the chunk is about to follow.
|
||||||
|
* @param streamPosition the position in stream, the chunk starts.<br>
|
||||||
|
* @return the read chunk. (Mostly a subclass of {@link Chunk}).<br>
|
||||||
|
* @throws IOException On I/O Errors.
|
||||||
|
*/
|
||||||
|
Chunk read(GUID guid, InputStream stream, long streamPosition)
|
||||||
|
throws IOException;
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
package org.jaudiotagger.audio.asf.io;
|
||||||
|
|
||||||
|
import org.jaudiotagger.audio.asf.data.GUID;
|
||||||
|
import org.jaudiotagger.audio.asf.util.Utils;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This {@link ChunkModifier} implementation is meant to remove selected chunks.<br>
|
||||||
|
*
|
||||||
|
* @author Christian Laireiter
|
||||||
|
*/
|
||||||
|
@SuppressWarnings({"ManualArrayToCollectionCopy"})
|
||||||
|
public class ChunkRemover implements ChunkModifier {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores the GUIDs, which are about to be removed by this modifier.<br>
|
||||||
|
*/
|
||||||
|
private final Set<GUID> toRemove;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance, for removing selected chunks.<br>
|
||||||
|
*
|
||||||
|
* @param guids the GUIDs which are about to be removed by this modifier.
|
||||||
|
*/
|
||||||
|
public ChunkRemover(final GUID... guids) {
|
||||||
|
this.toRemove = new HashSet<GUID>();
|
||||||
|
for (final GUID current : guids) {
|
||||||
|
this.toRemove.add(current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public boolean isApplicable(final GUID guid) {
|
||||||
|
return this.toRemove.contains(guid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public ModificationResult modify(final GUID guid, final InputStream source,
|
||||||
|
final OutputStream destination) throws IOException {
|
||||||
|
ModificationResult result;
|
||||||
|
if (guid == null) {
|
||||||
|
// Now a chunk should be added, however, this implementation is for
|
||||||
|
// removal.
|
||||||
|
result = new ModificationResult(0, 0);
|
||||||
|
} else {
|
||||||
|
assert isApplicable(guid);
|
||||||
|
// skip the chunk length minus 24 bytes for the already read length
|
||||||
|
// and the guid.
|
||||||
|
final long chunkLen = Utils.readUINT64(source);
|
||||||
|
source.skip(chunkLen - 24);
|
||||||
|
result = new ModificationResult(-1, -1 * chunkLen, guid);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
package org.jaudiotagger.audio.asf.io;
|
||||||
|
|
||||||
|
import org.jaudiotagger.audio.asf.data.Chunk;
|
||||||
|
import org.jaudiotagger.audio.asf.data.ContentBranding;
|
||||||
|
import org.jaudiotagger.audio.asf.data.GUID;
|
||||||
|
import org.jaudiotagger.audio.asf.util.Utils;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This reader is used to read the content branding object of ASF streams.<br>
|
||||||
|
*
|
||||||
|
* @author Christian Laireiter
|
||||||
|
* @see org.jaudiotagger.audio.asf.data.ContainerType#CONTENT_BRANDING
|
||||||
|
* @see ContentBranding
|
||||||
|
*/
|
||||||
|
public class ContentBrandingReader implements ChunkReader {
|
||||||
|
/**
|
||||||
|
* The GUID this reader {@linkplain #getApplyingIds() applies to}
|
||||||
|
*/
|
||||||
|
private final static GUID[] APPLYING = {GUID.GUID_CONTENT_BRANDING};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should not be used for now.
|
||||||
|
*/
|
||||||
|
protected ContentBrandingReader() {
|
||||||
|
// NOTHING toDo
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public boolean canFail() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public GUID[] getApplyingIds() {
|
||||||
|
return APPLYING.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public Chunk read(final GUID guid, final InputStream stream,
|
||||||
|
final long streamPosition) throws IOException {
|
||||||
|
assert GUID.GUID_CONTENT_BRANDING.equals(guid);
|
||||||
|
final BigInteger chunkSize = Utils.readBig64(stream);
|
||||||
|
final long imageType = Utils.readUINT32(stream);
|
||||||
|
assert imageType >= 0 && imageType <= 3 : imageType;
|
||||||
|
final long imageDataSize = Utils.readUINT32(stream);
|
||||||
|
assert imageType > 0 || imageDataSize == 0 : imageDataSize;
|
||||||
|
assert imageDataSize < Integer.MAX_VALUE;
|
||||||
|
final byte[] imageData = Utils.readBinary(stream, imageDataSize);
|
||||||
|
final long copyRightUrlLen = Utils.readUINT32(stream);
|
||||||
|
final String copyRight = new String(Utils.readBinary(stream,
|
||||||
|
copyRightUrlLen));
|
||||||
|
final long imageUrlLen = Utils.readUINT32(stream);
|
||||||
|
final String imageUrl = new String(Utils
|
||||||
|
.readBinary(stream, imageUrlLen));
|
||||||
|
final ContentBranding result = new ContentBranding(streamPosition,
|
||||||
|
chunkSize);
|
||||||
|
result.setImage(imageType, imageData);
|
||||||
|
result.setCopyRightURL(copyRight);
|
||||||
|
result.setBannerImageURL(imageUrl);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,123 @@
|
||||||
|
/*
|
||||||
|
* Entagged Audio Tag library
|
||||||
|
* Copyright (c) 2004-2005 Christian Laireiter <liree@web.de>
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this library; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
package org.jaudiotagger.audio.asf.io;
|
||||||
|
|
||||||
|
import org.jaudiotagger.audio.asf.data.Chunk;
|
||||||
|
import org.jaudiotagger.audio.asf.data.ContentDescription;
|
||||||
|
import org.jaudiotagger.audio.asf.data.GUID;
|
||||||
|
import org.jaudiotagger.audio.asf.util.Utils;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads and interprets the data of a ASF chunk containing title, author... <br>
|
||||||
|
*
|
||||||
|
* @author Christian Laireiter
|
||||||
|
* @see org.jaudiotagger.audio.asf.data.ContentDescription
|
||||||
|
*/
|
||||||
|
public class ContentDescriptionReader implements ChunkReader {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The GUID this reader {@linkplain #getApplyingIds() applies to}
|
||||||
|
*/
|
||||||
|
private final static GUID[] APPLYING = {GUID.GUID_CONTENTDESCRIPTION};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should not be used for now.
|
||||||
|
*/
|
||||||
|
protected ContentDescriptionReader() {
|
||||||
|
// NOTHING toDo
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public boolean canFail() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public GUID[] getApplyingIds() {
|
||||||
|
return APPLYING.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the next 5 UINT16 values as an array.<br>
|
||||||
|
*
|
||||||
|
* @param stream stream to read from
|
||||||
|
* @return 5 int values read from stream.
|
||||||
|
* @throws IOException on I/O Errors.
|
||||||
|
*/
|
||||||
|
private int[] getStringSizes(final InputStream stream) throws IOException {
|
||||||
|
final int[] result = new int[5];
|
||||||
|
for (int i = 0; i < result.length; i++) {
|
||||||
|
result[i] = Utils.readUINT16(stream);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public Chunk read(final GUID guid, final InputStream stream,
|
||||||
|
final long chunkStart) throws IOException {
|
||||||
|
final BigInteger chunkSize = Utils.readBig64(stream);
|
||||||
|
/*
|
||||||
|
* Now comes 16-Bit values representing the length of the Strings which
|
||||||
|
* follows.
|
||||||
|
*/
|
||||||
|
final int[] stringSizes = getStringSizes(stream);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Now we know the String length of each occuring String.
|
||||||
|
*/
|
||||||
|
final String[] strings = new String[stringSizes.length];
|
||||||
|
for (int i = 0; i < strings.length; i++) {
|
||||||
|
if (stringSizes[i] > 0) {
|
||||||
|
strings[i] = Utils
|
||||||
|
.readFixedSizeUTF16Str(stream, stringSizes[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* Now create the result
|
||||||
|
*/
|
||||||
|
final ContentDescription result = new ContentDescription(chunkStart,
|
||||||
|
chunkSize);
|
||||||
|
if (stringSizes[0] > 0) {
|
||||||
|
result.setTitle(strings[0]);
|
||||||
|
}
|
||||||
|
if (stringSizes[1] > 0) {
|
||||||
|
result.setAuthor(strings[1]);
|
||||||
|
}
|
||||||
|
if (stringSizes[2] > 0) {
|
||||||
|
result.setCopyright(strings[2]);
|
||||||
|
}
|
||||||
|
if (stringSizes[3] > 0) {
|
||||||
|
result.setComment(strings[3]);
|
||||||
|
}
|
||||||
|
if (stringSizes[4] > 0) {
|
||||||
|
result.setRating(strings[4]);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,108 @@
|
||||||
|
package org.jaudiotagger.audio.asf.io;
|
||||||
|
|
||||||
|
import java.io.FilterInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This implementation of {@link FilterInputStream} counts each read byte.<br>
|
||||||
|
* So at each time, with {@link #getReadCount()} one can determine how many
|
||||||
|
* bytes have been read, by this classes read and skip methods (mark and reset
|
||||||
|
* are also taken into account).<br>
|
||||||
|
*
|
||||||
|
* @author Christian Laireiter
|
||||||
|
*/
|
||||||
|
class CountingInputStream extends FilterInputStream {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If {@link #mark(int)} has been called, the current value of
|
||||||
|
* {@link #readCount} is stored, in order to reset it upon {@link #reset()}.
|
||||||
|
*/
|
||||||
|
private long markPos;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The amount of read or skipped bytes.
|
||||||
|
*/
|
||||||
|
private long readCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance, which delegates the commands to the given stream.
|
||||||
|
*
|
||||||
|
* @param stream stream to actually work with.
|
||||||
|
*/
|
||||||
|
public CountingInputStream(final InputStream stream) {
|
||||||
|
super(stream);
|
||||||
|
this.markPos = 0;
|
||||||
|
this.readCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Counts the given amount of bytes.
|
||||||
|
*
|
||||||
|
* @param amountRead number of bytes to increase.
|
||||||
|
*/
|
||||||
|
private synchronized void bytesRead(final long amountRead) {
|
||||||
|
if (amountRead >= 0) {
|
||||||
|
this.readCount += amountRead;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the readCount
|
||||||
|
*/
|
||||||
|
public synchronized long getReadCount() {
|
||||||
|
return this.readCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public synchronized void mark(final int readlimit) {
|
||||||
|
super.mark(readlimit);
|
||||||
|
this.markPos = this.readCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int read() throws IOException {
|
||||||
|
final int result = super.read();
|
||||||
|
bytesRead(1);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int read(final byte[] destination, final int off, final int len)
|
||||||
|
throws IOException {
|
||||||
|
final int result = super.read(destination, off, len);
|
||||||
|
bytesRead(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public synchronized void reset() throws IOException {
|
||||||
|
super.reset();
|
||||||
|
synchronized (this) {
|
||||||
|
this.readCount = this.markPos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public long skip(final long amount) throws IOException {
|
||||||
|
final long skipped = super.skip(amount);
|
||||||
|
bytesRead(skipped);
|
||||||
|
return skipped;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
package org.jaudiotagger.audio.asf.io;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This output stream wraps around another {@link OutputStream} and delegates
|
||||||
|
* the write calls.<br>
|
||||||
|
* Additionally all written bytes are counted and available by
|
||||||
|
* {@link #getCount()}.
|
||||||
|
*
|
||||||
|
* @author Christian Laireiter
|
||||||
|
*/
|
||||||
|
public class CountingOutputstream extends OutputStream {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The stream to forward the write calls.
|
||||||
|
*/
|
||||||
|
private final OutputStream wrapped;
|
||||||
|
/**
|
||||||
|
* Stores the amount of bytes written.
|
||||||
|
*/
|
||||||
|
private long count = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance which will delegate the write calls to the given
|
||||||
|
* output stream.
|
||||||
|
*
|
||||||
|
* @param outputStream stream to wrap.
|
||||||
|
*/
|
||||||
|
public CountingOutputstream(final OutputStream outputStream) {
|
||||||
|
super();
|
||||||
|
assert outputStream != null;
|
||||||
|
this.wrapped = outputStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
this.wrapped.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void flush() throws IOException {
|
||||||
|
this.wrapped.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the count
|
||||||
|
*/
|
||||||
|
public long getCount() {
|
||||||
|
return this.count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void write(final byte[] bytes) throws IOException {
|
||||||
|
this.wrapped.write(bytes);
|
||||||
|
this.count += bytes.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void write(final byte[] bytes, final int off, final int len)
|
||||||
|
throws IOException {
|
||||||
|
this.wrapped.write(bytes, off, len);
|
||||||
|
this.count += len;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void write(final int toWrite) throws IOException {
|
||||||
|
this.wrapped.write(toWrite);
|
||||||
|
this.count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,100 @@
|
||||||
|
/*
|
||||||
|
* Entagged Audio Tag library
|
||||||
|
* Copyright (c) 2004-2005 Christian Laireiter <liree@web.de>
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this library; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
package org.jaudiotagger.audio.asf.io;
|
||||||
|
|
||||||
|
import org.jaudiotagger.audio.asf.data.Chunk;
|
||||||
|
import org.jaudiotagger.audio.asf.data.EncodingChunk;
|
||||||
|
import org.jaudiotagger.audio.asf.data.GUID;
|
||||||
|
import org.jaudiotagger.audio.asf.util.Utils;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class reads the chunk containing encoding data <br>
|
||||||
|
* <b>Warning:<b><br>
|
||||||
|
* Implementation is not completed. More analysis of this chunk is needed.
|
||||||
|
*
|
||||||
|
* @author Christian Laireiter
|
||||||
|
*/
|
||||||
|
class EncodingChunkReader implements ChunkReader {
|
||||||
|
/**
|
||||||
|
* The GUID this reader {@linkplain #getApplyingIds() applies to}
|
||||||
|
*/
|
||||||
|
private final static GUID[] APPLYING = {GUID.GUID_ENCODING};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should not be used for now.
|
||||||
|
*/
|
||||||
|
protected EncodingChunkReader() {
|
||||||
|
// NOTHING toDo
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public boolean canFail() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public GUID[] getApplyingIds() {
|
||||||
|
return APPLYING.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public Chunk read(final GUID guid, final InputStream stream,
|
||||||
|
final long chunkStart) throws IOException {
|
||||||
|
final BigInteger chunkLen = Utils.readBig64(stream);
|
||||||
|
final EncodingChunk result = new EncodingChunk(chunkLen);
|
||||||
|
int readBytes = 24;
|
||||||
|
// Can't be interpreted
|
||||||
|
/*
|
||||||
|
* What do I think of this data, well it seems to be another GUID. Then
|
||||||
|
* followed by a UINT16 indicating a length of data following (by half).
|
||||||
|
* My test files just had the length of one and a two bytes zero.
|
||||||
|
*/
|
||||||
|
stream.skip(20);
|
||||||
|
readBytes += 20;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Read the number of strings which will follow
|
||||||
|
*/
|
||||||
|
final int stringCount = Utils.readUINT16(stream);
|
||||||
|
readBytes += 2;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Now reading the specified amount of strings.
|
||||||
|
*/
|
||||||
|
for (int i = 0; i < stringCount; i++) {
|
||||||
|
final String curr = Utils.readCharacterSizedString(stream);
|
||||||
|
result.addString(curr);
|
||||||
|
readBytes += 4 + 2 * curr.length();
|
||||||
|
}
|
||||||
|
stream.skip(chunkLen.longValue() - readBytes);
|
||||||
|
result.setPosition(chunkStart);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,128 @@
|
||||||
|
/*
|
||||||
|
* Entagged Audio Tag library
|
||||||
|
* Copyright (c) 2004-2005 Christian Laireiter <liree@web.de>
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this library; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
package org.jaudiotagger.audio.asf.io;
|
||||||
|
|
||||||
|
import org.jaudiotagger.audio.asf.data.Chunk;
|
||||||
|
import org.jaudiotagger.audio.asf.data.EncryptionChunk;
|
||||||
|
import org.jaudiotagger.audio.asf.data.GUID;
|
||||||
|
import org.jaudiotagger.audio.asf.util.Utils;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class reads the chunk containing encoding data <br>
|
||||||
|
* <b>Warning:<b><br>
|
||||||
|
* Implementation is not completed. More analysis of this chunk is needed.
|
||||||
|
*
|
||||||
|
* @author Christian Laireiter
|
||||||
|
*/
|
||||||
|
class EncryptionChunkReader implements ChunkReader {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The GUID this reader {@linkplain #getApplyingIds() applies to}
|
||||||
|
*/
|
||||||
|
private final static GUID[] APPLYING = {GUID.GUID_CONTENT_ENCRYPTION};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should not be used for now.
|
||||||
|
*/
|
||||||
|
protected EncryptionChunkReader() {
|
||||||
|
// NOTHING toDo
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public boolean canFail() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public GUID[] getApplyingIds() {
|
||||||
|
return APPLYING.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public Chunk read(final GUID guid, final InputStream stream,
|
||||||
|
final long chunkStart) throws IOException {
|
||||||
|
EncryptionChunk result;
|
||||||
|
final BigInteger chunkLen = Utils.readBig64(stream);
|
||||||
|
result = new EncryptionChunk(chunkLen);
|
||||||
|
|
||||||
|
// Can't be interpreted
|
||||||
|
/*
|
||||||
|
* Object ID GUID 128 Object Size QWORD 64 Secret Data Length DWORD 32
|
||||||
|
* Secret Data INTEGER varies Protection Type Length DWORD 32 Protection
|
||||||
|
* Type char varies Key ID Length DWORD 32 Key ID char varies License
|
||||||
|
* URL Length DWORD 32 License URL char varies * Read the
|
||||||
|
*/
|
||||||
|
byte[] secretData;
|
||||||
|
byte[] protectionType;
|
||||||
|
byte[] keyID;
|
||||||
|
byte[] licenseURL;
|
||||||
|
|
||||||
|
// Secret Data length
|
||||||
|
int fieldLength;
|
||||||
|
fieldLength = (int) Utils.readUINT32(stream);
|
||||||
|
// Secret Data
|
||||||
|
secretData = new byte[fieldLength + 1];
|
||||||
|
stream.read(secretData, 0, fieldLength);
|
||||||
|
secretData[fieldLength] = 0;
|
||||||
|
|
||||||
|
// Protection type Length
|
||||||
|
fieldLength = 0;
|
||||||
|
fieldLength = (int) Utils.readUINT32(stream);
|
||||||
|
// Protection Data Length
|
||||||
|
protectionType = new byte[fieldLength + 1];
|
||||||
|
stream.read(protectionType, 0, fieldLength);
|
||||||
|
protectionType[fieldLength] = 0;
|
||||||
|
|
||||||
|
// Key ID length
|
||||||
|
fieldLength = 0;
|
||||||
|
fieldLength = (int) Utils.readUINT32(stream);
|
||||||
|
// Key ID
|
||||||
|
keyID = new byte[fieldLength + 1];
|
||||||
|
stream.read(keyID, 0, fieldLength);
|
||||||
|
keyID[fieldLength] = 0;
|
||||||
|
|
||||||
|
// License URL length
|
||||||
|
fieldLength = 0;
|
||||||
|
fieldLength = (int) Utils.readUINT32(stream);
|
||||||
|
// License URL
|
||||||
|
licenseURL = new byte[fieldLength + 1];
|
||||||
|
stream.read(licenseURL, 0, fieldLength);
|
||||||
|
licenseURL[fieldLength] = 0;
|
||||||
|
|
||||||
|
result.setSecretData(new String(secretData));
|
||||||
|
result.setProtectionType(new String(protectionType));
|
||||||
|
result.setKeyID(new String(keyID));
|
||||||
|
result.setLicenseURL(new String(licenseURL));
|
||||||
|
|
||||||
|
result.setPosition(chunkStart);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
/*
|
||||||
|
* Entagged Audio Tag library
|
||||||
|
* Copyright (c) 2004-2005 Christian Laireiter <liree@web.de>
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this library; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
package org.jaudiotagger.audio.asf.io;
|
||||||
|
|
||||||
|
import org.jaudiotagger.audio.asf.data.Chunk;
|
||||||
|
import org.jaudiotagger.audio.asf.data.FileHeader;
|
||||||
|
import org.jaudiotagger.audio.asf.data.GUID;
|
||||||
|
import org.jaudiotagger.audio.asf.util.Utils;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads and interprets the data of the file header. <br>
|
||||||
|
*
|
||||||
|
* @author Christian Laireiter
|
||||||
|
*/
|
||||||
|
public class FileHeaderReader implements ChunkReader {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The GUID this reader {@linkplain #getApplyingIds() applies to}
|
||||||
|
*/
|
||||||
|
private final static GUID[] APPLYING = {GUID.GUID_FILE};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should not be used for now.
|
||||||
|
*/
|
||||||
|
protected FileHeaderReader() {
|
||||||
|
// NOTHING toDo
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public boolean canFail() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public GUID[] getApplyingIds() {
|
||||||
|
return APPLYING.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public Chunk read(final GUID guid, final InputStream stream,
|
||||||
|
final long chunkStart) throws IOException {
|
||||||
|
final BigInteger chunkLen = Utils.readBig64(stream);
|
||||||
|
// Skip client GUID.
|
||||||
|
stream.skip(16);
|
||||||
|
final BigInteger fileSize = Utils.readBig64(stream);
|
||||||
|
// fileTime in 100 ns since midnight of 1st january 1601 GMT
|
||||||
|
final BigInteger fileTime = Utils.readBig64(stream);
|
||||||
|
|
||||||
|
final BigInteger packageCount = Utils.readBig64(stream);
|
||||||
|
|
||||||
|
final BigInteger timeEndPos = Utils.readBig64(stream);
|
||||||
|
final BigInteger duration = Utils.readBig64(stream);
|
||||||
|
final BigInteger timeStartPos = Utils.readBig64(stream);
|
||||||
|
|
||||||
|
final long flags = Utils.readUINT32(stream);
|
||||||
|
|
||||||
|
final long minPkgSize = Utils.readUINT32(stream);
|
||||||
|
final long maxPkgSize = Utils.readUINT32(stream);
|
||||||
|
final long uncompressedFrameSize = Utils.readUINT32(stream);
|
||||||
|
|
||||||
|
final FileHeader result = new FileHeader(chunkLen, fileSize, fileTime,
|
||||||
|
packageCount, duration, timeStartPos, timeEndPos, flags,
|
||||||
|
minPkgSize, maxPkgSize, uncompressedFrameSize);
|
||||||
|
result.setPosition(chunkStart);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
package org.jaudiotagger.audio.asf.io;
|
||||||
|
|
||||||
|
import java.io.FilterInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This implementation repeatedly reads from the wrapped input stream until the
|
||||||
|
* requested amount of bytes are read.<br>
|
||||||
|
*
|
||||||
|
* @author Christian Laireiter
|
||||||
|
*/
|
||||||
|
public class FullRequestInputStream extends FilterInputStream {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance.
|
||||||
|
*
|
||||||
|
* @param source stream to read from.
|
||||||
|
*/
|
||||||
|
public FullRequestInputStream(final InputStream source) {
|
||||||
|
super(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int read(final byte[] buffer) throws IOException {
|
||||||
|
return read(buffer, 0, buffer.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int read(final byte[] buffer, final int off, final int len)
|
||||||
|
throws IOException {
|
||||||
|
int totalRead = 0;
|
||||||
|
int read;
|
||||||
|
while (totalRead < len) {
|
||||||
|
read = super.read(buffer, off + totalRead, len - totalRead);
|
||||||
|
if (read >= 0) {
|
||||||
|
totalRead += read;
|
||||||
|
}
|
||||||
|
if (read == -1) {
|
||||||
|
throw new IOException((len - totalRead)
|
||||||
|
+ " more bytes expected.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return totalRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public long skip(final long amount) throws IOException {
|
||||||
|
long skipped = 0;
|
||||||
|
int zeroSkipCnt = 0;
|
||||||
|
long currSkipped;
|
||||||
|
while (skipped < amount) {
|
||||||
|
currSkipped = super.skip(amount - skipped);
|
||||||
|
if (currSkipped == 0) {
|
||||||
|
zeroSkipCnt++;
|
||||||
|
if (zeroSkipCnt == 2) {
|
||||||
|
// If the skip value exceeds streams size, this and the
|
||||||
|
// number is extremely large, this can lead to a very long
|
||||||
|
// running loop.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
skipped += currSkipped;
|
||||||
|
}
|
||||||
|
return skipped;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
package org.jaudiotagger.audio.asf.io;
|
||||||
|
|
||||||
|
import org.jaudiotagger.audio.asf.data.Chunk;
|
||||||
|
import org.jaudiotagger.audio.asf.data.GUID;
|
||||||
|
import org.jaudiotagger.audio.asf.data.LanguageList;
|
||||||
|
import org.jaudiotagger.audio.asf.util.Utils;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads and interprets the "Language List Object" of ASF files.<br>
|
||||||
|
*
|
||||||
|
* @author Christian Laireiter
|
||||||
|
*/
|
||||||
|
public class LanguageListReader implements ChunkReader {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The GUID this reader {@linkplain #getApplyingIds() applies to}
|
||||||
|
*/
|
||||||
|
private final static GUID[] APPLYING = {GUID.GUID_LANGUAGE_LIST};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public boolean canFail() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public GUID[] getApplyingIds() {
|
||||||
|
return APPLYING.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public Chunk read(final GUID guid, final InputStream stream,
|
||||||
|
final long streamPosition) throws IOException {
|
||||||
|
assert GUID.GUID_LANGUAGE_LIST.equals(guid);
|
||||||
|
final BigInteger chunkLen = Utils.readBig64(stream);
|
||||||
|
|
||||||
|
final int readUINT16 = Utils.readUINT16(stream);
|
||||||
|
|
||||||
|
final LanguageList result = new LanguageList(streamPosition, chunkLen);
|
||||||
|
for (int i = 0; i < readUINT16; i++) {
|
||||||
|
final int langIdLen = (stream.read() & 0xFF);
|
||||||
|
final String langId = Utils
|
||||||
|
.readFixedSizeUTF16Str(stream, langIdLen);
|
||||||
|
// langIdLen = 2 bytes for each char and optionally one zero
|
||||||
|
// termination character
|
||||||
|
assert langId.length() == langIdLen / 2 - 1
|
||||||
|
|| langId.length() == langIdLen / 2;
|
||||||
|
result.addLanguage(langId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
154
src/main/java/org/jaudiotagger/audio/asf/io/MetadataReader.java
Normal file
154
src/main/java/org/jaudiotagger/audio/asf/io/MetadataReader.java
Normal file
|
@ -0,0 +1,154 @@
|
||||||
|
package org.jaudiotagger.audio.asf.io;
|
||||||
|
|
||||||
|
import org.jaudiotagger.audio.asf.data.*;
|
||||||
|
import org.jaudiotagger.audio.asf.util.Utils;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads an interprets "Metadata Object", "Metadata Library
|
||||||
|
* Object" and "Extended Content Description" of ASF files.<br>
|
||||||
|
*
|
||||||
|
* @author Christian Laireiter
|
||||||
|
*/
|
||||||
|
public class MetadataReader implements ChunkReader {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The GUID this reader {@linkplain #getApplyingIds() applies to}
|
||||||
|
*/
|
||||||
|
private final static GUID[] APPLYING = {
|
||||||
|
ContainerType.EXTENDED_CONTENT.getContainerGUID(),
|
||||||
|
ContainerType.METADATA_OBJECT.getContainerGUID(),
|
||||||
|
ContainerType.METADATA_LIBRARY_OBJECT.getContainerGUID()};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public boolean canFail() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public GUID[] getApplyingIds() {
|
||||||
|
return APPLYING.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public Chunk read(final GUID guid, final InputStream stream,
|
||||||
|
final long streamPosition) throws IOException {
|
||||||
|
final BigInteger chunkLen = Utils.readBig64(stream);
|
||||||
|
|
||||||
|
final MetadataContainer result = new MetadataContainer(guid,
|
||||||
|
streamPosition, chunkLen);
|
||||||
|
// isExtDesc will be set to true, if a extended content description
|
||||||
|
// chunk is read
|
||||||
|
// otherwise it is a metadata object, there are only slight differences
|
||||||
|
final boolean isExtDesc = result.getContainerType() == ContainerType.EXTENDED_CONTENT;
|
||||||
|
final int recordCount = Utils.readUINT16(stream);
|
||||||
|
for (int i = 0; i < recordCount; i++) {
|
||||||
|
int languageIndex = 0;
|
||||||
|
int streamNumber = 0;
|
||||||
|
if (!isExtDesc) {
|
||||||
|
/*
|
||||||
|
* Metadata objects have a language index and a stream number
|
||||||
|
*/
|
||||||
|
languageIndex = Utils.readUINT16(stream);
|
||||||
|
assert languageIndex >= 0
|
||||||
|
&& languageIndex < MetadataDescriptor.MAX_LANG_INDEX;
|
||||||
|
assert result.getContainerType() == ContainerType.METADATA_LIBRARY_OBJECT
|
||||||
|
|| languageIndex == 0;
|
||||||
|
streamNumber = Utils.readUINT16(stream);
|
||||||
|
assert streamNumber >= 0
|
||||||
|
&& streamNumber <= MetadataDescriptor.MAX_STREAM_NUMBER;
|
||||||
|
}
|
||||||
|
final int nameLen = Utils.readUINT16(stream);
|
||||||
|
String recordName = null;
|
||||||
|
if (isExtDesc) {
|
||||||
|
recordName = Utils.readFixedSizeUTF16Str(stream, nameLen);
|
||||||
|
}
|
||||||
|
final int dataType = Utils.readUINT16(stream);
|
||||||
|
assert dataType >= 0 && dataType <= 6;
|
||||||
|
final long dataLen = isExtDesc ? Utils.readUINT16(stream) : Utils
|
||||||
|
.readUINT32(stream);
|
||||||
|
assert dataLen >= 0;
|
||||||
|
assert result.getContainerType() == ContainerType.METADATA_LIBRARY_OBJECT
|
||||||
|
|| dataLen <= MetadataDescriptor.DWORD_MAXVALUE;
|
||||||
|
if (!isExtDesc) {
|
||||||
|
recordName = Utils.readFixedSizeUTF16Str(stream, nameLen);
|
||||||
|
}
|
||||||
|
final MetadataDescriptor descriptor = new MetadataDescriptor(result
|
||||||
|
.getContainerType(), recordName, dataType, streamNumber,
|
||||||
|
languageIndex);
|
||||||
|
switch (dataType) {
|
||||||
|
case MetadataDescriptor.TYPE_STRING:
|
||||||
|
descriptor.setStringValue(Utils.readFixedSizeUTF16Str(stream,
|
||||||
|
(int) dataLen));
|
||||||
|
break;
|
||||||
|
case MetadataDescriptor.TYPE_BINARY:
|
||||||
|
descriptor.setBinaryValue(Utils.readBinary(stream, dataLen));
|
||||||
|
break;
|
||||||
|
case MetadataDescriptor.TYPE_BOOLEAN:
|
||||||
|
assert isExtDesc && dataLen == 4 || !isExtDesc && dataLen == 2;
|
||||||
|
descriptor.setBooleanValue(readBoolean(stream, (int) dataLen));
|
||||||
|
break;
|
||||||
|
case MetadataDescriptor.TYPE_DWORD:
|
||||||
|
assert dataLen == 4;
|
||||||
|
descriptor.setDWordValue(Utils.readUINT32(stream));
|
||||||
|
break;
|
||||||
|
case MetadataDescriptor.TYPE_WORD:
|
||||||
|
assert dataLen == 2;
|
||||||
|
descriptor.setWordValue(Utils.readUINT16(stream));
|
||||||
|
break;
|
||||||
|
case MetadataDescriptor.TYPE_QWORD:
|
||||||
|
assert dataLen == 8;
|
||||||
|
descriptor.setQWordValue(Utils.readUINT64(stream));
|
||||||
|
break;
|
||||||
|
case MetadataDescriptor.TYPE_GUID:
|
||||||
|
assert dataLen == GUID.GUID_LENGTH;
|
||||||
|
descriptor.setGUIDValue(Utils.readGUID(stream));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Unknown, hopefully the convention for the size of the
|
||||||
|
// value
|
||||||
|
// is given, so we could read it binary
|
||||||
|
descriptor.setStringValue("Invalid datatype: "
|
||||||
|
+ new String(Utils.readBinary(stream, dataLen)));
|
||||||
|
}
|
||||||
|
result.addDescriptor(descriptor);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the given amount of bytes and checks the last byte, if its equal to
|
||||||
|
* one or zero (true / false).<br>
|
||||||
|
* All other bytes must be zero. (if assertions enabled).
|
||||||
|
*
|
||||||
|
* @param stream stream to read from.
|
||||||
|
* @param bytes amount of bytes
|
||||||
|
* @return <code>true</code> or <code>false</code>.
|
||||||
|
* @throws IOException on I/O Errors
|
||||||
|
*/
|
||||||
|
private boolean readBoolean(final InputStream stream, final int bytes)
|
||||||
|
throws IOException {
|
||||||
|
final byte[] tmp = new byte[bytes];
|
||||||
|
stream.read(tmp);
|
||||||
|
boolean result = false;
|
||||||
|
for (int i = 0; i < bytes; i++) {
|
||||||
|
if (i == bytes - 1) {
|
||||||
|
result = tmp[i] == 1;
|
||||||
|
assert tmp[i] == 0 || tmp[i] == 1;
|
||||||
|
} else {
|
||||||
|
assert tmp[i] == 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
package org.jaudiotagger.audio.asf.io;
|
||||||
|
|
||||||
|
import org.jaudiotagger.audio.asf.data.GUID;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Structure to tell the differences occurred by altering a chunk.
|
||||||
|
*
|
||||||
|
* @author Christian Laireiter
|
||||||
|
*/
|
||||||
|
final class ModificationResult {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores the difference of bytes.<br>
|
||||||
|
*/
|
||||||
|
private final long byteDifference;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores the difference of the amount of chunks.<br>
|
||||||
|
* "-1" if the chunk disappeared upon modification.<br>
|
||||||
|
* "0" if the chunk was just modified.<br>
|
||||||
|
* "1" if a chunk has been created.<br>
|
||||||
|
*/
|
||||||
|
private final int chunkDifference;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores all GUIDs, which have been read.<br>
|
||||||
|
*/
|
||||||
|
private final Set<GUID> occuredGUIDs = new HashSet<GUID>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance.<br>
|
||||||
|
*
|
||||||
|
* @param chunkCountDiff amount of chunks appeared, disappeared
|
||||||
|
* @param bytesDiffer amount of bytes added or removed.
|
||||||
|
* @param occurred all GUIDs which have been occurred, during processing
|
||||||
|
*/
|
||||||
|
public ModificationResult(final int chunkCountDiff, final long bytesDiffer,
|
||||||
|
final GUID... occurred) {
|
||||||
|
assert occurred != null && occurred.length > 0;
|
||||||
|
this.chunkDifference = chunkCountDiff;
|
||||||
|
this.byteDifference = bytesDiffer;
|
||||||
|
this.occuredGUIDs.addAll(Arrays.asList(occurred));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance.<br>
|
||||||
|
*
|
||||||
|
* @param chunkCountDiff amount of chunks appeared, disappeared
|
||||||
|
* @param bytesDiffer amount of bytes added or removed.
|
||||||
|
* @param occurred all GUIDs which have been occurred, during processing
|
||||||
|
*/
|
||||||
|
public ModificationResult(final int chunkCountDiff, final long bytesDiffer,
|
||||||
|
final Set<GUID> occurred) {
|
||||||
|
this.chunkDifference = chunkCountDiff;
|
||||||
|
this.byteDifference = bytesDiffer;
|
||||||
|
this.occuredGUIDs.addAll(occurred);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the difference of bytes.
|
||||||
|
*
|
||||||
|
* @return the byte difference
|
||||||
|
*/
|
||||||
|
public long getByteDifference() {
|
||||||
|
return this.byteDifference;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the difference of the amount of chunks.
|
||||||
|
*
|
||||||
|
* @return the chunk count difference
|
||||||
|
*/
|
||||||
|
public int getChunkCountDifference() {
|
||||||
|
return this.chunkDifference;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all GUIDs which have been occurred during processing.
|
||||||
|
*
|
||||||
|
* @return see description.s
|
||||||
|
*/
|
||||||
|
public Set<GUID> getOccuredGUIDs() {
|
||||||
|
return new HashSet<GUID>(this.occuredGUIDs);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
package org.jaudiotagger.audio.asf.io;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.RandomAccessFile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps a {@link RandomAccessFile} into an {@link InputStream}.<br>
|
||||||
|
*
|
||||||
|
* @author Christian Laireiter
|
||||||
|
*/
|
||||||
|
public final class RandomAccessFileInputstream extends InputStream {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The file access to read from.<br>
|
||||||
|
*/
|
||||||
|
private final RandomAccessFile source;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance that will provide {@link InputStream} functionality
|
||||||
|
* on the given {@link RandomAccessFile} by delegating calls.<br>
|
||||||
|
*
|
||||||
|
* @param file The file to read.
|
||||||
|
*/
|
||||||
|
public RandomAccessFileInputstream(final RandomAccessFile file) {
|
||||||
|
super();
|
||||||
|
if (file == null) {
|
||||||
|
throw new IllegalArgumentException("null");
|
||||||
|
}
|
||||||
|
this.source = file;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int read() throws IOException {
|
||||||
|
return this.source.read();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int read(final byte[] buffer, final int off, final int len)
|
||||||
|
throws IOException {
|
||||||
|
return this.source.read(buffer, off, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public long skip(final long amount) throws IOException {
|
||||||
|
if (amount < 0) {
|
||||||
|
throw new IllegalArgumentException("invalid negative value");
|
||||||
|
}
|
||||||
|
long left = amount;
|
||||||
|
while (left > Integer.MAX_VALUE) {
|
||||||
|
this.source.skipBytes(Integer.MAX_VALUE);
|
||||||
|
left -= Integer.MAX_VALUE;
|
||||||
|
}
|
||||||
|
return this.source.skipBytes((int) left);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
package org.jaudiotagger.audio.asf.io;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.RandomAccessFile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps a {@link RandomAccessFile} into an {@link OutputStream}.<br>
|
||||||
|
*
|
||||||
|
* @author Christian Laireiter
|
||||||
|
*/
|
||||||
|
public final class RandomAccessFileOutputStream extends OutputStream {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the file to write to.
|
||||||
|
*/
|
||||||
|
private final RandomAccessFile targetFile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance.<br>
|
||||||
|
*
|
||||||
|
* @param target file to write to.
|
||||||
|
*/
|
||||||
|
public RandomAccessFileOutputStream(final RandomAccessFile target) {
|
||||||
|
super();
|
||||||
|
this.targetFile = target;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void write(final byte[] bytes, final int off, final int len)
|
||||||
|
throws IOException {
|
||||||
|
this.targetFile.write(bytes, off, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void write(final int toWrite) throws IOException {
|
||||||
|
this.targetFile.write(toWrite);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
/*
|
||||||
|
* Entagged Audio Tag library
|
||||||
|
* Copyright (c) 2004-2005 Christian Laireiter <liree@web.de>
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this library; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
package org.jaudiotagger.audio.asf.io;
|
||||||
|
|
||||||
|
import org.jaudiotagger.audio.asf.data.Chunk;
|
||||||
|
import org.jaudiotagger.audio.asf.data.GUID;
|
||||||
|
import org.jaudiotagger.audio.asf.data.StreamBitratePropertiesChunk;
|
||||||
|
import org.jaudiotagger.audio.asf.util.Utils;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class reads the chunk containing the stream bitrate properties.<br>
|
||||||
|
*
|
||||||
|
* @author Christian Laireiter
|
||||||
|
*/
|
||||||
|
public class StreamBitratePropertiesReader implements ChunkReader {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The GUID this reader {@linkplain #getApplyingIds() applies to}
|
||||||
|
*/
|
||||||
|
private final static GUID[] APPLYING = {GUID.GUID_STREAM_BITRATE_PROPERTIES};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should not be used for now.
|
||||||
|
*/
|
||||||
|
protected StreamBitratePropertiesReader() {
|
||||||
|
// NOTHING toDo
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public boolean canFail() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public GUID[] getApplyingIds() {
|
||||||
|
return APPLYING.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public Chunk read(final GUID guid, final InputStream stream,
|
||||||
|
final long chunkStart) throws IOException {
|
||||||
|
final BigInteger chunkLen = Utils.readBig64(stream);
|
||||||
|
final StreamBitratePropertiesChunk result = new StreamBitratePropertiesChunk(
|
||||||
|
chunkLen);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Read the amount of bitrate records
|
||||||
|
*/
|
||||||
|
final long recordCount = Utils.readUINT16(stream);
|
||||||
|
for (int i = 0; i < recordCount; i++) {
|
||||||
|
final int flags = Utils.readUINT16(stream);
|
||||||
|
final long avgBitrate = Utils.readUINT32(stream);
|
||||||
|
result.addBitrateRecord(flags & 0x00FF, avgBitrate);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.setPosition(chunkStart);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,188 @@
|
||||||
|
/*
|
||||||
|
* Entagged Audio Tag library
|
||||||
|
* Copyright (c) 2004-2005 Christian Laireiter <liree@web.de>
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this library; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
package org.jaudiotagger.audio.asf.io;
|
||||||
|
|
||||||
|
import org.jaudiotagger.audio.asf.data.*;
|
||||||
|
import org.jaudiotagger.audio.asf.util.Utils;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads and interprets the data of the audio or video stream information chunk. <br>
|
||||||
|
*
|
||||||
|
* @author Christian Laireiter
|
||||||
|
*/
|
||||||
|
public class StreamChunkReader implements ChunkReader {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The GUID this reader {@linkplain #getApplyingIds() applies to}
|
||||||
|
*/
|
||||||
|
private final static GUID[] APPLYING = {GUID.GUID_STREAM};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shouldn't be used for now.
|
||||||
|
*/
|
||||||
|
protected StreamChunkReader() {
|
||||||
|
// Nothing to do
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public boolean canFail() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public GUID[] getApplyingIds() {
|
||||||
|
return APPLYING.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public Chunk read(final GUID guid, final InputStream stream,
|
||||||
|
final long chunkStart) throws IOException {
|
||||||
|
StreamChunk result = null;
|
||||||
|
final BigInteger chunkLength = Utils.readBig64(stream);
|
||||||
|
// Now comes GUID indicating whether stream content type is audio or
|
||||||
|
// video
|
||||||
|
final GUID streamTypeGUID = Utils.readGUID(stream);
|
||||||
|
if (GUID.GUID_AUDIOSTREAM.equals(streamTypeGUID)
|
||||||
|
|| GUID.GUID_VIDEOSTREAM.equals(streamTypeGUID)) {
|
||||||
|
|
||||||
|
// A GUID is indicating whether the stream is error
|
||||||
|
// concealed
|
||||||
|
final GUID errorConcealment = Utils.readGUID(stream);
|
||||||
|
/*
|
||||||
|
* Read the Time Offset
|
||||||
|
*/
|
||||||
|
final long timeOffset = Utils.readUINT64(stream);
|
||||||
|
|
||||||
|
final long typeSpecificDataSize = Utils.readUINT32(stream);
|
||||||
|
final long streamSpecificDataSize = Utils.readUINT32(stream);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Read a bit field. (Contains stream number, and whether the stream
|
||||||
|
* content is encrypted.)
|
||||||
|
*/
|
||||||
|
final int mask = Utils.readUINT16(stream);
|
||||||
|
final int streamNumber = mask & 127;
|
||||||
|
final boolean contentEncrypted = (mask & 0x8000) != 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Skip a reserved field
|
||||||
|
*/
|
||||||
|
stream.skip(4);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* very important to set for every stream type. The size of bytes
|
||||||
|
* read by the specific stream type, in order to skip the remaining
|
||||||
|
* unread bytes of the stream chunk.
|
||||||
|
*/
|
||||||
|
long streamSpecificBytes;
|
||||||
|
|
||||||
|
if (GUID.GUID_AUDIOSTREAM.equals(streamTypeGUID)) {
|
||||||
|
/*
|
||||||
|
* Reading audio specific information
|
||||||
|
*/
|
||||||
|
final AudioStreamChunk audioStreamChunk = new AudioStreamChunk(
|
||||||
|
chunkLength);
|
||||||
|
result = audioStreamChunk;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* read WAVEFORMATEX and format extension.
|
||||||
|
*/
|
||||||
|
final long compressionFormat = Utils.readUINT16(stream);
|
||||||
|
final long channelCount = Utils.readUINT16(stream);
|
||||||
|
final long samplingRate = Utils.readUINT32(stream);
|
||||||
|
final long avgBytesPerSec = Utils.readUINT32(stream);
|
||||||
|
final long blockAlignment = Utils.readUINT16(stream);
|
||||||
|
final int bitsPerSample = Utils.readUINT16(stream);
|
||||||
|
final int codecSpecificDataSize = Utils.readUINT16(stream);
|
||||||
|
final byte[] codecSpecificData = new byte[codecSpecificDataSize];
|
||||||
|
stream.read(codecSpecificData);
|
||||||
|
|
||||||
|
audioStreamChunk.setCompressionFormat(compressionFormat);
|
||||||
|
audioStreamChunk.setChannelCount(channelCount);
|
||||||
|
audioStreamChunk.setSamplingRate(samplingRate);
|
||||||
|
audioStreamChunk.setAverageBytesPerSec(avgBytesPerSec);
|
||||||
|
audioStreamChunk.setErrorConcealment(errorConcealment);
|
||||||
|
audioStreamChunk.setBlockAlignment(blockAlignment);
|
||||||
|
audioStreamChunk.setBitsPerSample(bitsPerSample);
|
||||||
|
audioStreamChunk.setCodecData(codecSpecificData);
|
||||||
|
|
||||||
|
streamSpecificBytes = 18 + codecSpecificData.length;
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* Reading video specific information
|
||||||
|
*/
|
||||||
|
final VideoStreamChunk videoStreamChunk = new VideoStreamChunk(
|
||||||
|
chunkLength);
|
||||||
|
result = videoStreamChunk;
|
||||||
|
|
||||||
|
final long pictureWidth = Utils.readUINT32(stream);
|
||||||
|
final long pictureHeight = Utils.readUINT32(stream);
|
||||||
|
|
||||||
|
// Skip unknown field
|
||||||
|
stream.skip(1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Now read the format specific data
|
||||||
|
*/
|
||||||
|
// Size of the data section (formatDataSize)
|
||||||
|
stream.skip(2);
|
||||||
|
|
||||||
|
stream.skip(16);
|
||||||
|
final byte[] fourCC = new byte[4];
|
||||||
|
stream.read(fourCC);
|
||||||
|
|
||||||
|
videoStreamChunk.setPictureWidth(pictureWidth);
|
||||||
|
videoStreamChunk.setPictureHeight(pictureHeight);
|
||||||
|
videoStreamChunk.setCodecId(fourCC);
|
||||||
|
|
||||||
|
streamSpecificBytes = 31;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Setting common values for audio and video
|
||||||
|
*/
|
||||||
|
result.setStreamNumber(streamNumber);
|
||||||
|
result.setStreamSpecificDataSize(streamSpecificDataSize);
|
||||||
|
result.setTypeSpecificDataSize(typeSpecificDataSize);
|
||||||
|
result.setTimeOffset(timeOffset);
|
||||||
|
result.setContentEncrypted(contentEncrypted);
|
||||||
|
result.setPosition(chunkStart);
|
||||||
|
/*
|
||||||
|
* Now skip remainder of chunks bytes. chunk-length - 24 (size of
|
||||||
|
* GUID and chunklen) - streamSpecificBytes(stream type specific
|
||||||
|
* data) - 54 (common data)
|
||||||
|
*/
|
||||||
|
stream
|
||||||
|
.skip(chunkLength.longValue() - 24 - streamSpecificBytes
|
||||||
|
- 54);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
package org.jaudiotagger.audio.asf.io;
|
||||||
|
|
||||||
|
import org.jaudiotagger.audio.asf.data.GUID;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementors can write themselves directly to an output stream, and have the
|
||||||
|
* ability to tell the size they would need, as well as determine if they are
|
||||||
|
* empty.<br>
|
||||||
|
*
|
||||||
|
* @author Christian Laireiter
|
||||||
|
*/
|
||||||
|
public interface WriteableChunk {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method calculates the total amount of bytes, the chunk would consume
|
||||||
|
* in an ASF file.<br>
|
||||||
|
*
|
||||||
|
* @return amount of bytes the chunk would currently need in an ASF file.
|
||||||
|
*/
|
||||||
|
long getCurrentAsfChunkSize();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the GUID of the chunk.
|
||||||
|
*
|
||||||
|
* @return GUID of the chunk.
|
||||||
|
*/
|
||||||
|
GUID getGuid();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <code>true</code> if it is not necessary to write the chunk into an ASF
|
||||||
|
* file, since it contains no information.
|
||||||
|
*
|
||||||
|
* @return <code>true</code> if no useful data will be preserved.
|
||||||
|
*/
|
||||||
|
boolean isEmpty();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes the chunk into the specified output stream, as ASF stream chunk.<br>
|
||||||
|
*
|
||||||
|
* @param out stream to write into.
|
||||||
|
* @return amount of bytes written.
|
||||||
|
* @throws IOException on I/O errors
|
||||||
|
*/
|
||||||
|
long writeInto(OutputStream out) throws IOException;
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
package org.jaudiotagger.audio.asf.io;
|
||||||
|
|
||||||
|
import org.jaudiotagger.audio.asf.data.GUID;
|
||||||
|
import org.jaudiotagger.audio.asf.util.Utils;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A chunk modifier which works with information provided by
|
||||||
|
* {@link WriteableChunk} objects.<br>
|
||||||
|
*
|
||||||
|
* @author Christian Laireiter
|
||||||
|
*/
|
||||||
|
public class WriteableChunkModifer implements ChunkModifier {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The chunk to write.
|
||||||
|
*/
|
||||||
|
private final WriteableChunk writableChunk;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance.<br>
|
||||||
|
*
|
||||||
|
* @param chunk chunk to write
|
||||||
|
*/
|
||||||
|
public WriteableChunkModifer(final WriteableChunk chunk) {
|
||||||
|
this.writableChunk = chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public boolean isApplicable(final GUID guid) {
|
||||||
|
return guid.equals(this.writableChunk.getGuid());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public ModificationResult modify(final GUID guid, final InputStream chunk,
|
||||||
|
OutputStream destination) throws IOException { // NOPMD by Christian Laireiter on 5/9/09 5:03 PM
|
||||||
|
int chunkDiff = 0;
|
||||||
|
long newSize = 0;
|
||||||
|
long oldSize = 0;
|
||||||
|
/*
|
||||||
|
* Replace the outputstream with the counting one, only if assert's are
|
||||||
|
* evaluated.
|
||||||
|
*/
|
||||||
|
assert (destination = new CountingOutputstream(destination)) != null;
|
||||||
|
if (!this.writableChunk.isEmpty()) {
|
||||||
|
newSize = this.writableChunk.writeInto(destination);
|
||||||
|
assert newSize == this.writableChunk.getCurrentAsfChunkSize();
|
||||||
|
/*
|
||||||
|
* If assert's are evaluated, we have replaced destination by a
|
||||||
|
* CountingOutpustream and can now verify if
|
||||||
|
* getCurrentAsfChunkSize() really works correctly.
|
||||||
|
*/
|
||||||
|
assert ((CountingOutputstream) destination).getCount() == newSize;
|
||||||
|
if (guid == null) {
|
||||||
|
chunkDiff++;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
if (guid != null) {
|
||||||
|
assert isApplicable(guid);
|
||||||
|
if (this.writableChunk.isEmpty()) {
|
||||||
|
chunkDiff--;
|
||||||
|
}
|
||||||
|
oldSize = Utils.readUINT64(chunk);
|
||||||
|
chunk.skip(oldSize - 24);
|
||||||
|
}
|
||||||
|
return new ModificationResult(chunkDiff, (newSize - oldSize), guid);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
19
src/main/java/org/jaudiotagger/audio/asf/io/package.html
Normal file
19
src/main/java/org/jaudiotagger/audio/asf/io/package.html
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
|
||||||
|
"http://www.w3.org/TR/1999/REC-html401-19991224/loose.dtd">
|
||||||
|
|
||||||
|
<html long="en">
|
||||||
|
<head>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body bgcolor="white">
|
||||||
|
|
||||||
|
Classes for reading and writing Microsoft Advanced Systems Format files.
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- package.html by Gary McGath -->
|
||||||
|
<!-- Put @see and @since tags down here. -->
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
19
src/main/java/org/jaudiotagger/audio/asf/package.html
Normal file
19
src/main/java/org/jaudiotagger/audio/asf/package.html
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
|
||||||
|
"http://www.w3.org/TR/1999/REC-html401-19991224/loose.dtd">
|
||||||
|
|
||||||
|
<html long="en">
|
||||||
|
<head>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body bgcolor="white">
|
||||||
|
|
||||||
|
Classes for Microsoft Advanced Systems Format files.
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- package.html by Gary McGath -->
|
||||||
|
<!-- Put @see and @since tags down here. -->
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue