OpenAudible

This commit is contained in:
openaudible 2018-01-26 10:31:58 -08:00
commit 82033f9386
704 changed files with 97496 additions and 0 deletions

2
.gitattributes vendored Normal file
View file

@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto

87
.gitignore vendored Normal file
View 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
View 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
View 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

Binary file not shown.

BIN
bin/alglib1.so Normal file

Binary file not shown.

15
bin/charset.txt Normal file
View 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
View 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

Binary file not shown.

BIN
bin/linux_rcrack Normal file

Binary file not shown.

BIN
bin/mac_ffmpeg Normal file

Binary file not shown.

BIN
bin/mac_rcrack Normal file

Binary file not shown.

2
bin/rcrack.txt Normal file
View 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
View file

@ -0,0 +1 @@
This folder contains the RainbowTables generated by [inAudible-NG](https://github.com/inAudible-NG/tables)

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
bin/win_ffmpeg Normal file

Binary file not shown.

BIN
bin/win_rcrack.exe Normal file

Binary file not shown.

222
pom.xml Normal file
View 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>

View 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;
}

View 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();
}
}

View 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;
}
}

View 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);
}
}

View 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();
}

View file

@ -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;
}
}

View 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
}
}

View 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;
}
}

View 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;
}
}

View file

@ -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
}
}

View file

@ -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;
}
}
}

View 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
}

View file

@ -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;
}
}

View 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);
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View 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;
}
}

View 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();
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View file

@ -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;
}
}

View 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;
}
}

View file

@ -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;
}
}

View 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);
}
}

View 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;
}
}

View 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;
}
}

View 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>

View 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 &quot;isVbr&quot; field is set in the extended content
* description.<br>
*
* @param header the header to look up.
* @return <code>true</code> if &quot;isVbr&quot; 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);
}
}
}
}

View 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);
}
}

View file

@ -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);
}
}

View 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 &quot;UTF-16LE&quot; 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();
}
}

View file

@ -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();
}
}

View 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("");
}
}

View file

@ -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();
}
}

View 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 &quot;0&quot; 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
* &quot;0&quot;).
*/
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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View 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();
}
}

View 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 &quot;script command object&quot;.<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>&quot;-1&quot;</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();
}
}

View file

@ -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);
}
}

View 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);
}
}

View file

@ -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 &quot;Metadata Object&quot;,&quot;Metadata
* Library Object&quot; and &quot;Extended Content Description&quot;.<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;
}
}
}

View file

@ -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;
}
}

View file

@ -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 &quot;1&quot; if <code>true</code>,
* otherwise &quot;0&quot;.<br>
* String will be interpreted as number with radix &quot;10&quot;.<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;
}
}

View file

@ -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();
}
}

View 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();
}
}

View file

@ -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();
}
}

View 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>

View file

@ -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);
}
}

View file

@ -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();
}
}

View 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);
}
}
}

View 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);
}
}

View file

@ -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());
}
}
}

View file

@ -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);
}
}

View file

@ -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;
}

View 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;
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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++;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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 &quot;Language List Object&quot; 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;
}
}

View 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 &quot;Metadata Object&quot;, &quot;Metadata Library
* Object&quot; and &quot;Extended Content Description&quot; 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;
}
}

View file

@ -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>
* &quot;-1&quot; if the chunk disappeared upon modification.<br>
* &quot;0&quot; if the chunk was just modified.<br>
* &quot;1&quot; 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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}

View file

@ -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);
}
}

View 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>

View 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