push
This commit is contained in:
24
.gitignore
vendored
Normal file
24
.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# normal ignores
|
||||
.*
|
||||
*~
|
||||
*.[ao]
|
||||
*.lo
|
||||
*.so
|
||||
tags
|
||||
cscope.out
|
||||
!.gitignore
|
||||
!.travis.yml
|
||||
!.github
|
||||
|
||||
# top-level ignores
|
||||
/*.spec
|
||||
/config
|
||||
/config.mk
|
||||
/cmus
|
||||
/cmus-remote
|
||||
|
||||
# Cygwin stuff
|
||||
*.exe
|
||||
/cmus.base
|
||||
/cmus.def
|
||||
/cmus.exp
|
||||
69
AUTHORS
Normal file
69
AUTHORS
Normal file
@@ -0,0 +1,69 @@
|
||||
Maintainers
|
||||
-----------
|
||||
Gregory Petrosyan <gregory.petrosyan@gmail.com>
|
||||
Jason Woofenden <jason@jasonwoof.com>
|
||||
|
||||
Original Author
|
||||
---------------
|
||||
Timo Hirvonen <tihirvon@gmail.com>
|
||||
|
||||
NOTE: This list is not complete. Especially small changes/bug fixes may
|
||||
not be listed here. See the git repository for full list of
|
||||
contributors.
|
||||
|
||||
Credits
|
||||
-------
|
||||
original help window code and mad.charset option by Sergey Kuleshov
|
||||
<svyatogor@gentoo.org>
|
||||
|
||||
artist/album mode idea and "display artist/album as a tree instead of two
|
||||
windows" idea from divxero <divxero@gmx.net>
|
||||
|
||||
play queue idea and other misc ideas from Martin Stubenschrott
|
||||
<stubenschrott@gmx.net>
|
||||
|
||||
original RPM spec file by Eugene Vlasov <eugene@ikz.ru>
|
||||
|
||||
Claes Nästen <pekdon@gmail.com>
|
||||
:seek command
|
||||
--volume option for cmus-remote
|
||||
|
||||
Frank Terbeck <ft@bewatermyfriend.org>
|
||||
dynamic keybindings patch
|
||||
|
||||
alex <pukpuk@gmx.de>
|
||||
Sun output plugin
|
||||
Tremor support for vorbis plugin
|
||||
NetBSD and OpenBSD port
|
||||
Various bug fixes
|
||||
|
||||
Chun-Yu Shei <cshei@cs.indiana.edu>
|
||||
mpc plugin
|
||||
gapless MP3 playback
|
||||
|
||||
Johannes Weißl <jargon@molb.org>
|
||||
ao plugin
|
||||
|
||||
Gregory Petrosyan <gregory.petrosyan@gmail.com>
|
||||
PulseAudio output plugin
|
||||
|
||||
Philipp 'ph3-der-loewe' Schafft <lion@lion.leolix.org>
|
||||
RoarAudio output plugin
|
||||
|
||||
Jason Woofenden <jason@jasonwoof.com>
|
||||
Tutorial
|
||||
cmus-unofficial patch-commiter
|
||||
|
||||
Niko Efthymiou <nefthy-cmus@nefthy.de>
|
||||
Jack plugin
|
||||
|
||||
Tuncer Ayaz <tuncer.ayaz@gmail.com>
|
||||
Opus input plugin
|
||||
|
||||
Boris Timofeev <mashin87@gmail.com>
|
||||
vtx plugin
|
||||
|
||||
Yue Wang <yuleopen@gmail.com>
|
||||
CoreAudio plugin
|
||||
|
||||
Google Inc.
|
||||
339
COPYING
Normal file
339
COPYING
Normal file
@@ -0,0 +1,339 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 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.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, 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 or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
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 give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
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 Program or any portion
|
||||
of it, thus forming a work based on the Program, 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) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
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 Program, 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 Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) 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; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, 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 executable. However, as a
|
||||
special exception, the source code 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.
|
||||
|
||||
If distribution of executable or 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 counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program 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.
|
||||
|
||||
5. 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 Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program 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 to
|
||||
this License.
|
||||
|
||||
7. 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 Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program 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 Program.
|
||||
|
||||
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.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program 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.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the 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 Program
|
||||
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 Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, 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
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "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 PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. 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 PROGRAM 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 PROGRAM (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 PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), 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 Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. 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 program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; 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.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
||||
156
Doc/cmus-remote.txt
Normal file
156
Doc/cmus-remote.txt
Normal file
@@ -0,0 +1,156 @@
|
||||
@title CMUS-REMOTE 1 05/11/2006 cmus
|
||||
|
||||
@h1 NAME
|
||||
|
||||
cmus-remote - control cmus
|
||||
|
||||
|
||||
@h1 SYNOPSIS
|
||||
|
||||
cmus-remote [*OPTION*]... [`FILE`|`DIR`|`PLAYLIST`]...@br
|
||||
cmus-remote *-C* `COMMAND`...@br
|
||||
cmus-remote
|
||||
|
||||
|
||||
@h1 DESCRIPTION
|
||||
|
||||
Add `FILE/DIR/PLAYLIST` to playlist, library (*-l*) or play queue (*-q*).
|
||||
|
||||
If no arguments are given cmus-remote reads raw commands from stdin (one
|
||||
command per line). Raw commands are cmus' command mode commands. These same
|
||||
commands are used in configuration files and key bindings. *cmus*(1) contains
|
||||
full list of commands. For consistency also searching is supported:
|
||||
*-C /text*.
|
||||
|
||||
If *-C* is given, all command line arguments are treated as raw commands.
|
||||
|
||||
@h1 OPTIONS
|
||||
|
||||
--server SOCKET
|
||||
Connect using socket *SOCKET* instead of `$XDG_RUNTIME_DIR/cmus-socket`.
|
||||
|
||||
--passwd PASSWD
|
||||
password to use for TCP/IP connection
|
||||
|
||||
--help
|
||||
Display usage information and exit.
|
||||
|
||||
--version
|
||||
Display version information and exit.
|
||||
|
||||
-p, --play
|
||||
Start playing.
|
||||
|
||||
-u, --pause
|
||||
Toggle pause.
|
||||
|
||||
-U, --pause-playback
|
||||
Pause if currently playing.
|
||||
|
||||
-s, --stop
|
||||
Stop playing.
|
||||
|
||||
-n, --next
|
||||
Skip forward in playlist.
|
||||
|
||||
-r, --prev
|
||||
Skip backward in playlist.
|
||||
|
||||
-R, --repeat
|
||||
Toggle repeat.
|
||||
|
||||
-S, --shuffle
|
||||
Toggle shuffle.
|
||||
|
||||
-v, --volume VOL
|
||||
Change volume. See *vol* command in *cmus*(1).
|
||||
|
||||
-k, --seek SEEK
|
||||
Seek. See *seek* command in *cmus*(1).
|
||||
|
||||
-f, --file FILE
|
||||
Play from file.
|
||||
|
||||
-Q
|
||||
Get player status information. Same as *-C status*. Note that
|
||||
*status* is a special command only available to cmus-remote.
|
||||
|
||||
-l, --library
|
||||
Modify library instead of playlist.
|
||||
|
||||
-P, --playlist
|
||||
Modify playlist (default).
|
||||
|
||||
-q, --queue
|
||||
Modify play queue instead of playlist.
|
||||
|
||||
-c, --clear
|
||||
Clear playlist, library (*-l*) or play queue (*-q*).
|
||||
|
||||
-C, --raw
|
||||
Treat arguments (instead of stdin) as raw commands.
|
||||
|
||||
@h1 REMOTE COMMANDS
|
||||
|
||||
Special commands only available to cmus-remote.
|
||||
|
||||
status
|
||||
Print information about currently playing track.
|
||||
|
||||
format_print
|
||||
Print arguments as `Format Strings`. Each argument starts a new line.
|
||||
|
||||
@h1 EXAMPLES
|
||||
|
||||
Add playlists/files/directories/URLs to library view (1 & 2):
|
||||
|
||||
@pre
|
||||
$ cmus-remote -l music.m3u \\
|
||||
http://live.urn1350.net:8080/urn_high.ogg
|
||||
@endpre
|
||||
|
||||
Load (clear and add) playlist to playlist view (3):
|
||||
|
||||
@pre
|
||||
$ cmus-remote -c music.m3u
|
||||
@endpre
|
||||
|
||||
Three different ways to toggle repeat:
|
||||
|
||||
@pre
|
||||
$ cmus-remote -R
|
||||
$ cmus-remote -C "toggle repeat"
|
||||
$ cmus-remote
|
||||
toggle repeat
|
||||
^D
|
||||
@endpre
|
||||
|
||||
Query settings or key bindings:
|
||||
|
||||
@pre
|
||||
$ cmus-remote -C "set repeat?"
|
||||
setting: 'repeat=false'
|
||||
$ cmus-remote -C "showbind common a"
|
||||
bind common a win-add-l
|
||||
@endpre
|
||||
|
||||
Dump the playlist to stdout:
|
||||
|
||||
@pre
|
||||
$ cmus-remote -C "save -p -"
|
||||
[...]
|
||||
@endpre
|
||||
|
||||
Search works too:
|
||||
|
||||
@pre
|
||||
$ cmus-remote -C /beatles
|
||||
@endpre
|
||||
|
||||
@h1 SEE ALSO
|
||||
|
||||
*cmus*(1)
|
||||
|
||||
@h1 AUTHOR
|
||||
|
||||
Written by Timo Hirvonen <tihirvon\@gmail.com>
|
||||
264
Doc/cmus-tutorial.txt
Normal file
264
Doc/cmus-tutorial.txt
Normal file
@@ -0,0 +1,264 @@
|
||||
@title cmus-tutorial 7 14/02/2010 cmus
|
||||
|
||||
|
||||
@h1 NAME
|
||||
|
||||
cmus - C\* Music Player tutorial
|
||||
|
||||
|
||||
@h1 CONTENTS
|
||||
|
||||
Step 1: Starting Cmus
|
||||
|
||||
Step 2: Adding Music
|
||||
|
||||
Step 3: Playing Tracks From The Library
|
||||
|
||||
Step 4: Managing The Queue
|
||||
|
||||
Step 5: The Playlists
|
||||
|
||||
Step 6: Find that track
|
||||
|
||||
Step 7: Customization
|
||||
|
||||
Step 8: Quit
|
||||
|
||||
Step 9: Further Reading
|
||||
|
||||
|
||||
@h1 Step 1: Starting Cmus
|
||||
|
||||
When you first launch cmus (just type `cmus` in a terminal and press Enter) it
|
||||
will open to the album/artist view, which looks something like this:
|
||||
|
||||
@pre
|
||||
+---------------------------------------------------------------------+
|
||||
| Library Empty (use :add) |
|
||||
| | |
|
||||
| | |
|
||||
| | |
|
||||
| | |
|
||||
| | |
|
||||
| | |
|
||||
| | |
|
||||
| |
|
||||
| . 00:00 library | 100% | C |
|
||||
| |
|
||||
+---------------------------------------------------------------------+
|
||||
@endpre
|
||||
|
||||
This is the view where your artists and albums will be displayed.
|
||||
|
||||
|
||||
@h1 Step 2: Adding Music
|
||||
|
||||
Press *5* to switch to the file-browser view so we can add some music. You
|
||||
should see something like this:
|
||||
|
||||
@pre
|
||||
+---------------------------------------------------------------------+
|
||||
| Browser - /home/jasonwoof |
|
||||
| ../ |
|
||||
| Desktop/ |
|
||||
| MySqueak/ |
|
||||
| audio-projects/ |
|
||||
| audio/ |
|
||||
| bin/ |
|
||||
| config/ |
|
||||
| |
|
||||
| . 00:00 library | 100% | C |
|
||||
| |
|
||||
+---------------------------------------------------------------------+
|
||||
@endpre
|
||||
|
||||
Now, use the arrow keys, Enter and Backspace to navigate to where you have
|
||||
audio files stored. To add music to your cmus library, use the arrow keys to
|
||||
highlight a file or folder, and press *a*. When you press *a* cmus will move you
|
||||
to the next line down (so that it is easy to add a bunch of files/folders in a
|
||||
row) and start adding the file/folder you pressed *a* on to your library. This
|
||||
can take a while if you added a folder with a lot in it. As files are added,
|
||||
you will see the second time in the bottom right go up. This is the total
|
||||
duration of all the music in the cmus library.
|
||||
|
||||
Note: cmus does not move, duplicate or change your files. It just remembers
|
||||
where they are and caches the metadata (duration, artist, etc.)
|
||||
|
||||
Just to be on the safe side, let's save. Type *:save* and press Enter.
|
||||
|
||||
Note: Cmus automatically saves your settings and library and everything when
|
||||
you quit, so you probably won't use the save command much.
|
||||
|
||||
|
||||
@h1 Step 3: Playing Tracks From The Library
|
||||
|
||||
Press *2* to go to the simple library view. You should see something like
|
||||
this:
|
||||
|
||||
@pre
|
||||
+---------------------------------------------------------------------+
|
||||
| Library - 31 tracks (1:35:11) sorted by albumartist date album dis… |
|
||||
| Flying Lizards . Money (That's What I Want) 02:31 |
|
||||
| Jason Woofenden . VoR Theme 2009 01:20 |
|
||||
| Keali'i Reichel 06. Wanting Memories 1994 04:28 |
|
||||
| Molly Lewis . Tom Cruise Crazy 03:13 |
|
||||
| NonMemory . pista1 2009 03:18 |
|
||||
| NonMemory 01. pista1 2009-04-21 04:13 |
|
||||
| Ray Charles 06. Halleluja I Love Her So 02:33 |
|
||||
| |
|
||||
| . 00:00 artist from library | 100% | C |
|
||||
| |
|
||||
+---------------------------------------------------------------------+
|
||||
@endpre
|
||||
|
||||
Use the up and down arrow keys to select a track you'd like to hear, and press
|
||||
Enter to play it. Here are some keys to control playback:
|
||||
|
||||
Press *c* to pause/unpause.
|
||||
Press right/left to seek by 10 seconds.
|
||||
Press *,*/*.* seek backwards/forwards one minute.
|
||||
Press *z* to play the previous track and *b* to play the next track.
|
||||
|
||||
cmus has some great options to control what plays next (if anything) when the
|
||||
track ends. The state of these settings is shown in the bottom right corner.
|
||||
The first of these shows what collection of tracks we are playing (shown here
|
||||
as "artist from library"). Press *m* to cycle through the different options for
|
||||
this setting. To the right of that (past the volume) cmus shows the state of four
|
||||
toggles. Only toggles which are "on" are shown, so now we only see the *C*.
|
||||
Here are the toggles:
|
||||
|
||||
[C]ontinue
|
||||
|
||||
If this is off, cmus will always stop at the end of the track. You can
|
||||
toggle this setting by pressing *shift-C*.
|
||||
|
||||
[F]ollow
|
||||
|
||||
If this is on, cmus will select the currently playing track on track change.
|
||||
Press *f* to toggle this option.
|
||||
|
||||
[R]epeat
|
||||
|
||||
If this is on (and continue is on), when cmus reaches the end of the group
|
||||
of tracks you're playing (selected with the *m* key) it will start again from
|
||||
the beginning. Press *r* to toggle this setting.
|
||||
|
||||
[S]huffle or [&]lbum shuffle
|
||||
|
||||
If this is 'S', cmus will choose a random order to play all tracks once,
|
||||
while '&' will do the same for whole albums. Press *s* to toggle this option.
|
||||
|
||||
|
||||
@h1 Step 4: Managing The Queue
|
||||
|
||||
Lets say you're listening to a song, and you want to select which song will
|
||||
play next, without interrupting the currently playing song. No problem! Just go
|
||||
to the song you want to hear next (in any of the views) and press *e*. The
|
||||
queue is FIFO, meaning if you queue up another track, it will play after the
|
||||
one you already had queued up.
|
||||
|
||||
Note: The queue is not affected by the "shuffle" option described above.
|
||||
|
||||
Press *4* to view/edit the queue. This view works and looks a lot like the
|
||||
simple library view. The main difference is that you can change the order of
|
||||
the tracks with the *p* and *P* keys. You can press *shift-D* to remove a track
|
||||
from the queue.
|
||||
|
||||
When cmus is ready to play another track (it's reached the end of a track and
|
||||
the "continue" setting is on) it will remove the top entry from the queue and
|
||||
start playing it.
|
||||
|
||||
|
||||
@h1 Step 5: The Playlists
|
||||
|
||||
The playlists work like another set of libraries (like view *2*) except that
|
||||
(like the queue) you manually set the order of the tracks. This can be quite
|
||||
useful if you want to create a mix of specific tracks or if you want to
|
||||
listen to an audio book without having the chapters play when you're playing
|
||||
from the library.
|
||||
|
||||
The playlists are on view *3*. But before we go there, let's add some tracks.
|
||||
Press *2* to go to the simple library view, go to a track you want and press
|
||||
*y* to add it to a playlist. The only visual feedback you'll get that anything
|
||||
happened is that the highlight will move down one row. Add a few more so you
|
||||
have something to work with.
|
||||
|
||||
Now press *3* to go to the playlist. You should see something like this:
|
||||
|
||||
@pre
|
||||
+---------------------------------------------------------------------+
|
||||
| Playlist Default 11:32 |
|
||||
| * Default | Flying Lizards . Money (Th... 02:31 |
|
||||
| | Jason Woofenden . VoR T... 2009 01:20 |
|
||||
| | Keali'i Reichel 06. Wanti... 1994 04:28 |
|
||||
| | Molly Lewis . Tom Cruis... 03:13 |
|
||||
| | |
|
||||
| | |
|
||||
| | |
|
||||
| |
|
||||
| . 00:00 library | 100% | C |
|
||||
| |
|
||||
+---------------------------------------------------------------------+
|
||||
@endpre
|
||||
|
||||
Just like the queue, you can use the *p*, *P* and *D* keys to move and delete
|
||||
tracks from the playlist.
|
||||
|
||||
Note: Changing the view (e.g. by pressing *3*) does not affect what cmus will
|
||||
play next. To put cmus into "play from the playlist" mode, press Enter on one
|
||||
of the tracks in the playlist. To switch modes without interrupting the
|
||||
currently-playing song, you can press *shift-M*.
|
||||
|
||||
|
||||
@h1 Step 6: Find that track
|
||||
|
||||
This step shows various ways you can find track(s) you're looking for.
|
||||
|
||||
Search: Press *2* to be sure you're on the simple library view, then press */*
|
||||
to start a search. Type a word or two from the track you're looking for. cmus
|
||||
will search for tracks that have all those words in them. Press enter to get
|
||||
the keyboard out of the search command, and *n* to find the next match.
|
||||
|
||||
Tree View: Press *1* to select the tree view. Scroll to the artist, press
|
||||
*space* to show their albums, scroll to the album you want, then press tab so
|
||||
the keyboard controls the right column. Press tab again to get back to the left
|
||||
column.
|
||||
|
||||
Filters: See the reference manual (see Further Reading below) for a detailed
|
||||
description on how to quickly (and temporarily) hide most of your music.
|
||||
|
||||
|
||||
@h1 Step 7: Customization
|
||||
|
||||
Cmus has some very cool settings you can tweak, like changing the way tracks
|
||||
are displayed (e.g. to display disk numbers), enabling replaygain support or
|
||||
changing the keybindings.
|
||||
|
||||
Press *7* for a quick overview of the current keybindings and settings.
|
||||
|
||||
To change a setting or keybind, just select it (up/down keys) and press enter.
|
||||
This will put the command for the current setting in the command line (bottom
|
||||
left of your screen), which you can edit to put in a new value/key.
|
||||
|
||||
Please see the reference manual (see Further Reading below) for a detailed
|
||||
description of all the commands and settings available.
|
||||
|
||||
|
||||
@h1 Step 8: Quit
|
||||
|
||||
When you're done, type *:q* and press Enter to quit. This will save your
|
||||
settings, library, playlist and queue.
|
||||
|
||||
|
||||
@h1 Step 9: Further Reading
|
||||
|
||||
Cmus comes with a great reference manual. Now that you've got the basics down
|
||||
it should be intelligible. Try *man cmus* in a terminal. If that's not
|
||||
installed, try opening up `cmus.txt` from the `Doc` directory, or read the latest
|
||||
version online:
|
||||
|
||||
`https://github.com/cmus/cmus/blob/master/Doc/cmus.txt`
|
||||
|
||||
There are more commands and features not covered here like loading and saving
|
||||
playlists, controlling cmus remotely with `cmus-remote`, etc.
|
||||
|
||||
1856
Doc/cmus.txt
Normal file
1856
Doc/cmus.txt
Normal file
File diff suppressed because it is too large
Load Diff
883
Doc/ttman.c
Normal file
883
Doc/ttman.c
Normal file
@@ -0,0 +1,883 @@
|
||||
/*
|
||||
* ttman - text to man converter
|
||||
*
|
||||
* Copyright 2006 Timo Hirvonen <tihirvon@gmail.com>
|
||||
*
|
||||
* This file is licensed under the GPLv2.
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
|
||||
struct token {
|
||||
struct token *next;
|
||||
struct token *prev;
|
||||
enum {
|
||||
TOK_TEXT, // max one line w/o \n
|
||||
TOK_NL, // \n
|
||||
TOK_ITALIC, // `
|
||||
TOK_BOLD, // *
|
||||
TOK_INDENT, // \t
|
||||
|
||||
// keywords (@...)
|
||||
TOK_H1,
|
||||
TOK_H2,
|
||||
TOK_LI,
|
||||
TOK_BR,
|
||||
TOK_PRE,
|
||||
TOK_ENDPRE, // must be after TOK_PRE
|
||||
TOK_RAW,
|
||||
TOK_ENDRAW, // must be after TOK_RAW
|
||||
TOK_TITLE, // WRITE 2 2001-12-13 "Linux 2.0.32" "Linux Programmer's Manual"
|
||||
} type;
|
||||
int line;
|
||||
|
||||
// not NUL-terminated
|
||||
const char *text;
|
||||
// length of text
|
||||
int len;
|
||||
};
|
||||
|
||||
static const char *program;
|
||||
static const char *filename;
|
||||
static char tmp_file[1024];
|
||||
static FILE *outfile;
|
||||
static int cur_line = 1;
|
||||
static struct token head = { &head, &head, TOK_TEXT, 0, NULL, 0 };
|
||||
|
||||
#define CONST_STR(str) { str, sizeof(str) - 1 }
|
||||
static const struct {
|
||||
const char *str;
|
||||
int len;
|
||||
} token_names[] = {
|
||||
CONST_STR("text"),
|
||||
CONST_STR("nl"),
|
||||
CONST_STR("italic"),
|
||||
CONST_STR("bold"),
|
||||
CONST_STR("indent"),
|
||||
|
||||
// keywords
|
||||
CONST_STR("h1"),
|
||||
CONST_STR("h2"),
|
||||
CONST_STR("li"),
|
||||
CONST_STR("br"),
|
||||
CONST_STR("pre"),
|
||||
CONST_STR("endpre"),
|
||||
CONST_STR("raw"),
|
||||
CONST_STR("endraw"),
|
||||
CONST_STR("title")
|
||||
};
|
||||
#define NR_TOKEN_NAMES (sizeof(token_names) / sizeof(token_names[0]))
|
||||
#define BUG() die("BUG in %s\n", __FUNCTION__)
|
||||
|
||||
#ifdef __GNUC__
|
||||
#define CMUS_NORETURN __attribute__((__noreturn__))
|
||||
#else
|
||||
#define CMUS_NORETURN
|
||||
#endif
|
||||
|
||||
static CMUS_NORETURN void quit(void)
|
||||
{
|
||||
if (tmp_file[0])
|
||||
unlink(tmp_file);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static CMUS_NORETURN void die(const char *format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
fprintf(stderr, "%s: ", program);
|
||||
va_start(ap, format);
|
||||
vfprintf(stderr, format, ap);
|
||||
va_end(ap);
|
||||
quit();
|
||||
}
|
||||
|
||||
static CMUS_NORETURN void syntax(int line, const char *format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
fprintf(stderr, "%s:%d: error: ", filename, line);
|
||||
va_start(ap, format);
|
||||
vfprintf(stderr, format, ap);
|
||||
va_end(ap);
|
||||
quit();
|
||||
}
|
||||
|
||||
static inline const char *keyword_name(int type)
|
||||
{
|
||||
if (type < TOK_H1 || type > TOK_TITLE)
|
||||
die("BUG: no keyword name for type %d\n", type);
|
||||
return token_names[type].str;
|
||||
}
|
||||
|
||||
static void *xmalloc(size_t size)
|
||||
{
|
||||
void *ret = malloc(size);
|
||||
|
||||
if (!ret)
|
||||
die("OOM when allocating %ul bytes\n", size);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static char *memdup(const char *str, int len)
|
||||
{
|
||||
char *s = xmalloc(len + 1);
|
||||
memcpy(s, str, len);
|
||||
s[len] = 0;
|
||||
return s;
|
||||
}
|
||||
|
||||
static struct token *new_token(int type)
|
||||
{
|
||||
struct token *tok = xmalloc(sizeof(struct token));
|
||||
|
||||
tok->prev = NULL;
|
||||
tok->next = NULL;
|
||||
tok->type = type;
|
||||
tok->line = cur_line;
|
||||
return tok;
|
||||
}
|
||||
|
||||
static void free_token(struct token *tok)
|
||||
{
|
||||
struct token *prev = tok->prev;
|
||||
struct token *next = tok->next;
|
||||
|
||||
if (tok == &head)
|
||||
BUG();
|
||||
|
||||
prev->next = next;
|
||||
next->prev = prev;
|
||||
free(tok);
|
||||
}
|
||||
|
||||
static void emit_token(struct token *tok)
|
||||
{
|
||||
tok->prev = head.prev;
|
||||
tok->next = &head;
|
||||
head.prev->next = tok;
|
||||
head.prev = tok;
|
||||
}
|
||||
|
||||
static void emit(int type)
|
||||
{
|
||||
struct token *tok = new_token(type);
|
||||
tok->len = 0;
|
||||
tok->text = NULL;
|
||||
emit_token(tok);
|
||||
}
|
||||
|
||||
static int emit_keyword(const char *buf, int size)
|
||||
{
|
||||
int i, len;
|
||||
|
||||
for (len = 0; len < size; len++) {
|
||||
if (!isalnum((unsigned char)buf[len]))
|
||||
break;
|
||||
}
|
||||
|
||||
if (!len)
|
||||
syntax(cur_line, "keyword expected\n");
|
||||
|
||||
for (i = TOK_H1; i < NR_TOKEN_NAMES; i++) {
|
||||
if (len != token_names[i].len)
|
||||
continue;
|
||||
if (!strncmp(buf, token_names[i].str, len)) {
|
||||
emit(i);
|
||||
return len;
|
||||
}
|
||||
}
|
||||
syntax(cur_line, "invalid keyword '@%s'\n", memdup(buf, len));
|
||||
}
|
||||
|
||||
static int emit_text(const char *buf, int size)
|
||||
{
|
||||
struct token *tok;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < size; i++) {
|
||||
int c = buf[i];
|
||||
if (c == '@' || c == '`' || c == '*' || c == '\n' || c == '\\' || c == '\t')
|
||||
break;
|
||||
}
|
||||
tok = new_token(TOK_TEXT);
|
||||
tok->text = buf;
|
||||
tok->len = i;
|
||||
emit_token(tok);
|
||||
return i;
|
||||
}
|
||||
|
||||
static void tokenize(const char *buf, int size)
|
||||
{
|
||||
int pos = 0;
|
||||
|
||||
while (pos < size) {
|
||||
struct token *tok;
|
||||
int ch;
|
||||
|
||||
ch = buf[pos++];
|
||||
switch (ch) {
|
||||
case '@':
|
||||
pos += emit_keyword(buf + pos, size - pos);
|
||||
break;
|
||||
case '`':
|
||||
emit(TOK_ITALIC);
|
||||
break;
|
||||
case '*':
|
||||
emit(TOK_BOLD);
|
||||
break;
|
||||
case '\n':
|
||||
emit(TOK_NL);
|
||||
cur_line++;
|
||||
break;
|
||||
case '\t':
|
||||
emit(TOK_INDENT);
|
||||
break;
|
||||
case '\\':
|
||||
tok = new_token(TOK_TEXT);
|
||||
tok->text = buf + pos;
|
||||
tok->len = 1;
|
||||
pos++;
|
||||
if (pos == size || buf[pos] == '\n') {
|
||||
// just one '\\'
|
||||
tok->text--;
|
||||
}
|
||||
|
||||
if (tok->text[0] == '\\') {
|
||||
tok->text = "\\\\";
|
||||
tok->len = 2;
|
||||
}
|
||||
|
||||
emit_token(tok);
|
||||
break;
|
||||
default:
|
||||
pos--;
|
||||
pos += emit_text(buf + pos, size - pos);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int is_empty_line(const struct token *tok)
|
||||
{
|
||||
while (tok != &head) {
|
||||
int i;
|
||||
|
||||
switch (tok->type) {
|
||||
case TOK_TEXT:
|
||||
for (i = 0; i < tok->len; i++) {
|
||||
if (tok->text[i] != ' ')
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
case TOK_INDENT:
|
||||
break;
|
||||
case TOK_NL:
|
||||
return 1;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
tok = tok->next;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static struct token *remove_line(struct token *tok)
|
||||
{
|
||||
while (tok != &head) {
|
||||
struct token *next = tok->next;
|
||||
int type = tok->type;
|
||||
|
||||
free_token(tok);
|
||||
tok = next;
|
||||
if (type == TOK_NL)
|
||||
break;
|
||||
}
|
||||
return tok;
|
||||
}
|
||||
|
||||
static struct token *skip_after(struct token *tok, int type)
|
||||
{
|
||||
struct token *save = tok;
|
||||
|
||||
while (tok != &head) {
|
||||
if (tok->type == type) {
|
||||
tok = tok->next;
|
||||
if (tok->type != TOK_NL)
|
||||
syntax(tok->line, "newline expected after @%s\n",
|
||||
keyword_name(type));
|
||||
return tok->next;
|
||||
}
|
||||
if (tok->type >= TOK_H1)
|
||||
syntax(tok->line, "keywords not allowed betweed @%s and @%s\n",
|
||||
keyword_name(type-1), keyword_name(type));
|
||||
tok = tok->next;
|
||||
}
|
||||
syntax(save->prev->line, "missing @%s\n", keyword_name(type));
|
||||
}
|
||||
|
||||
static struct token *get_next_line(struct token *tok)
|
||||
{
|
||||
while (tok != &head) {
|
||||
int type = tok->type;
|
||||
|
||||
tok = tok->next;
|
||||
if (type == TOK_NL)
|
||||
break;
|
||||
}
|
||||
return tok;
|
||||
}
|
||||
|
||||
static struct token *get_indent(struct token *tok, int *ip)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
while (tok != &head && tok->type == TOK_INDENT) {
|
||||
tok = tok->next;
|
||||
i++;
|
||||
}
|
||||
*ip = i;
|
||||
return tok;
|
||||
}
|
||||
|
||||
// line must be non-empty
|
||||
static struct token *check_line(struct token *tok, int *ip)
|
||||
{
|
||||
struct token *start;
|
||||
int tok_type;
|
||||
|
||||
start = tok = get_indent(tok, ip);
|
||||
|
||||
tok_type = tok->type;
|
||||
switch (tok_type) {
|
||||
case TOK_TEXT:
|
||||
case TOK_BOLD:
|
||||
case TOK_ITALIC:
|
||||
case TOK_BR:
|
||||
tok = tok->next;
|
||||
while (tok != &head) {
|
||||
switch (tok->type) {
|
||||
case TOK_TEXT:
|
||||
case TOK_BOLD:
|
||||
case TOK_ITALIC:
|
||||
case TOK_BR:
|
||||
case TOK_INDENT:
|
||||
break;
|
||||
case TOK_NL:
|
||||
return start;
|
||||
default:
|
||||
syntax(tok->line, "@%s not allowed inside paragraph\n",
|
||||
keyword_name(tok->type));
|
||||
}
|
||||
tok = tok->next;
|
||||
}
|
||||
break;
|
||||
case TOK_H1:
|
||||
case TOK_H2:
|
||||
case TOK_TITLE:
|
||||
if (*ip)
|
||||
goto indentation;
|
||||
|
||||
// check arguments
|
||||
tok = tok->next;
|
||||
while (tok != &head) {
|
||||
switch (tok->type) {
|
||||
case TOK_TEXT:
|
||||
case TOK_INDENT:
|
||||
break;
|
||||
case TOK_NL:
|
||||
return start;
|
||||
default:
|
||||
syntax(tok->line, "@%s can contain only text\n",
|
||||
keyword_name(tok_type));
|
||||
}
|
||||
tok = tok->next;
|
||||
}
|
||||
break;
|
||||
case TOK_LI:
|
||||
// check arguments
|
||||
tok = tok->next;
|
||||
while (tok != &head) {
|
||||
switch (tok->type) {
|
||||
case TOK_TEXT:
|
||||
case TOK_BOLD:
|
||||
case TOK_ITALIC:
|
||||
case TOK_INDENT:
|
||||
break;
|
||||
case TOK_NL:
|
||||
return start;
|
||||
default:
|
||||
syntax(tok->line, "@%s not allowed inside @li\n",
|
||||
keyword_name(tok->type));
|
||||
}
|
||||
tok = tok->next;
|
||||
}
|
||||
break;
|
||||
case TOK_PRE:
|
||||
// checked later
|
||||
break;
|
||||
case TOK_RAW:
|
||||
if (*ip)
|
||||
goto indentation;
|
||||
// checked later
|
||||
break;
|
||||
case TOK_ENDPRE:
|
||||
case TOK_ENDRAW:
|
||||
syntax(tok->line, "@%s not expected\n", keyword_name(tok->type));
|
||||
break;
|
||||
case TOK_NL:
|
||||
case TOK_INDENT:
|
||||
BUG();
|
||||
break;
|
||||
}
|
||||
return start;
|
||||
indentation:
|
||||
syntax(tok->line, "indentation before @%s\n", keyword_name(tok->type));
|
||||
}
|
||||
|
||||
static void insert_nl_before(struct token *next)
|
||||
{
|
||||
struct token *prev = next->prev;
|
||||
struct token *new = new_token(TOK_NL);
|
||||
|
||||
new->prev = prev;
|
||||
new->next = next;
|
||||
prev->next = new;
|
||||
next->prev = new;
|
||||
}
|
||||
|
||||
static void normalize(void)
|
||||
{
|
||||
struct token *tok = head.next;
|
||||
/*
|
||||
* >= 0 if previous line was text (== amount of indent)
|
||||
* -1 if previous block was @pre (amount of indent doesn't matter)
|
||||
* -2 otherwise (@h1 etc., indent was 0)
|
||||
*/
|
||||
int prev_indent = -2;
|
||||
|
||||
while (tok != &head) {
|
||||
struct token *start;
|
||||
int i, new_para = 0;
|
||||
|
||||
// remove empty lines
|
||||
while (is_empty_line(tok)) {
|
||||
tok = remove_line(tok);
|
||||
new_para = 1;
|
||||
if (tok == &head)
|
||||
return;
|
||||
}
|
||||
|
||||
// skips indent
|
||||
start = tok;
|
||||
tok = check_line(tok, &i);
|
||||
|
||||
switch (tok->type) {
|
||||
case TOK_TEXT:
|
||||
case TOK_ITALIC:
|
||||
case TOK_BOLD:
|
||||
case TOK_BR:
|
||||
// normal text
|
||||
if (new_para && prev_indent >= -1) {
|
||||
// previous line/block was text or @pre
|
||||
// and there was a empty line after it
|
||||
insert_nl_before(start);
|
||||
}
|
||||
|
||||
if (!new_para && prev_indent == i) {
|
||||
// join with previous line
|
||||
struct token *nl = start->prev;
|
||||
|
||||
if (nl->type != TOK_NL)
|
||||
BUG();
|
||||
|
||||
if ((nl->prev != &head && nl->prev->type == TOK_BR) ||
|
||||
tok->type == TOK_BR) {
|
||||
// don't convert \n after/before @br to ' '
|
||||
free_token(nl);
|
||||
} else {
|
||||
// convert "\n" to " "
|
||||
nl->type = TOK_TEXT;
|
||||
nl->text = " ";
|
||||
nl->len = 1;
|
||||
}
|
||||
|
||||
// remove indent
|
||||
while (start->type == TOK_INDENT) {
|
||||
struct token *next = start->next;
|
||||
free_token(start);
|
||||
start = next;
|
||||
}
|
||||
}
|
||||
|
||||
prev_indent = i;
|
||||
tok = get_next_line(tok);
|
||||
break;
|
||||
case TOK_PRE:
|
||||
case TOK_RAW:
|
||||
// these can be directly after normal text
|
||||
// but not joined with the previous line
|
||||
if (new_para && prev_indent >= -1) {
|
||||
// previous line/block was text or @pre
|
||||
// and there was a empty line after it
|
||||
insert_nl_before(start);
|
||||
}
|
||||
tok = skip_after(tok->next, tok->type + 1);
|
||||
prev_indent = -1;
|
||||
break;
|
||||
case TOK_H1:
|
||||
case TOK_H2:
|
||||
case TOK_LI:
|
||||
case TOK_TITLE:
|
||||
// remove white space after H1, H2, L1 and TITLE
|
||||
tok = tok->next;
|
||||
while (tok != &head) {
|
||||
int type = tok->type;
|
||||
struct token *next;
|
||||
|
||||
if (type == TOK_TEXT) {
|
||||
while (tok->len && *tok->text == ' ') {
|
||||
tok->text++;
|
||||
tok->len--;
|
||||
}
|
||||
if (tok->len)
|
||||
break;
|
||||
}
|
||||
if (type != TOK_INDENT)
|
||||
break;
|
||||
|
||||
// empty TOK_TEXT or TOK_INDENT
|
||||
next = tok->next;
|
||||
free_token(tok);
|
||||
tok = next;
|
||||
}
|
||||
// not normal text. can't be joined
|
||||
prev_indent = -2;
|
||||
tok = get_next_line(tok);
|
||||
break;
|
||||
case TOK_NL:
|
||||
case TOK_INDENT:
|
||||
case TOK_ENDPRE:
|
||||
case TOK_ENDRAW:
|
||||
BUG();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define output(...) fprintf(outfile, __VA_ARGS__)
|
||||
|
||||
static void output_buf(const char *buf, int len)
|
||||
{
|
||||
fwrite(buf, 1, len, outfile);
|
||||
}
|
||||
|
||||
static void output_text(struct token *tok)
|
||||
{
|
||||
char buf[1024];
|
||||
const char *str = tok->text;
|
||||
int len = tok->len;
|
||||
int pos = 0;
|
||||
|
||||
while (len) {
|
||||
int c = *str++;
|
||||
|
||||
if (pos >= sizeof(buf) - 1) {
|
||||
output_buf(buf, pos);
|
||||
pos = 0;
|
||||
}
|
||||
if (c == '-')
|
||||
buf[pos++] = '\\';
|
||||
buf[pos++] = c;
|
||||
len--;
|
||||
}
|
||||
|
||||
if (pos)
|
||||
output_buf(buf, pos);
|
||||
}
|
||||
|
||||
static int bold = 0;
|
||||
static int italic = 0;
|
||||
static int indent = 0;
|
||||
|
||||
static struct token *output_pre(struct token *tok)
|
||||
{
|
||||
int bol = 1;
|
||||
|
||||
if (tok->type != TOK_NL)
|
||||
syntax(tok->line, "newline expected after @pre\n");
|
||||
|
||||
output(".nf\n");
|
||||
tok = tok->next;
|
||||
while (tok != &head) {
|
||||
if (bol) {
|
||||
int i;
|
||||
|
||||
tok = get_indent(tok, &i);
|
||||
if (i != indent && tok->type != TOK_NL)
|
||||
syntax(tok->line, "indent changed in @pre\n");
|
||||
}
|
||||
|
||||
switch (tok->type) {
|
||||
case TOK_TEXT:
|
||||
if (bol && tok->len && tok->text[0] == '.')
|
||||
output("\\&");
|
||||
output_text(tok);
|
||||
break;
|
||||
case TOK_NL:
|
||||
output("\n");
|
||||
bol = 1;
|
||||
tok = tok->next;
|
||||
continue;
|
||||
case TOK_ITALIC:
|
||||
output("`");
|
||||
break;
|
||||
case TOK_BOLD:
|
||||
output("*");
|
||||
break;
|
||||
case TOK_INDENT:
|
||||
// FIXME: warn
|
||||
output(" ");
|
||||
break;
|
||||
case TOK_ENDPRE:
|
||||
output(".fi\n");
|
||||
tok = tok->next;
|
||||
if (tok != &head && tok->type == TOK_NL)
|
||||
tok = tok->next;
|
||||
return tok;
|
||||
default:
|
||||
BUG();
|
||||
break;
|
||||
}
|
||||
bol = 0;
|
||||
tok = tok->next;
|
||||
}
|
||||
return tok;
|
||||
}
|
||||
|
||||
static struct token *output_raw(struct token *tok)
|
||||
{
|
||||
if (tok->type != TOK_NL)
|
||||
syntax(tok->line, "newline expected after @raw\n");
|
||||
|
||||
tok = tok->next;
|
||||
while (tok != &head) {
|
||||
switch (tok->type) {
|
||||
case TOK_TEXT:
|
||||
if (tok->len == 2 && !strncmp(tok->text, "\\\\", 2)) {
|
||||
/* ugly special case
|
||||
* "\\" (\) was converted to "\\\\" (\\) because
|
||||
* nroff does escaping too.
|
||||
*/
|
||||
output("\\");
|
||||
} else {
|
||||
output_buf(tok->text, tok->len);
|
||||
}
|
||||
break;
|
||||
case TOK_NL:
|
||||
output("\n");
|
||||
break;
|
||||
case TOK_ITALIC:
|
||||
output("`");
|
||||
break;
|
||||
case TOK_BOLD:
|
||||
output("*");
|
||||
break;
|
||||
case TOK_INDENT:
|
||||
output("\t");
|
||||
break;
|
||||
case TOK_ENDRAW:
|
||||
tok = tok->next;
|
||||
if (tok != &head && tok->type == TOK_NL)
|
||||
tok = tok->next;
|
||||
return tok;
|
||||
default:
|
||||
BUG();
|
||||
break;
|
||||
}
|
||||
tok = tok->next;
|
||||
}
|
||||
return tok;
|
||||
}
|
||||
|
||||
static struct token *output_para(struct token *tok)
|
||||
{
|
||||
int bol = 1;
|
||||
|
||||
while (tok != &head) {
|
||||
switch (tok->type) {
|
||||
case TOK_TEXT:
|
||||
output_text(tok);
|
||||
break;
|
||||
case TOK_ITALIC:
|
||||
italic ^= 1;
|
||||
if (italic) {
|
||||
output("\\fI");
|
||||
} else {
|
||||
output("\\fR");
|
||||
}
|
||||
break;
|
||||
case TOK_BOLD:
|
||||
bold ^= 1;
|
||||
if (bold) {
|
||||
output("\\fB");
|
||||
} else {
|
||||
output("\\fR");
|
||||
}
|
||||
break;
|
||||
case TOK_BR:
|
||||
if (bol) {
|
||||
output(".br\n");
|
||||
} else {
|
||||
output("\n.br\n");
|
||||
}
|
||||
bol = 1;
|
||||
tok = tok->next;
|
||||
continue;
|
||||
case TOK_NL:
|
||||
output("\n");
|
||||
return tok->next;
|
||||
case TOK_INDENT:
|
||||
output(" ");
|
||||
break;
|
||||
default:
|
||||
BUG();
|
||||
break;
|
||||
}
|
||||
bol = 0;
|
||||
tok = tok->next;
|
||||
}
|
||||
return tok;
|
||||
}
|
||||
|
||||
static struct token *title(struct token *tok, const char *cmd)
|
||||
{
|
||||
output("%s", cmd);
|
||||
return output_para(tok->next);
|
||||
}
|
||||
|
||||
static struct token *dump_one(struct token *tok)
|
||||
{
|
||||
int i;
|
||||
|
||||
tok = get_indent(tok, &i);
|
||||
if (tok->type != TOK_RAW) {
|
||||
while (indent < i) {
|
||||
output(".RS\n");
|
||||
indent++;
|
||||
}
|
||||
while (indent > i) {
|
||||
output(".RE\n");
|
||||
indent--;
|
||||
}
|
||||
}
|
||||
|
||||
switch (tok->type) {
|
||||
case TOK_TEXT:
|
||||
case TOK_ITALIC:
|
||||
case TOK_BOLD:
|
||||
case TOK_BR:
|
||||
if (tok->type == TOK_TEXT && tok->len && tok->text[0] == '.')
|
||||
output("\\&");
|
||||
tok = output_para(tok);
|
||||
break;
|
||||
case TOK_H1:
|
||||
tok = title(tok, ".SH ");
|
||||
break;
|
||||
case TOK_H2:
|
||||
tok = title(tok, ".SS ");
|
||||
break;
|
||||
case TOK_LI:
|
||||
tok = title(tok, ".TP\n");
|
||||
break;
|
||||
case TOK_PRE:
|
||||
tok = output_pre(tok->next);
|
||||
break;
|
||||
case TOK_RAW:
|
||||
tok = output_raw(tok->next);
|
||||
break;
|
||||
case TOK_TITLE:
|
||||
tok = title(tok, ".TH ");
|
||||
// must be after .TH
|
||||
// no hyphenation, adjust left
|
||||
output(".nh\n.ad l\n");
|
||||
break;
|
||||
case TOK_NL:
|
||||
output("\n");
|
||||
tok = tok->next;
|
||||
break;
|
||||
case TOK_ENDPRE:
|
||||
case TOK_ENDRAW:
|
||||
case TOK_INDENT:
|
||||
BUG();
|
||||
break;
|
||||
}
|
||||
return tok;
|
||||
}
|
||||
|
||||
static void dump(void)
|
||||
{
|
||||
struct token *tok = head.next;
|
||||
|
||||
while (tok != &head)
|
||||
tok = dump_one(tok);
|
||||
}
|
||||
|
||||
static void process(void)
|
||||
{
|
||||
struct stat s = {};
|
||||
const char *buf;
|
||||
int fd;
|
||||
|
||||
fd = open(filename, O_RDONLY);
|
||||
if (fd == -1)
|
||||
die("opening `%s' for reading: %s\n", filename, strerror(errno));
|
||||
fstat(fd, &s);
|
||||
if (s.st_size) {
|
||||
buf = mmap(NULL, s.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
|
||||
if (buf == MAP_FAILED)
|
||||
die("mmap: %s\n", strerror(errno));
|
||||
|
||||
tokenize(buf, s.st_size);
|
||||
normalize();
|
||||
}
|
||||
close(fd);
|
||||
dump();
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
const char *dest;
|
||||
int fd;
|
||||
|
||||
program = argv[0];
|
||||
if (argc != 3) {
|
||||
fprintf(stderr, "Usage: %s <in> <out>\n", program);
|
||||
return 1;
|
||||
}
|
||||
filename = argv[1];
|
||||
dest = argv[2];
|
||||
|
||||
snprintf(tmp_file, sizeof(tmp_file), "%s.XXXXXX", dest);
|
||||
fd = mkstemp(tmp_file);
|
||||
if (fd < 0)
|
||||
die("creating %s: %s\n", tmp_file, strerror(errno));
|
||||
outfile = fdopen(fd, "w");
|
||||
if (!outfile)
|
||||
die("opening %s: %s\n", tmp_file, strerror(errno));
|
||||
|
||||
process();
|
||||
if (rename(tmp_file, dest))
|
||||
die("renaming %s to %s: %s\n", tmp_file, dest, strerror(errno));
|
||||
return 0;
|
||||
}
|
||||
307
Makefile
Normal file
307
Makefile
Normal file
@@ -0,0 +1,307 @@
|
||||
REV = HEAD
|
||||
|
||||
# version from an annotated tag
|
||||
_ver0 = $(shell git describe $(REV) 2>/dev/null)
|
||||
# version from a plain tag
|
||||
_ver1 = $(shell git describe --tags $(REV) 2>/dev/null)
|
||||
# SHA1
|
||||
_ver2 = $(shell git rev-parse --verify --short $(REV) 2>/dev/null)
|
||||
# hand-made
|
||||
_ver3 = v2.12.0
|
||||
|
||||
VERSION = $(or $(_ver0),$(_ver1),$(_ver2),$(_ver3))
|
||||
|
||||
all: main plugins man
|
||||
|
||||
-include config.mk
|
||||
include scripts/lib.mk
|
||||
|
||||
CFLAGS += -D_FILE_OFFSET_BITS=64
|
||||
|
||||
CMUS_LIBS = $(PTHREAD_LIBS) $(NCURSES_LIBS) $(ICONV_LIBS) $(DL_LIBS) $(DISCID_LIBS) \
|
||||
-lm $(COMPAT_LIBS) $(LIBSYSTEMD_LIBS)
|
||||
|
||||
command_mode.o input.o main.o ui_curses.o op/pulse.lo: .version
|
||||
command_mode.o input.o main.o ui_curses.o op/pulse.lo: CFLAGS += -DVERSION=\"$(VERSION)\"
|
||||
main.o server.o: CFLAGS += -DDEFAULT_PORT=3000
|
||||
discid.o: CFLAGS += $(DISCID_CFLAGS)
|
||||
mpris.o: CFLAGS += $(LIBSYSTEMD_CFLAGS)
|
||||
|
||||
.version: Makefile
|
||||
@test "`cat $@ 2> /dev/null`" = "$(VERSION)" && exit 0; \
|
||||
echo " GEN $@"; echo $(VERSION) > $@
|
||||
|
||||
# programs {{{
|
||||
cmus-y := \
|
||||
ape.o browser.o buffer.o cache.o channelmap.o cmdline.o cmus.o command_mode.o \
|
||||
comment.o convert.lo cue.o cue_utils.o debug.o discid.o editable.o expr.o \
|
||||
filters.o format_print.o gbuf.o glob.o help.o history.o http.o id3.o input.o \
|
||||
job.o keys.o keyval.o lib.o load_dir.o locking.o mergesort.o misc.o options.o \
|
||||
output.o pcm.o player.o play_queue.o pl.o pl_env.o rbtree.o read_wrapper.o \
|
||||
search_mode.o search.o server.o spawn.o tabexp_file.o tabexp.o track_info.o \
|
||||
track.o tree.o uchar.o u_collate.o ui_curses.o window.o worker.o xstrjoin.o
|
||||
|
||||
cmus-$(CONFIG_MPRIS) += mpris.o
|
||||
|
||||
$(cmus-y): CFLAGS += $(PTHREAD_CFLAGS) $(NCURSES_CFLAGS) $(ICONV_CFLAGS) $(DL_CFLAGS)
|
||||
|
||||
cmus: $(cmus-y) file.o path.o prog.o xmalloc.o
|
||||
$(call cmd,ld,$(CMUS_LIBS))
|
||||
|
||||
cmus-remote: main.o file.o misc.o path.o prog.o xmalloc.o xstrjoin.o
|
||||
$(call cmd,ld,$(COMPAT_LIBS))
|
||||
|
||||
# cygwin compat
|
||||
DLLTOOL=dlltool
|
||||
|
||||
libcmus-$(CONFIG_CYGWIN) := libcmus.a
|
||||
|
||||
libcmus.a: $(cmus-y) file.o path.o prog.o xmalloc.o
|
||||
$(LD) -shared -o cmus.exe -Wl,--out-implib=libcmus.a -Wl,--base-file,cmus.base \
|
||||
-Wl,--export-all-symbols -Wl,--no-whole-archive $^ $(CMUS_LIBS)
|
||||
$(DLLTOOL) --output-def cmus.def --dllname cmus.exe --export-all-symbols $^
|
||||
$(DLLTOOL) --base-file cmus.base --dllname cmus.exe --input-def cmus.def --output-exp cmus.exp
|
||||
$(LD) -o cmus.exe -Wl,cmus.exp $^ $(CMUS_LIBS)
|
||||
|
||||
# }}}
|
||||
|
||||
# input plugins {{{
|
||||
cdio-objs := ip/cdio.lo
|
||||
flac-objs := ip/flac.lo
|
||||
mad-objs := ip/mad.lo ip/nomad.lo
|
||||
mikmod-objs := ip/mikmod.lo
|
||||
modplug-objs := ip/modplug.lo
|
||||
bass-objs := ip/bass.lo
|
||||
mpc-objs := ip/mpc.lo
|
||||
vorbis-objs := ip/vorbis.lo
|
||||
opus-objs := ip/opus.lo
|
||||
wavpack-objs := ip/wavpack.lo
|
||||
wav-objs := ip/wav.lo
|
||||
mp4-objs := ip/mp4.lo
|
||||
aac-objs := ip/aac.lo
|
||||
ffmpeg-objs := ip/ffmpeg.lo
|
||||
cue-objs := ip/cue.lo
|
||||
vtx-objs := ip/vtx.lo
|
||||
|
||||
ip-$(CONFIG_CDIO) += ip/cdio.so
|
||||
ip-$(CONFIG_FLAC) += ip/flac.so
|
||||
ip-$(CONFIG_MAD) += ip/mad.so
|
||||
ip-$(CONFIG_MIKMOD) += ip/mikmod.so
|
||||
ip-$(CONFIG_MODPLUG) += ip/modplug.so
|
||||
ip-$(CONFIG_BASS) += ip/bass.so
|
||||
ip-$(CONFIG_MPC) += ip/mpc.so
|
||||
ip-$(CONFIG_VORBIS) += ip/vorbis.so
|
||||
ip-$(CONFIG_OPUS) += ip/opus.so
|
||||
ip-$(CONFIG_WAVPACK) += ip/wavpack.so
|
||||
ip-$(CONFIG_WAV) += ip/wav.so
|
||||
ip-$(CONFIG_MP4) += ip/mp4.so
|
||||
ip-$(CONFIG_AAC) += ip/aac.so
|
||||
ip-$(CONFIG_FFMPEG) += ip/ffmpeg.so
|
||||
ip-$(CONFIG_CUE) += ip/cue.so
|
||||
ip-$(CONFIG_VTX) += ip/vtx.so
|
||||
|
||||
$(cdio-objs): CFLAGS += $(CDIO_CFLAGS) $(CDDB_CFLAGS)
|
||||
$(flac-objs): CFLAGS += $(FLAC_CFLAGS)
|
||||
$(mad-objs): CFLAGS += $(MAD_CFLAGS)
|
||||
$(mikmod-objs): CFLAGS += $(MIKMOD_CFLAGS)
|
||||
$(modplug-objs): CFLAGS += $(MODPLUG_CFLAGS)
|
||||
$(bass-objs): CFLAGS += $(BASS_CFLAGS)
|
||||
$(mpc-objs): CFLAGS += $(MPC_CFLAGS)
|
||||
$(vorbis-objs): CFLAGS += $(VORBIS_CFLAGS)
|
||||
$(opus-objs): CFLAGS += $(OPUS_CFLAGS)
|
||||
$(wavpack-objs): CFLAGS += $(WAVPACK_CFLAGS)
|
||||
$(mp4-objs): CFLAGS += $(MP4_CFLAGS)
|
||||
$(aac-objs): CFLAGS += $(AAC_CFLAGS)
|
||||
$(ffmpeg-objs): CFLAGS += $(FFMPEG_CFLAGS)
|
||||
$(vtx-objs): CFLAGS += $(VTX_CFLAGS)
|
||||
|
||||
ip/cdio.so: $(cdio-objs) $(libcmus-y)
|
||||
$(call cmd,ld_dl,$(CDIO_LIBS) $(CDDB_LIBS))
|
||||
|
||||
ip/flac.so: $(flac-objs) $(libcmus-y)
|
||||
$(call cmd,ld_dl,$(FLAC_LIBS))
|
||||
|
||||
ip/mad.so: $(mad-objs) $(libcmus-y)
|
||||
$(call cmd,ld_dl,-lm $(MAD_LIBS) $(ICONV_LIBS))
|
||||
|
||||
ip/mikmod.so: $(mikmod-objs) $(libcmus-y)
|
||||
$(call cmd,ld_dl,$(MIKMOD_LIBS))
|
||||
|
||||
ip/modplug.so: $(modplug-objs) $(libcmus-y)
|
||||
$(call cmd,ld_dl,$(MODPLUG_LIBS))
|
||||
|
||||
ip/bass.so: $(bass-objs) $(libcmus-y)
|
||||
$(call cmd,ld_dl,$(BASS_LIBS))
|
||||
|
||||
ip/mpc.so: $(mpc-objs) $(libcmus-y)
|
||||
$(call cmd,ld_dl,$(MPC_LIBS))
|
||||
|
||||
ip/vorbis.so: $(vorbis-objs) $(libcmus-y)
|
||||
$(call cmd,ld_dl,-lm $(VORBIS_LIBS))
|
||||
|
||||
ip/opus.so: $(opus-objs) $(libcmus-y)
|
||||
$(call cmd,ld_dl,$(OPUS_LIBS))
|
||||
|
||||
ip/wavpack.so: $(wavpack-objs) $(libcmus-y)
|
||||
$(call cmd,ld_dl,$(WAVPACK_LIBS))
|
||||
|
||||
ip/wav.so: $(wav-objs) $(libcmus-y)
|
||||
$(call cmd,ld_dl,)
|
||||
|
||||
ip/mp4.so: $(mp4-objs) $(libcmus-y)
|
||||
$(call cmd,ld_dl,$(MP4_LIBS))
|
||||
|
||||
ip/aac.so: $(aac-objs) $(libcmus-y)
|
||||
$(call cmd,ld_dl,$(AAC_LIBS))
|
||||
|
||||
ip/ffmpeg.so: $(ffmpeg-objs) $(libcmus-y)
|
||||
$(call cmd,ld_dl,$(FFMPEG_LIBS))
|
||||
|
||||
ip/cue.so: $(cue-objs) $(libcmus-y)
|
||||
$(call cmd,ld_dl,-lm)
|
||||
|
||||
ip/vtx.so: $(vtx-objs) $(libcmus-y)
|
||||
$(call cmd,ld_dl,$(VTX_LIBS))
|
||||
|
||||
# }}}
|
||||
|
||||
# output plugins {{{
|
||||
pulse-objs := op/pulse.lo
|
||||
alsa-objs := op/alsa.lo op/mixer_alsa.lo
|
||||
jack-objs := op/jack.lo
|
||||
arts-objs := op/arts.lo
|
||||
oss-objs := op/oss.lo op/mixer_oss.lo
|
||||
sun-objs := op/sun.lo op/mixer_sun.lo
|
||||
sndio-objs := op/sndio.lo
|
||||
ao-objs := op/ao.lo
|
||||
coreaudio-objs := op/coreaudio.lo
|
||||
waveout-objs := op/waveout.lo
|
||||
roar-objs := op/roar.lo
|
||||
aaudio-objs := op/aaudio.lo
|
||||
|
||||
op-$(CONFIG_PULSE) += op/pulse.so
|
||||
op-$(CONFIG_ALSA) += op/alsa.so
|
||||
op-$(CONFIG_JACK) += op/jack.so
|
||||
op-$(CONFIG_ARTS) += op/arts.so
|
||||
op-$(CONFIG_OSS) += op/oss.so
|
||||
op-$(CONFIG_SNDIO) += op/sndio.so
|
||||
op-$(CONFIG_SUN) += op/sun.so
|
||||
op-$(CONFIG_COREAUDIO) += op/coreaudio.so
|
||||
op-$(CONFIG_AO) += op/ao.so
|
||||
op-$(CONFIG_WAVEOUT) += op/waveout.so
|
||||
op-$(CONFIG_ROAR) += op/roar.so
|
||||
op-$(CONFIG_AAUDIO) += op/aaudio.so
|
||||
|
||||
$(pulse-objs): CFLAGS += $(PULSE_CFLAGS)
|
||||
$(alsa-objs): CFLAGS += $(ALSA_CFLAGS)
|
||||
$(jack-objs): CFLAGS += $(JACK_CFLAGS) $(SAMPLERATE_CFLAGS)
|
||||
$(arts-objs): CFLAGS += $(ARTS_CFLAGS)
|
||||
$(oss-objs): CFLAGS += $(OSS_CFLAGS)
|
||||
$(sndio-objs): CFLAGS += $(SNDIO_CFLAGS)
|
||||
$(sun-objs): CFLAGS += $(SUN_CFLAGS)
|
||||
$(ao-objs): CFLAGS += $(AO_CFLAGS)
|
||||
$(coreaudio-objs): CFLAGS += $(COREAUDIO_CFLAGS)
|
||||
$(waveout-objs): CFLAGS += $(WAVEOUT_CFLAGS)
|
||||
$(roar-objs): CFLAGS += $(ROAR_CFLAGS)
|
||||
$(aaudio-objs): CFLAGS += $(AAUDIO_CFLAGS)
|
||||
|
||||
op/pulse.so: $(pulse-objs) $(libcmus-y)
|
||||
$(call cmd,ld_dl,$(PULSE_LIBS))
|
||||
|
||||
op/alsa.so: $(alsa-objs) $(libcmus-y)
|
||||
$(call cmd,ld_dl,$(ALSA_LIBS) -lm)
|
||||
|
||||
op/jack.so: $(jack-objs) $(libcmus-y)
|
||||
$(call cmd,ld_dl,$(JACK_LIBS) $(SAMPLERATE_LIBS))
|
||||
|
||||
op/arts.so: $(arts-objs) $(libcmus-y)
|
||||
$(call cmd,ld_dl,$(ARTS_LIBS))
|
||||
|
||||
op/oss.so: $(oss-objs) $(libcmus-y)
|
||||
$(call cmd,ld_dl,$(OSS_LIBS))
|
||||
|
||||
op/sndio.so: $(sndio-objs) $(libcmus-y)
|
||||
$(call cmd,ld_dl,$(SNDIO_LIBS))
|
||||
|
||||
op/sun.so: $(sun-objs) $(libcmus-y)
|
||||
$(call cmd,ld_dl,$(SUN_LIBS))
|
||||
|
||||
op/ao.so: $(ao-objs) $(libcmus-y)
|
||||
$(call cmd,ld_dl,$(AO_LIBS))
|
||||
|
||||
op/coreaudio.so: $(coreaudio-objs) $(libcmus-y)
|
||||
$(call cmd,ld_dl,$(COREAUDIO_LIBS))
|
||||
|
||||
op/waveout.so: $(waveout-objs) $(libcmus-y)
|
||||
$(call cmd,ld_dl,$(WAVEOUT_LIBS))
|
||||
|
||||
op/roar.so: $(roar-objs) $(libcmus-y)
|
||||
$(call cmd,ld_dl,$(ROAR_LIBS))
|
||||
|
||||
op/aaudio.so: $(aaudio-objs) $(libcmus-y)
|
||||
$(call cmd,ld_dl,$(AAUDIO_LIBS))
|
||||
# }}}
|
||||
|
||||
# man {{{
|
||||
man1 := Doc/cmus.1 Doc/cmus-remote.1
|
||||
man7 := Doc/cmus-tutorial.7
|
||||
|
||||
$(man1): Doc/ttman
|
||||
$(man7): Doc/ttman
|
||||
|
||||
%.1: %.txt
|
||||
$(call cmd,ttman)
|
||||
|
||||
%.7: %.txt
|
||||
$(call cmd,ttman)
|
||||
|
||||
Doc/ttman.o: Doc/ttman.c
|
||||
$(call cmd,hostcc,)
|
||||
|
||||
Doc/ttman: Doc/ttman.o
|
||||
$(call cmd,hostld,)
|
||||
|
||||
quiet_cmd_ttman = MAN $@
|
||||
cmd_ttman = Doc/ttman $< $@
|
||||
# }}}
|
||||
|
||||
data = $(wildcard data/*)
|
||||
|
||||
clean += *.o ip/*.lo op/*.lo ip/*.so op/*.so *.lo cmus libcmus.a cmus.def cmus.base cmus.exp cmus-remote Doc/*.o Doc/ttman Doc/*.1 Doc/*.7 .install.log
|
||||
distclean += .version config.mk config/*.h tags
|
||||
|
||||
main: cmus cmus-remote
|
||||
plugins: $(ip-y) $(op-y)
|
||||
man: $(man1) $(man7)
|
||||
|
||||
install-main: main
|
||||
$(INSTALL) -m755 $(bindir) cmus cmus-remote
|
||||
|
||||
install-plugins: plugins
|
||||
$(INSTALL) -m755 $(libdir)/cmus/ip $(ip-y)
|
||||
$(INSTALL) -m755 $(libdir)/cmus/op $(op-y)
|
||||
|
||||
install-data: man
|
||||
$(INSTALL) -m644 $(datadir)/cmus $(data)
|
||||
$(INSTALL) -m644 $(mandir)/man1 $(man1)
|
||||
$(INSTALL) -m644 $(mandir)/man7 $(man7)
|
||||
$(INSTALL) -m755 $(exampledir) cmus-status-display
|
||||
|
||||
install: all install-main install-plugins install-data
|
||||
|
||||
tags:
|
||||
exuberant-ctags *.[ch]
|
||||
|
||||
# generating tarball using GIT {{{
|
||||
TARNAME = cmus-$(VERSION)
|
||||
|
||||
dist:
|
||||
@tarname=$(TARNAME); \
|
||||
test "$(_ver2)" || { echo "No such revision $(REV)"; exit 1; }; \
|
||||
echo " DIST $$tarname.tar.bz2"; \
|
||||
git archive --format=tar --prefix=$$tarname/ $(REV)^{tree} | bzip2 -c -9 > $$tarname.tar.bz2
|
||||
|
||||
# }}}
|
||||
|
||||
.PHONY: all main plugins man dist tags
|
||||
.PHONY: install install-main install-plugins install-man
|
||||
102
README.md
Normal file
102
README.md
Normal file
@@ -0,0 +1,102 @@
|
||||
# cmus — C\* Music Player
|
||||
|
||||
https://cmus.github.io/
|
||||
|
||||
[](https://github.com/cmus/cmus/actions/workflows/build.yml)
|
||||
|
||||
Copyright © 2004-2008 Timo Hirvonen <tihirvon@gmail.com>
|
||||
|
||||
Copyright © 2008-2017 Various Authors
|
||||
|
||||
|
||||
## Configuration
|
||||
|
||||
$ ./configure
|
||||
|
||||
By default, features are auto-detected. To list all configuration options, run
|
||||
`./configure --help`. Some common autoconf-style options like `--prefix` are
|
||||
also available.
|
||||
|
||||
After running configure you can see from the generated `config.mk` file
|
||||
what features have been configured in (see the `CONFIG_*` options).
|
||||
|
||||
The packages containing dependencies on common distributions are listed below. All dependencies other than pkg-config and ncurses, iconv, and elogind/systemd are for optional input/output plugins. It is assumed that libc headers, a C compiler, git, and GNU Make are available.
|
||||
|
||||
| Distro | Dependencies |
|
||||
| :-- | :-- |
|
||||
| **Debian/Ubuntu** | apt install pkg-config libncursesw5-dev libfaad-dev libao-dev libasound2-dev libcddb2-dev libcdio-cdda-dev libdiscid-dev libavformat-dev libavcodec-dev libswresample-dev libflac-dev libjack-dev libmad0-dev libmodplug-dev libmpcdec-dev libsystemd-dev libopusfile-dev libpulse-dev libsamplerate0-dev libsndio-dev libvorbis-dev libwavpack-dev |
|
||||
| **Fedora/RHEL** | dnf install 'pkgconfig(ncursesw)' 'pkgconfig(alsa)' 'pkgconfig(ao)' 'pkgconfig(libcddb)' 'pkgconfig(libcdio_cdda)' 'pkgconfig(libdiscid)' 'pkgconfig(libavformat)' 'pkgconfig(libavcodec)' 'pkgconfig(libswresample)' 'pkgconfig(flac)' 'pkgconfig(jack)' 'pkgconfig(mad)' 'pkgconfig(libmodplug)' libmpcdec-devel 'pkgconfig(libsystemd)' 'pkgconfig(opusfile)' 'pkgconfig(libpulse)' 'pkgconfig(samplerate)' 'pkgconfig(vorbisfile)' 'pkgconfig(wavpack)' |
|
||||
| **+ RPMFusion** | dnf install faad2-devel libmp4v2-devel |
|
||||
| **Arch Linux** | pacman -S pkg-config ncurses libiconv faad2 alsa-lib libao libcddb libcdio-paranoia libdiscid ffmpeg flac jack libmad libmodplug libmp4v2 libmpcdec systemd opusfile libpulse libsamplerate libvorbis wavpack |
|
||||
| **Alpine** | apk add pkgconf ncurses-dev gnu-libiconv-dev alsa-lib-dev libao-dev libcddb-dev ffmpeg-dev flac-dev jack-dev libmad-dev libmodplug-dev elogind-dev opus-dev opusfile-dev pulseaudio-dev libsamplerate-dev libvorbis-dev wavpack-dev |
|
||||
| **Termux** | apt install libandroid-support ncurses libiconv ffmpeg libmad libmodplug opusfile pulseaudio libflac libvorbis libwavpack |
|
||||
| **Homebrew** | brew install pkg-config ncurses faad2 libao libcddb libcdio libdiscid ffmpeg flac jack mad libmodplug mp4v2 musepack opusfile libsamplerate libvorbis wavpack |
|
||||
|
||||
|
||||
## Building
|
||||
|
||||
$ make
|
||||
|
||||
Or on some BSD systems you need to explicitly use GNU make:
|
||||
|
||||
$ gmake
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
$ make install
|
||||
|
||||
Or to install to a temporary directory:
|
||||
|
||||
$ make install DESTDIR=~/tmp/cmus
|
||||
|
||||
This is useful when creating binary packages.
|
||||
|
||||
Remember to replace `make` with `gmake` if needed.
|
||||
|
||||
|
||||
## Manuals
|
||||
|
||||
$ man cmus-tutorial
|
||||
|
||||
And
|
||||
|
||||
$ man cmus
|
||||
|
||||
|
||||
## IRC Channel
|
||||
|
||||
Feel free to join IRC channel #cmus on Libera.chat and share you experience,
|
||||
problems and issues. Note: This is an unofficial channel and all people hanging
|
||||
around there are for the love of cmus.
|
||||
|
||||
|
||||
## Reporting Bugs
|
||||
|
||||
Bugs should be reported using the GitHub [issue
|
||||
tracker](https://github.com/cmus/cmus/issues). When creating a new issue, a
|
||||
template will be shown containing instructions on how to collect the necessary
|
||||
information.
|
||||
|
||||
Additional debug information can be found in `~/cmus-debug.txt` if you
|
||||
configured cmus with maximum debug level (`./configure DEBUG=2`). In case of a
|
||||
crash the last lines may be helpful.
|
||||
|
||||
|
||||
## Git Repository
|
||||
|
||||
https://github.com/cmus/cmus
|
||||
|
||||
$ git clone https://github.com/cmus/cmus.git
|
||||
|
||||
|
||||
## Hacking
|
||||
|
||||
cmus uses the [Linux kernel coding
|
||||
style](https://www.kernel.org/doc/html/latest/process/coding-style.html). Use
|
||||
hard tabs. Tabs are _always_ 8 characters wide. Keep the style consistent with
|
||||
rest of the code.
|
||||
|
||||
Bug fixes and implementations of new features should be suggested as a
|
||||
[pull request](https://github.com/cmus/cmus/pulls) directly on GitHub.
|
||||
|
||||
258
ape.c
Normal file
258
ape.c
Normal file
@@ -0,0 +1,258 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2006 Chun-Yu Shei <cshei AT cs.indiana.edu>
|
||||
*
|
||||
* Cleaned up by Timo Hirvonen <tihirvon@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "ape.h"
|
||||
#include "file.h"
|
||||
#include "xmalloc.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <strings.h>
|
||||
|
||||
/* http://www.personal.uni-jena.de/~pfk/mpp/sv8/apetag.html */
|
||||
|
||||
#define PREAMBLE_SIZE (8)
|
||||
static const char preamble[PREAMBLE_SIZE] = { 'A', 'P', 'E', 'T', 'A', 'G', 'E', 'X' };
|
||||
|
||||
/* NOTE: not sizeof(struct ape_header)! */
|
||||
#define HEADER_SIZE (32)
|
||||
|
||||
/* returns position of APE header or -1 if not found */
|
||||
static int find_ape_tag_slow(int fd)
|
||||
{
|
||||
char buf[4096];
|
||||
int match = 0;
|
||||
int pos = 0;
|
||||
|
||||
/* seek to start of file */
|
||||
if (lseek(fd, pos, SEEK_SET) == -1)
|
||||
return -1;
|
||||
|
||||
while (1) {
|
||||
int i, got = read(fd, buf, sizeof(buf));
|
||||
|
||||
if (got == -1) {
|
||||
if (errno == EAGAIN || errno == EINTR)
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
if (got == 0)
|
||||
break;
|
||||
|
||||
for (i = 0; i < got; i++) {
|
||||
if (buf[i] != preamble[match]) {
|
||||
match = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
match++;
|
||||
if (match == PREAMBLE_SIZE)
|
||||
return pos + i + 1 - PREAMBLE_SIZE;
|
||||
}
|
||||
pos += got;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int ape_parse_header(const char *buf, struct ape_header *h)
|
||||
{
|
||||
if (memcmp(buf, preamble, PREAMBLE_SIZE))
|
||||
return 0;
|
||||
|
||||
h->version = read_le32(buf + 8);
|
||||
h->size = read_le32(buf + 12);
|
||||
h->count = read_le32(buf + 16);
|
||||
h->flags = read_le32(buf + 20);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int read_header(int fd, struct ape_header *h)
|
||||
{
|
||||
char buf[HEADER_SIZE];
|
||||
|
||||
if (read_all(fd, buf, sizeof(buf)) != sizeof(buf))
|
||||
return 0;
|
||||
|
||||
return ape_parse_header(buf, h);
|
||||
}
|
||||
|
||||
/* sets fd right after the header and returns 1 if found,
|
||||
* otherwise returns 0
|
||||
*/
|
||||
static int find_ape_tag(int fd, struct ape_header *h, int slow)
|
||||
{
|
||||
int pos;
|
||||
|
||||
if (lseek(fd, -HEADER_SIZE, SEEK_END) == -1)
|
||||
return 0;
|
||||
if (read_header(fd, h))
|
||||
return 1;
|
||||
|
||||
/* try to skip ID3v1 tag at the end of the file */
|
||||
if (lseek(fd, -(HEADER_SIZE + 128), SEEK_END) == -1)
|
||||
return 0;
|
||||
if (read_header(fd, h))
|
||||
return 1;
|
||||
|
||||
if (!slow)
|
||||
return 0;
|
||||
|
||||
pos = find_ape_tag_slow(fd);
|
||||
if (pos == -1)
|
||||
return 0;
|
||||
if (lseek(fd, pos, SEEK_SET) == -1)
|
||||
return 0;
|
||||
return read_header(fd, h);
|
||||
}
|
||||
|
||||
/*
|
||||
* All keys are ASCII and length is 2..255
|
||||
*
|
||||
* UTF-8: Artist, Album, Title, Genre
|
||||
* Integer: Track (N or N/M)
|
||||
* Date: Year (release), "Record Date"
|
||||
*
|
||||
* UTF-8 strings are NOT zero terminated.
|
||||
*
|
||||
* Also support "discnumber" (vorbis) and "disc" (non-standard)
|
||||
*/
|
||||
static int ape_parse_one(const char *buf, int size, char **keyp, char **valp)
|
||||
{
|
||||
int pos = 0;
|
||||
|
||||
while (size - pos > 8) {
|
||||
uint32_t val_len, flags;
|
||||
char *key, *val;
|
||||
int64_t max_key_len, key_len;
|
||||
|
||||
val_len = read_le32(buf + pos); pos += 4;
|
||||
flags = read_le32(buf + pos); pos += 4;
|
||||
|
||||
max_key_len = size - pos - (int64_t)val_len - 1;
|
||||
if (max_key_len < 0) {
|
||||
/* corrupt */
|
||||
break;
|
||||
}
|
||||
|
||||
for (key_len = 0; key_len < max_key_len && buf[pos + key_len]; key_len++)
|
||||
; /* nothing */
|
||||
if (buf[pos + key_len]) {
|
||||
/* corrupt */
|
||||
break;
|
||||
}
|
||||
|
||||
if (!AF_IS_UTF8(flags)) {
|
||||
/* ignore binary data */
|
||||
pos += key_len + 1 + val_len;
|
||||
continue;
|
||||
}
|
||||
|
||||
key = xstrdup(buf + pos);
|
||||
pos += key_len + 1;
|
||||
|
||||
/* should not be NUL-terminated */
|
||||
val = xstrndup(buf + pos, val_len);
|
||||
pos += val_len;
|
||||
|
||||
/* could be moved to comment.c but I don't think anyone else would use it */
|
||||
if (!strcasecmp(key, "record date") || !strcasecmp(key, "year")) {
|
||||
free(key);
|
||||
key = xstrdup("date");
|
||||
}
|
||||
|
||||
if (!strcasecmp(key, "date")) {
|
||||
/* Date format
|
||||
*
|
||||
* 1999-08-11 12:34:56
|
||||
* 1999-08-11 12:34
|
||||
* 1999-08-11
|
||||
* 1999-08
|
||||
* 1999
|
||||
* 1999-W34 (week 34, totally crazy)
|
||||
*
|
||||
* convert to year, pl.c supports only years anyways
|
||||
*
|
||||
* FIXME: which one is the most common tag (year or record date)?
|
||||
*/
|
||||
if (strlen(val) > 4)
|
||||
val[4] = 0;
|
||||
}
|
||||
|
||||
*keyp = key;
|
||||
*valp = val;
|
||||
return pos;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* return the number of comments, or -1 */
|
||||
int ape_read_tags(struct apetag *ape, int fd, int slow)
|
||||
{
|
||||
struct ape_header *h = &ape->header;
|
||||
int rc = -1;
|
||||
off_t old_pos;
|
||||
|
||||
/* save position */
|
||||
old_pos = lseek(fd, 0, SEEK_CUR);
|
||||
|
||||
if (!find_ape_tag(fd, h, slow))
|
||||
goto fail;
|
||||
|
||||
if (AF_IS_FOOTER(h->flags)) {
|
||||
/* seek back right after the header */
|
||||
if (lseek(fd, -((int)h->size), SEEK_CUR) == -1)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* ignore insane tags */
|
||||
if (h->size > 1024 * 1024)
|
||||
goto fail;
|
||||
|
||||
ape->buf = xnew(char, h->size);
|
||||
if (read_all(fd, ape->buf, h->size) != h->size)
|
||||
goto fail;
|
||||
|
||||
rc = h->count;
|
||||
|
||||
fail:
|
||||
lseek(fd, old_pos, SEEK_SET);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* returned key-name must be free'd */
|
||||
char *ape_get_comment(struct apetag *ape, char **val)
|
||||
{
|
||||
struct ape_header *h = &ape->header;
|
||||
char *key;
|
||||
int rc;
|
||||
|
||||
if (ape->pos >= h->size)
|
||||
return NULL;
|
||||
|
||||
rc = ape_parse_one(ape->buf + ape->pos, h->size - ape->pos, &key, val);
|
||||
if (rc < 0)
|
||||
return NULL;
|
||||
ape->pos += rc;
|
||||
|
||||
return key;
|
||||
}
|
||||
62
ape.h
Normal file
62
ape.h
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2007 Johannes Weißl
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CMUS_APE_H
|
||||
#define CMUS_APE_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
struct ape_header {
|
||||
/* 1000 or 2000 (1.0, 2.0) */
|
||||
uint32_t version;
|
||||
|
||||
/* tag size (header + tags, excluding footer) */
|
||||
uint32_t size;
|
||||
|
||||
/* number of items */
|
||||
uint32_t count;
|
||||
|
||||
/* global flags for each tag
|
||||
* there are also private flags for every tag
|
||||
* NOTE: 0 for version 1.0 (1000)
|
||||
*/
|
||||
uint32_t flags;
|
||||
};
|
||||
|
||||
/* ape flags */
|
||||
#define AF_IS_UTF8(f) (((f) & 6) == 0)
|
||||
#define AF_IS_FOOTER(f) (((f) & (1 << 29)) == 0)
|
||||
|
||||
struct apetag {
|
||||
char *buf;
|
||||
int pos;
|
||||
struct ape_header header;
|
||||
};
|
||||
|
||||
#define APETAG(name) struct apetag name = { .buf = NULL, .pos = 0, }
|
||||
|
||||
int ape_read_tags(struct apetag *ape, int fd, int slow);
|
||||
char *ape_get_comment(struct apetag *ape, char **val);
|
||||
|
||||
static inline void ape_free(struct apetag *ape)
|
||||
{
|
||||
free(ape->buf);
|
||||
}
|
||||
|
||||
#endif
|
||||
499
browser.c
Normal file
499
browser.c
Normal file
@@ -0,0 +1,499 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2004-2005 Timo Hirvonen
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "browser.h"
|
||||
#include "load_dir.h"
|
||||
#include "cmus.h"
|
||||
#include "xmalloc.h"
|
||||
#include "ui_curses.h"
|
||||
#include "file.h"
|
||||
#include "misc.h"
|
||||
#include "options.h"
|
||||
#include "uchar.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <dirent.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
struct window *browser_win;
|
||||
struct searchable *browser_searchable;
|
||||
char *browser_dir;
|
||||
|
||||
static LIST_HEAD(browser_head);
|
||||
|
||||
static inline void browser_entry_to_iter(struct browser_entry *e, struct iter *iter)
|
||||
{
|
||||
iter->data0 = &browser_head;
|
||||
iter->data1 = e;
|
||||
iter->data2 = NULL;
|
||||
}
|
||||
|
||||
/* filter out names starting with '.' except '..' */
|
||||
static int normal_filter(const char *name, const struct stat *s)
|
||||
{
|
||||
if (name[0] == '.') {
|
||||
if (name[1] == '.' && name[2] == 0)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
if (S_ISDIR(s->st_mode))
|
||||
return 1;
|
||||
return cmus_is_supported(name);
|
||||
}
|
||||
|
||||
/* filter out '.' */
|
||||
static int hidden_filter(const char *name, const struct stat *s)
|
||||
{
|
||||
if (name[0] == '.' && name[1] == 0)
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* only works for BROWSER_ENTRY_DIR and BROWSER_ENTRY_FILE */
|
||||
static int entry_cmp(const struct browser_entry *a, const struct browser_entry *b)
|
||||
{
|
||||
if (a->type == BROWSER_ENTRY_DIR) {
|
||||
if (b->type == BROWSER_ENTRY_FILE)
|
||||
return -1;
|
||||
if (!strcmp(a->name, "../"))
|
||||
return -1;
|
||||
if (!strcmp(b->name, "../"))
|
||||
return 1;
|
||||
return strcmp(a->name, b->name);
|
||||
}
|
||||
if (b->type == BROWSER_ENTRY_DIR)
|
||||
return 1;
|
||||
return strcmp(a->name, b->name);
|
||||
}
|
||||
|
||||
static char *fullname(const char *path, const char *name)
|
||||
{
|
||||
int l1, l2;
|
||||
char *full;
|
||||
|
||||
l1 = strlen(path);
|
||||
l2 = strlen(name);
|
||||
if (path[l1 - 1] == '/')
|
||||
l1--;
|
||||
full = xnew(char, l1 + 1 + l2 + 1);
|
||||
memcpy(full, path, l1);
|
||||
full[l1] = '/';
|
||||
memcpy(full + l1 + 1, name, l2 + 1);
|
||||
return full;
|
||||
}
|
||||
|
||||
static void free_browser_list(void)
|
||||
{
|
||||
struct list_head *item;
|
||||
|
||||
item = browser_head.next;
|
||||
while (item != &browser_head) {
|
||||
struct list_head *next = item->next;
|
||||
struct browser_entry *entry;
|
||||
|
||||
entry = list_entry(item, struct browser_entry, node);
|
||||
free(entry);
|
||||
item = next;
|
||||
}
|
||||
list_init(&browser_head);
|
||||
}
|
||||
|
||||
static int add_pl_line(void *data, const char *line)
|
||||
{
|
||||
struct browser_entry *e;
|
||||
int name_size = strlen(line) + 1;
|
||||
|
||||
e = xmalloc(sizeof(struct browser_entry) + name_size);
|
||||
memcpy(e->name, line, name_size);
|
||||
e->type = BROWSER_ENTRY_PLLINE;
|
||||
list_add_tail(&e->node, &browser_head);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int do_browser_load(const char *name)
|
||||
{
|
||||
struct stat st;
|
||||
|
||||
if (stat(name, &st))
|
||||
return -1;
|
||||
|
||||
if (S_ISREG(st.st_mode) && cmus_is_playlist(name)) {
|
||||
char *buf;
|
||||
ssize_t size;
|
||||
|
||||
buf = mmap_file(name, &size);
|
||||
if (size == -1)
|
||||
return -1;
|
||||
|
||||
free_browser_list();
|
||||
|
||||
if (buf) {
|
||||
struct browser_entry *parent_dir_e = xmalloc(sizeof(struct browser_entry) + 4);
|
||||
strcpy(parent_dir_e->name, "../");
|
||||
parent_dir_e->type = BROWSER_ENTRY_DIR;
|
||||
list_add_tail(&parent_dir_e->node, &browser_head);
|
||||
|
||||
cmus_playlist_for_each(buf, size, 0, add_pl_line, NULL);
|
||||
munmap(buf, size);
|
||||
}
|
||||
} else if (S_ISDIR(st.st_mode)) {
|
||||
int (*filter)(const char *, const struct stat *) = normal_filter;
|
||||
struct directory dir;
|
||||
const char *str;
|
||||
int root = !strcmp(name, "/");
|
||||
|
||||
if (show_hidden)
|
||||
filter = hidden_filter;
|
||||
|
||||
if (dir_open(&dir, name))
|
||||
return -1;
|
||||
|
||||
free_browser_list();
|
||||
while ((str = dir_read(&dir))) {
|
||||
struct browser_entry *e;
|
||||
struct list_head *item;
|
||||
int len;
|
||||
|
||||
if (!filter(str, &dir.st))
|
||||
continue;
|
||||
|
||||
/* ignore .. if we are in the root dir */
|
||||
if (root && !strcmp(str, ".."))
|
||||
continue;
|
||||
|
||||
len = strlen(str);
|
||||
e = xmalloc(sizeof(struct browser_entry) + len + 2);
|
||||
e->type = BROWSER_ENTRY_FILE;
|
||||
memcpy(e->name, str, len);
|
||||
if (S_ISDIR(dir.st.st_mode)) {
|
||||
e->type = BROWSER_ENTRY_DIR;
|
||||
e->name[len++] = '/';
|
||||
}
|
||||
e->name[len] = 0;
|
||||
|
||||
item = browser_head.prev;
|
||||
while (item != &browser_head) {
|
||||
struct browser_entry *other;
|
||||
|
||||
other = container_of(item, struct browser_entry, node);
|
||||
if (entry_cmp(e, other) >= 0)
|
||||
break;
|
||||
item = item->prev;
|
||||
}
|
||||
/* add after item */
|
||||
list_add(&e->node, item);
|
||||
}
|
||||
dir_close(&dir);
|
||||
|
||||
/* try to update currect working directory */
|
||||
if (chdir(name))
|
||||
return -1;
|
||||
} else {
|
||||
errno = ENOTDIR;
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int browser_load(const char *name)
|
||||
{
|
||||
int rc;
|
||||
|
||||
rc = do_browser_load(name);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
window_set_contents(browser_win, &browser_head);
|
||||
free(browser_dir);
|
||||
browser_dir = xstrdup(name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static GENERIC_ITER_PREV(browser_get_prev, struct browser_entry, node)
|
||||
static GENERIC_ITER_NEXT(browser_get_next, struct browser_entry, node)
|
||||
|
||||
static int browser_search_get_current(void *data, struct iter *iter, enum search_direction dir)
|
||||
{
|
||||
return window_get_sel(browser_win, iter);
|
||||
}
|
||||
|
||||
static int browser_search_matches(void *data, struct iter *iter, const char *text)
|
||||
{
|
||||
char **words = get_words(text);
|
||||
int matched = 0;
|
||||
|
||||
if (words[0] != NULL) {
|
||||
struct browser_entry *e;
|
||||
int i;
|
||||
|
||||
e = iter_to_browser_entry(iter);
|
||||
for (i = 0; ; i++) {
|
||||
if (words[i] == NULL) {
|
||||
window_set_sel(browser_win, iter);
|
||||
matched = 1;
|
||||
break;
|
||||
}
|
||||
if (u_strcasestr_filename(e->name, words[i]) == NULL)
|
||||
break;
|
||||
}
|
||||
}
|
||||
free_str_array(words);
|
||||
return matched;
|
||||
}
|
||||
|
||||
static const struct searchable_ops browser_search_ops = {
|
||||
.get_prev = browser_get_prev,
|
||||
.get_next = browser_get_next,
|
||||
.get_current = browser_search_get_current,
|
||||
.matches = browser_search_matches
|
||||
};
|
||||
|
||||
void browser_init(void)
|
||||
{
|
||||
struct iter iter;
|
||||
char cwd[1024];
|
||||
char *dir;
|
||||
|
||||
if (getcwd(cwd, sizeof(cwd)) == NULL) {
|
||||
dir = xstrdup("/");
|
||||
} else {
|
||||
dir = xstrdup(cwd);
|
||||
}
|
||||
if (do_browser_load(dir)) {
|
||||
free(dir);
|
||||
do_browser_load("/");
|
||||
browser_dir = xstrdup("/");
|
||||
} else {
|
||||
browser_dir = dir;
|
||||
}
|
||||
|
||||
browser_win = window_new(browser_get_prev, browser_get_next);
|
||||
window_set_contents(browser_win, &browser_head);
|
||||
window_changed(browser_win);
|
||||
|
||||
iter.data0 = &browser_head;
|
||||
iter.data1 = NULL;
|
||||
iter.data2 = NULL;
|
||||
browser_searchable = searchable_new(NULL, &iter, &browser_search_ops);
|
||||
}
|
||||
|
||||
void browser_exit(void)
|
||||
{
|
||||
searchable_free(browser_searchable);
|
||||
free_browser_list();
|
||||
window_free(browser_win);
|
||||
free(browser_dir);
|
||||
}
|
||||
|
||||
int browser_chdir(const char *dir)
|
||||
{
|
||||
if (browser_load(dir)) {
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void browser_up(void)
|
||||
{
|
||||
char *new, *ptr, *pos;
|
||||
struct browser_entry *e;
|
||||
int len;
|
||||
|
||||
if (strcmp(browser_dir, "/") == 0)
|
||||
return;
|
||||
|
||||
ptr = strrchr(browser_dir, '/');
|
||||
if (ptr == browser_dir) {
|
||||
new = xstrdup("/");
|
||||
} else {
|
||||
new = xstrndup(browser_dir, ptr - browser_dir);
|
||||
}
|
||||
|
||||
/* remember old position */
|
||||
ptr++;
|
||||
len = strlen(ptr);
|
||||
pos = xstrdup(ptr);
|
||||
|
||||
errno = 0;
|
||||
if (browser_load(new)) {
|
||||
if (errno == ENOENT) {
|
||||
free(pos);
|
||||
free(browser_dir);
|
||||
browser_dir = new;
|
||||
browser_up();
|
||||
return;
|
||||
}
|
||||
error_msg("could not open directory '%s': %s\n", new, strerror(errno));
|
||||
free(new);
|
||||
free(pos);
|
||||
return;
|
||||
}
|
||||
free(new);
|
||||
|
||||
/* select old position */
|
||||
list_for_each_entry(e, &browser_head, node) {
|
||||
if (strncmp(e->name, pos, len) == 0 &&
|
||||
(e->name[len] == '/' || e->name[len] == '\0')) {
|
||||
struct iter iter;
|
||||
|
||||
browser_entry_to_iter(e, &iter);
|
||||
window_set_sel(browser_win, &iter);
|
||||
break;
|
||||
}
|
||||
}
|
||||
free(pos);
|
||||
}
|
||||
|
||||
static void browser_cd(const char *dir)
|
||||
{
|
||||
char *new;
|
||||
int len;
|
||||
|
||||
if (strcmp(dir, "../") == 0) {
|
||||
browser_up();
|
||||
return;
|
||||
}
|
||||
|
||||
new = fullname(browser_dir, dir);
|
||||
len = strlen(new);
|
||||
if (new[len - 1] == '/')
|
||||
new[len - 1] = 0;
|
||||
if (browser_load(new))
|
||||
error_msg("could not open directory '%s': %s\n", dir, strerror(errno));
|
||||
free(new);
|
||||
}
|
||||
|
||||
static void browser_cd_playlist(const char *filename)
|
||||
{
|
||||
if (browser_load(filename))
|
||||
error_msg("could not read playlist '%s': %s\n", filename, strerror(errno));
|
||||
}
|
||||
|
||||
void browser_enter(void)
|
||||
{
|
||||
struct browser_entry *e;
|
||||
struct iter sel;
|
||||
int len;
|
||||
|
||||
if (!window_get_sel(browser_win, &sel))
|
||||
return;
|
||||
e = iter_to_browser_entry(&sel);
|
||||
len = strlen(e->name);
|
||||
if (len == 0)
|
||||
return;
|
||||
if (e->type == BROWSER_ENTRY_DIR) {
|
||||
browser_cd(e->name);
|
||||
} else {
|
||||
if (e->type == BROWSER_ENTRY_PLLINE) {
|
||||
cmus_play_file(e->name);
|
||||
} else {
|
||||
char *filename;
|
||||
|
||||
filename = fullname(browser_dir, e->name);
|
||||
if (cmus_is_playlist(filename)) {
|
||||
browser_cd_playlist(filename);
|
||||
} else {
|
||||
cmus_play_file(filename);
|
||||
}
|
||||
free(filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
char *browser_get_sel(void)
|
||||
{
|
||||
struct browser_entry *e;
|
||||
struct iter sel;
|
||||
|
||||
if (!window_get_sel(browser_win, &sel))
|
||||
return NULL;
|
||||
|
||||
e = iter_to_browser_entry(&sel);
|
||||
if (e->type == BROWSER_ENTRY_PLLINE)
|
||||
return xstrdup(e->name);
|
||||
|
||||
return fullname(browser_dir, e->name);
|
||||
}
|
||||
|
||||
void browser_delete(void)
|
||||
{
|
||||
struct browser_entry *e;
|
||||
struct iter sel;
|
||||
int len;
|
||||
|
||||
if (!window_get_sel(browser_win, &sel))
|
||||
return;
|
||||
e = iter_to_browser_entry(&sel);
|
||||
len = strlen(e->name);
|
||||
if (len == 0)
|
||||
return;
|
||||
if (e->type == BROWSER_ENTRY_FILE) {
|
||||
char *name;
|
||||
|
||||
name = fullname(browser_dir, e->name);
|
||||
if (yes_no_query("Delete file '%s'? [y/N]", e->name) == UI_QUERY_ANSWER_YES) {
|
||||
if (unlink(name) == -1) {
|
||||
error_msg("deleting '%s': %s", e->name, strerror(errno));
|
||||
} else {
|
||||
window_row_vanishes(browser_win, &sel);
|
||||
list_del(&e->node);
|
||||
free(e);
|
||||
}
|
||||
}
|
||||
free(name);
|
||||
}
|
||||
}
|
||||
|
||||
void browser_reload(void)
|
||||
{
|
||||
char *tmp = xstrdup(browser_dir);
|
||||
char *sel = NULL;
|
||||
struct iter iter;
|
||||
struct browser_entry *e;
|
||||
|
||||
/* remember selection */
|
||||
if (window_get_sel(browser_win, &iter)) {
|
||||
e = iter_to_browser_entry(&iter);
|
||||
sel = xstrdup(e->name);
|
||||
}
|
||||
|
||||
/* have to use tmp */
|
||||
if (browser_load(tmp)) {
|
||||
error_msg("could not update contents '%s': %s\n", tmp, strerror(errno));
|
||||
free(tmp);
|
||||
free(sel);
|
||||
return;
|
||||
}
|
||||
|
||||
if (sel) {
|
||||
/* set selection */
|
||||
list_for_each_entry(e, &browser_head, node) {
|
||||
if (strcmp(e->name, sel) == 0) {
|
||||
browser_entry_to_iter(e, &iter);
|
||||
window_set_sel(browser_win, &iter);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
free(tmp);
|
||||
free(sel);
|
||||
}
|
||||
52
browser.h
Normal file
52
browser.h
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2004 Timo Hirvonen
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CMUS_BROWSER_H
|
||||
#define CMUS_BROWSER_H
|
||||
|
||||
#include "list.h"
|
||||
#include "window.h"
|
||||
#include "search.h"
|
||||
|
||||
struct browser_entry {
|
||||
struct list_head node;
|
||||
|
||||
enum { BROWSER_ENTRY_DIR, BROWSER_ENTRY_FILE, BROWSER_ENTRY_PLLINE } type;
|
||||
char name[];
|
||||
};
|
||||
|
||||
static inline struct browser_entry *iter_to_browser_entry(struct iter *iter)
|
||||
{
|
||||
return iter->data1;
|
||||
}
|
||||
|
||||
extern struct window *browser_win;
|
||||
extern char *browser_dir;
|
||||
extern struct searchable *browser_searchable;
|
||||
|
||||
void browser_init(void);
|
||||
void browser_exit(void);
|
||||
int browser_chdir(const char *dir);
|
||||
char *browser_get_sel(void);
|
||||
void browser_up(void);
|
||||
void browser_enter(void);
|
||||
void browser_delete(void);
|
||||
void browser_reload(void);
|
||||
void browser_toggle_show_hidden(void);
|
||||
|
||||
#endif
|
||||
209
buffer.c
Normal file
209
buffer.c
Normal file
@@ -0,0 +1,209 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2004 Timo Hirvonen
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "buffer.h"
|
||||
#include "xmalloc.h"
|
||||
#include "locking.h"
|
||||
#include "debug.h"
|
||||
|
||||
/*
|
||||
* chunk can be accessed by either consumer OR producer, not both at same time
|
||||
* -> no need to lock
|
||||
*/
|
||||
struct chunk {
|
||||
char data[CHUNK_SIZE];
|
||||
|
||||
/* index to data, first filled byte */
|
||||
unsigned int l;
|
||||
|
||||
/* index to data, last filled byte + 1
|
||||
*
|
||||
* there are h - l bytes available (filled)
|
||||
*/
|
||||
unsigned int h : 31;
|
||||
|
||||
/* if chunk is marked filled it can only be accessed by consumer
|
||||
* otherwise only producer is allowed to access the chunk
|
||||
*/
|
||||
unsigned int filled : 1;
|
||||
};
|
||||
|
||||
unsigned int buffer_nr_chunks;
|
||||
|
||||
static pthread_mutex_t buffer_mutex = CMUS_MUTEX_INITIALIZER;
|
||||
static struct chunk *buffer_chunks = NULL;
|
||||
static unsigned int buffer_ridx;
|
||||
static unsigned int buffer_widx;
|
||||
|
||||
void buffer_init(void)
|
||||
{
|
||||
free(buffer_chunks);
|
||||
buffer_chunks = xnew(struct chunk, buffer_nr_chunks);
|
||||
buffer_reset();
|
||||
}
|
||||
|
||||
void buffer_free(void)
|
||||
{
|
||||
free(buffer_chunks);
|
||||
}
|
||||
|
||||
/*
|
||||
* @pos: returned pointer to available data
|
||||
*
|
||||
* Returns number of bytes available at @pos
|
||||
*
|
||||
* After reading bytes mark them consumed calling buffer_consume().
|
||||
*/
|
||||
int buffer_get_rpos(char **pos)
|
||||
{
|
||||
struct chunk *c;
|
||||
int size = 0;
|
||||
|
||||
cmus_mutex_lock(&buffer_mutex);
|
||||
c = &buffer_chunks[buffer_ridx];
|
||||
if (c->filled) {
|
||||
size = c->h - c->l;
|
||||
*pos = c->data + c->l;
|
||||
}
|
||||
cmus_mutex_unlock(&buffer_mutex);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
/*
|
||||
* @pos: pointer to buffer position where data can be written
|
||||
*
|
||||
* Returns number of bytes can be written to @pos. If the return value is
|
||||
* non-zero it is guaranteed to be >= 1024.
|
||||
*
|
||||
* After writing bytes mark them filled calling buffer_fill().
|
||||
*/
|
||||
int buffer_get_wpos(char **pos)
|
||||
{
|
||||
struct chunk *c;
|
||||
int size = 0;
|
||||
|
||||
cmus_mutex_lock(&buffer_mutex);
|
||||
c = &buffer_chunks[buffer_widx];
|
||||
if (!c->filled) {
|
||||
size = CHUNK_SIZE - c->h;
|
||||
*pos = c->data + c->h;
|
||||
}
|
||||
cmus_mutex_unlock(&buffer_mutex);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
void buffer_consume(int count)
|
||||
{
|
||||
struct chunk *c;
|
||||
|
||||
BUG_ON(count < 0);
|
||||
cmus_mutex_lock(&buffer_mutex);
|
||||
c = &buffer_chunks[buffer_ridx];
|
||||
BUG_ON(!c->filled);
|
||||
c->l += count;
|
||||
if (c->l == c->h) {
|
||||
c->l = 0;
|
||||
c->h = 0;
|
||||
c->filled = 0;
|
||||
buffer_ridx++;
|
||||
buffer_ridx %= buffer_nr_chunks;
|
||||
}
|
||||
cmus_mutex_unlock(&buffer_mutex);
|
||||
}
|
||||
|
||||
/* chunk is marked filled if free bytes < 1024 or count == 0 */
|
||||
int buffer_fill(int count)
|
||||
{
|
||||
struct chunk *c;
|
||||
int filled = 0;
|
||||
|
||||
cmus_mutex_lock(&buffer_mutex);
|
||||
c = &buffer_chunks[buffer_widx];
|
||||
BUG_ON(c->filled);
|
||||
c->h += count;
|
||||
|
||||
if (CHUNK_SIZE - c->h < 1024 || (count == 0 && c->h > 0)) {
|
||||
c->filled = 1;
|
||||
buffer_widx++;
|
||||
buffer_widx %= buffer_nr_chunks;
|
||||
filled = 1;
|
||||
}
|
||||
|
||||
cmus_mutex_unlock(&buffer_mutex);
|
||||
return filled;
|
||||
}
|
||||
|
||||
void buffer_reset(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
cmus_mutex_lock(&buffer_mutex);
|
||||
buffer_ridx = 0;
|
||||
buffer_widx = 0;
|
||||
for (i = 0; i < buffer_nr_chunks; i++) {
|
||||
buffer_chunks[i].l = 0;
|
||||
buffer_chunks[i].h = 0;
|
||||
buffer_chunks[i].filled = 0;
|
||||
}
|
||||
cmus_mutex_unlock(&buffer_mutex);
|
||||
}
|
||||
|
||||
int buffer_get_filled_chunks(void)
|
||||
{
|
||||
int c;
|
||||
|
||||
cmus_mutex_lock(&buffer_mutex);
|
||||
if (buffer_ridx < buffer_widx) {
|
||||
/*
|
||||
* |__##########____|
|
||||
* r w
|
||||
*
|
||||
* |############____|
|
||||
* r w
|
||||
*/
|
||||
c = buffer_widx - buffer_ridx;
|
||||
} else if (buffer_ridx > buffer_widx) {
|
||||
/*
|
||||
* |#######______###|
|
||||
* w r
|
||||
*
|
||||
* |_____________###|
|
||||
* w r
|
||||
*/
|
||||
c = buffer_nr_chunks - buffer_ridx + buffer_widx;
|
||||
} else {
|
||||
/*
|
||||
* |################|
|
||||
* r
|
||||
* w
|
||||
*
|
||||
* |________________|
|
||||
* r
|
||||
* w
|
||||
*/
|
||||
if (buffer_chunks[buffer_ridx].filled) {
|
||||
c = buffer_nr_chunks;
|
||||
} else {
|
||||
c = 0;
|
||||
}
|
||||
}
|
||||
cmus_mutex_unlock(&buffer_mutex);
|
||||
return c;
|
||||
}
|
||||
47
buffer.h
Normal file
47
buffer.h
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2004 Timo Hirvonen
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CMUS_BUFFER_H
|
||||
#define CMUS_BUFFER_H
|
||||
|
||||
/*
|
||||
* must be a multiple of any supported frame size
|
||||
*
|
||||
* 12 is the LCM of 1, 2, 3 and 4, which corresponds to
|
||||
* 8, 16, 24 and 32 bits respectively
|
||||
*
|
||||
* 840 is the LCM of 1, 2, 3, 4, 5, 6, 7 and 8, which
|
||||
* are the numbers of supported channels
|
||||
*
|
||||
* we used to define the value as 60 * 1024 = 61440
|
||||
* hence the extra 6, which makes the new value 60480
|
||||
*/
|
||||
#define CHUNK_SIZE (12 * 840 * 6)
|
||||
|
||||
extern unsigned int buffer_nr_chunks;
|
||||
|
||||
void buffer_init(void);
|
||||
void buffer_free(void);
|
||||
int buffer_get_rpos(char **pos);
|
||||
int buffer_get_wpos(char **pos);
|
||||
void buffer_consume(int count);
|
||||
int buffer_fill(int count);
|
||||
void buffer_reset(void);
|
||||
int buffer_get_filled_chunks(void);
|
||||
|
||||
#endif
|
||||
565
cache.c
Normal file
565
cache.c
Normal file
@@ -0,0 +1,565 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2004 Timo Hirvonen
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "cache.h"
|
||||
#include "misc.h"
|
||||
#include "file.h"
|
||||
#include "input.h"
|
||||
#include "track_info.h"
|
||||
#include "utils.h"
|
||||
#include "xmalloc.h"
|
||||
#include "xstrjoin.h"
|
||||
#include "gbuf.h"
|
||||
#include "options.h"
|
||||
#include "pl_env.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <stdbool.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
#define CACHE_VERSION 0x0d
|
||||
|
||||
#define CACHE_64_BIT 0x01
|
||||
#define CACHE_BE 0x02
|
||||
|
||||
#define CACHE_RESERVED_PATTERN 0xff
|
||||
|
||||
#define CACHE_ENTRY_USED_SIZE 28
|
||||
#define CACHE_ENTRY_RESERVED_SIZE 52
|
||||
#define CACHE_ENTRY_TOTAL_SIZE (CACHE_ENTRY_RESERVED_SIZE + CACHE_ENTRY_USED_SIZE)
|
||||
|
||||
// Cmus Track Cache version X + 4 bytes flags
|
||||
static char cache_header[8] CMUS_NONSTRING = "CTC\0\0\0\0\0";
|
||||
|
||||
// host byte order
|
||||
// mtime is either 32 or 64 bits
|
||||
struct cache_entry {
|
||||
// size of this struct including size itself
|
||||
uint32_t size;
|
||||
|
||||
int32_t play_count;
|
||||
int64_t mtime;
|
||||
int32_t duration;
|
||||
int32_t bitrate;
|
||||
int32_t bpm;
|
||||
|
||||
// when introducing new fields decrease the reserved space accordingly
|
||||
uint8_t _reserved[CACHE_ENTRY_RESERVED_SIZE];
|
||||
|
||||
// filename, codec, codec_profile and N * (key, val)
|
||||
char strings[];
|
||||
};
|
||||
|
||||
// make sure our mmap/sizeof-based code works
|
||||
STATIC_ASSERT(CACHE_ENTRY_TOTAL_SIZE == sizeof(struct cache_entry));
|
||||
STATIC_ASSERT(CACHE_ENTRY_TOTAL_SIZE == offsetof(struct cache_entry, strings));
|
||||
|
||||
|
||||
#define ALIGN(size) (((size) + sizeof(long) - 1) & ~(sizeof(long) - 1))
|
||||
#define HASH_SIZE 1023
|
||||
|
||||
static struct track_info *hash_table[HASH_SIZE];
|
||||
static char *cache_filename;
|
||||
static int total;
|
||||
|
||||
struct fifo_mutex cache_mutex = FIFO_MUTEX_INITIALIZER;
|
||||
|
||||
|
||||
static void add_ti(struct track_info *ti, unsigned int hash)
|
||||
{
|
||||
unsigned int pos = hash % HASH_SIZE;
|
||||
struct track_info *next = hash_table[pos];
|
||||
|
||||
ti->next = next;
|
||||
hash_table[pos] = ti;
|
||||
total++;
|
||||
}
|
||||
|
||||
static int valid_cache_entry(const struct cache_entry *e, unsigned int avail)
|
||||
{
|
||||
unsigned int min_size = sizeof(*e);
|
||||
unsigned int str_size;
|
||||
int i, count;
|
||||
|
||||
if (avail < min_size)
|
||||
return 0;
|
||||
|
||||
if (e->size < min_size || e->size > avail)
|
||||
return 0;
|
||||
|
||||
str_size = e->size - min_size;
|
||||
count = 0;
|
||||
for (i = 0; i < str_size; i++) {
|
||||
if (!e->strings[i])
|
||||
count++;
|
||||
}
|
||||
if (count % 2 == 0)
|
||||
return 0;
|
||||
if (e->strings[str_size - 1])
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static struct track_info *cache_entry_to_ti(struct cache_entry *e)
|
||||
{
|
||||
const char *strings = e->strings;
|
||||
struct track_info *ti;
|
||||
struct keyval *kv;
|
||||
int str_size = e->size - sizeof(*e);
|
||||
int pos, i, count;
|
||||
char *proc_filename;
|
||||
|
||||
if (pl_env_var(strings, NULL) && (proc_filename = pl_env_expand(strings))) {
|
||||
ti = track_info_new(proc_filename);
|
||||
free(proc_filename);
|
||||
} else {
|
||||
ti = track_info_new(strings);
|
||||
}
|
||||
|
||||
ti->duration = e->duration;
|
||||
ti->bitrate = e->bitrate;
|
||||
ti->mtime = e->mtime;
|
||||
ti->play_count = e->play_count;
|
||||
ti->bpm = e->bpm;
|
||||
|
||||
// count strings (filename + codec + codec_profile + key/val pairs)
|
||||
count = 0;
|
||||
for (i = 0; i < str_size; i++) {
|
||||
if (!strings[i])
|
||||
count++;
|
||||
}
|
||||
count = (count - 3) / 2;
|
||||
|
||||
// NOTE: filename already copied by track_info_new()
|
||||
pos = strlen(strings) + 1;
|
||||
ti->codec = strings[pos] ? xstrdup(strings + pos) : NULL;
|
||||
pos += strlen(strings + pos) + 1;
|
||||
ti->codec_profile = strings[pos] ? xstrdup(strings + pos) : NULL;
|
||||
pos += strlen(strings + pos) + 1;
|
||||
kv = xnew(struct keyval, count + 1);
|
||||
for (i = 0; i < count; i++) {
|
||||
int size;
|
||||
|
||||
size = strlen(strings + pos) + 1;
|
||||
kv[i].key = xstrdup(strings + pos);
|
||||
pos += size;
|
||||
|
||||
size = strlen(strings + pos) + 1;
|
||||
kv[i].val = xstrdup(strings + pos);
|
||||
pos += size;
|
||||
}
|
||||
kv[i].key = NULL;
|
||||
kv[i].val = NULL;
|
||||
track_info_set_comments(ti, kv);
|
||||
return ti;
|
||||
}
|
||||
|
||||
struct track_info *lookup_cache_entry(const char *filename, unsigned int hash)
|
||||
{
|
||||
struct track_info *ti = hash_table[hash % HASH_SIZE];
|
||||
|
||||
while (ti) {
|
||||
if (!strcmp(filename, ti->filename))
|
||||
return ti;
|
||||
ti = ti->next;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void do_cache_remove_ti(struct track_info *ti, unsigned int hash)
|
||||
{
|
||||
unsigned int pos = hash % HASH_SIZE;
|
||||
struct track_info *t = hash_table[pos];
|
||||
struct track_info *next, *prev = NULL;
|
||||
|
||||
while (t) {
|
||||
next = t->next;
|
||||
if (t == ti) {
|
||||
if (prev) {
|
||||
prev->next = next;
|
||||
} else {
|
||||
hash_table[pos] = next;
|
||||
}
|
||||
total--;
|
||||
track_info_unref(ti);
|
||||
return;
|
||||
}
|
||||
prev = t;
|
||||
t = next;
|
||||
}
|
||||
}
|
||||
|
||||
void cache_remove_ti(struct track_info *ti)
|
||||
{
|
||||
do_cache_remove_ti(ti, hash_str(ti->filename));
|
||||
}
|
||||
|
||||
static int read_cache(void)
|
||||
{
|
||||
unsigned int size, offset = 0;
|
||||
struct stat st = {};
|
||||
char *buf;
|
||||
int fd;
|
||||
|
||||
fd = open(cache_filename, O_RDONLY);
|
||||
if (fd < 0) {
|
||||
if (errno == ENOENT)
|
||||
return 0;
|
||||
return -1;
|
||||
}
|
||||
fstat(fd, &st);
|
||||
if (st.st_size < sizeof(cache_header))
|
||||
goto close;
|
||||
size = st.st_size;
|
||||
|
||||
buf = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
|
||||
if (buf == MAP_FAILED) {
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (memcmp(buf, cache_header, sizeof(cache_header)))
|
||||
goto corrupt;
|
||||
|
||||
offset = sizeof(cache_header);
|
||||
while (offset < size) {
|
||||
struct cache_entry *e = (void *)(buf + offset);
|
||||
struct track_info *ti;
|
||||
|
||||
if (!valid_cache_entry(e, size - offset))
|
||||
goto corrupt;
|
||||
|
||||
ti = cache_entry_to_ti(e);
|
||||
add_ti(ti, hash_str(ti->filename));
|
||||
offset += ALIGN(e->size);
|
||||
}
|
||||
munmap(buf, size);
|
||||
close(fd);
|
||||
return 0;
|
||||
corrupt:
|
||||
munmap(buf, size);
|
||||
close:
|
||||
close(fd);
|
||||
// corrupt
|
||||
return -2;
|
||||
}
|
||||
|
||||
int cache_init(void)
|
||||
{
|
||||
unsigned int flags = 0;
|
||||
|
||||
#ifdef WORDS_BIGENDIAN
|
||||
flags |= CACHE_BE;
|
||||
#endif
|
||||
if (sizeof(long) == 8)
|
||||
flags |= CACHE_64_BIT;
|
||||
|
||||
cache_header[7] = flags & 0xff; flags >>= 8;
|
||||
cache_header[6] = flags & 0xff; flags >>= 8;
|
||||
cache_header[5] = flags & 0xff; flags >>= 8;
|
||||
cache_header[4] = flags & 0xff;
|
||||
|
||||
/* assumed version */
|
||||
cache_header[3] = CACHE_VERSION;
|
||||
|
||||
cache_filename = xstrjoin(cmus_config_dir, "/cache");
|
||||
return read_cache();
|
||||
}
|
||||
|
||||
static int ti_filename_cmp(const void *a, const void *b)
|
||||
{
|
||||
const struct track_info *ai = *(const struct track_info **)a;
|
||||
const struct track_info *bi = *(const struct track_info **)b;
|
||||
|
||||
return strcmp(ai->filename, bi->filename);
|
||||
}
|
||||
|
||||
static struct track_info **get_track_infos(bool reference)
|
||||
{
|
||||
struct track_info **tis;
|
||||
int i, c;
|
||||
|
||||
tis = xnew(struct track_info *, total);
|
||||
c = 0;
|
||||
for (i = 0; i < HASH_SIZE; i++) {
|
||||
struct track_info *ti = hash_table[i];
|
||||
|
||||
while (ti) {
|
||||
if (reference)
|
||||
track_info_ref(ti);
|
||||
tis[c++] = ti;
|
||||
ti = ti->next;
|
||||
}
|
||||
}
|
||||
qsort(tis, total, sizeof(struct track_info *), ti_filename_cmp);
|
||||
return tis;
|
||||
}
|
||||
|
||||
static void flush_buffer(int fd, struct gbuf *buf)
|
||||
{
|
||||
if (buf->len) {
|
||||
write_all(fd, buf->buffer, buf->len);
|
||||
gbuf_clear(buf);
|
||||
}
|
||||
}
|
||||
|
||||
static void write_ti(int fd, struct gbuf *buf, struct track_info *ti, unsigned int *offsetp)
|
||||
{
|
||||
char *proc_filename = pl_env_reduce(ti->filename);
|
||||
const struct keyval *kv = ti->comments;
|
||||
unsigned int offset = *offsetp;
|
||||
unsigned int pad;
|
||||
struct cache_entry e;
|
||||
int *len, alloc = 64, count, i;
|
||||
|
||||
memset(e._reserved, CACHE_RESERVED_PATTERN, sizeof(e._reserved));
|
||||
|
||||
count = 0;
|
||||
len = xnew(int, alloc);
|
||||
e.size = sizeof(e);
|
||||
e.duration = ti->duration;
|
||||
e.bitrate = ti->bitrate;
|
||||
e.mtime = ti->mtime;
|
||||
e.play_count = ti->play_count;
|
||||
e.bpm = ti->bpm;
|
||||
len[count] = strlen(proc_filename) + 1;
|
||||
e.size += len[count++];
|
||||
len[count] = (ti->codec ? strlen(ti->codec) : 0) + 1;
|
||||
e.size += len[count++];
|
||||
len[count] = (ti->codec_profile ? strlen(ti->codec_profile) : 0) + 1;
|
||||
e.size += len[count++];
|
||||
for (i = 0; kv[i].key; i++) {
|
||||
if (count + 2 > alloc) {
|
||||
alloc *= 2;
|
||||
len = xrenew(int, len, alloc);
|
||||
}
|
||||
len[count] = strlen(kv[i].key) + 1;
|
||||
e.size += len[count++];
|
||||
len[count] = strlen(kv[i].val) + 1;
|
||||
e.size += len[count++];
|
||||
}
|
||||
|
||||
pad = ALIGN(offset) - offset;
|
||||
if (gbuf_avail(buf) < pad + e.size)
|
||||
flush_buffer(fd, buf);
|
||||
|
||||
count = 0;
|
||||
if (pad)
|
||||
gbuf_set(buf, 0, pad);
|
||||
gbuf_add_bytes(buf, &e, sizeof(e));
|
||||
gbuf_add_bytes(buf, proc_filename, len[count++]);
|
||||
gbuf_add_bytes(buf, ti->codec ? ti->codec : "", len[count++]);
|
||||
gbuf_add_bytes(buf, ti->codec_profile ? ti->codec_profile : "", len[count++]);
|
||||
for (i = 0; kv[i].key; i++) {
|
||||
gbuf_add_bytes(buf, kv[i].key, len[count++]);
|
||||
gbuf_add_bytes(buf, kv[i].val, len[count++]);
|
||||
}
|
||||
|
||||
free(len);
|
||||
*offsetp = offset + pad + e.size;
|
||||
|
||||
free(proc_filename);
|
||||
}
|
||||
|
||||
int cache_close(void)
|
||||
{
|
||||
GBUF(buf);
|
||||
struct track_info **tis;
|
||||
unsigned int offset;
|
||||
int i, fd, rc;
|
||||
char *tmp;
|
||||
|
||||
tmp = xstrjoin(cmus_config_dir, "/cache.tmp");
|
||||
fd = open(tmp, O_WRONLY | O_CREAT | O_TRUNC, 0666);
|
||||
if (fd < 0) {
|
||||
free(tmp);
|
||||
return -1;
|
||||
}
|
||||
|
||||
tis = get_track_infos(false);
|
||||
|
||||
gbuf_grow(&buf, 64 * 1024 - 1);
|
||||
gbuf_add_bytes(&buf, cache_header, sizeof(cache_header));
|
||||
offset = sizeof(cache_header);
|
||||
for (i = 0; i < total; i++)
|
||||
write_ti(fd, &buf, tis[i], &offset);
|
||||
flush_buffer(fd, &buf);
|
||||
gbuf_free(&buf);
|
||||
free(tis);
|
||||
|
||||
close(fd);
|
||||
rc = rename(tmp, cache_filename);
|
||||
free(tmp);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static struct track_info *ip_get_ti(const char *filename)
|
||||
{
|
||||
struct track_info *ti = NULL;
|
||||
struct input_plugin *ip;
|
||||
struct keyval *comments;
|
||||
int rc;
|
||||
|
||||
ip = ip_new(filename);
|
||||
rc = ip_open(ip);
|
||||
if (rc) {
|
||||
ip_delete(ip);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
rc = ip_read_comments(ip, &comments);
|
||||
if (!rc) {
|
||||
ti = track_info_new(filename);
|
||||
track_info_set_comments(ti, comments);
|
||||
ti->duration = ip_duration(ip);
|
||||
ti->bitrate = ip_bitrate(ip);
|
||||
ti->codec = ip_codec(ip);
|
||||
ti->codec_profile = ip_codec_profile(ip);
|
||||
ti->mtime = ip_is_remote(ip) ? -1 : file_get_mtime(filename);
|
||||
}
|
||||
ip_delete(ip);
|
||||
return ti;
|
||||
}
|
||||
|
||||
struct track_info *cache_get_ti(const char *filename, int force)
|
||||
{
|
||||
unsigned int hash = hash_str(filename);
|
||||
struct track_info *ti;
|
||||
int reload = 0;
|
||||
|
||||
if (pl_env_var(filename, NULL)) {
|
||||
struct growing_keyvals c = {NULL, 0, 0};
|
||||
keyvals_terminate(&c);
|
||||
|
||||
ti = track_info_new(filename);
|
||||
ti->duration = 0;
|
||||
track_info_set_comments(ti, c.keyvals);
|
||||
|
||||
track_info_ref(ti);
|
||||
return ti;
|
||||
}
|
||||
|
||||
ti = lookup_cache_entry(filename, hash);
|
||||
if (ti) {
|
||||
if ((!skip_track_info && ti->duration == 0 && !is_http_url(filename)) || force){
|
||||
do_cache_remove_ti(ti, hash);
|
||||
ti = NULL;
|
||||
reload = 1;
|
||||
}
|
||||
}
|
||||
if (!ti) {
|
||||
if (skip_track_info && !reload && !force) {
|
||||
struct growing_keyvals c = {NULL, 0, 0};
|
||||
|
||||
ti = track_info_new(filename);
|
||||
|
||||
keyvals_terminate(&c);
|
||||
track_info_set_comments(ti, c.keyvals);
|
||||
|
||||
ti->duration = 0;
|
||||
} else {
|
||||
ti = ip_get_ti(filename);
|
||||
}
|
||||
if (!ti)
|
||||
return NULL;
|
||||
add_ti(ti, hash);
|
||||
}
|
||||
track_info_ref(ti);
|
||||
return ti;
|
||||
}
|
||||
|
||||
struct track_info **cache_refresh(int *count, int force)
|
||||
{
|
||||
struct track_info **tis = get_track_infos(true);
|
||||
int i, n = total;
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
unsigned int hash;
|
||||
struct track_info *ti = tis[i];
|
||||
struct stat st;
|
||||
int rc = 0;
|
||||
|
||||
cache_yield();
|
||||
|
||||
/*
|
||||
* If no-one else has reference to tis[i] then it is set to NULL
|
||||
* otherwise:
|
||||
*
|
||||
* unchanged: tis[i] = NULL
|
||||
* deleted: tis[i]->next = NULL
|
||||
* changed: tis[i]->next = new
|
||||
*/
|
||||
|
||||
if (!is_url(ti->filename)) {
|
||||
rc = stat(ti->filename, &st);
|
||||
if (!rc && !force && ti->mtime == st.st_mtime) {
|
||||
// unchanged
|
||||
track_info_unref(ti);
|
||||
tis[i] = NULL;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
hash = hash_str(ti->filename);
|
||||
do_cache_remove_ti(ti, hash);
|
||||
|
||||
if (!rc) {
|
||||
// changed
|
||||
struct track_info *new_ti;
|
||||
|
||||
// clear cache-only entries
|
||||
if (force && track_info_unique_ref(ti)) {
|
||||
track_info_unref(ti);
|
||||
tis[i] = NULL;
|
||||
continue;
|
||||
}
|
||||
|
||||
new_ti = ip_get_ti(ti->filename);
|
||||
if (new_ti) {
|
||||
add_ti(new_ti, hash);
|
||||
|
||||
if (track_info_unique_ref(ti)) {
|
||||
track_info_unref(ti);
|
||||
tis[i] = NULL;
|
||||
} else {
|
||||
track_info_ref(new_ti);
|
||||
ti->next = new_ti;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// treat as deleted
|
||||
}
|
||||
|
||||
// deleted
|
||||
if (track_info_unique_ref(ti)) {
|
||||
track_info_unref(ti);
|
||||
tis[i] = NULL;
|
||||
} else {
|
||||
ti->next = NULL;
|
||||
}
|
||||
}
|
||||
*count = n;
|
||||
return tis;
|
||||
}
|
||||
38
cache.h
Normal file
38
cache.h
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2004 Timo Hirvonen
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CMUS_CACHE_H
|
||||
#define CMUS_CACHE_H
|
||||
|
||||
#include "track_info.h"
|
||||
#include "locking.h"
|
||||
|
||||
extern struct fifo_mutex cache_mutex;
|
||||
|
||||
#define cache_lock() fifo_mutex_lock(&cache_mutex)
|
||||
#define cache_yield() fifo_mutex_yield(&cache_mutex)
|
||||
#define cache_unlock() fifo_mutex_unlock(&cache_mutex)
|
||||
|
||||
int cache_init(void);
|
||||
int cache_close(void);
|
||||
struct track_info *cache_get_ti(const char *filename, int force);
|
||||
void cache_remove_ti(struct track_info *ti);
|
||||
struct track_info **cache_refresh(int *count, int force);
|
||||
struct track_info *lookup_cache_entry(const char *filename, unsigned int hash);
|
||||
|
||||
#endif
|
||||
63
channelmap.c
Normal file
63
channelmap.c
Normal file
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2011-2013 Various Authors
|
||||
* Copyright 2011 Johannes Weißl
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "channelmap.h"
|
||||
#include "utils.h"
|
||||
|
||||
void channel_map_init_waveex(int channels, unsigned int mask, channel_position_t *map)
|
||||
{
|
||||
/* http://www.microsoft.com/whdc/device/audio/multichaud.mspx#EMLAC */
|
||||
const channel_position_t channel_map_waveex[] = {
|
||||
CHANNEL_POSITION_FRONT_LEFT,
|
||||
CHANNEL_POSITION_FRONT_RIGHT,
|
||||
CHANNEL_POSITION_FRONT_CENTER,
|
||||
CHANNEL_POSITION_LFE,
|
||||
CHANNEL_POSITION_REAR_LEFT,
|
||||
CHANNEL_POSITION_REAR_RIGHT,
|
||||
CHANNEL_POSITION_FRONT_LEFT_OF_CENTER,
|
||||
CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER,
|
||||
CHANNEL_POSITION_REAR_CENTER,
|
||||
CHANNEL_POSITION_SIDE_LEFT,
|
||||
CHANNEL_POSITION_SIDE_RIGHT,
|
||||
CHANNEL_POSITION_TOP_CENTER,
|
||||
CHANNEL_POSITION_TOP_FRONT_LEFT,
|
||||
CHANNEL_POSITION_TOP_FRONT_CENTER,
|
||||
CHANNEL_POSITION_TOP_FRONT_RIGHT,
|
||||
CHANNEL_POSITION_TOP_REAR_LEFT,
|
||||
CHANNEL_POSITION_TOP_REAR_CENTER,
|
||||
CHANNEL_POSITION_TOP_REAR_RIGHT
|
||||
};
|
||||
|
||||
if (channels == 1) {
|
||||
map[0] = CHANNEL_POSITION_MONO;
|
||||
} else if (channels > 1 && channels < N_ELEMENTS(channel_map_waveex)) {
|
||||
int i, j = 0;
|
||||
|
||||
if (!mask)
|
||||
mask = (1 << channels) - 1;
|
||||
|
||||
for (i = 0; i < N_ELEMENTS(channel_map_waveex); i++) {
|
||||
if (mask & (1 << i))
|
||||
map[j++] = channel_map_waveex[i];
|
||||
}
|
||||
if (j != channels)
|
||||
map[0] = CHANNEL_POSITION_INVALID;
|
||||
} else {
|
||||
map[0] = CHANNEL_POSITION_INVALID;
|
||||
}
|
||||
}
|
||||
94
channelmap.h
Normal file
94
channelmap.h
Normal file
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright 2011-2013 Various Authors
|
||||
* Copyright 2011 Johannes Weißl
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CMUS_CHANNELMAP_H
|
||||
#define CMUS_CHANNELMAP_H
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#define CHANNELS_MAX 32
|
||||
|
||||
/* Modelled after PulseAudio */
|
||||
enum channel_position {
|
||||
CHANNEL_POSITION_INVALID = -1,
|
||||
CHANNEL_POSITION_MONO = 0,
|
||||
CHANNEL_POSITION_FRONT_LEFT,
|
||||
CHANNEL_POSITION_FRONT_RIGHT,
|
||||
CHANNEL_POSITION_FRONT_CENTER,
|
||||
|
||||
CHANNEL_POSITION_LEFT = CHANNEL_POSITION_FRONT_LEFT,
|
||||
CHANNEL_POSITION_RIGHT = CHANNEL_POSITION_FRONT_RIGHT,
|
||||
CHANNEL_POSITION_CENTER = CHANNEL_POSITION_FRONT_CENTER,
|
||||
|
||||
CHANNEL_POSITION_REAR_CENTER,
|
||||
CHANNEL_POSITION_REAR_LEFT,
|
||||
CHANNEL_POSITION_REAR_RIGHT,
|
||||
|
||||
CHANNEL_POSITION_LFE,
|
||||
CHANNEL_POSITION_SUBWOOFER = CHANNEL_POSITION_LFE,
|
||||
|
||||
CHANNEL_POSITION_FRONT_LEFT_OF_CENTER,
|
||||
CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER,
|
||||
|
||||
CHANNEL_POSITION_SIDE_LEFT,
|
||||
CHANNEL_POSITION_SIDE_RIGHT,
|
||||
|
||||
CHANNEL_POSITION_TOP_CENTER,
|
||||
|
||||
CHANNEL_POSITION_TOP_FRONT_LEFT,
|
||||
CHANNEL_POSITION_TOP_FRONT_RIGHT,
|
||||
CHANNEL_POSITION_TOP_FRONT_CENTER,
|
||||
|
||||
CHANNEL_POSITION_TOP_REAR_LEFT,
|
||||
CHANNEL_POSITION_TOP_REAR_RIGHT,
|
||||
CHANNEL_POSITION_TOP_REAR_CENTER,
|
||||
|
||||
CHANNEL_POSITION_MAX
|
||||
};
|
||||
|
||||
typedef enum channel_position channel_position_t;
|
||||
|
||||
#define CHANNEL_MAP_INIT { CHANNEL_POSITION_INVALID }
|
||||
|
||||
#define CHANNEL_MAP(name) \
|
||||
channel_position_t name[CHANNELS_MAX] = CHANNEL_MAP_INIT
|
||||
|
||||
static inline int channel_map_valid(const channel_position_t *map)
|
||||
{
|
||||
return map[0] != CHANNEL_POSITION_INVALID;
|
||||
}
|
||||
|
||||
static inline int channel_map_equal(const channel_position_t *a, const channel_position_t *b, int channels)
|
||||
{
|
||||
return memcmp(a, b, sizeof(*a) * channels) == 0;
|
||||
}
|
||||
|
||||
static inline channel_position_t *channel_map_copy(channel_position_t *dst, const channel_position_t *src)
|
||||
{
|
||||
return memcpy(dst, src, sizeof(*dst) * CHANNELS_MAX);
|
||||
}
|
||||
|
||||
static inline void channel_map_init_stereo(channel_position_t *map)
|
||||
{
|
||||
map[0] = CHANNEL_POSITION_LEFT;
|
||||
map[1] = CHANNEL_POSITION_RIGHT;
|
||||
}
|
||||
|
||||
void channel_map_init_waveex(int channels, unsigned int mask, channel_position_t *map);
|
||||
|
||||
#endif
|
||||
225
cmdline.c
Normal file
225
cmdline.c
Normal file
@@ -0,0 +1,225 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2005 Timo Hirvonen
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "cmdline.h"
|
||||
#include "uchar.h"
|
||||
#include "xmalloc.h"
|
||||
|
||||
struct cmdline cmdline;
|
||||
|
||||
const char cmdline_word_delimiters[] = " ";
|
||||
const char cmdline_filename_delimiters[] = "/";
|
||||
|
||||
void cmdline_init(void)
|
||||
{
|
||||
cmdline.blen = 0;
|
||||
cmdline.clen = 0;
|
||||
cmdline.bpos = 0;
|
||||
cmdline.cpos = 0;
|
||||
cmdline.size = 128;
|
||||
cmdline.line = xnew(char, cmdline.size);
|
||||
cmdline.line[0] = 0;
|
||||
}
|
||||
|
||||
void cmdline_insert_ch(uchar ch)
|
||||
{
|
||||
int size;
|
||||
|
||||
size = u_char_size(ch);
|
||||
if (cmdline.blen + size > cmdline.size) {
|
||||
cmdline.size *= 2;
|
||||
cmdline.line = xrenew(char, cmdline.line, cmdline.size);
|
||||
}
|
||||
memmove(cmdline.line + cmdline.bpos + size,
|
||||
cmdline.line + cmdline.bpos,
|
||||
cmdline.blen - cmdline.bpos + 1);
|
||||
u_set_char_raw(cmdline.line, &cmdline.bpos, ch);
|
||||
cmdline.cpos++;
|
||||
cmdline.blen += size;
|
||||
cmdline.clen++;
|
||||
}
|
||||
|
||||
void cmdline_backspace(void)
|
||||
{
|
||||
int bpos, size;
|
||||
|
||||
if (cmdline.bpos == 0)
|
||||
return;
|
||||
|
||||
bpos = cmdline.bpos;
|
||||
u_prev_char_pos(cmdline.line, &bpos);
|
||||
size = cmdline.bpos - bpos;
|
||||
|
||||
memmove(cmdline.line + bpos,
|
||||
cmdline.line + cmdline.bpos,
|
||||
cmdline.blen - cmdline.bpos + 1);
|
||||
cmdline.bpos -= size;
|
||||
cmdline.cpos--;
|
||||
cmdline.blen -= size;
|
||||
cmdline.clen--;
|
||||
}
|
||||
|
||||
void cmdline_backspace_to_bol(void)
|
||||
{
|
||||
while (cmdline.bpos)
|
||||
cmdline_backspace();
|
||||
}
|
||||
|
||||
void cmdline_delete_ch(void)
|
||||
{
|
||||
uchar ch;
|
||||
int size, bpos;
|
||||
|
||||
if (cmdline.bpos == cmdline.blen)
|
||||
return;
|
||||
bpos = cmdline.bpos;
|
||||
ch = u_get_char(cmdline.line, &bpos);
|
||||
size = u_char_size(ch);
|
||||
cmdline.blen -= size;
|
||||
cmdline.clen--;
|
||||
memmove(cmdline.line + cmdline.bpos,
|
||||
cmdline.line + cmdline.bpos + size,
|
||||
cmdline.blen - cmdline.bpos + 1);
|
||||
}
|
||||
|
||||
void cmdline_set_text(const char *text)
|
||||
{
|
||||
int len = strlen(text);
|
||||
|
||||
if (len >= cmdline.size) {
|
||||
while (len >= cmdline.size)
|
||||
cmdline.size *= 2;
|
||||
cmdline.line = xrenew(char, cmdline.line, cmdline.size);
|
||||
}
|
||||
memcpy(cmdline.line, text, len + 1);
|
||||
cmdline.cpos = u_strlen_safe(cmdline.line);
|
||||
cmdline.bpos = len;
|
||||
cmdline.clen = cmdline.cpos;
|
||||
cmdline.blen = len;
|
||||
}
|
||||
|
||||
void cmdline_clear(void)
|
||||
{
|
||||
cmdline.blen = 0;
|
||||
cmdline.clen = 0;
|
||||
cmdline.bpos = 0;
|
||||
cmdline.cpos = 0;
|
||||
cmdline.line[0] = 0;
|
||||
}
|
||||
|
||||
void cmdline_clear_end(void)
|
||||
{
|
||||
cmdline.line[cmdline.bpos] = 0;
|
||||
|
||||
cmdline.clen = u_strlen_safe(cmdline.line);
|
||||
cmdline.blen = strlen(cmdline.line);
|
||||
}
|
||||
|
||||
void cmdline_move_left(void)
|
||||
{
|
||||
if (cmdline.bpos > 0) {
|
||||
cmdline.cpos--;
|
||||
u_prev_char_pos(cmdline.line, &cmdline.bpos);
|
||||
}
|
||||
}
|
||||
|
||||
void cmdline_move_right(void)
|
||||
{
|
||||
if (cmdline.bpos < cmdline.blen) {
|
||||
u_get_char(cmdline.line, &cmdline.bpos);
|
||||
cmdline.cpos++;
|
||||
}
|
||||
}
|
||||
|
||||
void cmdline_move_home(void)
|
||||
{
|
||||
cmdline.cpos = 0;
|
||||
cmdline.bpos = 0;
|
||||
}
|
||||
|
||||
void cmdline_move_end(void)
|
||||
{
|
||||
cmdline.cpos = cmdline.clen;
|
||||
cmdline.bpos = cmdline.blen;
|
||||
}
|
||||
|
||||
static int next_word(const char *str, int bpos, int *cdiff, const char *delim, int direction)
|
||||
{
|
||||
int skip_delim = 1;
|
||||
while ((direction > 0) ? str[bpos] : (bpos > 0)) {
|
||||
uchar ch;
|
||||
int oldp = bpos;
|
||||
|
||||
if (direction > 0) {
|
||||
ch = u_get_char(str, &bpos);
|
||||
} else {
|
||||
u_prev_char_pos(str, &bpos);
|
||||
oldp = bpos;
|
||||
ch = u_get_char(str, &oldp);
|
||||
}
|
||||
|
||||
if (u_strchr(delim, ch)) {
|
||||
if (!skip_delim) {
|
||||
bpos -= bpos - oldp;
|
||||
break;
|
||||
}
|
||||
} else
|
||||
skip_delim = 0;
|
||||
|
||||
*cdiff += direction;
|
||||
}
|
||||
return bpos;
|
||||
}
|
||||
|
||||
void cmdline_forward_word(const char *delim)
|
||||
{
|
||||
cmdline.bpos = next_word(cmdline.line, cmdline.bpos, &cmdline.cpos, delim, +1);
|
||||
}
|
||||
|
||||
void cmdline_backward_word(const char *delim)
|
||||
{
|
||||
cmdline.bpos = next_word(cmdline.line, cmdline.bpos, &cmdline.cpos, delim, -1);
|
||||
}
|
||||
|
||||
void cmdline_delete_word(const char *delim)
|
||||
{
|
||||
int bpos, cdiff = 0;
|
||||
|
||||
bpos = next_word(cmdline.line, cmdline.bpos, &cdiff, delim, +1);
|
||||
|
||||
memmove(cmdline.line + cmdline.bpos,
|
||||
cmdline.line + bpos,
|
||||
cmdline.blen - bpos + 1);
|
||||
cmdline.blen -= bpos - cmdline.bpos;
|
||||
cmdline.clen -= cdiff;
|
||||
}
|
||||
|
||||
void cmdline_backward_delete_word(const char *delim)
|
||||
{
|
||||
int bpos, cdiff = 0;
|
||||
|
||||
bpos = next_word(cmdline.line, cmdline.bpos, &cdiff, delim, -1);
|
||||
|
||||
cmdline.blen += bpos - cmdline.bpos;
|
||||
memmove(cmdline.line + bpos,
|
||||
cmdline.line + cmdline.bpos,
|
||||
cmdline.blen - bpos + 1);
|
||||
cmdline.bpos = bpos;
|
||||
cmdline.clen += cdiff;
|
||||
cmdline.cpos += cdiff;
|
||||
}
|
||||
66
cmdline.h
Normal file
66
cmdline.h
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2005 Timo Hirvonen
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CMDLINE_H
|
||||
#define CMDLINE_H
|
||||
|
||||
#include "uchar.h"
|
||||
|
||||
struct cmdline {
|
||||
/* length in bytes */
|
||||
int blen;
|
||||
|
||||
/* length in characters */
|
||||
int clen;
|
||||
|
||||
/* pos in bytes */
|
||||
int bpos;
|
||||
|
||||
/* pos in characters */
|
||||
int cpos;
|
||||
|
||||
/* allocated size */
|
||||
int size;
|
||||
|
||||
char *line;
|
||||
};
|
||||
|
||||
extern struct cmdline cmdline;
|
||||
|
||||
extern const char cmdline_word_delimiters[];
|
||||
extern const char cmdline_filename_delimiters[];
|
||||
|
||||
void cmdline_init(void);
|
||||
void cmdline_insert_ch(uchar ch);
|
||||
void cmdline_backspace(void);
|
||||
void cmdline_backspace_to_bol(void);
|
||||
void cmdline_delete_ch(void);
|
||||
void cmdline_set_text(const char *text);
|
||||
void cmdline_clear(void);
|
||||
void cmdline_clear_end(void);
|
||||
void cmdline_move_left(void);
|
||||
void cmdline_move_right(void);
|
||||
void cmdline_move_home(void);
|
||||
void cmdline_move_end(void);
|
||||
|
||||
void cmdline_forward_word(const char *delim);
|
||||
void cmdline_backward_word(const char *delim);
|
||||
void cmdline_delete_word(const char *delim);
|
||||
void cmdline_backward_delete_word(const char *delim);
|
||||
|
||||
#endif
|
||||
50
cmus-status-display
Executable file
50
cmus-status-display
Executable file
@@ -0,0 +1,50 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# cmus-status-display
|
||||
#
|
||||
# Usage:
|
||||
# in cmus command ":set status_display_program=cmus-status-display"
|
||||
#
|
||||
# This scripts is executed by cmus when status changes:
|
||||
# cmus-status-display key1 val1 key2 val2 ...
|
||||
#
|
||||
# All keys contain only chars a-z. Values are UTF-8 strings.
|
||||
#
|
||||
# Keys: status file url artist album discnumber tracknumber title date
|
||||
# - status (stopped, playing, paused) is always given
|
||||
# - file or url is given only if track is 'loaded' in cmus
|
||||
# - other keys/values are given only if they are available
|
||||
#
|
||||
|
||||
output()
|
||||
{
|
||||
# write status to ~/cmus-status.txt (not very useful though)
|
||||
echo "$*" >> ~/cmus-status.txt 2>&1
|
||||
|
||||
# WMI (http://wmi.modprobe.de/)
|
||||
#wmiremote -t "$*" &> /dev/null
|
||||
}
|
||||
|
||||
while test $# -ge 2
|
||||
do
|
||||
eval _$1='$2'
|
||||
shift
|
||||
shift
|
||||
done
|
||||
|
||||
if test -n "$_file"
|
||||
then
|
||||
h=$(($_duration / 3600))
|
||||
m=$(($_duration % 3600))
|
||||
|
||||
duration=""
|
||||
test $h -gt 0 && dur="$h:"
|
||||
duration="$dur$(printf '%02d:%02d' $(($m / 60)) $(($m % 60)))"
|
||||
|
||||
output "[$_status] $_artist - $_album - $_title ($_date) $duration"
|
||||
elif test -n "$_url"
|
||||
then
|
||||
output "[$_status] $_url - $_title"
|
||||
else
|
||||
output "[$_status]"
|
||||
fi
|
||||
549
cmus.c
Normal file
549
cmus.c
Normal file
@@ -0,0 +1,549 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2004 Timo Hirvonen
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "cmus.h"
|
||||
#include "job.h"
|
||||
#include "lib.h"
|
||||
#include "pl.h"
|
||||
#include "player.h"
|
||||
#include "input.h"
|
||||
#include "play_queue.h"
|
||||
#include "cache.h"
|
||||
#include "misc.h"
|
||||
#include "file.h"
|
||||
#include "utils.h"
|
||||
#include "path.h"
|
||||
#include "options.h"
|
||||
#include "command_mode.h"
|
||||
#include "xmalloc.h"
|
||||
#include "debug.h"
|
||||
#include "load_dir.h"
|
||||
#include "ui_curses.h"
|
||||
#include "cache.h"
|
||||
#include "gbuf.h"
|
||||
#include "discid.h"
|
||||
#include "locking.h"
|
||||
#include "pl_env.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <dirent.h>
|
||||
#include <dlfcn.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <strings.h>
|
||||
|
||||
/* save_playlist_cb, save_ext_playlist_cb */
|
||||
typedef int (*save_tracks_cb)(void *data, struct track_info *ti);
|
||||
|
||||
static char **playable_exts;
|
||||
static const char * const playlist_exts[] = { "m3u", "pl", "pls", NULL };
|
||||
|
||||
int cmus_next_track_request_fd;
|
||||
static bool play_queue_active = false;
|
||||
static int cmus_next_track_request_fd_priv;
|
||||
static pthread_mutex_t cmus_next_file_mutex = CMUS_MUTEX_INITIALIZER;
|
||||
static pthread_cond_t cmus_next_file_cond = CMUS_COND_INITIALIZER;
|
||||
static int cmus_next_file_provided;
|
||||
static struct track_info *cmus_next_file;
|
||||
|
||||
static int x11_init_done = 0;
|
||||
static void *(*x11_open)(void *) = NULL;
|
||||
static int (*x11_raise)(void *, int) = NULL;
|
||||
static int (*x11_close)(void *) = NULL;
|
||||
|
||||
int cmus_init(void)
|
||||
{
|
||||
playable_exts = ip_get_supported_extensions();
|
||||
cache_init();
|
||||
job_init();
|
||||
play_queue_init();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void cmus_exit(void)
|
||||
{
|
||||
job_exit();
|
||||
if (cache_close())
|
||||
d_print("error: %s\n", strerror(errno));
|
||||
}
|
||||
|
||||
void cmus_next(void)
|
||||
{
|
||||
struct track_info *info = cmus_get_next_track();
|
||||
if (info)
|
||||
player_set_file(info);
|
||||
}
|
||||
|
||||
void cmus_prev(void)
|
||||
{
|
||||
struct track_info *info;
|
||||
|
||||
if (play_library) {
|
||||
info = lib_goto_prev();
|
||||
} else {
|
||||
info = pl_goto_prev();
|
||||
}
|
||||
|
||||
if (info)
|
||||
player_set_file(info);
|
||||
}
|
||||
|
||||
void cmus_next_album(void)
|
||||
{
|
||||
struct track_info *info;
|
||||
|
||||
if (play_library) {
|
||||
info = lib_goto_next_album();
|
||||
} else {
|
||||
info = pl_goto_next();
|
||||
}
|
||||
|
||||
if (info)
|
||||
player_set_file(info);
|
||||
}
|
||||
|
||||
void cmus_prev_album(void)
|
||||
{
|
||||
struct track_info *info;
|
||||
|
||||
if (play_library) {
|
||||
info = lib_goto_prev_album();
|
||||
} else {
|
||||
info = pl_goto_prev();
|
||||
}
|
||||
|
||||
if (info)
|
||||
player_set_file(info);
|
||||
}
|
||||
|
||||
void cmus_play_file(const char *filename)
|
||||
{
|
||||
struct track_info *ti;
|
||||
|
||||
cache_lock();
|
||||
ti = cache_get_ti(filename, 0);
|
||||
cache_unlock();
|
||||
if (!ti) {
|
||||
error_msg("Couldn't get file information for %s\n", filename);
|
||||
return;
|
||||
}
|
||||
|
||||
player_play_file(ti);
|
||||
}
|
||||
|
||||
enum file_type cmus_detect_ft(const char *name, char **ret)
|
||||
{
|
||||
char *absolute;
|
||||
struct stat st;
|
||||
|
||||
if (is_http_url(name) || is_cue_url(name)) {
|
||||
*ret = xstrdup(name);
|
||||
return FILE_TYPE_URL;
|
||||
}
|
||||
|
||||
if (is_cdda_url(name)) {
|
||||
*ret = complete_cdda_url(cdda_device, name);
|
||||
return FILE_TYPE_CDDA;
|
||||
}
|
||||
|
||||
*ret = NULL;
|
||||
absolute = path_absolute(name);
|
||||
if (absolute == NULL)
|
||||
return FILE_TYPE_INVALID;
|
||||
|
||||
/* stat follows symlinks, lstat does not */
|
||||
if (stat(absolute, &st) == -1) {
|
||||
free(absolute);
|
||||
return FILE_TYPE_INVALID;
|
||||
}
|
||||
|
||||
if (S_ISDIR(st.st_mode)) {
|
||||
*ret = absolute;
|
||||
return FILE_TYPE_DIR;
|
||||
}
|
||||
if (!S_ISREG(st.st_mode)) {
|
||||
free(absolute);
|
||||
errno = EINVAL;
|
||||
return FILE_TYPE_INVALID;
|
||||
}
|
||||
|
||||
*ret = absolute;
|
||||
if (cmus_is_playlist(absolute))
|
||||
return FILE_TYPE_PL;
|
||||
|
||||
/* NOTE: it could be FILE_TYPE_PL too! */
|
||||
return FILE_TYPE_FILE;
|
||||
}
|
||||
|
||||
void cmus_add(add_ti_cb add, const char *name, enum file_type ft, int jt, int force,
|
||||
void *opaque)
|
||||
{
|
||||
struct add_data *data = xnew(struct add_data, 1);
|
||||
|
||||
data->add = add;
|
||||
data->name = xstrdup(name);
|
||||
data->type = ft;
|
||||
data->force = force;
|
||||
data->opaque = opaque;
|
||||
|
||||
job_schedule_add(jt, data);
|
||||
}
|
||||
|
||||
static int save_ext_playlist_cb(void *data, struct track_info *ti)
|
||||
{
|
||||
GBUF(buf);
|
||||
int fd = *(int *)data;
|
||||
int i, rc;
|
||||
|
||||
gbuf_addf(&buf, "file %s\n", escape(ti->filename));
|
||||
gbuf_addf(&buf, "duration %d\n", ti->duration);
|
||||
gbuf_addf(&buf, "codec %s\n", ti->codec);
|
||||
gbuf_addf(&buf, "bitrate %ld\n", ti->bitrate);
|
||||
for (i = 0; ti->comments[i].key; i++)
|
||||
gbuf_addf(&buf, "tag %s %s\n",
|
||||
ti->comments[i].key,
|
||||
escape(ti->comments[i].val));
|
||||
|
||||
rc = write_all(fd, buf.buffer, buf.len);
|
||||
gbuf_free(&buf);
|
||||
|
||||
if (rc == -1)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int save_playlist_cb(void *data, struct track_info *ti)
|
||||
{
|
||||
char *proc_filename = pl_env_reduce(ti->filename);
|
||||
int fd = *(int *)data;
|
||||
const char nl = '\n';
|
||||
int rc;
|
||||
|
||||
rc = write_all(fd, proc_filename, strlen(proc_filename));
|
||||
free(proc_filename);
|
||||
if (rc == -1)
|
||||
return -1;
|
||||
|
||||
rc = write_all(fd, &nl, 1);
|
||||
if (rc == -1)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int do_cmus_save(for_each_ti_cb for_each_ti, const char *filename,
|
||||
save_tracks_cb save_tracks, void *opaque)
|
||||
{
|
||||
int fd, rc;
|
||||
|
||||
if (strcmp(filename, "-") == 0) {
|
||||
if (get_client_fd() == -1) {
|
||||
error_msg("saving to stdout works only remotely");
|
||||
return 0;
|
||||
}
|
||||
fd = dup(get_client_fd());
|
||||
} else
|
||||
fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0666);
|
||||
if (fd == -1)
|
||||
return -1;
|
||||
rc = for_each_ti(save_tracks, &fd, opaque);
|
||||
close(fd);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int cmus_save(for_each_ti_cb for_each_ti, const char *filename, void *opaque)
|
||||
{
|
||||
return do_cmus_save(for_each_ti, filename, save_playlist_cb, opaque);
|
||||
}
|
||||
|
||||
int cmus_save_ext(for_each_ti_cb for_each_ti, const char *filename,
|
||||
void *opaque)
|
||||
{
|
||||
return do_cmus_save(for_each_ti, filename, save_ext_playlist_cb,
|
||||
opaque);
|
||||
}
|
||||
|
||||
static int update_cb(void *data, struct track_info *ti)
|
||||
{
|
||||
struct update_data *d = data;
|
||||
|
||||
if (d->size == d->used) {
|
||||
if (d->size == 0)
|
||||
d->size = 16;
|
||||
d->size *= 2;
|
||||
d->ti = xrenew(struct track_info *, d->ti, d->size);
|
||||
}
|
||||
track_info_ref(ti);
|
||||
d->ti[d->used++] = ti;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void cmus_update_cache(int force)
|
||||
{
|
||||
struct update_cache_data *data;
|
||||
|
||||
data = xnew(struct update_cache_data, 1);
|
||||
data->force = force;
|
||||
|
||||
job_schedule_update_cache(JOB_TYPE_LIB, data);
|
||||
}
|
||||
|
||||
void cmus_update_lib(void)
|
||||
{
|
||||
struct update_data *data;
|
||||
|
||||
data = xnew0(struct update_data, 1);
|
||||
|
||||
lib_for_each(update_cb, data, NULL);
|
||||
|
||||
job_schedule_update(data);
|
||||
}
|
||||
|
||||
void cmus_update_tis(struct track_info **tis, int nr, int force)
|
||||
{
|
||||
struct update_data *data;
|
||||
|
||||
data = xnew(struct update_data, 1);
|
||||
data->size = nr;
|
||||
data->used = nr;
|
||||
data->ti = tis;
|
||||
data->force = force;
|
||||
|
||||
job_schedule_update(data);
|
||||
}
|
||||
|
||||
static const char *get_ext(const char *filename)
|
||||
{
|
||||
const char *ext = strrchr(filename, '.');
|
||||
|
||||
if (ext)
|
||||
ext++;
|
||||
return ext;
|
||||
}
|
||||
|
||||
static int str_in_array(const char *str, const char * const *array)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; array[i]; i++) {
|
||||
if (strcasecmp(str, array[i]) == 0)
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cmus_is_playlist(const char *filename)
|
||||
{
|
||||
const char *ext = get_ext(filename);
|
||||
|
||||
return ext && str_in_array(ext, playlist_exts);
|
||||
}
|
||||
|
||||
int cmus_is_playable(const char *filename)
|
||||
{
|
||||
const char *ext = get_ext(filename);
|
||||
|
||||
return ext && str_in_array(ext, (const char * const *)playable_exts);
|
||||
}
|
||||
|
||||
int cmus_is_supported(const char *filename)
|
||||
{
|
||||
const char *ext = get_ext(filename);
|
||||
|
||||
return ext && (str_in_array(ext, (const char * const *)playable_exts) ||
|
||||
str_in_array(ext, playlist_exts));
|
||||
}
|
||||
|
||||
struct pl_data {
|
||||
int (*cb)(void *data, const char *line);
|
||||
void *data;
|
||||
};
|
||||
|
||||
static int pl_handle_line(void *data, const char *line)
|
||||
{
|
||||
struct pl_data *d = data;
|
||||
int i = 0;
|
||||
|
||||
while (isspace((unsigned char)line[i]))
|
||||
i++;
|
||||
if (line[i] == 0)
|
||||
return 0;
|
||||
|
||||
if (line[i] == '#')
|
||||
return 0;
|
||||
|
||||
return d->cb(d->data, line);
|
||||
}
|
||||
|
||||
static int pls_handle_line(void *data, const char *line)
|
||||
{
|
||||
struct pl_data *d = data;
|
||||
|
||||
if (strncasecmp(line, "file", 4))
|
||||
return 0;
|
||||
line = strchr(line, '=');
|
||||
if (line == NULL)
|
||||
return 0;
|
||||
return d->cb(d->data, line + 1);
|
||||
}
|
||||
|
||||
int cmus_playlist_for_each(const char *buf, int size, int reverse,
|
||||
int (*cb)(void *data, const char *line),
|
||||
void *data)
|
||||
{
|
||||
struct pl_data d = { cb, data };
|
||||
int (*handler)(void *, const char *);
|
||||
|
||||
handler = pl_handle_line;
|
||||
if (size >= 10 && strncasecmp(buf, "[playlist]", 10) == 0)
|
||||
handler = pls_handle_line;
|
||||
|
||||
if (reverse) {
|
||||
buffer_for_each_line_reverse(buf, size, handler, &d);
|
||||
} else {
|
||||
buffer_for_each_line(buf, size, handler, &d);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* multi-threaded next track requests */
|
||||
|
||||
#define cmus_next_file_lock() cmus_mutex_lock(&cmus_next_file_mutex)
|
||||
#define cmus_next_file_unlock() cmus_mutex_unlock(&cmus_next_file_mutex)
|
||||
|
||||
static struct track_info *cmus_get_next_from_main_thread(void)
|
||||
{
|
||||
struct track_info *ti = play_queue_remove();
|
||||
if (ti) {
|
||||
play_queue_active = true;
|
||||
} else {
|
||||
if (!play_queue_active || !stop_after_queue)
|
||||
ti = play_library ? lib_goto_next() : pl_goto_next();
|
||||
play_queue_active = false;
|
||||
}
|
||||
return ti;
|
||||
}
|
||||
|
||||
static struct track_info *cmus_get_next_from_other_thread(void)
|
||||
{
|
||||
static pthread_mutex_t mutex = CMUS_MUTEX_INITIALIZER;
|
||||
cmus_mutex_lock(&mutex);
|
||||
|
||||
/* only one thread may request a track at a time */
|
||||
|
||||
notify_via_pipe(cmus_next_track_request_fd_priv);
|
||||
|
||||
cmus_next_file_lock();
|
||||
while (!cmus_next_file_provided)
|
||||
pthread_cond_wait(&cmus_next_file_cond, &cmus_next_file_mutex);
|
||||
struct track_info *ti = cmus_next_file;
|
||||
cmus_next_file_provided = 0;
|
||||
cmus_next_file_unlock();
|
||||
|
||||
cmus_mutex_unlock(&mutex);
|
||||
|
||||
return ti;
|
||||
}
|
||||
|
||||
struct track_info *cmus_get_next_track(void)
|
||||
{
|
||||
pthread_t this_thread = pthread_self();
|
||||
if (pthread_equal(this_thread, main_thread))
|
||||
return cmus_get_next_from_main_thread();
|
||||
return cmus_get_next_from_other_thread();
|
||||
}
|
||||
|
||||
void cmus_provide_next_track(void)
|
||||
{
|
||||
clear_pipe(cmus_next_track_request_fd, 1);
|
||||
|
||||
cmus_next_file_lock();
|
||||
cmus_next_file = cmus_get_next_from_main_thread();
|
||||
cmus_next_file_provided = 1;
|
||||
cmus_next_file_unlock();
|
||||
|
||||
pthread_cond_broadcast(&cmus_next_file_cond);
|
||||
}
|
||||
|
||||
void cmus_track_request_init(void)
|
||||
{
|
||||
init_pipes(&cmus_next_track_request_fd, &cmus_next_track_request_fd_priv);
|
||||
}
|
||||
|
||||
static int cmus_can_raise_vte_x11(void)
|
||||
{
|
||||
return getenv("DISPLAY") && getenv("WINDOWID");
|
||||
}
|
||||
|
||||
int cmus_can_raise_vte(void)
|
||||
{
|
||||
return cmus_can_raise_vte_x11();
|
||||
}
|
||||
|
||||
static int cmus_raise_vte_x11_error(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
void cmus_raise_vte(void)
|
||||
{
|
||||
if (cmus_can_raise_vte_x11()) {
|
||||
if (!x11_init_done) {
|
||||
void *x11;
|
||||
|
||||
x11_init_done = 1;
|
||||
x11 = dlopen("libX11.so", RTLD_LAZY);
|
||||
|
||||
if (x11) {
|
||||
int (*x11_error)(void *);
|
||||
|
||||
x11_error = dlsym(x11, "XSetErrorHandler");
|
||||
x11_open = dlsym(x11, "XOpenDisplay");
|
||||
x11_raise = dlsym(x11, "XRaiseWindow");
|
||||
x11_close = dlsym(x11, "XCloseDisplay");
|
||||
|
||||
if (x11_error) {
|
||||
x11_error(cmus_raise_vte_x11_error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (x11_open && x11_raise && x11_close) {
|
||||
char *xid_str;
|
||||
long int xid = 0;
|
||||
|
||||
xid_str = getenv("WINDOWID");
|
||||
if (!str_to_int(xid_str, &xid) && xid != 0) {
|
||||
void *display;
|
||||
|
||||
display = x11_open(NULL);
|
||||
if (display) {
|
||||
x11_raise(display, (int) xid);
|
||||
x11_close(display);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool cmus_queue_active(void) {
|
||||
return play_queue_active;
|
||||
}
|
||||
100
cmus.h
Normal file
100
cmus.h
Normal file
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2004 Timo Hirvonen
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CMUS_API_H
|
||||
#define CMUS_API_H
|
||||
|
||||
#include "track_info.h"
|
||||
|
||||
enum file_type {
|
||||
/* not found, device file... */
|
||||
FILE_TYPE_INVALID,
|
||||
|
||||
FILE_TYPE_URL,
|
||||
FILE_TYPE_PL,
|
||||
FILE_TYPE_DIR,
|
||||
FILE_TYPE_FILE,
|
||||
FILE_TYPE_CDDA
|
||||
};
|
||||
|
||||
typedef int (*track_info_cb)(void *data, struct track_info *ti);
|
||||
|
||||
/* lib_for_each, lib_for_each_filtered, pl_for_each, play_queue_for_each */
|
||||
typedef int (*for_each_ti_cb)(track_info_cb cb, void *data, void *opaque);
|
||||
|
||||
/* lib_for_each_sel, pl_for_each_sel, play_queue_for_each_sel */
|
||||
typedef int (*for_each_sel_ti_cb)(track_info_cb cb, void *data, int reverse, int advance);
|
||||
|
||||
/* lib_add_track, pl_add_track, play_queue_append, play_queue_prepend */
|
||||
typedef void (*add_ti_cb)(struct track_info *, void *opaque);
|
||||
|
||||
/* cmus_save, cmus_save_ext */
|
||||
typedef int (*save_ti_cb)(for_each_ti_cb for_each_ti, const char *filename,
|
||||
void *opaque);
|
||||
|
||||
int cmus_init(void);
|
||||
void cmus_exit(void);
|
||||
void cmus_play_file(const char *filename);
|
||||
|
||||
/* detect file type, returns absolute path or url in @ret */
|
||||
enum file_type cmus_detect_ft(const char *name, char **ret);
|
||||
|
||||
/* add to library, playlist or queue view
|
||||
*
|
||||
* @add callback that does the actual adding
|
||||
* @name playlist, directory, file, URL
|
||||
* @ft detected FILE_TYPE_*
|
||||
* @jt JOB_TYPE_{LIB,PL,QUEUE}
|
||||
*
|
||||
* returns immediately, actual work is done in the worker thread.
|
||||
*/
|
||||
void cmus_add(add_ti_cb, const char *name, enum file_type ft, int jt,
|
||||
int force, void *opaque);
|
||||
|
||||
int cmus_save(for_each_ti_cb for_each_ti, const char *filename, void *opaque);
|
||||
int cmus_save_ext(for_each_ti_cb for_each_ti, const char *filename,
|
||||
void *opaque);
|
||||
|
||||
void cmus_update_cache(int force);
|
||||
void cmus_update_lib(void);
|
||||
void cmus_update_tis(struct track_info **tis, int nr, int force);
|
||||
|
||||
int cmus_is_playlist(const char *filename);
|
||||
int cmus_is_playable(const char *filename);
|
||||
int cmus_is_supported(const char *filename);
|
||||
|
||||
int cmus_playlist_for_each(const char *buf, int size, int reverse,
|
||||
int (*cb)(void *data, const char *line),
|
||||
void *data);
|
||||
|
||||
void cmus_next(void);
|
||||
void cmus_prev(void);
|
||||
void cmus_next_album(void);
|
||||
void cmus_prev_album(void);
|
||||
|
||||
extern int cmus_next_track_request_fd;
|
||||
struct track_info *cmus_get_next_track(void);
|
||||
void cmus_provide_next_track(void);
|
||||
void cmus_track_request_init(void);
|
||||
|
||||
int cmus_can_raise_vte(void);
|
||||
void cmus_raise_vte(void);
|
||||
|
||||
bool cmus_queue_active(void);
|
||||
|
||||
#endif
|
||||
3192
command_mode.c
Normal file
3192
command_mode.c
Normal file
File diff suppressed because it is too large
Load Diff
79
command_mode.h
Normal file
79
command_mode.h
Normal file
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2004-2006 Timo Hirvonen
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CMUS_COMMAND_MODE_H
|
||||
#define CMUS_COMMAND_MODE_H
|
||||
|
||||
#include "uchar.h"
|
||||
|
||||
#if defined(__sun__)
|
||||
#include <ncurses.h>
|
||||
#else
|
||||
#include <curses.h>
|
||||
#endif
|
||||
|
||||
enum {
|
||||
/* executing command is disabled over net */
|
||||
CMD_UNSAFE = 1 << 0,
|
||||
/* execute command after every typed/deleted character */
|
||||
CMD_LIVE = 1 << 1,
|
||||
/* hide command from completion, useful for deprecated commands */
|
||||
CMD_HIDDEN = 1 << 2,
|
||||
};
|
||||
|
||||
struct command {
|
||||
const char *name;
|
||||
void (*func)(char *arg);
|
||||
|
||||
/* min/max number of arguments */
|
||||
int min_args;
|
||||
int max_args;
|
||||
|
||||
void (*expand)(const char *str);
|
||||
|
||||
/* bind count (0 means: unbound) */
|
||||
int bc;
|
||||
|
||||
/* CMD_* */
|
||||
unsigned int flags;
|
||||
};
|
||||
|
||||
extern struct command commands[];
|
||||
extern int run_only_safe_commands;
|
||||
|
||||
void command_mode_ch(uchar ch);
|
||||
void command_mode_escape(int c);
|
||||
void command_mode_key(int key);
|
||||
void command_mode_mouse(MEVENT *event);
|
||||
void commands_init(void);
|
||||
void commands_exit(void);
|
||||
int parse_command(const char *buf, char **cmdp, char **argp);
|
||||
char **parse_cmd(const char *cmd, int *args_idx, int *ac);
|
||||
void run_parsed_command(char *cmd, char *arg);
|
||||
void run_command(const char *buf);
|
||||
|
||||
struct command *get_command(const char *str);
|
||||
|
||||
void view_clear(int view);
|
||||
void view_add(int view, char *arg, int prepend);
|
||||
void view_load(int view, char *arg);
|
||||
void view_save(int view, char *arg, int to_stdout, int filtered, int extended);
|
||||
|
||||
struct window *current_win(void);
|
||||
|
||||
#endif
|
||||
296
comment.c
Normal file
296
comment.c
Normal file
@@ -0,0 +1,296 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2004-2007 Timo Hirvonen
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "comment.h"
|
||||
#include "xmalloc.h"
|
||||
#include "utils.h"
|
||||
#include "uchar.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
|
||||
static int is_various_artists(const char *a)
|
||||
{
|
||||
return strcasecmp(a, "Various Artists") == 0 ||
|
||||
strcasecmp(a, "Various") == 0 ||
|
||||
strcasecmp(a, "VA") == 0 ||
|
||||
strcasecmp(a, "V/A") == 0;
|
||||
}
|
||||
|
||||
int track_is_compilation(const struct keyval *comments)
|
||||
{
|
||||
const char *c, *a, *aa;
|
||||
|
||||
c = keyvals_get_val(comments, "compilation");
|
||||
if (c && is_freeform_true(c))
|
||||
return 1;
|
||||
|
||||
c = keyvals_get_val(comments, "partofacompilation");
|
||||
if (c && is_freeform_true(c))
|
||||
return 1;
|
||||
|
||||
aa = keyvals_get_val(comments, "albumartist");
|
||||
if (aa && is_various_artists(aa))
|
||||
return 1;
|
||||
|
||||
a = keyvals_get_val(comments, "artist");
|
||||
if (a && is_various_artists(a))
|
||||
return 1;
|
||||
|
||||
if (aa && a && !u_strcase_equal(aa, a))
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int track_is_va_compilation(const struct keyval *comments)
|
||||
{
|
||||
const char *c, *a, *aa;
|
||||
|
||||
aa = keyvals_get_val(comments, "albumartist");
|
||||
if (aa)
|
||||
return is_various_artists(aa);
|
||||
|
||||
a = keyvals_get_val(comments, "artist");
|
||||
if (a && is_various_artists(a))
|
||||
return 1;
|
||||
|
||||
c = keyvals_get_val(comments, "compilation");
|
||||
if (c && is_freeform_true(c))
|
||||
return 1;
|
||||
|
||||
c = keyvals_get_val(comments, "partofacompilation");
|
||||
if (c && is_freeform_true(c))
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *comments_get_albumartist(const struct keyval *comments)
|
||||
{
|
||||
const char *val = keyvals_get_val(comments, "albumartist");
|
||||
|
||||
if (!val || strcmp(val, "") == 0)
|
||||
val = keyvals_get_val(comments, "artist");
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
const char *comments_get_artistsort(const struct keyval *comments)
|
||||
{
|
||||
const char *val;
|
||||
|
||||
if (track_is_va_compilation(comments))
|
||||
return NULL;
|
||||
|
||||
val = keyvals_get_val(comments, "albumartistsort");
|
||||
if (!track_is_compilation(comments)) {
|
||||
if (!val || strcmp(val, "") == 0)
|
||||
val = keyvals_get_val(comments, "artistsort");
|
||||
}
|
||||
|
||||
if (!val || strcmp(val, "") == 0)
|
||||
return NULL;
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
int comments_get_int(const struct keyval *comments, const char *key)
|
||||
{
|
||||
const char *val;
|
||||
long int ival;
|
||||
|
||||
val = keyvals_get_val(comments, key);
|
||||
if (val == NULL)
|
||||
return -1;
|
||||
while (*val && !(*val >= '0' && *val <= '9'))
|
||||
val++;
|
||||
if (str_to_int(val, &ival) == -1)
|
||||
return -1;
|
||||
return ival;
|
||||
}
|
||||
|
||||
int comments_get_signed_int(const struct keyval *comments, const char *key, long int *ival)
|
||||
{
|
||||
const char *val;
|
||||
|
||||
val = keyvals_get_val(comments, key);
|
||||
if (val == NULL)
|
||||
return -1;
|
||||
while (*val && !(*val == '+' || *val == '-' || (*val >= '0' && *val <= '9')))
|
||||
val++;
|
||||
return str_to_int(val, ival);
|
||||
}
|
||||
|
||||
double comments_get_double(const struct keyval *comments, const char *key)
|
||||
{
|
||||
const char *val;
|
||||
char *end;
|
||||
double d;
|
||||
|
||||
val = keyvals_get_val(comments, key);
|
||||
if (!val || strcmp(val, "") == 0)
|
||||
goto error;
|
||||
|
||||
d = strtod(val, &end);
|
||||
if (val == end)
|
||||
goto error;
|
||||
|
||||
return d;
|
||||
|
||||
error:
|
||||
return strtod("NAN", NULL);
|
||||
}
|
||||
|
||||
/* Return date as an integer in the form YYYYMMDD, for sorting purposes.
|
||||
* This function is not year 10000 compliant. */
|
||||
int comments_get_date(const struct keyval *comments, const char *key)
|
||||
{
|
||||
const char *val;
|
||||
char *endptr;
|
||||
int year, month, day;
|
||||
long int ival;
|
||||
|
||||
val = keyvals_get_val(comments, key);
|
||||
if (val == NULL)
|
||||
return -1;
|
||||
|
||||
year = strtol(val, &endptr, 10);
|
||||
/* Looking for a four-digit number */
|
||||
if (year < 1000 || year > 9999)
|
||||
return -1;
|
||||
ival = year * 10000;
|
||||
|
||||
if (*endptr == '-' || *endptr == ' ' || *endptr == '/') {
|
||||
month = strtol(endptr+1, &endptr, 10);
|
||||
if (month < 1 || month > 12)
|
||||
return ival;
|
||||
ival += month * 100;
|
||||
}
|
||||
|
||||
if (*endptr == '-' || *endptr == ' ' || *endptr == '/') {
|
||||
day = strtol(endptr+1, &endptr, 10);
|
||||
if (day < 1 || day > 31)
|
||||
return ival;
|
||||
ival += day;
|
||||
}
|
||||
|
||||
|
||||
return ival;
|
||||
}
|
||||
|
||||
static const char *interesting[] = {
|
||||
"artist", "album", "title", "tracknumber", "discnumber", "totaldiscs", "genre",
|
||||
"date", "compilation", "partofacompilation", "albumartist", "artistsort", "albumartistsort",
|
||||
"albumsort",
|
||||
"originaldate",
|
||||
"r128_track_gain",
|
||||
"r128_album_gain",
|
||||
"replaygain_track_gain",
|
||||
"replaygain_track_peak",
|
||||
"replaygain_album_gain",
|
||||
"replaygain_album_peak",
|
||||
"musicbrainz_trackid",
|
||||
"comment",
|
||||
"bpm",
|
||||
"arranger", "composer", "conductor", "lyricist", "performer",
|
||||
"remixer", "label", "publisher", "work", "opus",
|
||||
"subtitle", "media",
|
||||
NULL
|
||||
};
|
||||
|
||||
static struct {
|
||||
const char *old;
|
||||
const char *new;
|
||||
} key_map[] = {
|
||||
{ "album_artist", "albumartist" },
|
||||
{ "album artist", "albumartist" },
|
||||
{ "disc", "discnumber" },
|
||||
{ "part", "discnumber" },
|
||||
{ "partnumber", "discnumber" },
|
||||
{ "disctotal", "totaldiscs" },
|
||||
{ "tempo", "bpm" },
|
||||
{ "track", "tracknumber" },
|
||||
{ "WM/Year", "date" },
|
||||
{ "WM/ArtistSortOrder", "artistsort" },
|
||||
{ "WM/AlbumArtistSortOrder", "albumartistsort" },
|
||||
{ "WM/AlbumSortOrder", "albumsort" },
|
||||
{ "WM/OriginalReleaseYear", "originaldate" },
|
||||
{ "WM/Media", "media" },
|
||||
{ "sourcemedia", "media" },
|
||||
{ "MusicBrainz Track Id", "musicbrainz_trackid" },
|
||||
{ "version", "subtitle" },
|
||||
/* ffmpeg id3 */
|
||||
{ "artist-sort", "artistsort" },
|
||||
{ "TSO2", "albumartistsort" },
|
||||
{ "album-sort", "albumsort" },
|
||||
/* ffmpeg mp4 */
|
||||
{ "sort_artist", "artistsort" },
|
||||
{ "sort_album_artist", "albumartistsort" },
|
||||
{ "sort_album", "albumsort" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static const char *fix_key(const char *key)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; interesting[i]; i++) {
|
||||
if (!strcasecmp(key, interesting[i]))
|
||||
return interesting[i];
|
||||
}
|
||||
for (i = 0; key_map[i].old; i++) {
|
||||
if (!strcasecmp(key, key_map[i].old))
|
||||
return key_map[i].new;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int comments_add(struct growing_keyvals *c, const char *key, char *val)
|
||||
{
|
||||
if (!strcasecmp(key, "songwriter")) {
|
||||
int r = comments_add_const(c, "lyricist", val);
|
||||
return comments_add(c, "composer", val) && r;
|
||||
}
|
||||
|
||||
key = fix_key(key);
|
||||
if (!key) {
|
||||
free(val);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!strcmp(key, "tracknumber") || !strcmp(key, "discnumber")) {
|
||||
char *slash = strchr(val, '/');
|
||||
if (slash)
|
||||
*slash = 0;
|
||||
}
|
||||
|
||||
/* don't add duplicates */
|
||||
if (keyvals_get_val_growing(c, key)) {
|
||||
free(val);
|
||||
return 0;
|
||||
}
|
||||
|
||||
keyvals_add(c, key, val);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int comments_add_const(struct growing_keyvals *c, const char *key, const char *val)
|
||||
{
|
||||
return comments_add(c, key, xstrdup(val));
|
||||
}
|
||||
38
comment.h
Normal file
38
comment.h
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2004 Timo Hirvonen
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CMUS_COMMENT_H
|
||||
#define CMUS_COMMENT_H
|
||||
|
||||
#include "keyval.h"
|
||||
|
||||
int track_is_compilation(const struct keyval *comments);
|
||||
int track_is_va_compilation(const struct keyval *comments);
|
||||
|
||||
const char *comments_get_albumartist(const struct keyval *comments);
|
||||
const char *comments_get_artistsort(const struct keyval *comments); /* can return NULL */
|
||||
|
||||
int comments_get_int(const struct keyval *comments, const char *key);
|
||||
int comments_get_signed_int(const struct keyval *comments, const char *key, long int *ival);
|
||||
double comments_get_double(const struct keyval *comments, const char *key);
|
||||
int comments_get_date(const struct keyval *comments, const char *key);
|
||||
|
||||
int comments_add(struct growing_keyvals *c, const char *key, char *val);
|
||||
int comments_add_const(struct growing_keyvals *c, const char *key, const char *val);
|
||||
|
||||
#endif
|
||||
101
compiler.h
Normal file
101
compiler.h
Normal file
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2005 Timo Hirvonen
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CMUS_COMPILER_H
|
||||
#define CMUS_COMPILER_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
/*
|
||||
* GCC 2.96 or compatible required
|
||||
*/
|
||||
#if defined(__GNUC__)
|
||||
|
||||
#if __GNUC__ > 3
|
||||
#undef offsetof
|
||||
#define offsetof(type, member) __builtin_offsetof(type, member)
|
||||
#endif
|
||||
|
||||
/* Optimization: Condition @x is likely */
|
||||
#define likely(x) __builtin_expect(!!(x), 1)
|
||||
|
||||
/* Optimization: Condition @x is unlikely */
|
||||
#define unlikely(x) __builtin_expect(!!(x), 0)
|
||||
|
||||
#ifndef UNUSED
|
||||
#define UNUSED __attribute__((unused))
|
||||
#endif
|
||||
|
||||
#else
|
||||
|
||||
#define likely(x) (x)
|
||||
#define unlikely(x) (x)
|
||||
#define UNUSED
|
||||
|
||||
#endif
|
||||
|
||||
/* Optimization: Function never returns */
|
||||
#define CMUS_NORETURN __attribute__((__noreturn__))
|
||||
|
||||
/* Argument at index @fmt_idx is printf compatible format string and
|
||||
* argument at index @first_idx is the first format argument */
|
||||
#define CMUS_FORMAT(fmt_idx, first_idx) __attribute__((format(printf, (fmt_idx), (first_idx))))
|
||||
|
||||
#if defined(__GNUC__) && (__GNUC__ >= 3)
|
||||
|
||||
/* Optimization: Pointer returned can't alias other pointers */
|
||||
#define CMUS_MALLOC __attribute__((__malloc__))
|
||||
|
||||
#else
|
||||
|
||||
#define CMUS_MALLOC
|
||||
|
||||
#endif
|
||||
|
||||
#if defined(__GNUC__) && (__GNUC__ >= 8)
|
||||
|
||||
/* Silences the unterminated-string-initialization warnings */
|
||||
#define CMUS_NONSTRING __attribute__((nonstring))
|
||||
|
||||
#else
|
||||
|
||||
#define CMUS_NONSTRING
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
/**
|
||||
* container_of - cast a member of a structure out to the containing structure
|
||||
*
|
||||
* @ptr: the pointer to the member.
|
||||
* @type: the type of the container struct this is embedded in.
|
||||
* @member: the name of the member within the struct.
|
||||
*
|
||||
*/
|
||||
#define container_of_portable(ptr, type, member) \
|
||||
((type *)(void *)( (char *)(ptr) - offsetof(type,member)))
|
||||
#undef container_of
|
||||
#if defined(__GNUC__)
|
||||
#define container_of(ptr, type, member) __extension__ ({ \
|
||||
const __typeof__( ((type *)0)->member) *_mptr = (ptr); \
|
||||
container_of_portable(_mptr, type, member);})
|
||||
#else
|
||||
#define container_of(ptr, type, member) container_of_portable(ptr, type, member)
|
||||
#endif
|
||||
|
||||
#endif
|
||||
721
configure
vendored
Executable file
721
configure
vendored
Executable file
@@ -0,0 +1,721 @@
|
||||
#!/bin/sh
|
||||
|
||||
. scripts/configure.sh || exit 1
|
||||
|
||||
c11_code="
|
||||
#include <stdatomic.h>
|
||||
|
||||
int main(void)
|
||||
{
|
||||
#ifdef __STDC_NO_ATOMICS__
|
||||
#error No C11 atomics
|
||||
#endif
|
||||
_Atomic int res = ATOMIC_VAR_INIT(0);
|
||||
|
||||
return res;
|
||||
}
|
||||
"
|
||||
|
||||
check_c11()
|
||||
{
|
||||
msg_checking "for C11 (with atomics support)"
|
||||
|
||||
for flag in -std=gnu11 -std=c11 ""
|
||||
do
|
||||
if try_compile_link "$c11_code" $flag
|
||||
then
|
||||
EXTRA_CFLAGS="$EXTRA_CFLAGS $flag"
|
||||
msg_result yes
|
||||
working_c11=y
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if test -z "$working_c11"
|
||||
then
|
||||
msg_result no
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
check_cflags()
|
||||
{
|
||||
check_cc_flag -pipe -Wall -Wshadow -Wcast-align -Wpointer-arith \
|
||||
-Wwrite-strings -Wundef -Wmissing-prototypes -Wredundant-decls \
|
||||
-Wextra -Wno-sign-compare -Wformat-security
|
||||
|
||||
for i in -Wold-style-definition \
|
||||
-Wno-pointer-sign \
|
||||
-Werror-implicit-function-declaration \
|
||||
-Wno-unused-parameter \
|
||||
-Wno-missing-field-initializers
|
||||
do
|
||||
check_cc_flag $i
|
||||
done
|
||||
return 0
|
||||
}
|
||||
|
||||
check_sndio()
|
||||
{
|
||||
check_library SNDIO "" "-lsndio"
|
||||
return $?
|
||||
}
|
||||
|
||||
check_coreaudio()
|
||||
{
|
||||
case `uname -s` in
|
||||
Darwin)
|
||||
check_library COREAUDIO "" "-framework CoreAudio -framework AudioUnit"
|
||||
return $?
|
||||
esac
|
||||
return 1
|
||||
}
|
||||
|
||||
wcwidth_code="
|
||||
#include <wchar.h>
|
||||
|
||||
/* wchar_t must be 4 bytes to support full unicode */
|
||||
extern char check[1/!(4 - sizeof(wchar_t))];
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
(void) wcwidth('a');
|
||||
return 0;
|
||||
}
|
||||
"
|
||||
|
||||
check_wcwidth()
|
||||
{
|
||||
msg_checking "for uchar compatible wcwidth"
|
||||
if try_compile "$wcwidth_code"
|
||||
then
|
||||
msg_result yes
|
||||
HAVE_WCWIDTH=1
|
||||
else
|
||||
msg_result no
|
||||
HAVE_WCWIDTH=0
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
check_compat()
|
||||
{
|
||||
COMPAT_LIBS=
|
||||
case `uname -s` in
|
||||
SunOS)
|
||||
# connect() etc.
|
||||
try_link -lsocket && COMPAT_LIBS="$COMPAT_LIBS -lsocket"
|
||||
|
||||
# gethostbyname()
|
||||
try_link -lnsl && COMPAT_LIBS="$COMPAT_LIBS -lnsl"
|
||||
|
||||
# nanosleep()
|
||||
if try_link -lrt
|
||||
then
|
||||
COMPAT_LIBS="$COMPAT_LIBS -lrt"
|
||||
elif try_link -lposix4
|
||||
then
|
||||
COMPAT_LIBS="$COMPAT_LIBS -lposix4"
|
||||
fi
|
||||
;;
|
||||
CYGWIN*)
|
||||
CONFIG_CYGWIN=y
|
||||
makefile_vars CONFIG_CYGWIN
|
||||
esac
|
||||
makefile_vars COMPAT_LIBS
|
||||
}
|
||||
|
||||
rtsched_code="
|
||||
#include <pthread.h>
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
pthread_attr_t attr;
|
||||
struct sched_param param;
|
||||
|
||||
pthread_attr_init(&attr);
|
||||
pthread_attr_setschedpolicy(&attr, SCHED_RR);
|
||||
param.sched_priority = sched_get_priority_max(SCHED_RR);
|
||||
pthread_attr_setschedparam(&attr, ¶m);
|
||||
return 0;
|
||||
}
|
||||
"
|
||||
|
||||
check_rtsched()
|
||||
{
|
||||
msg_checking "for realtime scheduling"
|
||||
if try_compile_link "$rtsched_code" $PTHREAD_CFLAGS $PTHREAD_LIBS
|
||||
then
|
||||
msg_result yes
|
||||
EXTRA_CFLAGS="$EXTRA_CFLAGS -DREALTIME_SCHEDULING"
|
||||
else
|
||||
msg_result no
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
ncurses_include="
|
||||
#if defined(__sun__) || defined(__CYGWIN__)
|
||||
#include <termios.h>
|
||||
#include <ncurses.h>
|
||||
#else
|
||||
#include <curses.h>
|
||||
#endif
|
||||
"
|
||||
|
||||
ncurses_code="
|
||||
$ncurses_include
|
||||
|
||||
int main(void)
|
||||
{
|
||||
initscr();
|
||||
endwin();
|
||||
return 0;
|
||||
}
|
||||
"
|
||||
|
||||
check_ncurses()
|
||||
{
|
||||
if pkg_config NCURSES "ncursesw" "" "-lncursesw"
|
||||
then
|
||||
widechars=y
|
||||
elif pkg_config NCURSES "ncurses" "" "-lncurses" || pkg_config NCURSES "curses" "" "-lcurses"
|
||||
then
|
||||
widechars=n
|
||||
msg_error "Your ncurses does not support wide characters!"
|
||||
msg_error "Install ncursesw if you need wide character support,"
|
||||
msg_error "you can ignore this warning otherwise."
|
||||
fi
|
||||
test -z "$widechars" && return 1
|
||||
|
||||
msg_checking "for working ncurses setup"
|
||||
for flag in "" "-I/usr/include/ncurses" "-I/usr/include/ncursesw"
|
||||
do
|
||||
if try_compile_link "$ncurses_code" $NCURSES_CFLAGS $flag $NCURSES_LIBS
|
||||
then
|
||||
NCURSES_CFLAGS="$NCURSES_CFLAGS $flag"
|
||||
msg_result yes
|
||||
working_curses=y
|
||||
break
|
||||
fi
|
||||
done
|
||||
if test -z "$working_curses"
|
||||
then
|
||||
msg_result no
|
||||
return 1
|
||||
fi
|
||||
|
||||
check_function "resizeterm" $NCURSES_CFLAGS $NCURSES_LIBS
|
||||
HAVE_RESIZETERM=`test $? -ne 0 ; echo $?`
|
||||
|
||||
check_function "use_default_colors" $NCURSES_CFLAGS $NCURSES_LIBS
|
||||
HAVE_USE_DEFAULT_COLORS=`test $? -ne 0 ; echo $?`
|
||||
|
||||
msg_checking "for A_ITALIC"
|
||||
if try_compile_link "$ncurses_include int main(int argc, char *argv[]) { unsigned long x = A_ITALIC; return !!x; }" $NCURSES_CFLAGS $NCURSES_LIBS
|
||||
then
|
||||
msg_result yes
|
||||
HAVE_ITALIC="1"
|
||||
else
|
||||
msg_result no
|
||||
HAVE_ITALIC="0"
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
check_discid()
|
||||
{
|
||||
HAVE_DISCID=n
|
||||
pkg_config DISCID "libdiscid" "" "-ldiscid" && HAVE_DISCID=y
|
||||
return $?
|
||||
}
|
||||
|
||||
check_mpc()
|
||||
{
|
||||
MPC_SV8=0
|
||||
if check_header mpc/mpcdec.h
|
||||
then
|
||||
MPC_SV8=1
|
||||
else
|
||||
check_header mpcdec/mpcdec.h || return $?
|
||||
fi
|
||||
check_library MPC "" "-lmpcdec -lm"
|
||||
return $?
|
||||
}
|
||||
|
||||
check_cddb()
|
||||
{
|
||||
pkg_config CDDB "libcddb" "" "-lcddb" && HAVE_CDDB=y
|
||||
return $?
|
||||
}
|
||||
|
||||
check_cdio()
|
||||
{
|
||||
pkg_config CDIO "libcdio_cdda" "" "-lcdio_cdio -lcdio -lm"
|
||||
return $?
|
||||
}
|
||||
|
||||
check_flac()
|
||||
{
|
||||
pkg_config FLAC "flac" "" "-lFLAC -lm" || return $?
|
||||
|
||||
# Make sure the FLAC_CFLAGS value is sane, strip trailing '/FLAC'.
|
||||
FLAC_CFLAGS=`echo $FLAC_CFLAGS | sed "s/FLAC$//"`
|
||||
return 0
|
||||
}
|
||||
|
||||
check_mad()
|
||||
{
|
||||
pkg_config MAD "mad" "" "-lmad -lm"
|
||||
return $?
|
||||
}
|
||||
|
||||
mikmod_code="
|
||||
#include <mikmod.h>
|
||||
int main() {
|
||||
MikMod_RegisterAllDrivers();
|
||||
return 0;
|
||||
}
|
||||
"
|
||||
check_mikmod()
|
||||
{
|
||||
# mikmod is linked against pthread
|
||||
app_config MIKMOD libmikmod-config || \
|
||||
check_library MIKMOD "$PTHREAD_CFLAGS" "-lmikmod $PTHREAD_LIBS" || \
|
||||
return 1
|
||||
try_compile_link "$mikmod_code" $MIKMOD_CFLAGS $MIKMOD_LIBS
|
||||
return $?
|
||||
}
|
||||
|
||||
check_modplug()
|
||||
{
|
||||
pkg_config MODPLUG "libmodplug" "-I/usr/include/libmodplug" "-lmodplug -lstdc++ -lm" || return $?
|
||||
MODPLUG_API_8=0
|
||||
if check_function "ModPlug_GetModuleType" $MODPLUG_CFLAGS $MODPLUG_LIBS
|
||||
then
|
||||
MODPLUG_API_8=1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
check_bass()
|
||||
{
|
||||
check_header bass.h &&
|
||||
check_library BASS "" "-lbass"
|
||||
return $?
|
||||
}
|
||||
|
||||
check_vtx()
|
||||
{
|
||||
check_header ayemu.h &&
|
||||
check_library VTX "" "-layemu"
|
||||
return $?
|
||||
}
|
||||
|
||||
check_vorbis()
|
||||
{
|
||||
if test "$CONFIG_TREMOR" = y
|
||||
then
|
||||
pkg_config VORBIS "vorbisidec" "" "-lvorbisidec -lm"
|
||||
return $?
|
||||
else
|
||||
pkg_config VORBIS "vorbisfile" "" "-lvorbisfile -lvorbis -lm -logg"
|
||||
return $?
|
||||
fi
|
||||
}
|
||||
|
||||
check_libsystemd()
|
||||
{
|
||||
pkg_config LIBSYSTEMD "libsystemd" || pkg_config LIBSYSTEMD "libelogind >= 239.3" || {
|
||||
pkg_config LIBSYSTEMD "basu" && CFLAGS="${CFLAGS} -DCONFIG_MPRIS_BASU"
|
||||
}
|
||||
return $?
|
||||
}
|
||||
|
||||
check_opus()
|
||||
{
|
||||
pkg_config OPUS "opusfile"
|
||||
return $?
|
||||
}
|
||||
|
||||
check_wavpack()
|
||||
{
|
||||
pkg_config WAVPACK "wavpack >= 4.40" "" "-lwavpack"
|
||||
return $?
|
||||
}
|
||||
|
||||
check_pulse()
|
||||
{
|
||||
pkg_config PULSE "libpulse >= 0.9.19"
|
||||
return $?
|
||||
}
|
||||
|
||||
check_alsa()
|
||||
{
|
||||
# the alsa.pc file should be always available
|
||||
pkg_config ALSA "alsa >= 1.0.11"
|
||||
return $?
|
||||
}
|
||||
|
||||
check_jack()
|
||||
{
|
||||
pkg_config JACK "jack"
|
||||
return $?
|
||||
}
|
||||
|
||||
check_samplerate()
|
||||
{
|
||||
pkg_config SAMPLERATE "samplerate" && HAVE_SAMPLERATE=y
|
||||
return $?
|
||||
}
|
||||
|
||||
check_ao()
|
||||
{
|
||||
pkg_config AO "ao" "" "-lao"
|
||||
return $?
|
||||
}
|
||||
|
||||
arts_code="
|
||||
#include <artsc.h>
|
||||
int main() {
|
||||
return arts_init();
|
||||
}
|
||||
"
|
||||
check_arts()
|
||||
{
|
||||
app_config ARTS artsc-config || return 1
|
||||
try_compile_link "$arts_code" $ARTS_CFLAGS $ARTS_LIBS
|
||||
return $?
|
||||
}
|
||||
|
||||
check_oss()
|
||||
{
|
||||
case `uname -s` in
|
||||
Linux|*FreeBSD)
|
||||
;;
|
||||
*BSD)
|
||||
check_library OSS "" "-lossaudio"
|
||||
return $?
|
||||
;;
|
||||
*)
|
||||
# unknown
|
||||
;;
|
||||
esac
|
||||
|
||||
OSS_CFLAGS=""
|
||||
OSS_LIBS=""
|
||||
msg_checking "for header <sys/soundcard.h>"
|
||||
if test -f /usr/include/sys/soundcard.h
|
||||
then
|
||||
msg_result "yes"
|
||||
makefile_vars OSS_CFLAGS OSS_LIBS
|
||||
return 0
|
||||
else
|
||||
msg_result "no"
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
check_sun()
|
||||
{
|
||||
msg_checking "for header <sys/audioio.h>"
|
||||
if test -f /usr/include/sys/audioio.h
|
||||
then
|
||||
msg_result "yes"
|
||||
return 0
|
||||
else
|
||||
msg_result "no"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
check_waveout()
|
||||
{
|
||||
case `uname -s` in
|
||||
CYGWIN*)
|
||||
check_library WAVEOUT "" "-lwinmm"
|
||||
return $?
|
||||
esac
|
||||
return 1
|
||||
}
|
||||
|
||||
check_roar()
|
||||
{
|
||||
pkg_config ROAR "libroar >= 0.4.5"
|
||||
return $?
|
||||
}
|
||||
|
||||
check_mp4()
|
||||
{
|
||||
pkg_config MP4 "mp4v2 faad2" "" "-lmp4v2 -lfaad -lm" || return $?
|
||||
USE_MPEG4IP=0
|
||||
if ! check_header mp4v2/mp4v2.h -std=c11 $MP4_CFLAGS
|
||||
then
|
||||
# couldn't find the v2 header, try falling back to mp4.h
|
||||
USE_MPEG4IP=1
|
||||
check_header mp4.h $MP4_CFLAGS || return $?
|
||||
fi
|
||||
check_header neaacdec.h $MP4_CFLAGS
|
||||
return $?
|
||||
}
|
||||
|
||||
check_aac()
|
||||
{
|
||||
pkg_config AAC faad2 "" "-lfaad -lm" || return $?
|
||||
check_header neaacdec.h $AAC_CFLAGS
|
||||
return $?
|
||||
}
|
||||
|
||||
check_ffmpeg()
|
||||
{
|
||||
pkg_config FFMPEG "libavformat libavcodec libswresample libavutil" || return $?
|
||||
# check the existence of specific headers since they've been renamed before
|
||||
check_header "libavformat/avformat.h" $FFMPEG_CFLAGS || return $?
|
||||
check_header "libavcodec/avcodec.h" $FFMPEG_CFLAGS || return $?
|
||||
check_header "libswresample/swresample.h" $FFMPEG_CFLAGS || return $?
|
||||
check_header "libavutil/avutil.h" $FFMPEG_CFLAGS || return $?
|
||||
# ffmpeg api changes so frequently that it is best to compile the module
|
||||
libs="$LDDLFLAGS $FFMPEG_LIBS"
|
||||
cflags="$SOFLAGS $FFMPEG_CFLAGS"
|
||||
topdir=`dirname "$0"`
|
||||
ffmpeg_code=`cat "$topdir"/ip/ffmpeg.c | sed 's/\\\n//g'`
|
||||
msg_checking "for successful build of ffmpeg.c"
|
||||
if try_compile_link "$ffmpeg_code" $cflags -I$topdir/ip $libs
|
||||
then
|
||||
msg_result yes
|
||||
return 0
|
||||
fi
|
||||
msg_result no
|
||||
return 1
|
||||
}
|
||||
|
||||
aaudio_code="
|
||||
#include <aaudio/AAudio.h>
|
||||
int main() {
|
||||
// ensure basic aaudio support
|
||||
if (__builtin_available(android 26, *)) {
|
||||
AAudioStreamBuilder *bld;
|
||||
AAudio_createStreamBuilder(&bld);
|
||||
|
||||
// ensure we have at least api 32 headers
|
||||
if (__builtin_available(android 32, *)) {
|
||||
AAudioStreamBuilder_setChannelMask(bld, AAUDIO_CHANNEL_9POINT1POINT6);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
"
|
||||
|
||||
check_aaudio()
|
||||
{
|
||||
check_header aaudio/AAudio.h || return $?
|
||||
check_library AAUDIO "-D__ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__ -Werror=unguarded-availability" "-Wl,--no-as-needed -laaudio" || return $?
|
||||
|
||||
msg_checking "for working aaudio api 32 linkage"
|
||||
if try_compile_link "$aaudio_code" $AAUDIO_CFLAGS $AAUDIO_LIBS
|
||||
then
|
||||
msg_result yes
|
||||
return 0
|
||||
fi
|
||||
msg_result no
|
||||
return 1
|
||||
}
|
||||
|
||||
check_string_function()
|
||||
{
|
||||
msg_checking "for function $1"
|
||||
string_function_code="
|
||||
#include <string.h>
|
||||
int (*ptr)() = (int (*)()) &$1;
|
||||
int main() { return 0; }
|
||||
"
|
||||
if try_compile_link "$string_function_code"
|
||||
then
|
||||
msg_result yes
|
||||
return 0
|
||||
fi
|
||||
msg_result no
|
||||
return 1
|
||||
}
|
||||
|
||||
|
||||
# defaults
|
||||
prefix=/usr/local
|
||||
DEBUG=1
|
||||
HAVE_CDDB=n
|
||||
CONFIG_TREMOR=n
|
||||
CONFIG_MIKMOD=n
|
||||
CONFIG_BASS=n
|
||||
USE_FALLBACK_IP=n
|
||||
HAVE_BYTESWAP_H=n
|
||||
HAVE_STRDUP=n
|
||||
HAVE_STRNDUP=n
|
||||
HAVE_SAMPLERATE=n
|
||||
# unset CONFIG_* variables: if check succeeds 'y', otherwise 'n'
|
||||
|
||||
USAGE="
|
||||
Options:
|
||||
prefix Installation prefix [$prefix]
|
||||
bindir User executables [\$prefix/bin]
|
||||
datadir Read-only data [\$prefix/share]
|
||||
libdir Libraries [\$prefix/lib]
|
||||
mandir Man pages [\$datadir/man]
|
||||
docdir Other documentation [\$datadir/doc/cmus]
|
||||
exampledir Examples [\$docdir/examples]
|
||||
DEBUG Debugging level (0-2) [$DEBUG]
|
||||
|
||||
Optional Features: y/n
|
||||
CONFIG_AAC AAC (.aac, audio/aac, audio/aacp) [auto]
|
||||
CONFIG_AAUDIO Android 8.0+ native audio output [auto]
|
||||
CONFIG_ALSA ALSA [auto]
|
||||
CONFIG_AO Libao cross-platform audio library [auto]
|
||||
CONFIG_ARTS ARTS [auto]
|
||||
CONFIG_CDDB libcddb CDDA identification [auto]
|
||||
CONFIG_CDIO libcdio CDDA input [auto]
|
||||
CONFIG_COREAUDIO CoreAudio [auto]
|
||||
CONFIG_CUE CUE sheets (.cue) [y]
|
||||
CONFIG_DISCID libdiscid CDDA identification [auto]
|
||||
CONFIG_FFMPEG FFMPEG (.shn, .wma) [auto]
|
||||
CONFIG_FLAC Free Lossless Audio Codec (.flac, .fla) [auto]
|
||||
CONFIG_JACK JACK [auto]
|
||||
CONFIG_MAD MPEG Audio Decoder (.mp3, .mp2, streams) [auto]
|
||||
CONFIG_MIKMOD libmikmod (.mod, .x3m, ...) [n]
|
||||
CONFIG_BASS libbass (.mod, .x3m, ...) [n]
|
||||
CONFIG_MODPLUG libmodplug (.mod, .x3m, ...) [auto]
|
||||
CONFIG_MP4 MPEG-4 AAC (.mp4, .m4a, .m4b) [auto]
|
||||
CONFIG_MPC libmpcdec (Musepack .mpc, .mpp, .mp+) [auto]
|
||||
CONFIG_MPRIS MPRIS [auto]
|
||||
CONFIG_OPUS Opus (.opus) [auto]
|
||||
CONFIG_OSS Open Sound System [auto]
|
||||
CONFIG_PULSE native PulseAudio output [auto]
|
||||
CONFIG_ROAR native RoarAudio output [auto]
|
||||
CONFIG_SAMPLERATE Use libsamplerate to resample to JACK's rate [auto]
|
||||
CONFIG_SNDIO Sndio [auto]
|
||||
CONFIG_SUN Sun Audio [auto]
|
||||
CONFIG_TREMOR Use Tremor as Ogg/Vorbis input plugin [n]
|
||||
CONFIG_VORBIS Ogg/Vorbis (.ogg, application/ogg, audio/x-ogg) [auto]
|
||||
CONFIG_VTX libayemu (.vtx) [auto]
|
||||
CONFIG_WAVEOUT Windows Wave Out [auto]
|
||||
CONFIG_WAVPACK WavPack (.wv, audio/x-wavpack) [auto]
|
||||
CONFIG_WAV WAV [y]
|
||||
USE_FALLBACK_IP Use a specific IP for every unrecognized [n]
|
||||
input format. Currently set to FFMPEG.
|
||||
|
||||
Also many standard variables like CC, LD, CFLAGS, LDFLAGS are recognized.
|
||||
Cross compiling is supported via CROSS=target-prefix-
|
||||
optionally set HOSTCC=this-machine-gcc, HOSTLD, HOST_CFLAGS, HOST_LDFLAGS."
|
||||
|
||||
parse_command_line "$@"
|
||||
|
||||
case $DEBUG in
|
||||
[0-2])
|
||||
;;
|
||||
*)
|
||||
die "DEBUG must be 0-2"
|
||||
;;
|
||||
esac
|
||||
|
||||
var_default bindir "${prefix}/bin"
|
||||
var_default datadir "${prefix}/share"
|
||||
var_default libdir "${prefix}/lib"
|
||||
var_default mandir "${datadir}/man"
|
||||
var_default docdir "${datadir}/doc/cmus"
|
||||
var_default exampledir "${docdir}/examples"
|
||||
|
||||
check check_cc
|
||||
check check_host_cc
|
||||
check check_c11
|
||||
check check_cflags
|
||||
check check_cc_depgen
|
||||
check check_endianness
|
||||
check check_compat
|
||||
check check_dl
|
||||
check check_pthread
|
||||
check check_rtsched
|
||||
check check_ncurses
|
||||
check check_iconv
|
||||
check check_wcwidth
|
||||
check_header byteswap.h && HAVE_BYTESWAP_H=y
|
||||
check_string_function "strdup" && HAVE_STRDUP=y
|
||||
check_string_function "strndup" && HAVE_STRNDUP=y
|
||||
|
||||
check check_cddb CONFIG_CDDB
|
||||
check check_cdio CONFIG_CDIO
|
||||
check check_flac CONFIG_FLAC
|
||||
check check_mad CONFIG_MAD
|
||||
check check_mikmod CONFIG_MIKMOD
|
||||
check check_modplug CONFIG_MODPLUG
|
||||
check check_bass CONFIG_BASS
|
||||
check check_mpc CONFIG_MPC
|
||||
check check_vorbis CONFIG_VORBIS
|
||||
check check_opus CONFIG_OPUS
|
||||
check check_libsystemd CONFIG_MPRIS
|
||||
check check_wavpack CONFIG_WAVPACK
|
||||
check check_mp4 CONFIG_MP4
|
||||
check check_aac CONFIG_AAC
|
||||
check check_ffmpeg CONFIG_FFMPEG
|
||||
check check_vtx CONFIG_VTX
|
||||
# nothing to check, just validate the variable values
|
||||
check true CONFIG_TREMOR
|
||||
check true CONFIG_WAV
|
||||
check true CONFIG_CUE
|
||||
check check_pulse CONFIG_PULSE
|
||||
check check_alsa CONFIG_ALSA
|
||||
check check_jack CONFIG_JACK
|
||||
check check_samplerate CONFIG_SAMPLERATE
|
||||
check check_ao CONFIG_AO
|
||||
check check_coreaudio CONFIG_COREAUDIO
|
||||
check check_arts CONFIG_ARTS
|
||||
check check_oss CONFIG_OSS
|
||||
check check_sndio CONFIG_SNDIO
|
||||
check check_sun CONFIG_SUN
|
||||
check check_waveout CONFIG_WAVEOUT
|
||||
check check_roar CONFIG_ROAR
|
||||
check check_aaudio CONFIG_AAUDIO
|
||||
|
||||
# discid is only needed if at least one cdda plugin is active
|
||||
test -z "$CONFIG_DISCID" && CONFIG_DISCID=a
|
||||
if test "$CONFIG_DISCID" = a
|
||||
then
|
||||
test "$CONFIG_CDIO" = n && CONFIG_DISCID=n
|
||||
fi
|
||||
check check_discid CONFIG_DISCID
|
||||
|
||||
test "$WORDS_BIGENDIAN" = y && CFLAGS="${CFLAGS} -DWORDS_BIGENDIAN"
|
||||
test "$HAVE_DISCID" = y && CFLAGS="${CFLAGS} -DHAVE_DISCID"
|
||||
|
||||
DATADIR="$datadir"
|
||||
LIBDIR="$libdir"
|
||||
|
||||
config_header config/cdio.h HAVE_CDDB
|
||||
config_header config/mpris.h CONFIG_MPRIS
|
||||
config_header config/datadir.h DATADIR
|
||||
config_header config/libdir.h LIBDIR
|
||||
config_header config/debug.h DEBUG
|
||||
config_header config/tremor.h CONFIG_TREMOR
|
||||
config_header config/modplug.h MODPLUG_API_8
|
||||
config_header config/mpc.h MPC_SV8
|
||||
config_header config/mp4.h USE_MPEG4IP
|
||||
config_header config/curses.h HAVE_RESIZETERM HAVE_USE_DEFAULT_COLORS HAVE_ITALIC
|
||||
config_header config/ffmpeg.h HAVE_FFMPEG_AVCODEC_H USE_FALLBACK_IP
|
||||
config_header config/utils.h HAVE_BYTESWAP_H
|
||||
config_header config/iconv.h HAVE_ICONV
|
||||
config_header config/wcwidth.h HAVE_WCWIDTH
|
||||
config_header config/samplerate.h HAVE_SAMPLERATE
|
||||
config_header config/xmalloc.h HAVE_STRDUP HAVE_STRNDUP
|
||||
|
||||
CFLAGS="${CFLAGS} -DHAVE_CONFIG"
|
||||
|
||||
makefile_vars bindir datadir libdir mandir docdir exampledir
|
||||
makefile_vars \
|
||||
CONFIG_AAC CONFIG_ALSA CONFIG_AO CONFIG_ARTS CONFIG_CDIO \
|
||||
CONFIG_COREAUDIO CONFIG_CUE CONFIG_FFMPEG CONFIG_FLAC CONFIG_JACK \
|
||||
CONFIG_MAD CONFIG_MIKMOD CONFIG_MODPLUG CONFIG_MP4 CONFIG_MPC \
|
||||
CONFIG_MPRIS CONFIG_OPUS CONFIG_OSS CONFIG_PULSE CONFIG_ROAR \
|
||||
CONFIG_SAMPLERATE CONFIG_SNDIO CONFIG_SUN CONFIG_VORBIS CONFIG_VTX \
|
||||
CONFIG_WAV CONFIG_WAVEOUT CONFIG_WAVPACK CONFIG_BASS CONFIG_AAUDIO
|
||||
|
||||
generate_config_mk
|
||||
10
contrib/README
Normal file
10
contrib/README
Normal file
@@ -0,0 +1,10 @@
|
||||
_cmus (originally _cmus_remote)
|
||||
zsh completion for cmus-remote by Christian Schneider strcat AT gmx DOT net
|
||||
extended for the main cmus binary by Frank Terbeck.
|
||||
|
||||
cmus-updategaim.py
|
||||
Joshua Kwan <joshk@triplehelix.org>
|
||||
|
||||
cmus-updatepidgin.py
|
||||
Based on Joshua's cmus-updategaim.py script.
|
||||
David Thiel <lx@redundancy.redundancy.org>
|
||||
52
contrib/_cmus
Normal file
52
contrib/_cmus
Normal file
@@ -0,0 +1,52 @@
|
||||
#compdef cmus cmus-remote
|
||||
|
||||
local expl cmus_commands
|
||||
|
||||
cmus_commands=(
|
||||
add bind browser-up cd clear colorscheme echo factivate
|
||||
filter fset invert load mark player-next player-pause
|
||||
player-play quit refresh run save search-next search-prev
|
||||
seek set showbind shuffle source toggle unbind unmark view
|
||||
vol win-activate win-add-l win-add-p win-add-Q win-add-q
|
||||
win-bottom win-down win-mv-after win-mv-before win-next
|
||||
win-page-down win-page-up win-remove win-sel-cur
|
||||
win-toggle win-top win-up win-update
|
||||
)
|
||||
_cmus_volume() {
|
||||
local expl
|
||||
compset -P '[-+]'
|
||||
_wanted list expl volume compadd $expl - {0..100}
|
||||
}
|
||||
|
||||
case $service in
|
||||
(cmus-remote)
|
||||
_arguments -C -s\
|
||||
'--server[connect using socket SOCKET]:socket:_files' \
|
||||
'--help[display this help and exit]:' \
|
||||
'--version[Display version information and exit.]:' \
|
||||
'(--play -p)'{--play,-p}'[Start playing.]:' \
|
||||
'(--pause -u)'{--pause,-u}'[Toggle pause.]:' \
|
||||
'(--stop -s)'{--stop,-s}'[Stop playing.]:' \
|
||||
'(--next -n)'{--next,-n}'[Skip forward in playlist.]:' \
|
||||
'(--prev -r)'{--prev,-r}'[Skip backward in playlist.]:' \
|
||||
'(--file -f)'{--file,-f}'[Play a file.]:file:_files' \
|
||||
'(--repeat -R)'{--repeat,-R}'[Toggle repeat.]:' \
|
||||
'(--shuffle -S)'{--shuffle,-S}'[Toggle shuffle.]:' \
|
||||
'(--volume -v)'{--volume,-V+}'[Change volume. See vol command in cmus(1).]:volume:_cmus_volume' \
|
||||
'(--seek -k)'{--seek,-k+}'[Seek. See seek command in cmus(1).]:seek [+-]<num>[m/h]' \
|
||||
'(--query -Q)'{--query,-Q}'[Get player status (same as -C status).]:' \
|
||||
'(--library -l)'{--library,-l+}'[Modify library instead of playlist.]:playlists/files/directories/URLs:_files' \
|
||||
'(--playlist -P)'{--playlist,-P}'[Modify playlist (default).]::Playlist:_files' \
|
||||
'(--queue -q)'{--queue,-q}'[Modify play queue instead of playlist.]:' \
|
||||
'(--clear -c)'{--clear,-c}'[Clear playlist, library (-l) or play queue (-q).]:playlist' \
|
||||
'(--raw -C)'{--raw,-C+}'[Treat arguments (instead of stdin) as raw commands.]:command:(${cmus_commands[@]}):' \
|
||||
;;
|
||||
(cmus)
|
||||
_arguments \
|
||||
'--listen[listen on ADDR instead of $CMUS_SOCKET or $XDG_RUNTIME_DIR/cmus-socket]:socket:_files' \
|
||||
'--plugins[list available plugins and exit]' \
|
||||
'--help[display this help and exit]' \
|
||||
'--version[display version information]'
|
||||
;;
|
||||
esac
|
||||
|
||||
20
contrib/cmus-updategaim.py
Normal file
20
contrib/cmus-updategaim.py
Normal file
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import dbus
|
||||
import sys
|
||||
|
||||
args = {}
|
||||
|
||||
for n in range(1, len(sys.argv) - 1, 2):
|
||||
args[sys.argv[n]] = sys.argv[n + 1]
|
||||
|
||||
obj = dbus.SessionBus().get_object("net.sf.gaim.GaimService", "/net/sf/gaim/GaimObject")
|
||||
gaim = dbus.Interface(obj, "net.sf.gaim.GaimInterface")
|
||||
|
||||
current = gaim.GaimSavedstatusGetCurrent()
|
||||
status_type = gaim.GaimSavedstatusGetType(current)
|
||||
saved = gaim.GaimSavedstatusNew("", status_type)
|
||||
gaim.GaimSavedstatusSetMessage(saved, "♪ %s - %s" % (args["artist"], args["title"]))
|
||||
gaim.GaimSavedstatusActivate(saved)
|
||||
|
||||
20
contrib/cmus-updatepidgin.py
Normal file
20
contrib/cmus-updatepidgin.py
Normal file
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import dbus
|
||||
import sys
|
||||
|
||||
args = {}
|
||||
|
||||
for n in range(1, len(sys.argv) - 1, 2):
|
||||
args[sys.argv[n]] = sys.argv[n + 1]
|
||||
|
||||
obj = dbus.SessionBus().get_object("im.pidgin.purple.PurpleService", "/im/pidgin/purple/PurpleObject")
|
||||
pidgin = dbus.Interface(obj, "im.pidgin.purple.PurpleInterface")
|
||||
|
||||
current = pidgin.PurpleSavedstatusGetCurrent()
|
||||
status_type = pidgin.PurpleSavedstatusGetType(current)
|
||||
saved = pidgin.PurpleSavedstatusNew("", status_type)
|
||||
pidgin.PurpleSavedstatusSetMessage(saved, "♪ %s - %s" % (args["artist"], args["title"]))
|
||||
pidgin.PurpleSavedstatusActivate(saved)
|
||||
|
||||
75
contrib/cmus.bash-completion
Normal file
75
contrib/cmus.bash-completion
Normal file
@@ -0,0 +1,75 @@
|
||||
# bash completion for cmus-remote and cmus
|
||||
|
||||
_cmus-remote()
|
||||
{
|
||||
local cur prev longopts shortopts
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
||||
# maybe we'll differentiate between $cur starting with - or --
|
||||
longopts="--server --passwd --help --version --play --pause --stop
|
||||
--next --prev --file --repeat --shuffle --volume --seek
|
||||
--library --playlist --queue --clear --raw"
|
||||
shortopts="-p -u -s -n -r -f -R -S -v -k -Q -l -P -q -c -C"
|
||||
|
||||
COMPREPLY=()
|
||||
|
||||
case "${prev}" in
|
||||
--server) # can be a hostname[:port] or a filename
|
||||
compopt -o nospace
|
||||
_known_hosts_real -c "${cur}"
|
||||
;&
|
||||
--file|-f)
|
||||
_filedir
|
||||
return 0
|
||||
;;
|
||||
--passwd) # do not attempt to complete anything
|
||||
;&
|
||||
--volume|-v)
|
||||
;&
|
||||
--seek|-k)
|
||||
;&
|
||||
--raw|-C)
|
||||
# supporting completion for raw commands would be nice (TODO)
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ ${cur} == -* ]]; then
|
||||
COMPREPLY=(
|
||||
$(compgen -W "${shortopts[*]} ${longopts[*]}" -- ${cur})
|
||||
)
|
||||
else
|
||||
_filedir
|
||||
fi
|
||||
}
|
||||
|
||||
_cmus()
|
||||
{
|
||||
local cur prev opts
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
||||
opts="--listen --plugins --show-cursor --help --version"
|
||||
|
||||
COMPREPLY=()
|
||||
|
||||
case "${prev}" in
|
||||
--listen)
|
||||
compopt -o nospace
|
||||
_ip_addresses
|
||||
_filedir
|
||||
return 0;
|
||||
;;
|
||||
--plugins|--help|--version)
|
||||
return 0;
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
|
||||
COMPREPLY=($(compgen -W "${opts[*]}" -- ${cur}))
|
||||
}
|
||||
|
||||
complete -F _cmus-remote cmus-remote
|
||||
complete -F _cmus cmus
|
||||
129
convert.c
Normal file
129
convert.c
Normal file
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2004 Timo Hirvonen
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "convert.h"
|
||||
#include "xmalloc.h"
|
||||
#include "uchar.h"
|
||||
#ifdef HAVE_CONFIG
|
||||
#include "config/iconv.h"
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_ICONV
|
||||
#include <iconv.h>
|
||||
#endif
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
ssize_t convert(const char *inbuf, ssize_t inbuf_size,
|
||||
char **outbuf, ssize_t outbuf_estimate,
|
||||
const char *tocode, const char *fromcode)
|
||||
{
|
||||
#ifdef HAVE_ICONV
|
||||
const char *in;
|
||||
char *out;
|
||||
size_t rc, outbuf_size, inbytesleft, outbytesleft;
|
||||
iconv_t cd;
|
||||
int finished = 0, err_save;
|
||||
|
||||
cd = iconv_open(tocode, fromcode);
|
||||
if (cd == (iconv_t) -1)
|
||||
return -1;
|
||||
|
||||
if (inbuf_size < 0)
|
||||
inbuf_size = strlen(inbuf);
|
||||
inbytesleft = inbuf_size;
|
||||
|
||||
if (outbuf_estimate < 0)
|
||||
outbuf_size = inbuf_size;
|
||||
else
|
||||
outbuf_size = outbuf_estimate;
|
||||
outbytesleft = outbuf_size;
|
||||
|
||||
in = inbuf;
|
||||
out = *outbuf = xnew(char, outbuf_size + 1);
|
||||
|
||||
while (!finished) {
|
||||
finished = 1;
|
||||
rc = iconv(cd, (char **)&in, &inbytesleft, &out, &outbytesleft);
|
||||
if (rc == (size_t) -1) {
|
||||
if (errno == E2BIG) {
|
||||
size_t used = out - *outbuf;
|
||||
outbytesleft += outbuf_size;
|
||||
outbuf_size *= 2;
|
||||
*outbuf = xrenew(char, *outbuf, outbuf_size + 1);
|
||||
out = *outbuf + used;
|
||||
continue;
|
||||
} else if (errno != EINVAL)
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
/* NUL-terminate for safety reasons */
|
||||
*out = '\0';
|
||||
iconv_close(cd);
|
||||
return outbuf_size - outbytesleft;
|
||||
|
||||
error:
|
||||
err_save = errno;
|
||||
free(*outbuf);
|
||||
*outbuf = NULL;
|
||||
iconv_close(cd);
|
||||
errno = err_save;
|
||||
return -1;
|
||||
|
||||
#else
|
||||
if (inbuf_size < 0)
|
||||
inbuf_size = strlen(inbuf);
|
||||
*outbuf = xnew(char, inbuf_size + 1);
|
||||
memcpy(*outbuf, inbuf, inbuf_size);
|
||||
(*outbuf)[inbuf_size] = '\0';
|
||||
return inbuf_size;
|
||||
#endif
|
||||
}
|
||||
|
||||
int utf8_encode(const char *inbuf, const char *encoding, char **outbuf)
|
||||
{
|
||||
size_t inbuf_size, outbuf_size, i;
|
||||
int rc;
|
||||
|
||||
inbuf_size = strlen(inbuf);
|
||||
outbuf_size = inbuf_size;
|
||||
for (i = 0; i < inbuf_size; i++) {
|
||||
unsigned char ch;
|
||||
|
||||
ch = inbuf[i];
|
||||
if (ch > 127)
|
||||
outbuf_size++;
|
||||
}
|
||||
|
||||
rc = convert(inbuf, inbuf_size, outbuf, outbuf_size, "UTF-8", encoding);
|
||||
|
||||
return rc < 0 ? -1 : 0;
|
||||
}
|
||||
|
||||
char *to_utf8(const char *str, const char *enc)
|
||||
{
|
||||
char *outbuf = NULL;
|
||||
int rc;
|
||||
|
||||
if (u_is_valid(str)) {
|
||||
return xstrdup(str);
|
||||
} else {
|
||||
rc = utf8_encode(str, enc, &outbuf);
|
||||
return rc < 0 ? xstrdup(str) : outbuf;
|
||||
}
|
||||
}
|
||||
33
convert.h
Normal file
33
convert.h
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2004 Timo Hirvonen
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CMUS_CONVERT_H
|
||||
#define CMUS_CONVERT_H
|
||||
|
||||
#include <sys/types.h> /* ssize_t */
|
||||
|
||||
/* Returns length of *outbuf in bytes (without closing '\0'), -1 on error. */
|
||||
ssize_t convert(const char *inbuf, ssize_t inbuf_size,
|
||||
char **outbuf, ssize_t outbuf_estimate,
|
||||
const char *tocode, const char *fromcode);
|
||||
|
||||
int utf8_encode(const char *inbuf, const char *encoding, char **outbuf);
|
||||
|
||||
char *to_utf8(const char *str, const char *enc);
|
||||
|
||||
#endif
|
||||
554
cue.c
Normal file
554
cue.c
Normal file
@@ -0,0 +1,554 @@
|
||||
/*
|
||||
* Copyright 2016 Various Authors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <ctype.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/mman.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "cue.h"
|
||||
#include "xmalloc.h"
|
||||
#include "file.h"
|
||||
|
||||
#define ASCII_LOWER_TO_UPPER(c) ((c) & ~0x20)
|
||||
|
||||
struct cue_track_proto {
|
||||
struct list_head node;
|
||||
|
||||
char *file; /* owned by cue_parser */
|
||||
uint32_t nr;
|
||||
int32_t pregap;
|
||||
int32_t postgap;
|
||||
int32_t index0;
|
||||
int32_t index1;
|
||||
|
||||
struct cue_meta meta;
|
||||
};
|
||||
|
||||
struct cue_parser {
|
||||
const char *src;
|
||||
size_t len;
|
||||
bool err;
|
||||
|
||||
struct list_head files;
|
||||
struct list_head tracks;
|
||||
size_t num_tracks;
|
||||
|
||||
struct cue_meta meta;
|
||||
};
|
||||
|
||||
struct cue_switch {
|
||||
const char *cmd;
|
||||
void (*parser)(struct cue_parser *p);
|
||||
};
|
||||
|
||||
static struct cue_track_proto *cue_last_proto(struct cue_parser *p)
|
||||
{
|
||||
if (list_empty(&p->tracks))
|
||||
return NULL;
|
||||
return list_entry(p->tracks.prev, struct cue_track_proto, node);
|
||||
}
|
||||
|
||||
static inline void cue_consume(struct cue_parser *p)
|
||||
{
|
||||
p->len--;
|
||||
p->src++;
|
||||
}
|
||||
|
||||
static void cue_set_err(struct cue_parser *p)
|
||||
{
|
||||
p->err = true;
|
||||
}
|
||||
|
||||
static bool cue_str_eq(const char *a, size_t a_len, const char *b, size_t b_len)
|
||||
{
|
||||
if (a_len != b_len)
|
||||
return false;
|
||||
for (size_t i = 0; i < a_len; i++) {
|
||||
if (ASCII_LOWER_TO_UPPER(a[i]) != ASCII_LOWER_TO_UPPER(b[i]))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void cue_skip_spaces(struct cue_parser *p)
|
||||
{
|
||||
while (p->len > 0 && (*p->src == ' ' || *p->src == '\t'))
|
||||
cue_consume(p);
|
||||
}
|
||||
|
||||
static size_t cue_extract_token(struct cue_parser *p, const char **start)
|
||||
{
|
||||
cue_skip_spaces(p);
|
||||
|
||||
bool quoted = p->len > 0 && *p->src == '"';
|
||||
if (quoted)
|
||||
cue_consume(p);
|
||||
|
||||
*start = p->src;
|
||||
|
||||
while (p->len > 0) {
|
||||
char c = *p->src;
|
||||
if (c == '\n' || c == '\r')
|
||||
break;
|
||||
if (quoted) {
|
||||
if (c == '"')
|
||||
break;
|
||||
} else {
|
||||
if (c == ' ' || c == '\t')
|
||||
break;
|
||||
}
|
||||
cue_consume(p);
|
||||
}
|
||||
|
||||
if (quoted) {
|
||||
size_t len = p->src - *start;
|
||||
if (p->len > 0 && *p->src == '"')
|
||||
cue_consume(p);
|
||||
return len;
|
||||
}
|
||||
|
||||
return p->src - *start;
|
||||
}
|
||||
|
||||
static void cue_skip_line(struct cue_parser *p)
|
||||
{
|
||||
while (p->len > 0 && *p->src != '\n' && *p->src != '\r')
|
||||
cue_consume(p);
|
||||
|
||||
if (p->len > 0) {
|
||||
char c = *p->src;
|
||||
cue_consume(p);
|
||||
if (p->len > 0 && c == '\r' && *p->src == '\n')
|
||||
cue_consume(p);
|
||||
}
|
||||
}
|
||||
|
||||
static char *cue_strdup(const char *start, size_t len)
|
||||
{
|
||||
char *s = xnew(char, len + 1);
|
||||
s[len] = 0;
|
||||
memcpy(s, start, len);
|
||||
return s;
|
||||
}
|
||||
|
||||
static uint32_t cue_parse_int(struct cue_parser *p, const char *start, size_t len)
|
||||
{
|
||||
uint32_t val = 0;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
if (!isdigit(start[i])) {
|
||||
cue_set_err(p);
|
||||
return 0;
|
||||
}
|
||||
val = val * 10 + start[i] - '0';
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
static void cue_parse_str(struct cue_parser *p, char **dst)
|
||||
{
|
||||
const char *start;
|
||||
size_t len = cue_extract_token(p, &start);
|
||||
if (!*dst)
|
||||
*dst = cue_strdup(start, len);
|
||||
}
|
||||
|
||||
#define CUE_PARSE_STR(field) \
|
||||
static void cue_parse_##field(struct cue_parser *p) \
|
||||
{ \
|
||||
struct cue_track_proto *t = cue_last_proto(p); \
|
||||
if (t) \
|
||||
cue_parse_str(p, &t->meta.field); \
|
||||
else \
|
||||
cue_parse_str(p, &p->meta.field); \
|
||||
}
|
||||
|
||||
CUE_PARSE_STR(performer)
|
||||
CUE_PARSE_STR(songwriter)
|
||||
CUE_PARSE_STR(title)
|
||||
CUE_PARSE_STR(genre)
|
||||
CUE_PARSE_STR(date)
|
||||
CUE_PARSE_STR(comment)
|
||||
CUE_PARSE_STR(compilation);
|
||||
CUE_PARSE_STR(discnumber);
|
||||
CUE_PARSE_STR(rg_gain);
|
||||
CUE_PARSE_STR(rg_peak);
|
||||
|
||||
static void cue_parse_file(struct cue_parser *p)
|
||||
{
|
||||
struct cue_track_file *f = xnew(struct cue_track_file, 1);
|
||||
|
||||
f->file = NULL;
|
||||
cue_parse_str(p, &f->file);
|
||||
|
||||
list_add_tail(&f->node, &p->files);
|
||||
}
|
||||
|
||||
static char *cue_get_last_file(struct cue_parser *p)
|
||||
{
|
||||
if (list_empty(&p->files))
|
||||
return NULL;
|
||||
|
||||
struct list_head *tail = list_prev(&p->files);
|
||||
return list_entry(tail, struct cue_track_file, node)->file;
|
||||
}
|
||||
|
||||
static void cue_parse_track(struct cue_parser *p)
|
||||
{
|
||||
char *curr_file = cue_get_last_file(p);
|
||||
|
||||
if (!curr_file) {
|
||||
cue_set_err(p);
|
||||
return;
|
||||
}
|
||||
|
||||
const char *nr;
|
||||
size_t len = cue_extract_token(p, &nr);
|
||||
|
||||
uint32_t d = cue_parse_int(p, nr, len);
|
||||
if (p->err)
|
||||
return;
|
||||
|
||||
struct cue_track_proto *t = xnew(struct cue_track_proto, 1);
|
||||
*t = (struct cue_track_proto) {
|
||||
.nr = d,
|
||||
.pregap = -1,
|
||||
.postgap = -1,
|
||||
.index0 = -1,
|
||||
.index1 = -1,
|
||||
.file = curr_file,
|
||||
};
|
||||
|
||||
list_add_tail(&t->node, &p->tracks);
|
||||
p->num_tracks++;
|
||||
}
|
||||
|
||||
static uint32_t cue_parse_time(struct cue_parser *p, const char *start, size_t len)
|
||||
{
|
||||
uint32_t vals[] = { 0, 0, 0 };
|
||||
uint32_t *val = vals;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
if (start[i] == ':') {
|
||||
if (val != &vals[2]) {
|
||||
val++;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (!isdigit(start[i])) {
|
||||
cue_set_err(p);
|
||||
return 0;
|
||||
}
|
||||
*val = *val * 10 + start[i] - '0';
|
||||
}
|
||||
return (vals[0] * 60 + vals[1]) * 75 + vals[2];
|
||||
}
|
||||
|
||||
static void cue_parse_index(struct cue_parser *p)
|
||||
{
|
||||
const char *nr;
|
||||
size_t nr_len = cue_extract_token(p, &nr);
|
||||
|
||||
uint32_t d = cue_parse_int(p, nr, nr_len);
|
||||
if (p->err || d > 1)
|
||||
return;
|
||||
|
||||
const char *offset_str;
|
||||
size_t offset_len = cue_extract_token(p, &offset_str);
|
||||
|
||||
uint32_t offset = cue_parse_time(p, offset_str, offset_len);
|
||||
if (p->err)
|
||||
return;
|
||||
|
||||
struct cue_track_proto *last = cue_last_proto(p);
|
||||
if (!last)
|
||||
return;
|
||||
|
||||
if (d == 0)
|
||||
last->index0 = offset;
|
||||
else
|
||||
last->index1 = offset;
|
||||
}
|
||||
|
||||
static void cue_parse_cmd(struct cue_parser *p, struct cue_switch *s)
|
||||
{
|
||||
const char *start;
|
||||
size_t len = cue_extract_token(p, &start);
|
||||
|
||||
while (s->cmd) {
|
||||
if (cue_str_eq(start, len, s->cmd, strlen(s->cmd))) {
|
||||
s->parser(p);
|
||||
return;
|
||||
}
|
||||
s++;
|
||||
}
|
||||
}
|
||||
|
||||
static void cue_parse_rem(struct cue_parser *p)
|
||||
{
|
||||
struct cue_switch cmds[] = {
|
||||
{ "DATE", cue_parse_date },
|
||||
{ "GENRE", cue_parse_genre },
|
||||
{ "COMMENT", cue_parse_comment },
|
||||
{ "COMPILATION", cue_parse_compilation },
|
||||
{ "DISCNUMBER", cue_parse_discnumber },
|
||||
{ "REPLAYGAIN_ALBUM_GAIN", cue_parse_rg_gain },
|
||||
{ "REPLAYGAIN_TRACK_GAIN", cue_parse_rg_gain },
|
||||
{ "REPLAYGAIN_ALBUM_PEAK", cue_parse_rg_peak },
|
||||
{ "REPLAYGAIN_TRACK_PEAK", cue_parse_rg_peak },
|
||||
{ 0 },
|
||||
};
|
||||
|
||||
cue_parse_cmd(p, cmds);
|
||||
}
|
||||
|
||||
static void cue_parse_gap(struct cue_parser *p, bool post)
|
||||
{
|
||||
const char *gap_str;
|
||||
size_t gap_len = cue_extract_token(p, &gap_str);
|
||||
|
||||
uint32_t gap = cue_parse_time(p, gap_str, gap_len);
|
||||
if (p->err)
|
||||
return;
|
||||
|
||||
struct cue_track_proto *last = cue_last_proto(p);
|
||||
if (!last)
|
||||
return;
|
||||
|
||||
if (post)
|
||||
last->postgap = gap;
|
||||
else
|
||||
last->pregap = gap;
|
||||
}
|
||||
|
||||
static void cue_parse_pregap(struct cue_parser *p)
|
||||
{
|
||||
cue_parse_gap(p, false);
|
||||
}
|
||||
|
||||
static void cue_parse_postgap(struct cue_parser *p)
|
||||
{
|
||||
cue_parse_gap(p, true);
|
||||
}
|
||||
|
||||
static void cue_parse_line(struct cue_parser *p)
|
||||
{
|
||||
struct cue_switch cmds[] = {
|
||||
{ "FILE", cue_parse_file },
|
||||
{ "PERFORMER", cue_parse_performer },
|
||||
{ "SONGWRITER", cue_parse_songwriter },
|
||||
{ "TITLE", cue_parse_title },
|
||||
{ "TRACK", cue_parse_track },
|
||||
{ "INDEX", cue_parse_index },
|
||||
{ "REM", cue_parse_rem },
|
||||
{ "PREGAP", cue_parse_pregap },
|
||||
{ "POSTGAP", cue_parse_postgap },
|
||||
{ 0 },
|
||||
};
|
||||
|
||||
cue_parse_cmd(p, cmds);
|
||||
cue_skip_line(p);
|
||||
}
|
||||
|
||||
static void cue_post_process(struct cue_parser *p)
|
||||
{
|
||||
if (list_empty(&p->files) || p->num_tracks == 0)
|
||||
goto err;
|
||||
|
||||
struct cue_track_proto *t;
|
||||
struct cue_track_proto *prev = NULL;
|
||||
list_for_each_entry(t, &p->tracks, node) {
|
||||
if (prev && prev->nr >= t->nr)
|
||||
goto err;
|
||||
|
||||
if (t->index0 == -1 && t->index1 == -1)
|
||||
goto err;
|
||||
|
||||
/*
|
||||
* NOTE: if we don't have index1, then the pregap spans the
|
||||
* whole track, so we would have an empty track.
|
||||
* This is pretty useless, so we do the simple thing
|
||||
*/
|
||||
if (t->index1 == -1)
|
||||
t->index1 = t->index0;
|
||||
|
||||
if (t->index0 == -1)
|
||||
t->index0 = t->index1;
|
||||
|
||||
if (prev && prev->file == t->file && prev->index1 > t->index0)
|
||||
goto err;
|
||||
|
||||
prev = t;
|
||||
}
|
||||
|
||||
return;
|
||||
err:
|
||||
cue_set_err(p);
|
||||
}
|
||||
|
||||
static void cue_meta_move(struct cue_meta *l, struct cue_meta *r)
|
||||
{
|
||||
*l = *r;
|
||||
*r = (struct cue_meta) { 0 };
|
||||
}
|
||||
|
||||
static struct cue_sheet *cue_parser_to_sheet(struct cue_parser *p)
|
||||
{
|
||||
struct cue_sheet *s = xnew(struct cue_sheet, 1);
|
||||
|
||||
/* Move file list */
|
||||
list_add(&s->files, &p->files);
|
||||
list_del_init(&p->files);
|
||||
|
||||
s->tracks = xnew(struct cue_track, p->num_tracks);
|
||||
s->num_tracks = p->num_tracks;
|
||||
|
||||
cue_meta_move(&s->meta, &p->meta);
|
||||
|
||||
size_t i = 0;
|
||||
struct cue_track_proto *tp = NULL;
|
||||
struct cue_track_proto *prev_tp = NULL;
|
||||
list_for_each_entry(tp, &p->tracks, node) {
|
||||
struct cue_track *t = &s->tracks[i];
|
||||
t->file = tp->file;
|
||||
t->offset = tp->index1 / 75.0;
|
||||
t->length = -1;
|
||||
t->number = tp->nr;
|
||||
|
||||
if (i > 0 && t->file == s->tracks[i - 1].file) {
|
||||
s->tracks[i - 1].length = (tp->index0 - prev_tp->index1) / 75.0;
|
||||
}
|
||||
|
||||
cue_meta_move(&t->meta, &tp->meta);
|
||||
|
||||
prev_tp = tp;
|
||||
i++;
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
static void cue_meta_free(struct cue_meta *m)
|
||||
{
|
||||
free(m->performer);
|
||||
free(m->songwriter);
|
||||
free(m->title);
|
||||
free(m->genre);
|
||||
free(m->date);
|
||||
free(m->comment);
|
||||
free(m->compilation);
|
||||
free(m->discnumber);
|
||||
free(m->rg_gain);
|
||||
free(m->rg_peak);
|
||||
}
|
||||
|
||||
static void cue_free_files(struct list_head *files)
|
||||
{
|
||||
struct cue_track_file *tf, *next;
|
||||
list_for_each_entry_safe(tf, next, files, node) {
|
||||
free(tf->file);
|
||||
free(tf);
|
||||
}
|
||||
}
|
||||
|
||||
static void cue_parser_free(struct cue_parser *p)
|
||||
{
|
||||
struct cue_track_proto *t, *next;
|
||||
list_for_each_entry_safe(t, next, &p->tracks, node) {
|
||||
cue_meta_free(&t->meta);
|
||||
free(t);
|
||||
}
|
||||
|
||||
cue_free_files(&p->files);
|
||||
|
||||
cue_meta_free(&p->meta);
|
||||
}
|
||||
|
||||
struct cue_sheet *cue_parse(const char *src, size_t len)
|
||||
{
|
||||
struct cue_sheet *res = NULL;
|
||||
|
||||
struct cue_parser p = {
|
||||
.src = src,
|
||||
.len = len,
|
||||
};
|
||||
list_init(&p.tracks);
|
||||
list_init(&p.files);
|
||||
|
||||
while (p.len > 0 && !p.err)
|
||||
cue_parse_line(&p);
|
||||
|
||||
if (p.err)
|
||||
goto out;
|
||||
|
||||
cue_post_process(&p);
|
||||
|
||||
if (p.err)
|
||||
goto out;
|
||||
|
||||
res = cue_parser_to_sheet(&p);
|
||||
|
||||
out:
|
||||
cue_parser_free(&p);
|
||||
return res;
|
||||
}
|
||||
|
||||
struct cue_sheet *cue_from_file(const char *file)
|
||||
{
|
||||
ssize_t size;
|
||||
char *buf = mmap_file(file, &size);
|
||||
if (size == -1)
|
||||
return NULL;
|
||||
struct cue_sheet *rv;
|
||||
|
||||
// Check for UTF-8 BOM, and skip ahead if found
|
||||
if (size >= 3 && memcmp(buf, "\xEF\xBB\xBF", 3) == 0) {
|
||||
rv = cue_parse(buf + 3, size - 3);
|
||||
} else {
|
||||
rv = cue_parse(buf, size);
|
||||
}
|
||||
|
||||
munmap(buf, size);
|
||||
return rv;
|
||||
}
|
||||
|
||||
void cue_free(struct cue_sheet *s)
|
||||
{
|
||||
size_t i;
|
||||
for (i = 0; i < s->num_tracks; i++)
|
||||
cue_meta_free(&s->tracks[i].meta);
|
||||
free(s->tracks);
|
||||
|
||||
cue_free_files(&s->files);
|
||||
|
||||
cue_meta_free(&s->meta);
|
||||
free(s);
|
||||
}
|
||||
|
||||
struct cue_track *cue_get_track(struct cue_sheet *s, size_t n)
|
||||
{
|
||||
size_t i;
|
||||
for (i = 0; i < s->num_tracks; i++) {
|
||||
struct cue_track *t = &s->tracks[i];
|
||||
if (t->number == n)
|
||||
return t;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
66
cue.h
Normal file
66
cue.h
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2016 Various Authors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CMUS_CUE_H
|
||||
#define CMUS_CUE_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "list.h"
|
||||
|
||||
struct cue_meta {
|
||||
char *performer;
|
||||
char *songwriter;
|
||||
char *title;
|
||||
char *genre;
|
||||
char *date;
|
||||
char *comment;
|
||||
char *compilation;
|
||||
char *discnumber;
|
||||
char *rg_gain;
|
||||
char *rg_peak;
|
||||
};
|
||||
|
||||
struct cue_track {
|
||||
char *file; /* owned by cue_sheet */
|
||||
double offset;
|
||||
double length;
|
||||
size_t number;
|
||||
|
||||
struct cue_meta meta;
|
||||
};
|
||||
|
||||
struct cue_track_file {
|
||||
struct list_head node;
|
||||
|
||||
char *file;
|
||||
};
|
||||
|
||||
struct cue_sheet {
|
||||
struct list_head files;
|
||||
struct cue_track *tracks;
|
||||
size_t num_tracks;
|
||||
|
||||
struct cue_meta meta;
|
||||
};
|
||||
|
||||
struct cue_sheet *cue_parse(const char *src, size_t len);
|
||||
struct cue_sheet *cue_from_file(const char *file);
|
||||
void cue_free(struct cue_sheet *s);
|
||||
struct cue_track *cue_get_track(struct cue_sheet *s, size_t n);
|
||||
|
||||
#endif
|
||||
81
cue_utils.c
Normal file
81
cue_utils.c
Normal file
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright (C) 2008-2013 Various Authors
|
||||
* Copyright (C) 2011 Gregory Petrosyan
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "path.h"
|
||||
#include "utils.h"
|
||||
#include "cue_utils.h"
|
||||
#include "xmalloc.h"
|
||||
#include "cue.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
int is_cue(const char *filename)
|
||||
{
|
||||
const char *ext = get_extension(filename);
|
||||
return ext != NULL && strcmp(ext, "cue") == 0;
|
||||
}
|
||||
|
||||
|
||||
int cue_get_track_nums(const char *filename, int **out_nums)
|
||||
{
|
||||
struct cue_sheet *cd = cue_from_file(filename);
|
||||
if (!cd)
|
||||
return -1;
|
||||
|
||||
int n = cd->num_tracks;
|
||||
*out_nums = xnew(int, n);
|
||||
|
||||
int i;
|
||||
for (i = 0; i < n; i++)
|
||||
(*out_nums)[i] = cd->tracks[i].number;
|
||||
|
||||
cue_free(cd);
|
||||
return n;
|
||||
}
|
||||
|
||||
|
||||
int cue_get_files(const char *filename, char ***out_files)
|
||||
{
|
||||
struct cue_sheet *cd = cue_from_file(filename);
|
||||
if (!cd)
|
||||
return -1;
|
||||
|
||||
int n = list_len(&cd->files);
|
||||
*out_files = xnew(char *, n);
|
||||
|
||||
int i = 0;
|
||||
struct cue_track_file *tf;
|
||||
list_for_each_entry(tf, &cd->files, node) {
|
||||
(*out_files)[i] = tf->file;
|
||||
tf->file = NULL;
|
||||
i++;
|
||||
}
|
||||
|
||||
cue_free(cd);
|
||||
return n;
|
||||
}
|
||||
|
||||
|
||||
char *construct_cue_url(const char *cue_filename, int track_n)
|
||||
{
|
||||
char buf[4096] = {0};
|
||||
|
||||
snprintf(buf, sizeof(buf), "cue://%s/%d", cue_filename, track_n);
|
||||
|
||||
return xstrdup(buf);
|
||||
}
|
||||
30
cue_utils.h
Normal file
30
cue_utils.h
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (C) 2008-2013 Various Authors
|
||||
* Copyright (C) 2011 Gregory Petrosyan
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CMUS_CUE_UTILS_H
|
||||
#define CMUS_CUE_UTILS_H
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
int is_cue(const char *filename);
|
||||
int cue_get_track_nums(const char *filename, int **out_nums);
|
||||
int cue_get_files(const char *filename, char ***out_files);
|
||||
char *construct_cue_url(const char *cue_filename, int track_n);
|
||||
|
||||
|
||||
#endif
|
||||
26
data/amazon.theme
Normal file
26
data/amazon.theme
Normal file
@@ -0,0 +1,26 @@
|
||||
# May, 2020 by @nitwit
|
||||
set color_win_fg=228
|
||||
set color_win_bg=234
|
||||
set color_cmdline_bg=234
|
||||
set color_cmdline_fg=220
|
||||
set color_error=9
|
||||
set color_info=136
|
||||
set color_separator=202
|
||||
set color_statusline_bg=232
|
||||
set color_statusline_fg=208
|
||||
set color_statusline_progress_bg=166
|
||||
set color_statusline_progress_fg=232
|
||||
set color_titleline_bg=232
|
||||
set color_titleline_fg=220
|
||||
set color_win_title_bg=232
|
||||
set color_win_title_fg=220
|
||||
set color_win_cur=9
|
||||
set color_win_cur_sel_bg=220
|
||||
set color_win_cur_sel_fg=88
|
||||
set color_win_inactive_cur_sel_bg=214
|
||||
set color_win_inactive_cur_sel_fg=88
|
||||
set color_win_sel_bg=220
|
||||
set color_win_sel_fg=232
|
||||
set color_win_inactive_sel_bg=166
|
||||
set color_win_inactive_sel_fg=232
|
||||
set color_win_dir=208
|
||||
25
data/cyan.theme
Normal file
25
data/cyan.theme
Normal file
@@ -0,0 +1,25 @@
|
||||
set color_cmdline_bg=default
|
||||
set color_cmdline_fg=cyan
|
||||
set color_error=lightred
|
||||
set color_info=lightyellow
|
||||
set color_separator=cyan
|
||||
set color_statusline_bg=default
|
||||
set color_statusline_fg=cyan
|
||||
set color_statusline_progress_bg=cyan
|
||||
set color_statusline_progress_fg=black
|
||||
set color_titleline_bg=blue
|
||||
set color_titleline_fg=white
|
||||
set color_win_bg=default
|
||||
set color_win_cur=green
|
||||
set color_win_cur_sel_bg=blue
|
||||
set color_win_cur_sel_fg=lightyellow
|
||||
set color_win_dir=lightblue
|
||||
set color_win_fg=cyan
|
||||
set color_win_inactive_cur_sel_bg=cyan
|
||||
set color_win_inactive_cur_sel_fg=black
|
||||
set color_win_inactive_sel_bg=gray
|
||||
set color_win_inactive_sel_fg=black
|
||||
set color_win_sel_bg=red
|
||||
set color_win_sel_fg=gray
|
||||
set color_win_title_bg=blue
|
||||
set color_win_title_fg=white
|
||||
25
data/default.theme
Normal file
25
data/default.theme
Normal file
@@ -0,0 +1,25 @@
|
||||
set color_cmdline_bg=default
|
||||
set color_cmdline_fg=default
|
||||
set color_error=lightred
|
||||
set color_info=lightyellow
|
||||
set color_separator=blue
|
||||
set color_statusline_bg=gray
|
||||
set color_statusline_fg=black
|
||||
set color_statusline_progress_bg=blue
|
||||
set color_statusline_progress_fg=white
|
||||
set color_titleline_bg=blue
|
||||
set color_titleline_fg=white
|
||||
set color_win_bg=default
|
||||
set color_win_cur=lightyellow
|
||||
set color_win_cur_sel_bg=blue
|
||||
set color_win_cur_sel_fg=lightyellow
|
||||
set color_win_dir=lightblue
|
||||
set color_win_fg=default
|
||||
set color_win_inactive_cur_sel_bg=gray
|
||||
set color_win_inactive_cur_sel_fg=lightyellow
|
||||
set color_win_inactive_sel_bg=gray
|
||||
set color_win_inactive_sel_fg=black
|
||||
set color_win_sel_bg=blue
|
||||
set color_win_sel_fg=white
|
||||
set color_win_title_bg=blue
|
||||
set color_win_title_fg=white
|
||||
26
data/dracula.theme
Normal file
26
data/dracula.theme
Normal file
@@ -0,0 +1,26 @@
|
||||
### 'Dracula' theme for CMus (for 255 color terms)
|
||||
set color_cmdline_bg=default
|
||||
set color_cmdline_fg=231
|
||||
set color_error=009
|
||||
set color_info=231
|
||||
set color_separator=004
|
||||
set color_statusline_bg=default
|
||||
set color_statusline_fg=231
|
||||
set color_statusline_progress_bg=010
|
||||
set color_statusline_progress_fg=236
|
||||
set color_titleline_bg=010
|
||||
set color_titleline_fg=236
|
||||
set color_win_bg=default
|
||||
set color_win_cur=010
|
||||
set color_win_cur_sel_bg=010
|
||||
set color_win_cur_sel_fg=016
|
||||
set color_win_dir=004
|
||||
set color_win_fg=004
|
||||
set color_win_inactive_cur_sel_bg=default
|
||||
set color_win_inactive_cur_sel_fg=014
|
||||
set color_win_inactive_sel_bg=default
|
||||
set color_win_inactive_sel_fg=014
|
||||
set color_win_sel_bg=004
|
||||
set color_win_sel_fg=236
|
||||
set color_win_title_bg=default
|
||||
set color_win_title_fg=014
|
||||
26
data/gray-88.theme
Normal file
26
data/gray-88.theme
Normal file
@@ -0,0 +1,26 @@
|
||||
# 88 color gray theme for urxvt
|
||||
set color_cmdline_bg=80
|
||||
set color_cmdline_fg=85
|
||||
set color_error=lightgreen
|
||||
set color_info=green
|
||||
set color_separator=darkgray
|
||||
set color_statusline_bg=80
|
||||
set color_statusline_fg=85
|
||||
set color_statusline_progress_bg=darkgray
|
||||
set color_statusline_progress_fg=85
|
||||
set color_titleline_bg=darkgray
|
||||
set color_titleline_fg=85
|
||||
set color_win_bg=80
|
||||
set color_win_cur=lightgreen
|
||||
set color_win_cur_sel_bg=82
|
||||
set color_win_cur_sel_fg=lightyellow
|
||||
set color_win_dir=83
|
||||
set color_win_fg=85
|
||||
set color_win_inactive_cur_sel_bg=darkgray
|
||||
set color_win_inactive_cur_sel_fg=lightgreen
|
||||
set color_win_inactive_sel_bg=darkgray
|
||||
set color_win_inactive_sel_fg=black
|
||||
set color_win_sel_bg=82
|
||||
set color_win_sel_fg=black
|
||||
set color_win_title_bg=darkgray
|
||||
set color_win_title_fg=85
|
||||
64
data/green-mono-88.theme
Normal file
64
data/green-mono-88.theme
Normal file
@@ -0,0 +1,64 @@
|
||||
# Clay's Green Monochromatic Color Scheme 1.0 and example file.
|
||||
#
|
||||
# This theme is designed to be 1) Very readable on high-resolution or blurry
|
||||
# displays (dying CRTs and TVs, for example). 2) Be more accessable to color-
|
||||
# blind people, by removing chroma cues in favor of brightness and color
|
||||
# inversion. 3) Show how to incorperate named and numbered colors into a
|
||||
# single theme. 4) Provide a stating point for creating other mono-chromatic
|
||||
# themes.
|
||||
#
|
||||
# Questions, comments (just a heads-up that you use/like this, especially),
|
||||
# and such should be directed to clay.barnes@gmail.com
|
||||
|
||||
##### Common User Interface Components #########################################
|
||||
# Default text color
|
||||
set color_win_fg=green
|
||||
|
||||
# Overall background color
|
||||
set color_win_bg=black
|
||||
|
||||
# Command-line colors
|
||||
set color_cmdline_bg=black
|
||||
set color_cmdline_fg=green
|
||||
set color_error=lightgreen
|
||||
set color_info=green
|
||||
set color_separator=green
|
||||
|
||||
# Bottom status line
|
||||
set color_statusline_bg=black
|
||||
set color_statusline_fg=green
|
||||
set color_statusline_progress_bg=green
|
||||
set color_statusline_progress_fg=black
|
||||
|
||||
# Bottom title line
|
||||
set color_titleline_bg=green
|
||||
set color_titleline_fg=black
|
||||
|
||||
# Top title area
|
||||
set color_win_title_bg=green
|
||||
set color_win_title_fg=black
|
||||
##### Playing File Colors ######################################################
|
||||
# Unselected currently playing track's text
|
||||
set color_win_cur=lightgreen
|
||||
|
||||
# Active selection for currently playing track
|
||||
set color_win_cur_sel_bg=28
|
||||
set color_win_cur_sel_fg=darkgray
|
||||
|
||||
# Inactive selection for currently playing track
|
||||
set color_win_inactive_cur_sel_bg=20
|
||||
set color_win_inactive_cur_sel_fg=lightgreen
|
||||
|
||||
##### Non-Playing File Colors ##################################################
|
||||
# Active selection
|
||||
set color_win_sel_bg=green
|
||||
set color_win_sel_fg=black
|
||||
|
||||
# Inactive selection
|
||||
set color_win_inactive_sel_bg=20
|
||||
set color_win_inactive_sel_fg=black
|
||||
|
||||
##### File Browser View Colors #################################################
|
||||
# Directory listing color
|
||||
set color_win_dir=lightgreen
|
||||
|
||||
67
data/green.theme
Normal file
67
data/green.theme
Normal file
@@ -0,0 +1,67 @@
|
||||
# Another Green Theme for cmus
|
||||
#
|
||||
# Change this to a red or blue theme by doing the following in vim:
|
||||
#
|
||||
# :%s/lightgreen/light<color>/g
|
||||
# :%s/green/<color>/g
|
||||
#
|
||||
# - roobert@gmail.com
|
||||
|
||||
# Directory colors
|
||||
set color_win_dir=default
|
||||
|
||||
# Normal text
|
||||
set color_win_fg=default
|
||||
|
||||
# Window background color.
|
||||
set color_win_bg=default
|
||||
|
||||
# Command line color.
|
||||
set color_cmdline_bg=default
|
||||
set color_cmdline_fg=default
|
||||
|
||||
# Color of error messages displayed on the command line.
|
||||
set color_error=lightred
|
||||
|
||||
# Color of informational messages displayed on the command line.
|
||||
set color_info=lightgreen
|
||||
|
||||
# Color of currently playing track.
|
||||
set color_win_cur=lightgreen
|
||||
|
||||
# Color of the separator line between windows in view (1).
|
||||
set color_separator=black
|
||||
|
||||
# Color of window titles (topmost line of the screen).
|
||||
set color_win_title_bg=default
|
||||
set color_win_title_fg=green
|
||||
|
||||
# Status line color.
|
||||
set color_statusline_bg=default
|
||||
set color_statusline_fg=gray
|
||||
set color_statusline_progress_bg=234
|
||||
set color_statusline_progress_fg=gray
|
||||
|
||||
# Color of the line displaying currently playing track.
|
||||
set color_titleline_bg=default
|
||||
set color_titleline_fg=green
|
||||
|
||||
# Color of the selected row which is also the currently playing track in active window.
|
||||
set color_win_cur_sel_bg=default
|
||||
set color_win_cur_sel_fg=white
|
||||
|
||||
# Color of the selected row which is also the currently playing track in inactive window.
|
||||
set color_win_inactive_cur_sel_bg=default
|
||||
set color_win_inactive_cur_sel_fg=lightgreen
|
||||
|
||||
# Color of selected row in inactive window.
|
||||
set color_win_inactive_sel_bg=default
|
||||
set color_win_inactive_sel_fg=default
|
||||
|
||||
# Color of selected row in active window.
|
||||
set color_win_sel_bg=default
|
||||
set color_win_sel_fg=green
|
||||
|
||||
# Command line color.
|
||||
set color_cmdline_bg=default
|
||||
set color_cmdline_fg=default
|
||||
55
data/gruvbox-alt.theme
Normal file
55
data/gruvbox-alt.theme
Normal file
@@ -0,0 +1,55 @@
|
||||
# colors from gruvbox: https://github.com/morhetz/gruvbox
|
||||
|
||||
# default text color: fg
|
||||
set color_win_fg=223
|
||||
|
||||
# overall background color: bg
|
||||
set color_win_bg=235
|
||||
|
||||
# top title area: bg2/light-blue
|
||||
set color_win_title_bg=239
|
||||
set color_win_title_fg=109
|
||||
|
||||
# separator line between windows in view (1): dark-gray
|
||||
set color_separator=245
|
||||
|
||||
# bottom title line: dark-gray/bg0
|
||||
set color_titleline_bg=245
|
||||
set color_titleline_fg=235
|
||||
|
||||
# bottom status line: bg0_s/dark-aqua
|
||||
set color_statusline_bg=236
|
||||
set color_statusline_fg=72
|
||||
set color_statusline_progress_bg=234
|
||||
set color_statusline_progress_fg=72
|
||||
|
||||
# command-line colors: bg/orange/light-red/light-yellow
|
||||
set color_cmdline_bg=235
|
||||
set color_cmdline_fg=208
|
||||
set color_error=167
|
||||
set color_info=214
|
||||
|
||||
##### playing file colors ######################################################
|
||||
# unselected currently playing track's text: light-green
|
||||
set color_win_cur=142
|
||||
|
||||
# active selection for currently playing track: bg1/light-green
|
||||
set color_win_cur_sel_bg=237
|
||||
set color_win_cur_sel_fg=142
|
||||
|
||||
# inactive selection for currently playing track: bg/light-green
|
||||
set color_win_inactive_cur_sel_bg=235
|
||||
set color_win_inactive_cur_sel_fg=142
|
||||
|
||||
##### non-playing file colors ##################################################
|
||||
# active selection: bg1/fg0
|
||||
set color_win_sel_bg=237
|
||||
set color_win_sel_fg=229
|
||||
|
||||
# inactive selection: bg/fg0
|
||||
set color_win_inactive_sel_bg=235
|
||||
set color_win_inactive_sel_fg=229
|
||||
|
||||
##### file browser view colors #################################################
|
||||
# directory listing color: fg
|
||||
set color_win_dir=223
|
||||
53
data/gruvbox-warm.theme
Normal file
53
data/gruvbox-warm.theme
Normal file
@@ -0,0 +1,53 @@
|
||||
# colors from gruvbox: https://github.com/morhetz/gruvbox
|
||||
|
||||
# default text color (note: looks best with #ebdbb2)
|
||||
set color_win_fg=default
|
||||
|
||||
# overall background color (note: looks best with #1d2021)
|
||||
set color_win_bg=default
|
||||
|
||||
# command-line colors
|
||||
set color_cmdline_bg=default
|
||||
set color_cmdline_fg=default
|
||||
set color_error=124
|
||||
set color_info=72
|
||||
set color_separator=246
|
||||
|
||||
# bottom status line
|
||||
set color_statusline_bg=236
|
||||
set color_statusline_fg=66
|
||||
set color_statusline_progress_bg=237
|
||||
set color_statusline_progress_fg=66
|
||||
|
||||
# bottom title line
|
||||
set color_titleline_bg=237
|
||||
set color_titleline_fg=172
|
||||
|
||||
# top title area
|
||||
set color_win_title_bg=237
|
||||
set color_win_title_fg=172
|
||||
|
||||
##### playing file colors ######################################################
|
||||
# unselected currently playing track's text
|
||||
set color_win_cur=66
|
||||
|
||||
# active selection for currently playing track
|
||||
set color_win_cur_sel_bg=237
|
||||
set color_win_cur_sel_fg=109
|
||||
|
||||
# inactive selection for currently playing track
|
||||
set color_win_inactive_cur_sel_bg=236
|
||||
set color_win_inactive_cur_sel_fg=109
|
||||
|
||||
##### non-playing file colors ##################################################
|
||||
# active selection
|
||||
set color_win_sel_bg=237
|
||||
set color_win_sel_fg=223
|
||||
|
||||
# inactive selection
|
||||
set color_win_inactive_sel_bg=236
|
||||
set color_win_inactive_sel_fg=223
|
||||
|
||||
##### file browser view colors #################################################
|
||||
# directory listing color
|
||||
set color_win_dir=229
|
||||
51
data/gruvbox.theme
Normal file
51
data/gruvbox.theme
Normal file
@@ -0,0 +1,51 @@
|
||||
# colors from gruvbox: https://github.com/morhetz/gruvbox
|
||||
# default text color
|
||||
set color_win_fg=default
|
||||
|
||||
# overall background color
|
||||
set color_win_bg=default
|
||||
|
||||
# command-line colors
|
||||
set color_cmdline_bg=default
|
||||
set color_cmdline_fg=default
|
||||
set color_error=124
|
||||
set color_info=172
|
||||
set color_separator=246
|
||||
|
||||
# bottom status line
|
||||
set color_statusline_bg=237
|
||||
set color_statusline_fg=72
|
||||
set color_statusline_progress_bg=234
|
||||
set color_statusline_progress_fg=72
|
||||
|
||||
# bottom title line
|
||||
set color_titleline_bg=236
|
||||
set color_titleline_fg=142
|
||||
|
||||
# top title area
|
||||
set color_win_title_bg=246
|
||||
set color_win_title_fg=235
|
||||
##### playing file colors ######################################################
|
||||
# unselected currently playing track's text
|
||||
set color_win_cur=175
|
||||
|
||||
# active selection for currently playing track
|
||||
set color_win_cur_sel_bg=237
|
||||
set color_win_cur_sel_fg=175
|
||||
|
||||
# inactive selection for currently playing track
|
||||
set color_win_inactive_cur_sel_bg=236
|
||||
set color_win_inactive_cur_sel_fg=175
|
||||
|
||||
##### non-playing file colors ##################################################
|
||||
# active selection
|
||||
set color_win_sel_bg=237
|
||||
set color_win_sel_fg=229
|
||||
|
||||
# inactive selection
|
||||
set color_win_inactive_sel_bg=236
|
||||
set color_win_inactive_sel_fg=229
|
||||
|
||||
##### file browser view colors #################################################
|
||||
# directory listing color
|
||||
set color_win_dir=229
|
||||
54
data/jellybeans.theme
Normal file
54
data/jellybeans.theme
Normal file
@@ -0,0 +1,54 @@
|
||||
# Jellybeans colorscheme
|
||||
# by: Daniel Rivas (https://github.com/DanielRS)
|
||||
# based on: https://github.com/nanotech/jellybeans.vim
|
||||
# term codes from: http://vim.wikia.com/wiki/Xterm256_color_names_for_console_Vim
|
||||
|
||||
# Window
|
||||
set color_cmdline_bg=233
|
||||
set color_cmdline_fg=188
|
||||
set color_cmdline_attr=default
|
||||
|
||||
set color_win_title_bg=237
|
||||
set color_win_title_fg=110
|
||||
set color_win_title_attr=default
|
||||
|
||||
set color_win_fg=188
|
||||
set color_win_bg=233
|
||||
set color_win_attr=default
|
||||
|
||||
set color_separator=107
|
||||
|
||||
# Bottom status
|
||||
set color_titleline_bg=186
|
||||
set color_titleline_fg=237
|
||||
set color_titleline_attr=default
|
||||
|
||||
set color_statusline_bg=237
|
||||
set color_statusline_fg=145
|
||||
set color_statusline_progress_bg=233
|
||||
set color_statusline_progress_fg=145
|
||||
|
||||
# Text
|
||||
set color_win_cur=183
|
||||
set color_win_dir=186
|
||||
|
||||
# Menu
|
||||
set color_win_sel_bg=254
|
||||
set color_win_sel_fg=237
|
||||
set color_win_sel_attr=default
|
||||
|
||||
set color_win_inactive_sel_bg=237
|
||||
set color_win_inactive_sel_fg=145
|
||||
set color_win_inactive_sel_attr=default
|
||||
|
||||
set color_win_cur_sel_bg=186
|
||||
set color_win_cur_sel_fg=237
|
||||
set color_win_cur_sel_attr=default
|
||||
|
||||
set color_win_inactive_cur_sel_bg=242
|
||||
set color_win_inactive_cur_sel_fg=186
|
||||
set color_win_inactive_cur_sel_attr=default
|
||||
|
||||
# Messages
|
||||
set color_error=167
|
||||
set color_info=186
|
||||
29
data/night.theme
Normal file
29
data/night.theme
Normal file
@@ -0,0 +1,29 @@
|
||||
### 'Night' theme for CMus (for 255 color terms)
|
||||
### (C) 2013 Mladen Pejaković <pejakm@autistici.org>
|
||||
### {{{
|
||||
set color_cmdline_bg=default
|
||||
set color_cmdline_fg=255
|
||||
set color_error=196
|
||||
set color_info=220
|
||||
set color_separator=002
|
||||
set color_statusline_bg=234
|
||||
set color_statusline_fg=045
|
||||
set color_statusline_progress_bg=default
|
||||
set color_statusline_progress_fg=045
|
||||
set color_titleline_bg=default
|
||||
set color_titleline_fg=046
|
||||
set color_win_bg=default
|
||||
set color_win_cur=046
|
||||
set color_win_cur_sel_bg=235
|
||||
set color_win_cur_sel_fg=118
|
||||
set color_win_dir=250
|
||||
set color_win_fg=255
|
||||
set color_win_inactive_cur_sel_bg=233
|
||||
set color_win_inactive_cur_sel_fg=046
|
||||
set color_win_inactive_sel_bg=234
|
||||
set color_win_inactive_sel_fg=002
|
||||
set color_win_sel_bg=235
|
||||
set color_win_sel_fg=045
|
||||
set color_win_title_bg=234
|
||||
set color_win_title_fg=045
|
||||
### }}}
|
||||
141
data/rc
Normal file
141
data/rc
Normal file
@@ -0,0 +1,141 @@
|
||||
# Playback
|
||||
|
||||
bind common b player-next
|
||||
bind common B player-next-album
|
||||
bind common c player-pause
|
||||
bind common x player-play
|
||||
bind common z player-prev
|
||||
bind common Z player-prev-album
|
||||
bind common v player-stop
|
||||
|
||||
bind common ] vol +0 +1%
|
||||
bind common [ vol +1% +0
|
||||
bind common + vol +10%
|
||||
bind common = vol +10%
|
||||
bind common } vol -0 -1%
|
||||
bind common { vol -1% -0
|
||||
bind common - vol -10%
|
||||
|
||||
bind common , seek -1m
|
||||
bind common . seek +1m
|
||||
|
||||
bind common h seek -5
|
||||
bind common l seek +5
|
||||
|
||||
bind common left seek -5
|
||||
bind common right seek +5
|
||||
|
||||
bind common mlb_click_bar player-pause
|
||||
bind common mlb_click_bar_right player-pause
|
||||
bind common mouse_scroll_up_bar seek +5
|
||||
bind common mouse_scroll_down_bar seek -5
|
||||
bind common mouse_scroll_up_bar_right vol +1%
|
||||
bind common mouse_scroll_down_bar_right vol -1%
|
||||
|
||||
# Setting toggles
|
||||
|
||||
bind common m toggle aaa_mode
|
||||
bind common C toggle continue
|
||||
bind common M toggle play_library
|
||||
bind common o toggle play_sorted
|
||||
bind common r toggle repeat
|
||||
bind common ^R toggle repeat_current
|
||||
bind common t toggle show_remaining_time
|
||||
bind common s toggle shuffle
|
||||
bind common f toggle follow
|
||||
|
||||
# Commands
|
||||
|
||||
bind common q quit -i
|
||||
bind common ^C echo Type :quit<enter> to exit cmus.
|
||||
bind common I echo {}
|
||||
|
||||
# note: the single space at the end is intentional
|
||||
bind common ! push shell
|
||||
|
||||
# View/window navigation
|
||||
|
||||
bind common 1 view tree
|
||||
bind common 2 view sorted
|
||||
bind common 3 view playlist
|
||||
bind common 4 view queue
|
||||
bind common 5 view browser
|
||||
bind common 6 view filters
|
||||
bind common 7 view settings
|
||||
|
||||
bind common mouse_scroll_up_title left-view -n
|
||||
bind common mouse_scroll_down_title right-view -n
|
||||
|
||||
bind common tab win-next
|
||||
|
||||
bind common ^L refresh
|
||||
|
||||
# Navigation
|
||||
|
||||
bind common ^Y win-scroll-up
|
||||
bind common ^E win-scroll-down
|
||||
bind common ^B win-page-up
|
||||
bind common ^F win-page-down
|
||||
bind common ^U win-half-page-up
|
||||
bind common ^D win-half-page-down
|
||||
|
||||
bind common k win-up
|
||||
bind common j win-down
|
||||
bind common g win-top
|
||||
bind common G win-bottom
|
||||
|
||||
bind common up win-up
|
||||
bind common down win-down
|
||||
bind common home win-top
|
||||
bind common end win-bottom
|
||||
bind common page_up win-page-up
|
||||
bind common page_down win-page-down
|
||||
|
||||
bind common mouse_scroll_up win-up
|
||||
bind common mouse_scroll_down win-down
|
||||
|
||||
# Selection
|
||||
|
||||
bind common i win-sel-cur
|
||||
bind common enter win-activate
|
||||
bind common mlb_click_selected win-activate
|
||||
|
||||
bind common space win-toggle
|
||||
bind common D win-remove
|
||||
bind common delete win-remove
|
||||
bind common p win-mv-after
|
||||
bind common P win-mv-before
|
||||
|
||||
bind common E win-add-Q
|
||||
bind common a win-add-l
|
||||
bind common y win-add-p
|
||||
bind common e win-add-q
|
||||
|
||||
bind common u update-cache
|
||||
bind common U win-update-cache
|
||||
|
||||
# Filters
|
||||
|
||||
bind common / search-start
|
||||
bind common ? search-b-start
|
||||
bind common n search-next
|
||||
bind common N search-prev
|
||||
|
||||
# note: the single space at the end is intentional
|
||||
bind common F push filter
|
||||
bind common L push live-filter
|
||||
|
||||
fset 90s=date>=1990&date<2000
|
||||
fset classical=genre="Classical"
|
||||
fset unheard=play_count=0
|
||||
fset missing-tag=!stream&(artist=""|album=""|title=""|tracknumber=-1|date=-1)
|
||||
fset mp3=filename="*.mp3"
|
||||
fset ogg=filename="*.ogg"
|
||||
fset ogg-or-mp3=ogg|mp3
|
||||
|
||||
# File browser
|
||||
|
||||
bind browser backspace browser-up
|
||||
bind browser space win-activate
|
||||
bind browser i toggle show_hidden
|
||||
bind browser u win-update
|
||||
51
data/solarized-dark.theme
Normal file
51
data/solarized-dark.theme
Normal file
@@ -0,0 +1,51 @@
|
||||
# colors from solarized: http://ethanschoonover.com/solarized
|
||||
# default text color
|
||||
set color_win_fg=default
|
||||
|
||||
# overall background color
|
||||
set color_win_bg=default
|
||||
|
||||
# command-line colors
|
||||
set color_cmdline_bg=default
|
||||
set color_cmdline_fg=default
|
||||
set color_error=160
|
||||
set color_info=136
|
||||
set color_separator=240
|
||||
|
||||
# bottom status line
|
||||
set color_statusline_bg=0
|
||||
set color_statusline_fg=37
|
||||
set color_statusline_progress_bg=233
|
||||
set color_statusline_progress_fg=37
|
||||
|
||||
# bottom title line
|
||||
set color_titleline_bg=0
|
||||
set color_titleline_fg=136
|
||||
|
||||
# top title area
|
||||
set color_win_title_bg=0
|
||||
set color_win_title_fg=64
|
||||
##### playing file colors ######################################################
|
||||
# unselected currently playing track's text
|
||||
set color_win_cur=33
|
||||
|
||||
# active selection for currently playing track
|
||||
set color_win_cur_sel_bg=0
|
||||
set color_win_cur_sel_fg=33
|
||||
|
||||
# inactive selection for currently playing track
|
||||
set color_win_inactive_cur_sel_bg=default
|
||||
set color_win_inactive_cur_sel_fg=125
|
||||
|
||||
##### non-playing file colors ##################################################
|
||||
# active selection
|
||||
set color_win_sel_bg=0
|
||||
set color_win_sel_fg=166
|
||||
|
||||
# inactive selection
|
||||
set color_win_inactive_sel_bg=default
|
||||
set color_win_inactive_sel_fg=125
|
||||
|
||||
##### file browser view colors #################################################
|
||||
# directory listing color
|
||||
set color_win_dir=33
|
||||
51
data/solarized-light.theme
Normal file
51
data/solarized-light.theme
Normal file
@@ -0,0 +1,51 @@
|
||||
# colors from solarized: http://ethanschoonover.com/solarized
|
||||
# default text color
|
||||
set color_win_fg=default
|
||||
|
||||
# overall background color
|
||||
set color_win_bg=default
|
||||
|
||||
# command-line colors
|
||||
set color_cmdline_bg=default
|
||||
set color_cmdline_fg=default
|
||||
set color_error=160
|
||||
set color_info=136
|
||||
set color_separator=245
|
||||
|
||||
# bottom status line
|
||||
set color_statusline_bg=0
|
||||
set color_statusline_fg=37
|
||||
set color_statusline_progress_bg=233
|
||||
set color_statusline_progress_fg=37
|
||||
|
||||
# bottom title line
|
||||
set color_titleline_bg=0
|
||||
set color_titleline_fg=136
|
||||
|
||||
# top title area
|
||||
set color_win_title_bg=0
|
||||
set color_win_title_fg=64
|
||||
##### playing file colors ######################################################
|
||||
# unselected currently playing track's text
|
||||
set color_win_cur=33
|
||||
|
||||
# active selection for currently playing track
|
||||
set color_win_cur_sel_bg=0
|
||||
set color_win_cur_sel_fg=33
|
||||
|
||||
# inactive selection for currently playing track
|
||||
set color_win_inactive_cur_sel_bg=default
|
||||
set color_win_inactive_cur_sel_fg=125
|
||||
|
||||
##### non-playing file colors ##################################################
|
||||
# active selection
|
||||
set color_win_sel_bg=0
|
||||
set color_win_sel_fg=166
|
||||
|
||||
# inactive selection
|
||||
set color_win_inactive_sel_bg=default
|
||||
set color_win_inactive_sel_fg=125
|
||||
|
||||
##### file browser view colors #################################################
|
||||
# directory listing color
|
||||
set color_win_dir=33
|
||||
26
data/spotify.theme
Normal file
26
data/spotify.theme
Normal file
@@ -0,0 +1,26 @@
|
||||
# @October, 2021 by github.com/frogwine
|
||||
set color_win_fg=252
|
||||
set color_win_bg=233
|
||||
set color_cmdline_bg=233
|
||||
set color_cmdline_fg=231
|
||||
set color_error=196
|
||||
set color_info=81
|
||||
set color_separator=47
|
||||
set color_statusline_bg=232
|
||||
set color_statusline_fg=34
|
||||
set color_statusline_progress_bg=234
|
||||
set color_statusline_progress_fg=34
|
||||
set color_titleline_bg=232
|
||||
set color_titleline_fg=83
|
||||
set color_win_title_bg=232
|
||||
set color_win_title_fg=83
|
||||
set color_win_cur=47
|
||||
set color_win_cur_sel_bg=239
|
||||
set color_win_cur_sel_fg=47
|
||||
set color_win_inactive_cur_sel_bg=233
|
||||
set color_win_inactive_cur_sel_fg=47
|
||||
set color_win_sel_bg=240
|
||||
set color_win_sel_fg=47
|
||||
set color_win_inactive_sel_bg=235
|
||||
set color_win_inactive_sel_fg=47
|
||||
set color_win_dir=47
|
||||
26
data/xterm-white.theme
Normal file
26
data/xterm-white.theme
Normal file
@@ -0,0 +1,26 @@
|
||||
# looks good with xterm using default settings (white bg)
|
||||
set color_cmdline_bg=default
|
||||
set color_cmdline_fg=default
|
||||
set color_error=lightred
|
||||
set color_info=lightblue
|
||||
set color_separator=default
|
||||
set color_statusline_bg=default
|
||||
set color_statusline_fg=default
|
||||
set color_statusline_progress_bg=gray
|
||||
set color_statusline_progress_fg=default
|
||||
set color_titleline_bg=gray
|
||||
set color_titleline_fg=default
|
||||
set color_win_bg=default
|
||||
set color_win_cur=lightblue
|
||||
set color_win_cur_sel_bg=green
|
||||
set color_win_cur_sel_fg=lightblue
|
||||
set color_win_dir=blue
|
||||
set color_win_fg=default
|
||||
set color_win_inactive_cur_sel_bg=gray
|
||||
set color_win_inactive_cur_sel_fg=lightblue
|
||||
set color_win_inactive_sel_bg=gray
|
||||
set color_win_inactive_sel_fg=default
|
||||
set color_win_sel_bg=green
|
||||
set color_win_sel_fg=default
|
||||
set color_win_title_bg=gray
|
||||
set color_win_title_fg=black
|
||||
54
data/zenburn.theme
Normal file
54
data/zenburn.theme
Normal file
@@ -0,0 +1,54 @@
|
||||
# Directory colors
|
||||
set color_win_dir=108
|
||||
|
||||
# Normal text
|
||||
set color_win_fg=188
|
||||
|
||||
# Window background color.
|
||||
set color_win_bg=237
|
||||
|
||||
# Command line color.
|
||||
set color_cmdline_bg=237
|
||||
set color_cmdline_fg=108
|
||||
|
||||
# Color of error messages displayed on the command line.
|
||||
set color_error=lightred
|
||||
|
||||
# Color of informational messages displayed on the command line.
|
||||
set color_info=lightgreen
|
||||
|
||||
# Color of the separator line between windows in view (1).
|
||||
set color_separator=246
|
||||
|
||||
# Color of window titles (topmost line of the screen).
|
||||
set color_win_title_bg=235
|
||||
set color_win_title_fg=174
|
||||
|
||||
# Status line color.
|
||||
set color_statusline_bg=237
|
||||
set color_statusline_fg=142
|
||||
set color_statusline_progress_bg=240
|
||||
set color_statusline_progress_fg=142
|
||||
|
||||
# Color of currently playing track.
|
||||
set color_win_cur=172
|
||||
|
||||
# Color of the line displaying currently playing track.
|
||||
set color_titleline_bg=235
|
||||
set color_titleline_fg=144
|
||||
|
||||
# Color of the selected row which is also the currently playing track in active window.
|
||||
set color_win_cur_sel_bg=235
|
||||
set color_win_cur_sel_fg=223
|
||||
|
||||
# Color of the selected row which is also the currently playing track in inactive window.
|
||||
set color_win_inactive_cur_sel_bg=238
|
||||
set color_win_inactive_cur_sel_fg=116
|
||||
|
||||
# Color of selected row in active window.
|
||||
set color_win_sel_bg=235
|
||||
set color_win_sel_fg=223
|
||||
|
||||
# Color of selected row in inactive window.
|
||||
set color_win_inactive_sel_bg=238
|
||||
set color_win_inactive_sel_fg=116
|
||||
107
debug.c
Normal file
107
debug.c
Normal file
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2004-2005 Timo Hirvonen
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "debug.h"
|
||||
#include "prog.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#if DEBUG > 1
|
||||
static FILE *debug_stream = NULL;
|
||||
#endif
|
||||
|
||||
void debug_init(void)
|
||||
{
|
||||
#if DEBUG > 1
|
||||
char filename[512];
|
||||
const char *dir = getenv("CMUS_HOME");
|
||||
|
||||
if (!dir || !dir[0]) {
|
||||
dir = getenv("HOME");
|
||||
if (!dir)
|
||||
die("error: environment variable HOME not set\n");
|
||||
}
|
||||
snprintf(filename, sizeof(filename), "%s/cmus-debug.txt", dir);
|
||||
|
||||
debug_stream = fopen(filename, "w");
|
||||
if (debug_stream == NULL)
|
||||
die_errno("error opening `%s' for writing", filename);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* This function must be defined even if debugging is disabled in the program
|
||||
* because debugging might still be enabled in some plugin.
|
||||
*/
|
||||
void _debug_bug(const char *function, const char *fmt, ...)
|
||||
{
|
||||
const char *format = "\n%s: BUG: ";
|
||||
va_list ap;
|
||||
|
||||
/* debug_stream exists only if debugging is enabled */
|
||||
#if DEBUG > 1
|
||||
fprintf(debug_stream, format, function);
|
||||
va_start(ap, fmt);
|
||||
vfprintf(debug_stream, fmt, ap);
|
||||
va_end(ap);
|
||||
#endif
|
||||
|
||||
/* always print bug message to stderr */
|
||||
fprintf(stderr, format, function);
|
||||
va_start(ap, fmt);
|
||||
vfprintf(stderr, fmt, ap);
|
||||
va_end(ap);
|
||||
exit(127);
|
||||
}
|
||||
|
||||
void _debug_print(const char *function, const char *fmt, ...)
|
||||
{
|
||||
#if DEBUG > 1
|
||||
va_list ap;
|
||||
|
||||
fprintf(debug_stream, "%s: ", function);
|
||||
va_start(ap, fmt);
|
||||
vfprintf(debug_stream, fmt, ap);
|
||||
va_end(ap);
|
||||
fflush(debug_stream);
|
||||
#endif
|
||||
}
|
||||
|
||||
uint64_t timer_get(void)
|
||||
{
|
||||
#if DEBUG > 1
|
||||
struct timeval tv;
|
||||
|
||||
gettimeofday(&tv, NULL);
|
||||
return tv.tv_sec * 1e6L + tv.tv_usec;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
void timer_print(const char *what, uint64_t usec)
|
||||
{
|
||||
#if DEBUG > 1
|
||||
uint64_t a = usec / 1e6;
|
||||
uint64_t b = usec - a * 1e6;
|
||||
|
||||
_debug_print("TIMER", "%s: %11u.%06u\n", what, (unsigned int)a, (unsigned int)b);
|
||||
#endif
|
||||
}
|
||||
49
debug.h
Normal file
49
debug.h
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2004-2005 Timo Hirvonen
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CMUS_DEBUG_H
|
||||
#define CMUS_DEBUG_H
|
||||
|
||||
#include "compiler.h"
|
||||
#ifdef HAVE_CONFIG
|
||||
#include "config/debug.h"
|
||||
#endif
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
|
||||
void debug_init(void);
|
||||
void _debug_bug(const char *function, const char *fmt, ...) CMUS_FORMAT(2, 3) CMUS_NORETURN;
|
||||
void _debug_print(const char *function, const char *fmt, ...) CMUS_FORMAT(2, 3);
|
||||
|
||||
uint64_t timer_get(void);
|
||||
void timer_print(const char *what, uint64_t usec);
|
||||
|
||||
#define BUG(...) _debug_bug(__FUNCTION__, __VA_ARGS__)
|
||||
|
||||
#define CMUS_STR(a) #a
|
||||
|
||||
#define BUG_ON(a) \
|
||||
do { \
|
||||
if (unlikely(a)) \
|
||||
BUG("%s\n", CMUS_STR(a)); \
|
||||
} while (0)
|
||||
|
||||
#define d_print(...) _debug_print(__FUNCTION__, __VA_ARGS__)
|
||||
|
||||
#endif
|
||||
152
discid.c
Normal file
152
discid.c
Normal file
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Copyright 2011-2013 Various Authors
|
||||
* Copyright 2011 Johannes Weißl
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "discid.h"
|
||||
#include "xmalloc.h"
|
||||
#include "path.h"
|
||||
#include "utils.h"
|
||||
#include "debug.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <limits.h>
|
||||
|
||||
#ifdef HAVE_DISCID
|
||||
#include <discid/discid.h>
|
||||
#endif
|
||||
|
||||
char *get_default_cdda_device(void)
|
||||
{
|
||||
const char *dev = NULL;
|
||||
#ifdef HAVE_DISCID
|
||||
dev = discid_get_default_device();
|
||||
#endif
|
||||
if (!dev)
|
||||
dev = "/dev/cdrom";
|
||||
return xstrdup(dev);
|
||||
}
|
||||
|
||||
int parse_cdda_url(const char *url, char **disc_id, int *start_track, int *end_track)
|
||||
{
|
||||
char *slash, *dash;
|
||||
long int t;
|
||||
|
||||
if (!is_cdda_url(url))
|
||||
return 0;
|
||||
url += 7;
|
||||
|
||||
slash = strrchr(url, '/');
|
||||
if (slash) {
|
||||
*disc_id = xstrndup(url, slash - url);
|
||||
url = slash + 1;
|
||||
}
|
||||
dash = strchr(url, '-');
|
||||
if (dash) {
|
||||
char *tmp = xstrndup(url, dash - url);
|
||||
if (str_to_int(tmp, &t) == 0)
|
||||
*start_track = t;
|
||||
if (end_track) {
|
||||
if (str_to_int(dash + 1, &t) == 0)
|
||||
*end_track = t;
|
||||
else
|
||||
*end_track = INT_MAX;
|
||||
}
|
||||
free(tmp);
|
||||
} else {
|
||||
if (str_to_int(url, &t) == 0)
|
||||
*start_track = t;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
char *gen_cdda_url(const char *disc_id, int start_track, int end_track)
|
||||
{
|
||||
char buf[256];
|
||||
if (end_track != -1)
|
||||
snprintf(buf, sizeof(buf), "cdda://%s/%d-%d", disc_id, start_track, end_track);
|
||||
else
|
||||
snprintf(buf, sizeof(buf), "cdda://%s/%d", disc_id, start_track);
|
||||
return xstrdup(buf);
|
||||
}
|
||||
|
||||
char *complete_cdda_url(const char *device, const char *url)
|
||||
{
|
||||
char *new_url, *url_disc_id = NULL, *disc_id = NULL;
|
||||
int is_range, start_track = -1, end_track = -1, num_tracks = -1;
|
||||
|
||||
parse_cdda_url(url, &url_disc_id, &start_track, &end_track);
|
||||
is_range = (start_track == -1 && end_track == -1) || end_track == INT_MAX;
|
||||
if (!url_disc_id || is_range) {
|
||||
if (url_disc_id && strchr(url_disc_id, '/'))
|
||||
device = url_disc_id;
|
||||
get_disc_id(device, &disc_id, &num_tracks);
|
||||
if (is_range)
|
||||
end_track = num_tracks;
|
||||
if (!url_disc_id)
|
||||
url_disc_id = disc_id;
|
||||
}
|
||||
if (start_track == -1)
|
||||
start_track = 1;
|
||||
|
||||
new_url = gen_cdda_url(url_disc_id, start_track, end_track);
|
||||
free(disc_id);
|
||||
|
||||
return new_url;
|
||||
}
|
||||
|
||||
static int get_device_disc_id(const char *device, char **disc_id, int *num_tracks)
|
||||
{
|
||||
#ifdef HAVE_DISCID
|
||||
DiscId *disc = discid_new();
|
||||
if (!disc)
|
||||
return 0;
|
||||
|
||||
if (!discid_read(disc, device)) {
|
||||
d_print("%s\n", discid_get_error_msg(disc));
|
||||
discid_free(disc);
|
||||
return 0;
|
||||
}
|
||||
|
||||
*disc_id = xstrdup(discid_get_id(disc));
|
||||
if (num_tracks)
|
||||
*num_tracks = discid_get_last_track_num(disc);
|
||||
|
||||
discid_free(disc);
|
||||
return 1;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
int get_disc_id(const char *device, char **disc_id, int *num_tracks)
|
||||
{
|
||||
struct stat st;
|
||||
|
||||
if (stat(device, &st) == -1)
|
||||
return 0;
|
||||
|
||||
if (S_ISBLK(st.st_mode))
|
||||
return get_device_disc_id(device, disc_id, num_tracks);
|
||||
|
||||
*disc_id = path_absolute(device);
|
||||
return 1;
|
||||
}
|
||||
28
discid.h
Normal file
28
discid.h
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2011-2013 Various Authors
|
||||
* Copyright 2011 Johannes Weißl
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CMUS_DISCID_H
|
||||
#define CMUS_DISCID_H
|
||||
|
||||
char *get_default_cdda_device(void);
|
||||
int parse_cdda_url(const char *url, char **disc_id, int *start_track, int *end_track);
|
||||
char *gen_cdda_url(const char *disc_id, int start_track, int end_track);
|
||||
char *complete_cdda_url(const char *device, const char *url);
|
||||
int get_disc_id(const char *device, char **disc_id, int *num_tracks);
|
||||
|
||||
#endif
|
||||
466
editable.c
Normal file
466
editable.c
Normal file
@@ -0,0 +1,466 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2006 Timo Hirvonen
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "editable.h"
|
||||
#include "search.h"
|
||||
#include "track.h"
|
||||
#include "track_info.h"
|
||||
#include "expr.h"
|
||||
#include "filters.h"
|
||||
#include "locking.h"
|
||||
#include "mergesort.h"
|
||||
#include "xmalloc.h"
|
||||
|
||||
static const struct searchable_ops simple_search_ops = {
|
||||
.get_prev = simple_track_get_prev,
|
||||
.get_next = simple_track_get_next,
|
||||
.get_current = simple_track_search_get_current,
|
||||
.matches = simple_track_search_matches
|
||||
};
|
||||
|
||||
static struct simple_track *get_selected(struct editable *e)
|
||||
{
|
||||
struct iter sel;
|
||||
|
||||
if (window_get_sel(e->shared->win, &sel))
|
||||
return iter_to_simple_track(&sel);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void editable_shared_init(struct editable_shared *shared,
|
||||
editable_free_track free_track)
|
||||
{
|
||||
shared->win = window_new(simple_track_get_prev, simple_track_get_next);
|
||||
shared->sort_keys = xnew(sort_key_t, 1);
|
||||
shared->sort_keys[0] = SORT_INVALID;
|
||||
shared->sort_str[0] = 0;
|
||||
shared->free_track = free_track;
|
||||
shared->owner = NULL;
|
||||
|
||||
struct iter iter = { 0 };
|
||||
shared->searchable = searchable_new(shared->win, &iter,
|
||||
&simple_search_ops);
|
||||
}
|
||||
|
||||
void editable_init(struct editable *e, struct editable_shared *shared,
|
||||
int take_ownership)
|
||||
{
|
||||
list_init(&e->head);
|
||||
e->tree_root = RB_ROOT;
|
||||
e->nr_tracks = 0;
|
||||
e->nr_marked = 0;
|
||||
e->total_time = 0;
|
||||
e->shared = shared;
|
||||
|
||||
|
||||
if (take_ownership)
|
||||
editable_take_ownership(e);
|
||||
}
|
||||
|
||||
static int editable_owns_shared(struct editable *e)
|
||||
{
|
||||
return e->shared->owner == e;
|
||||
}
|
||||
|
||||
void editable_take_ownership(struct editable *e)
|
||||
{
|
||||
if (!editable_owns_shared(e)) {
|
||||
e->shared->owner = e;
|
||||
window_set_contents(e->shared->win, &e->head);
|
||||
e->shared->win->changed = 1;
|
||||
|
||||
struct iter iter = { .data0 = &e->head };
|
||||
searchable_set_head(e->shared->searchable, &iter);
|
||||
}
|
||||
}
|
||||
|
||||
static void do_editable_add(struct editable *e, struct simple_track *track, int tiebreak)
|
||||
{
|
||||
sorted_list_add_track(&e->head, &e->tree_root, track,
|
||||
e->shared->sort_keys, tiebreak);
|
||||
e->nr_tracks++;
|
||||
if (track->info->duration != -1)
|
||||
e->total_time += track->info->duration;
|
||||
if (editable_owns_shared(e))
|
||||
window_changed(e->shared->win);
|
||||
}
|
||||
|
||||
void editable_add(struct editable *e, struct simple_track *track)
|
||||
{
|
||||
do_editable_add(e, track, +1);
|
||||
}
|
||||
|
||||
void editable_add_before(struct editable *e, struct simple_track *track)
|
||||
{
|
||||
do_editable_add(e, track, -1);
|
||||
}
|
||||
|
||||
void editable_remove_track(struct editable *e, struct simple_track *track)
|
||||
{
|
||||
struct track_info *ti = track->info;
|
||||
struct iter iter;
|
||||
|
||||
editable_track_to_iter(e, track, &iter);
|
||||
if (editable_owns_shared(e))
|
||||
window_row_vanishes(e->shared->win, &iter);
|
||||
|
||||
e->nr_tracks--;
|
||||
e->nr_marked -= track->marked;
|
||||
if (ti->duration != -1)
|
||||
e->total_time -= ti->duration;
|
||||
|
||||
sorted_list_remove_track(&e->head, &e->tree_root, track);
|
||||
e->shared->free_track(e, &track->node);
|
||||
}
|
||||
|
||||
void editable_remove_sel(struct editable *e)
|
||||
{
|
||||
struct simple_track *t;
|
||||
|
||||
if (e->nr_marked) {
|
||||
/* treat marked tracks as selected */
|
||||
struct list_head *next, *item = e->head.next;
|
||||
|
||||
while (item != &e->head) {
|
||||
next = item->next;
|
||||
t = to_simple_track(item);
|
||||
if (t->marked)
|
||||
editable_remove_track(e, t);
|
||||
item = next;
|
||||
}
|
||||
} else {
|
||||
t = get_selected(e);
|
||||
if (t)
|
||||
editable_remove_track(e, t);
|
||||
}
|
||||
}
|
||||
|
||||
void editable_sort(struct editable *e)
|
||||
{
|
||||
if (e->nr_tracks <= 1)
|
||||
return;
|
||||
sorted_list_rebuild(&e->head, &e->tree_root, e->shared->sort_keys);
|
||||
|
||||
if (editable_owns_shared(e)) {
|
||||
window_changed(e->shared->win);
|
||||
window_goto_top(e->shared->win);
|
||||
}
|
||||
}
|
||||
|
||||
void editable_shared_set_sort_keys(struct editable_shared *shared,
|
||||
sort_key_t *keys)
|
||||
{
|
||||
free(shared->sort_keys);
|
||||
shared->sort_keys = keys;
|
||||
}
|
||||
|
||||
void editable_toggle_mark(struct editable *e)
|
||||
{
|
||||
struct simple_track *t;
|
||||
|
||||
t = get_selected(e);
|
||||
if (t) {
|
||||
e->nr_marked -= t->marked;
|
||||
t->marked ^= 1;
|
||||
e->nr_marked += t->marked;
|
||||
if (editable_owns_shared(e)) {
|
||||
e->shared->win->changed = 1;
|
||||
window_down(e->shared->win, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void move_item(struct editable *e, struct list_head *head, struct list_head *item)
|
||||
{
|
||||
struct simple_track *t = to_simple_track(item);
|
||||
struct iter iter;
|
||||
|
||||
editable_track_to_iter(e, t, &iter);
|
||||
if (editable_owns_shared(e))
|
||||
window_row_vanishes(e->shared->win, &iter);
|
||||
|
||||
list_del(item);
|
||||
list_add(item, head);
|
||||
}
|
||||
|
||||
static void reset_tree(struct editable *e)
|
||||
{
|
||||
struct simple_track *old, *first_track;
|
||||
|
||||
old = tree_node_to_simple_track(rb_first(&e->tree_root));
|
||||
first_track = to_simple_track(e->head.next);
|
||||
if (old != first_track) {
|
||||
rb_replace_node(&old->tree_node, &first_track->tree_node, &e->tree_root);
|
||||
RB_CLEAR_NODE(&old->tree_node);
|
||||
}
|
||||
}
|
||||
|
||||
static void move_sel(struct editable *e, struct list_head *after)
|
||||
{
|
||||
struct simple_track *t;
|
||||
struct list_head *item, *next;
|
||||
struct iter iter;
|
||||
LIST_HEAD(tmp_head);
|
||||
|
||||
if (e->nr_marked) {
|
||||
/* collect marked */
|
||||
item = e->head.next;
|
||||
while (item != &e->head) {
|
||||
t = to_simple_track(item);
|
||||
next = item->next;
|
||||
if (t->marked)
|
||||
move_item(e, &tmp_head, item);
|
||||
item = next;
|
||||
}
|
||||
} else {
|
||||
/* collect the selected track */
|
||||
t = get_selected(e);
|
||||
if (t)
|
||||
move_item(e, &tmp_head, &t->node);
|
||||
}
|
||||
|
||||
/* put them back to the list after @after */
|
||||
item = tmp_head.next;
|
||||
while (item != &tmp_head) {
|
||||
next = item->next;
|
||||
list_add(item, after);
|
||||
item = next;
|
||||
}
|
||||
reset_tree(e);
|
||||
|
||||
/* select top-most of the moved tracks */
|
||||
editable_track_to_iter(e, to_simple_track(after->next), &iter);
|
||||
|
||||
if (editable_owns_shared(e)) {
|
||||
window_changed(e->shared->win);
|
||||
window_set_sel(e->shared->win, &iter);
|
||||
}
|
||||
}
|
||||
|
||||
static struct list_head *find_insert_after_point(struct editable *e, struct list_head *item)
|
||||
{
|
||||
if (e->nr_marked == 0) {
|
||||
/* move the selected track down one row */
|
||||
return item->next;
|
||||
}
|
||||
|
||||
/* move marked after the selected
|
||||
*
|
||||
* if the selected track itself is marked we find the first unmarked
|
||||
* track (or head) before the selected one
|
||||
*/
|
||||
while (item != &e->head) {
|
||||
struct simple_track *t = to_simple_track(item);
|
||||
|
||||
if (!t->marked)
|
||||
break;
|
||||
item = item->prev;
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
static struct list_head *find_insert_before_point(struct editable *e, struct list_head *item)
|
||||
{
|
||||
item = item->prev;
|
||||
if (e->nr_marked == 0) {
|
||||
/* move the selected track up one row */
|
||||
return item->prev;
|
||||
}
|
||||
|
||||
/* move marked before the selected
|
||||
*
|
||||
* if the selected track itself is marked we find the first unmarked
|
||||
* track (or head) before the selected one
|
||||
*/
|
||||
while (item != &e->head) {
|
||||
struct simple_track *t = to_simple_track(item);
|
||||
|
||||
if (!t->marked)
|
||||
break;
|
||||
item = item->prev;
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
void editable_move_after(struct editable *e)
|
||||
{
|
||||
struct simple_track *sel;
|
||||
|
||||
if (e->nr_tracks <= 1 || e->shared->sort_keys[0] != SORT_INVALID)
|
||||
return;
|
||||
|
||||
sel = get_selected(e);
|
||||
if (sel)
|
||||
move_sel(e, find_insert_after_point(e, &sel->node));
|
||||
}
|
||||
|
||||
void editable_move_before(struct editable *e)
|
||||
{
|
||||
struct simple_track *sel;
|
||||
|
||||
if (e->nr_tracks <= 1 || e->shared->sort_keys[0] != SORT_INVALID)
|
||||
return;
|
||||
|
||||
sel = get_selected(e);
|
||||
if (sel)
|
||||
move_sel(e, find_insert_before_point(e, &sel->node));
|
||||
}
|
||||
|
||||
void editable_clear(struct editable *e)
|
||||
{
|
||||
struct list_head *item, *tmp;
|
||||
|
||||
list_for_each_safe(item, tmp, &e->head)
|
||||
editable_remove_track(e, to_simple_track(item));
|
||||
}
|
||||
|
||||
void editable_remove_matching_tracks(struct editable *e,
|
||||
int (*cb)(void *data, struct track_info *ti), void *data)
|
||||
{
|
||||
struct list_head *item, *tmp;
|
||||
|
||||
list_for_each_safe(item, tmp, &e->head) {
|
||||
struct simple_track *t = to_simple_track(item);
|
||||
if (cb(data, t->info))
|
||||
editable_remove_track(e, t);
|
||||
}
|
||||
}
|
||||
|
||||
void editable_mark(struct editable *e, const char *filter)
|
||||
{
|
||||
struct expr *expr = NULL;
|
||||
struct simple_track *t;
|
||||
|
||||
if (filter) {
|
||||
expr = parse_filter(filter);
|
||||
if (expr == NULL)
|
||||
return;
|
||||
}
|
||||
|
||||
list_for_each_entry(t, &e->head, node) {
|
||||
e->nr_marked -= t->marked;
|
||||
t->marked = 0;
|
||||
if (expr == NULL || expr_eval(expr, t->info)) {
|
||||
t->marked = 1;
|
||||
e->nr_marked++;
|
||||
}
|
||||
}
|
||||
|
||||
if (editable_owns_shared(e))
|
||||
e->shared->win->changed = 1;
|
||||
}
|
||||
|
||||
void editable_unmark(struct editable *e)
|
||||
{
|
||||
struct simple_track *t;
|
||||
|
||||
list_for_each_entry(t, &e->head, node) {
|
||||
e->nr_marked -= t->marked;
|
||||
t->marked = 0;
|
||||
}
|
||||
|
||||
if (editable_owns_shared(e))
|
||||
e->shared->win->changed = 1;
|
||||
}
|
||||
|
||||
void editable_invert_marks(struct editable *e)
|
||||
{
|
||||
struct simple_track *t;
|
||||
|
||||
list_for_each_entry(t, &e->head, node) {
|
||||
e->nr_marked -= t->marked;
|
||||
t->marked ^= 1;
|
||||
e->nr_marked += t->marked;
|
||||
}
|
||||
|
||||
if (editable_owns_shared(e))
|
||||
e->shared->win->changed = 1;
|
||||
}
|
||||
|
||||
int _editable_for_each_sel(struct editable *e, track_info_cb cb, void *data,
|
||||
int reverse)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
if (e->nr_marked) {
|
||||
/* treat marked tracks as selected */
|
||||
rc = simple_list_for_each_marked(&e->head, cb, data, reverse);
|
||||
} else {
|
||||
struct simple_track *t = get_selected(e);
|
||||
|
||||
if (t)
|
||||
rc = cb(data, t->info);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
int editable_for_each_sel(struct editable *e, track_info_cb cb, void *data,
|
||||
int reverse, int advance)
|
||||
{
|
||||
int rc;
|
||||
|
||||
rc = _editable_for_each_sel(e, cb, data, reverse);
|
||||
if (advance && e->nr_marked == 0 && editable_owns_shared(e))
|
||||
window_down(e->shared->win, 1);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int editable_for_each(struct editable *e, track_info_cb cb, void *data,
|
||||
int reverse)
|
||||
{
|
||||
return simple_list_for_each(&e->head, cb, data, reverse);
|
||||
}
|
||||
|
||||
void editable_update_track(struct editable *e, struct track_info *old, struct track_info *new)
|
||||
{
|
||||
struct list_head *item, *tmp;
|
||||
int changed = 0;
|
||||
|
||||
list_for_each_safe(item, tmp, &e->head) {
|
||||
struct simple_track *track = to_simple_track(item);
|
||||
if (track->info == old) {
|
||||
if (new) {
|
||||
track_info_unref(old);
|
||||
track_info_ref(new);
|
||||
track->info = new;
|
||||
} else {
|
||||
editable_remove_track(e, track);
|
||||
}
|
||||
changed = 1;
|
||||
}
|
||||
}
|
||||
if (editable_owns_shared(e))
|
||||
e->shared->win->changed |= changed;
|
||||
}
|
||||
|
||||
void editable_rand(struct editable *e)
|
||||
{
|
||||
if (e->nr_tracks <=1)
|
||||
return;
|
||||
rand_list_rebuild(&e->head, &e->tree_root);
|
||||
|
||||
if (editable_owns_shared(e)) {
|
||||
window_changed(e->shared->win);
|
||||
window_goto_top(e->shared->win);
|
||||
}
|
||||
}
|
||||
|
||||
int editable_empty(struct editable *e)
|
||||
{
|
||||
return list_empty(&e->head);
|
||||
}
|
||||
91
editable.h
Normal file
91
editable.h
Normal file
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2006 Timo Hirvonen
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CMUS_EDITABLE_H
|
||||
#define CMUS_EDITABLE_H
|
||||
|
||||
#include "window.h"
|
||||
#include "list.h"
|
||||
#include "rbtree.h"
|
||||
#include "track.h"
|
||||
#include "locking.h"
|
||||
#include "cmus.h"
|
||||
|
||||
struct editable;
|
||||
|
||||
typedef void (*editable_free_track)(struct editable *e, struct list_head *head);
|
||||
|
||||
struct editable_shared {
|
||||
struct editable *owner;
|
||||
|
||||
struct window *win;
|
||||
sort_key_t *sort_keys;
|
||||
char sort_str[128];
|
||||
editable_free_track free_track;
|
||||
struct searchable *searchable;
|
||||
};
|
||||
|
||||
struct editable {
|
||||
struct list_head head;
|
||||
struct rb_root tree_root;
|
||||
unsigned int nr_tracks;
|
||||
unsigned int nr_marked;
|
||||
unsigned int total_time;
|
||||
struct editable_shared *shared;
|
||||
};
|
||||
|
||||
void editable_shared_init(struct editable_shared *shared,
|
||||
editable_free_track free_track);
|
||||
void editable_shared_set_sort_keys(struct editable_shared *shared,
|
||||
sort_key_t *keys);
|
||||
|
||||
void editable_init(struct editable *e, struct editable_shared *shared,
|
||||
int take_ownership);
|
||||
void editable_take_ownership(struct editable *e);
|
||||
void editable_add(struct editable *e, struct simple_track *track);
|
||||
void editable_add_before(struct editable *e, struct simple_track *track);
|
||||
void editable_remove_track(struct editable *e, struct simple_track *track);
|
||||
void editable_remove_sel(struct editable *e);
|
||||
void editable_sort(struct editable *e);
|
||||
void editable_rand(struct editable *e);
|
||||
void editable_toggle_mark(struct editable *e);
|
||||
void editable_move_after(struct editable *e);
|
||||
void editable_move_before(struct editable *e);
|
||||
void editable_clear(struct editable *e);
|
||||
void editable_remove_matching_tracks(struct editable *e,
|
||||
int (*cb)(void *data, struct track_info *ti), void *data);
|
||||
void editable_mark(struct editable *e, const char *filter);
|
||||
void editable_unmark(struct editable *e);
|
||||
void editable_invert_marks(struct editable *e);
|
||||
int _editable_for_each_sel(struct editable *e, track_info_cb cb, void *data,
|
||||
int reverse);
|
||||
int editable_for_each_sel(struct editable *e, track_info_cb cb, void *data,
|
||||
int reverse, int advance);
|
||||
int editable_for_each(struct editable *e, track_info_cb cb, void *data,
|
||||
int reverse);
|
||||
void editable_update_track(struct editable *e, struct track_info *old, struct track_info *new);
|
||||
int editable_empty(struct editable *e);
|
||||
|
||||
static inline void editable_track_to_iter(struct editable *e, struct simple_track *track, struct iter *iter)
|
||||
{
|
||||
iter->data0 = &e->head;
|
||||
iter->data1 = track;
|
||||
iter->data2 = NULL;
|
||||
}
|
||||
|
||||
#endif
|
||||
91
expr.h
Normal file
91
expr.h
Normal file
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2005 Timo Hirvonen
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CMUS_EXPR_H
|
||||
#define CMUS_EXPR_H
|
||||
|
||||
#include "track_info.h"
|
||||
#include "list.h"
|
||||
|
||||
enum { OP_LT, OP_LE, OP_EQ, OP_GE, OP_GT, OP_NE };
|
||||
#define NR_OPS (OP_NE + 1)
|
||||
|
||||
enum expr_type {
|
||||
EXPR_AND,
|
||||
EXPR_OR,
|
||||
|
||||
EXPR_NOT,
|
||||
|
||||
EXPR_STR,
|
||||
EXPR_INT,
|
||||
EXPR_ID,
|
||||
EXPR_BOOL
|
||||
};
|
||||
#define NR_EXPRS (EXPR_BOOL + 1)
|
||||
|
||||
struct expr {
|
||||
struct expr *left, *right, *parent;
|
||||
enum expr_type type;
|
||||
char *key;
|
||||
union {
|
||||
struct {
|
||||
struct list_head glob_head;
|
||||
enum {
|
||||
SOP_EQ = OP_EQ,
|
||||
SOP_NE = OP_NE
|
||||
} op;
|
||||
} estr;
|
||||
struct {
|
||||
int val;
|
||||
enum {
|
||||
IOP_LT = OP_LT,
|
||||
IOP_LE = OP_LE,
|
||||
IOP_EQ = OP_EQ,
|
||||
IOP_GE = OP_GE,
|
||||
IOP_GT = OP_GT,
|
||||
IOP_NE = OP_NE
|
||||
} op;
|
||||
} eint;
|
||||
struct {
|
||||
char* key;
|
||||
enum {
|
||||
KOP_LT = OP_LT,
|
||||
KOP_LE = OP_LE,
|
||||
KOP_EQ = OP_EQ,
|
||||
KOP_GE = OP_GE,
|
||||
KOP_GT = OP_GT,
|
||||
KOP_NE = OP_NE
|
||||
} op;
|
||||
} eid;
|
||||
};
|
||||
};
|
||||
|
||||
struct expr *expr_parse(const char *str);
|
||||
struct expr* expr_parse_i(const char *str, const char *err_msg, int check_short);
|
||||
int expr_check_leaves(struct expr **exprp, const char *(*get_filter)(const char *name));
|
||||
int expr_op_to_bool(int res, int op);
|
||||
int expr_eval(struct expr *expr, struct track_info *ti);
|
||||
void expr_free(struct expr *expr);
|
||||
const char *expr_error(void);
|
||||
int expr_is_short(const char *str);
|
||||
|
||||
unsigned int expr_get_match_type(struct expr *expr);
|
||||
/* "harmless" expressions will reduce filter results when adding characters at the beginning/end */
|
||||
int expr_is_harmless(const struct expr *expr);
|
||||
|
||||
#endif
|
||||
185
file.c
Normal file
185
file.c
Normal file
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2005 Timo Hirvonen
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "file.h"
|
||||
#include "xmalloc.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/mman.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
|
||||
ssize_t read_all(int fd, void *buf, size_t count)
|
||||
{
|
||||
char *buffer = buf;
|
||||
ssize_t pos = 0;
|
||||
|
||||
do {
|
||||
ssize_t rc;
|
||||
|
||||
rc = read(fd, buffer + pos, count - pos);
|
||||
if (rc == -1) {
|
||||
if (errno == EINTR || errno == EAGAIN)
|
||||
continue;
|
||||
return -1;
|
||||
}
|
||||
if (rc == 0) {
|
||||
/* eof */
|
||||
break;
|
||||
}
|
||||
pos += rc;
|
||||
} while (count - pos > 0);
|
||||
return pos;
|
||||
}
|
||||
|
||||
ssize_t write_all(int fd, const void *buf, size_t count)
|
||||
{
|
||||
const char *buffer = buf;
|
||||
int count_save = count;
|
||||
|
||||
do {
|
||||
int rc;
|
||||
|
||||
rc = write(fd, buffer, count);
|
||||
if (rc == -1) {
|
||||
if (errno == EINTR || errno == EAGAIN)
|
||||
continue;
|
||||
return -1;
|
||||
}
|
||||
buffer += rc;
|
||||
count -= rc;
|
||||
} while (count > 0);
|
||||
return count_save;
|
||||
}
|
||||
|
||||
char *mmap_file(const char *filename, ssize_t *size)
|
||||
{
|
||||
struct stat st;
|
||||
char *buf;
|
||||
int fd;
|
||||
|
||||
fd = open(filename, O_RDONLY);
|
||||
if (fd == -1)
|
||||
goto err;
|
||||
|
||||
if (fstat(fd, &st) == -1)
|
||||
goto close_err;
|
||||
|
||||
/* can't mmap empty files */
|
||||
buf = NULL;
|
||||
if (st.st_size) {
|
||||
buf = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
|
||||
if (buf == MAP_FAILED)
|
||||
goto close_err;
|
||||
}
|
||||
|
||||
close(fd);
|
||||
*size = st.st_size;
|
||||
return buf;
|
||||
|
||||
close_err:
|
||||
close(fd);
|
||||
err:
|
||||
*size = -1;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void buffer_for_each_line(const char *buf, int size,
|
||||
int (*cb)(void *data, const char *line),
|
||||
void *data)
|
||||
{
|
||||
char *line = NULL;
|
||||
int line_size = 0, pos = 0;
|
||||
|
||||
while (pos < size) {
|
||||
int end, len;
|
||||
|
||||
end = pos;
|
||||
while (end < size && buf[end] != '\n')
|
||||
end++;
|
||||
|
||||
len = end - pos;
|
||||
if (end > pos && buf[end - 1] == '\r')
|
||||
len--;
|
||||
|
||||
if (len >= line_size) {
|
||||
line_size = len + 1;
|
||||
line = xrenew(char, line, line_size);
|
||||
}
|
||||
memcpy(line, buf + pos, len);
|
||||
line[len] = 0;
|
||||
pos = end + 1;
|
||||
|
||||
if (cb(data, line))
|
||||
break;
|
||||
}
|
||||
free(line);
|
||||
}
|
||||
|
||||
void buffer_for_each_line_reverse(const char *buf, int size,
|
||||
int (*cb)(void *data, const char *line),
|
||||
void *data)
|
||||
{
|
||||
char *line = NULL;
|
||||
int line_size = 0, end = size - 1;
|
||||
|
||||
while (end >= 0) {
|
||||
int pos, len;
|
||||
|
||||
if (end > 1 && buf[end] == '\n' && buf[end - 1] == '\r')
|
||||
end--;
|
||||
|
||||
pos = end;
|
||||
while (pos > 0 && buf[pos - 1] != '\n')
|
||||
pos--;
|
||||
|
||||
len = end - pos;
|
||||
if (len >= line_size) {
|
||||
line_size = len + 1;
|
||||
line = xrenew(char, line, line_size);
|
||||
}
|
||||
memcpy(line, buf + pos, len);
|
||||
line[len] = 0;
|
||||
end = pos - 1;
|
||||
|
||||
if (cb(data, line))
|
||||
break;
|
||||
}
|
||||
free(line);
|
||||
}
|
||||
|
||||
int file_for_each_line(const char *filename,
|
||||
int (*cb)(void *data, const char *line),
|
||||
void *data)
|
||||
{
|
||||
char *buf;
|
||||
ssize_t size;
|
||||
|
||||
buf = mmap_file(filename, &size);
|
||||
if (size == -1)
|
||||
return -1;
|
||||
|
||||
if (buf) {
|
||||
buffer_for_each_line(buf, size, cb, data);
|
||||
munmap(buf, size);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
45
file.h
Normal file
45
file.h
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2005 Timo Hirvonen
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CMUS_FILE_H
|
||||
#define CMUS_FILE_H
|
||||
|
||||
#include <stddef.h> /* size_t */
|
||||
#include <sys/types.h> /* ssize_t */
|
||||
|
||||
ssize_t read_all(int fd, void *buf, size_t count);
|
||||
ssize_t write_all(int fd, const void *buf, size_t count);
|
||||
|
||||
/* @filename file to mmap for reading
|
||||
* @size returned size of the file or -1 if failed
|
||||
*
|
||||
* returns buffer or NULL if empty file or failed
|
||||
*/
|
||||
char *mmap_file(const char *filename, ssize_t *size);
|
||||
|
||||
void buffer_for_each_line(const char *buf, int size,
|
||||
int (*cb)(void *data, const char *line),
|
||||
void *data);
|
||||
void buffer_for_each_line_reverse(const char *buf, int size,
|
||||
int (*cb)(void *data, const char *line),
|
||||
void *data);
|
||||
int file_for_each_line(const char *filename,
|
||||
int (*cb)(void *data, const char *line),
|
||||
void *data);
|
||||
|
||||
#endif
|
||||
474
filters.c
Normal file
474
filters.c
Normal file
@@ -0,0 +1,474 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2005 Timo Hirvonen
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "filters.h"
|
||||
#include "cmdline.h"
|
||||
#include "expr.h"
|
||||
#include "window.h"
|
||||
#include "search.h"
|
||||
#include "uchar.h"
|
||||
#include "lib.h"
|
||||
#include "misc.h"
|
||||
#include "file.h"
|
||||
#include "ui_curses.h"
|
||||
#include "xmalloc.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
|
||||
struct window *filters_win;
|
||||
struct searchable *filters_searchable;
|
||||
LIST_HEAD(filters_head);
|
||||
|
||||
static const char *recursive_filter;
|
||||
|
||||
static inline void filter_entry_to_iter(struct filter_entry *e, struct iter *iter)
|
||||
{
|
||||
iter->data0 = &filters_head;
|
||||
iter->data1 = e;
|
||||
iter->data2 = NULL;
|
||||
}
|
||||
|
||||
static GENERIC_ITER_PREV(filters_get_prev, struct filter_entry, node)
|
||||
static GENERIC_ITER_NEXT(filters_get_next, struct filter_entry, node)
|
||||
|
||||
static int filters_search_get_current(void *data, struct iter *iter, enum search_direction dir)
|
||||
{
|
||||
return window_get_sel(filters_win, iter);
|
||||
}
|
||||
|
||||
static int filters_search_matches(void *data, struct iter *iter, const char *text)
|
||||
{
|
||||
char **words = get_words(text);
|
||||
int matched = 0;
|
||||
|
||||
if (words[0] != NULL) {
|
||||
struct filter_entry *e;
|
||||
int i;
|
||||
|
||||
e = iter_to_filter_entry(iter);
|
||||
for (i = 0; ; i++) {
|
||||
if (words[i] == NULL) {
|
||||
window_set_sel(filters_win, iter);
|
||||
matched = 1;
|
||||
break;
|
||||
}
|
||||
if (u_strcasestr(e->name, words[i]) == NULL)
|
||||
break;
|
||||
}
|
||||
}
|
||||
free_str_array(words);
|
||||
return matched;
|
||||
}
|
||||
|
||||
static const struct searchable_ops filters_search_ops = {
|
||||
.get_prev = filters_get_prev,
|
||||
.get_next = filters_get_next,
|
||||
.get_current = filters_search_get_current,
|
||||
.matches = filters_search_matches
|
||||
};
|
||||
|
||||
static void free_filter(struct filter_entry *e)
|
||||
{
|
||||
free(e->name);
|
||||
free(e->filter);
|
||||
free(e);
|
||||
}
|
||||
|
||||
static struct filter_entry *find_filter(const char *name)
|
||||
{
|
||||
struct filter_entry *e;
|
||||
|
||||
list_for_each_entry(e, &filters_head, node) {
|
||||
if (strcmp(e->name, name) == 0)
|
||||
return e;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const char *get_filter(const char *name)
|
||||
{
|
||||
struct filter_entry *e = find_filter(name);
|
||||
|
||||
if (e) {
|
||||
if (e->visited) {
|
||||
recursive_filter = e->name;
|
||||
return NULL;
|
||||
}
|
||||
e->visited = 1;
|
||||
return e->filter;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void edit_sel_filter(void)
|
||||
{
|
||||
struct iter sel;
|
||||
struct filter_entry *e;
|
||||
char buf[512];
|
||||
|
||||
if (!window_get_sel(filters_win, &sel))
|
||||
return;
|
||||
|
||||
e = iter_to_filter_entry(&sel);
|
||||
snprintf(buf, sizeof(buf), "fset %s=%s", e->name, e->filter);
|
||||
cmdline_set_text(buf);
|
||||
enter_command_mode();
|
||||
}
|
||||
|
||||
void filters_activate(int win_activate)
|
||||
{
|
||||
struct filter_entry *f;
|
||||
struct expr *e, *expr = NULL;
|
||||
int unchanged = 1;
|
||||
|
||||
/* if no pending selection is to apply, edit currently select filter */
|
||||
list_for_each_entry(f, &filters_head, node) {
|
||||
if (f->act_stat != f->sel_stat)
|
||||
unchanged = 0;
|
||||
}
|
||||
|
||||
if (unchanged) {
|
||||
if (win_activate)
|
||||
edit_sel_filter();
|
||||
else
|
||||
return;
|
||||
}
|
||||
|
||||
/* mark visited and AND together all selected filters
|
||||
* mark any other filters unvisited */
|
||||
list_for_each_entry(f, &filters_head, node) {
|
||||
f->visited = 0;
|
||||
if (f->sel_stat == FS_IGNORE)
|
||||
continue;
|
||||
|
||||
f->visited = 1;
|
||||
e = expr_parse(f->filter);
|
||||
if (e == NULL) {
|
||||
error_msg("error parsing filter %s: %s", f->name, expr_error());
|
||||
if (expr)
|
||||
expr_free(expr);
|
||||
return;
|
||||
}
|
||||
|
||||
if (f->sel_stat == FS_NO) {
|
||||
/* add ! */
|
||||
struct expr *not = xnew(struct expr, 1);
|
||||
|
||||
not->type = EXPR_NOT;
|
||||
not->key = NULL;
|
||||
not->left = e;
|
||||
not->right = NULL;
|
||||
e = not;
|
||||
}
|
||||
if (expr == NULL) {
|
||||
expr = e;
|
||||
} else {
|
||||
struct expr *and = xnew(struct expr, 1);
|
||||
|
||||
and->type = EXPR_AND;
|
||||
and->key = NULL;
|
||||
and->left = expr;
|
||||
and->right = e;
|
||||
expr->parent = and;
|
||||
e->parent = and;
|
||||
expr = and;
|
||||
}
|
||||
}
|
||||
|
||||
recursive_filter = NULL;
|
||||
if (expr && expr_check_leaves(&expr, get_filter)) {
|
||||
if (recursive_filter) {
|
||||
error_msg("recursion detected in filter %s", recursive_filter);
|
||||
} else {
|
||||
error_msg("error parsing filter: %s", expr_error());
|
||||
}
|
||||
expr_free(expr);
|
||||
return;
|
||||
}
|
||||
|
||||
/* update active flag */
|
||||
list_for_each_entry(f, &filters_head, node) {
|
||||
f->act_stat = f->sel_stat;
|
||||
}
|
||||
lib_set_filter(expr);
|
||||
filters_win->changed = 1;
|
||||
}
|
||||
|
||||
static int for_each_name(const char *str, int (*cb)(const char *name, int sel_stat))
|
||||
{
|
||||
char buf[64];
|
||||
int s, e, len;
|
||||
|
||||
e = 0;
|
||||
do {
|
||||
int sel_stat = FS_YES;
|
||||
|
||||
s = e;
|
||||
while (str[s] == ' ')
|
||||
s++;
|
||||
if (str[s] == '!') {
|
||||
sel_stat = FS_NO;
|
||||
s++;
|
||||
}
|
||||
e = s;
|
||||
while (str[e] && str[e] != ' ')
|
||||
e++;
|
||||
|
||||
len = e - s;
|
||||
if (len == 0)
|
||||
return 0;
|
||||
if (len >= sizeof(buf)) {
|
||||
error_msg("filter name too long");
|
||||
return -1;
|
||||
}
|
||||
|
||||
memcpy(buf, str + s, len);
|
||||
buf[len] = 0;
|
||||
|
||||
if (cb(buf, sel_stat))
|
||||
return -1;
|
||||
} while (1);
|
||||
}
|
||||
|
||||
static int ensure_filter_name(const char *name, int sel_stat)
|
||||
{
|
||||
if (find_filter(name) == NULL) {
|
||||
error_msg("no such filter %s", name);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int select_filter(const char *name, int sel_stat)
|
||||
{
|
||||
struct filter_entry *e = find_filter(name);
|
||||
|
||||
e->sel_stat = sel_stat;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void filters_activate_names(const char *str)
|
||||
{
|
||||
struct filter_entry *f;
|
||||
|
||||
/* first validate all filter names */
|
||||
if (str && for_each_name(str, ensure_filter_name))
|
||||
return;
|
||||
|
||||
/* mark all filters unselected */
|
||||
list_for_each_entry(f, &filters_head, node)
|
||||
f->sel_stat = FS_IGNORE;
|
||||
|
||||
/* select the filters */
|
||||
if (str)
|
||||
for_each_name(str, select_filter);
|
||||
|
||||
/* activate selected */
|
||||
filters_activate(0);
|
||||
}
|
||||
|
||||
void filters_toggle_filter(void)
|
||||
{
|
||||
struct iter iter;
|
||||
|
||||
if (window_get_sel(filters_win, &iter)) {
|
||||
struct filter_entry *e;
|
||||
|
||||
e = iter_to_filter_entry(&iter);
|
||||
e->sel_stat = (e->sel_stat + 1) % 3;
|
||||
filters_win->changed = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void filters_delete_filter(void)
|
||||
{
|
||||
struct iter iter;
|
||||
|
||||
if (window_get_sel(filters_win, &iter)) {
|
||||
struct filter_entry *e;
|
||||
|
||||
e = iter_to_filter_entry(&iter);
|
||||
if (yes_no_query("Delete filter '%s'? [y/N]", e->name) == UI_QUERY_ANSWER_YES) {
|
||||
window_row_vanishes(filters_win, &iter);
|
||||
list_del(&e->node);
|
||||
free_filter(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int validate_filter_name(const char *name)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; name[i]; i++) {
|
||||
if (isalnum((unsigned char)name[i]))
|
||||
continue;
|
||||
if (name[i] == '_' || name[i] == '-')
|
||||
continue;
|
||||
return 0;
|
||||
}
|
||||
return i != 0;
|
||||
}
|
||||
|
||||
static void do_filters_set_filter(const char *keyval)
|
||||
{
|
||||
const char *eq = strchr(keyval, '=');
|
||||
char *key, *val;
|
||||
struct expr *expr;
|
||||
struct filter_entry *new;
|
||||
struct list_head *item;
|
||||
|
||||
if (eq == NULL) {
|
||||
if (ui_initialized)
|
||||
error_msg("invalid argument ('key=value' expected)");
|
||||
return;
|
||||
}
|
||||
key = xstrndup(keyval, eq - keyval);
|
||||
val = xstrdup(eq + 1);
|
||||
if (!validate_filter_name(key)) {
|
||||
if (ui_initialized)
|
||||
error_msg("invalid filter name (can only contain 'a-zA-Z0-9_-' characters)");
|
||||
free(key);
|
||||
free(val);
|
||||
return;
|
||||
}
|
||||
expr = expr_parse(val);
|
||||
if (expr == NULL) {
|
||||
if (ui_initialized)
|
||||
error_msg("error parsing filter %s: %s", val, expr_error());
|
||||
free(key);
|
||||
free(val);
|
||||
return;
|
||||
}
|
||||
expr_free(expr);
|
||||
|
||||
new = xnew(struct filter_entry, 1);
|
||||
new->name = key;
|
||||
new->filter = val;
|
||||
new->act_stat = FS_IGNORE;
|
||||
new->sel_stat = FS_IGNORE;
|
||||
|
||||
/* add or replace filter */
|
||||
list_for_each(item, &filters_head) {
|
||||
struct filter_entry *e = container_of(item, struct filter_entry, node);
|
||||
int res = strcmp(key, e->name);
|
||||
|
||||
if (res < 0)
|
||||
break;
|
||||
if (res == 0) {
|
||||
/* replace */
|
||||
struct iter iter;
|
||||
|
||||
new->sel_stat = e->sel_stat;
|
||||
if (ui_initialized) {
|
||||
filter_entry_to_iter(e, &iter);
|
||||
window_row_vanishes(filters_win, &iter);
|
||||
}
|
||||
item = item->next;
|
||||
list_del(&e->node);
|
||||
free_filter(e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* add before item */
|
||||
list_add_tail(&new->node, item);
|
||||
if (ui_initialized)
|
||||
window_changed(filters_win);
|
||||
}
|
||||
|
||||
void filters_init(void)
|
||||
{
|
||||
struct iter iter;
|
||||
|
||||
filters_win = window_new(filters_get_prev, filters_get_next);
|
||||
window_set_contents(filters_win, &filters_head);
|
||||
window_changed(filters_win);
|
||||
|
||||
iter.data0 = &filters_head;
|
||||
iter.data1 = NULL;
|
||||
iter.data2 = NULL;
|
||||
filters_searchable = searchable_new(NULL, &iter, &filters_search_ops);
|
||||
}
|
||||
|
||||
void filters_exit(void)
|
||||
{
|
||||
searchable_free(filters_searchable);
|
||||
window_free(filters_win);
|
||||
}
|
||||
|
||||
void filters_set_filter(const char *keyval)
|
||||
{
|
||||
do_filters_set_filter(keyval);
|
||||
}
|
||||
|
||||
struct expr *parse_filter(const char *val)
|
||||
{
|
||||
struct expr *e = NULL;
|
||||
struct filter_entry *f;
|
||||
|
||||
if (val) {
|
||||
e = expr_parse(val);
|
||||
if (e == NULL) {
|
||||
error_msg("error parsing filter %s: %s", val, expr_error());
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* mark all unvisited so that we can check recursion */
|
||||
list_for_each_entry(f, &filters_head, node)
|
||||
f->visited = 0;
|
||||
|
||||
recursive_filter = NULL;
|
||||
if (e && expr_check_leaves(&e, get_filter)) {
|
||||
if (recursive_filter) {
|
||||
error_msg("recursion detected in filter %s", recursive_filter);
|
||||
} else {
|
||||
error_msg("error parsing filter: %s", expr_error());
|
||||
}
|
||||
expr_free(e);
|
||||
return NULL;
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
void filters_set_anonymous(const char *val)
|
||||
{
|
||||
struct filter_entry *f;
|
||||
struct expr *e = NULL;
|
||||
|
||||
if (val) {
|
||||
e = parse_filter(val);
|
||||
if (e == NULL)
|
||||
return;
|
||||
}
|
||||
|
||||
/* deactive all filters */
|
||||
list_for_each_entry(f, &filters_head, node)
|
||||
f->act_stat = FS_IGNORE;
|
||||
|
||||
lib_set_filter(e);
|
||||
|
||||
filters_win->changed = 1;
|
||||
}
|
||||
|
||||
void filters_set_live(const char *val)
|
||||
{
|
||||
lib_set_live_filter(val);
|
||||
update_filterline();
|
||||
}
|
||||
92
filters.h
Normal file
92
filters.h
Normal file
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2005 Timo Hirvonen
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CMUS_FILTERS_H
|
||||
#define CMUS_FILTERS_H
|
||||
|
||||
#include "list.h"
|
||||
#include "window.h"
|
||||
#include "search.h"
|
||||
|
||||
/* factivate foo !bar
|
||||
*
|
||||
* foo: FS_YES
|
||||
* bar: FS_NO
|
||||
* baz: FS_IGNORE
|
||||
*/
|
||||
enum {
|
||||
/* [ ] filter not selected */
|
||||
FS_IGNORE,
|
||||
/* [*] filter selected */
|
||||
FS_YES,
|
||||
/* [!] filter selected and inverted */
|
||||
FS_NO,
|
||||
};
|
||||
|
||||
struct filter_entry {
|
||||
struct list_head node;
|
||||
|
||||
char *name;
|
||||
char *filter;
|
||||
unsigned visited : 1;
|
||||
|
||||
/* selected and activated status (FS_* enum) */
|
||||
unsigned sel_stat : 2;
|
||||
unsigned act_stat : 2;
|
||||
};
|
||||
|
||||
static inline struct filter_entry *iter_to_filter_entry(struct iter *iter)
|
||||
{
|
||||
return iter->data1;
|
||||
}
|
||||
|
||||
extern struct window *filters_win;
|
||||
extern struct searchable *filters_searchable;
|
||||
extern struct list_head filters_head;
|
||||
|
||||
void filters_init(void);
|
||||
void filters_exit(void);
|
||||
|
||||
/* parse filter and expand sub filters */
|
||||
struct expr *parse_filter(const char *val);
|
||||
|
||||
/* add filter to filter list (replaces old filter with same name)
|
||||
*
|
||||
* @keyval "name=value" where value is filter
|
||||
*/
|
||||
void filters_set_filter(const char *keyval);
|
||||
|
||||
/* set throwaway filter (not saved to the filter list)
|
||||
*
|
||||
* @val filter or NULL to disable filtering
|
||||
*/
|
||||
void filters_set_anonymous(const char *val);
|
||||
|
||||
/* set live filter (not saved to the filter list)
|
||||
*
|
||||
* @val filter or NULL to disable filtering
|
||||
*/
|
||||
void filters_set_live(const char *val);
|
||||
|
||||
void filters_activate_names(const char *str);
|
||||
|
||||
void filters_activate(int win_activate);
|
||||
void filters_toggle_filter(void);
|
||||
void filters_delete_filter(void);
|
||||
|
||||
#endif
|
||||
690
format_print.c
Normal file
690
format_print.c
Normal file
@@ -0,0 +1,690 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2004 Timo Hirvonen
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "format_print.h"
|
||||
#include "expr.h"
|
||||
#include "glob.h"
|
||||
#include "utils.h"
|
||||
#include "options.h"
|
||||
#include "uchar.h"
|
||||
#include "xmalloc.h"
|
||||
#include "debug.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
static int width;
|
||||
static int align_left;
|
||||
static int pad;
|
||||
static bool width_is_exact;
|
||||
|
||||
static GBUF(cond_buffer);
|
||||
static GBUF(l_str);
|
||||
static GBUF(m_str);
|
||||
static GBUF(r_str);
|
||||
static struct fp_len str_len = {0, 0, 0};
|
||||
static int *len = &str_len.llen;
|
||||
static struct gbuf* str = &l_str;
|
||||
|
||||
static void stack_print(char *stack, int stack_len)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
gbuf_grow(str, width ? width : stack_len);
|
||||
char* buf = str->buffer + str->len;
|
||||
|
||||
if (width) {
|
||||
if (align_left) {
|
||||
while (i < width && stack_len)
|
||||
buf[i++] = stack[--stack_len];
|
||||
while (i < width)
|
||||
buf[i++] = pad;
|
||||
} else {
|
||||
int pad_len;
|
||||
|
||||
if (stack_len > width)
|
||||
stack_len = width;
|
||||
pad_len = width - stack_len;
|
||||
while (i < pad_len)
|
||||
buf[i++] = pad;
|
||||
while (i < width)
|
||||
buf[i++] = stack[--stack_len];
|
||||
}
|
||||
} else {
|
||||
while (stack_len)
|
||||
buf[i++] = stack[--stack_len];
|
||||
}
|
||||
gbuf_used(str, i);
|
||||
*len += i;
|
||||
}
|
||||
|
||||
static void print_num(int num)
|
||||
{
|
||||
char stack[20];
|
||||
int i, p;
|
||||
|
||||
if (num < 0) {
|
||||
if (width == 0)
|
||||
width = 1;
|
||||
for (i = 0; i < width; i++)
|
||||
gbuf_add_ch(str, '?');
|
||||
*len += width;
|
||||
return;
|
||||
}
|
||||
p = 0;
|
||||
do {
|
||||
stack[p++] = num % 10 + '0';
|
||||
num /= 10;
|
||||
} while (num);
|
||||
|
||||
stack_print(stack, p);
|
||||
}
|
||||
|
||||
#define DBL_MAX_LEN (20)
|
||||
|
||||
static void print_double(double num)
|
||||
{
|
||||
char stack[DBL_MAX_LEN], b[DBL_MAX_LEN];
|
||||
int i, p = 0;
|
||||
|
||||
i = snprintf(b, DBL_MAX_LEN, "%g", num) - 1;
|
||||
while (i >= 0) {
|
||||
stack[p++] = b[i];
|
||||
i--;
|
||||
}
|
||||
stack_print(stack, p);
|
||||
}
|
||||
|
||||
/* print '{,-}{h:,}mm:ss' */
|
||||
static void print_time(int t)
|
||||
{
|
||||
int h, m, s;
|
||||
char stack[32];
|
||||
int neg = 0;
|
||||
int p = 0;
|
||||
|
||||
if (t < 0) {
|
||||
neg = 1;
|
||||
t *= -1;
|
||||
}
|
||||
h = t / 3600;
|
||||
t = t % 3600;
|
||||
m = t / 60;
|
||||
s = t % 60;
|
||||
|
||||
/* put all chars to stack in reverse order ;) */
|
||||
stack[p++] = s % 10 + '0';
|
||||
stack[p++] = s / 10 + '0';
|
||||
stack[p++] = ':';
|
||||
stack[p++] = m % 10 + '0';
|
||||
if (m / 10 || h || time_show_leading_zero)
|
||||
stack[p++] = m / 10 + '0';
|
||||
if (h) {
|
||||
stack[p++] = ':';
|
||||
do {
|
||||
stack[p++] = h % 10 + '0';
|
||||
h /= 10;
|
||||
} while (h);
|
||||
}
|
||||
if (neg)
|
||||
stack[p++] = '-';
|
||||
|
||||
stack_print(stack, p);
|
||||
}
|
||||
|
||||
static void print_str(const char *src)
|
||||
{
|
||||
int str_width = u_str_width(src);
|
||||
|
||||
if (width && width_is_exact) {
|
||||
*len += width;
|
||||
if (align_left) {
|
||||
gbuf_add_ustr(str, src, &width);
|
||||
gbuf_set(str, ' ', width);
|
||||
} else {
|
||||
int s = 0;
|
||||
int ws_len = width - str_width;
|
||||
if (ws_len < 0) {
|
||||
int skip = -ws_len;
|
||||
int clipped_mark_len = min_u(u_str_width(clipped_text_internal), width);
|
||||
skip += clipped_mark_len;
|
||||
gbuf_add_ustr(str, clipped_text_internal, &clipped_mark_len);
|
||||
s = u_skip_chars(src, &skip, true);
|
||||
/* pad if a wide character caused us to skip too much */
|
||||
ws_len = -skip;
|
||||
|
||||
}
|
||||
gbuf_set(str, ' ', ws_len);
|
||||
gbuf_add_ustr(str, src + s, &width);
|
||||
}
|
||||
} else {
|
||||
if (!width)
|
||||
width = str_width;
|
||||
*len += width;
|
||||
gbuf_add_ustr(str, src, &width);
|
||||
*len -= width;
|
||||
}
|
||||
}
|
||||
|
||||
static inline int strnequal(const char *a, const char *b, size_t b_len)
|
||||
{
|
||||
return a && (strlen(a) == b_len) && (memcmp(a, b, b_len) == 0);
|
||||
}
|
||||
|
||||
static const struct format_option *find_fopt(const struct format_option *fopts, const char *key)
|
||||
{
|
||||
const struct format_option *fo;
|
||||
char ch = strlen(key) == 1 ? *key : 0;
|
||||
for (fo = fopts; fo->type != 0; fo++) {
|
||||
if ((ch != 0 && fo->ch == ch) || strnequal(fo->str, key, strlen(key))) {
|
||||
return fo;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const char *str_val(const char *key, const struct format_option *fopts, char *buf)
|
||||
{
|
||||
const struct format_option *fo;
|
||||
const struct cmus_opt *opt;
|
||||
const char *val = NULL;
|
||||
|
||||
fo = find_fopt(fopts, key);
|
||||
if (fo && !fo->empty) {
|
||||
if (fo->type == FO_STR)
|
||||
val = fo->fo_str;
|
||||
} else {
|
||||
opt = option_find_silent(key);
|
||||
if (opt) {
|
||||
opt->get(opt->data, buf, OPTION_MAX_SIZE);
|
||||
val = buf;
|
||||
}
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
static int int_val(const char *key, const struct format_option *fopts, char *buf)
|
||||
{
|
||||
const struct format_option *fo;
|
||||
int val = -1;
|
||||
|
||||
fo = find_fopt(fopts, key);
|
||||
if (fo && !fo->empty) {
|
||||
if (fo->type == FO_INT)
|
||||
val = fo->fo_int;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
static int format_eval_cond(struct expr* expr, const struct format_option *fopts)
|
||||
{
|
||||
if (!expr)
|
||||
return -1;
|
||||
enum expr_type type = expr->type;
|
||||
const char *key;
|
||||
const struct format_option *fo;
|
||||
const struct cmus_opt *opt;
|
||||
char buf[OPTION_MAX_SIZE];
|
||||
|
||||
if (expr->left) {
|
||||
int left = format_eval_cond(expr->left, fopts);
|
||||
|
||||
if (type == EXPR_AND)
|
||||
return left && format_eval_cond(expr->right, fopts);
|
||||
if (type == EXPR_OR)
|
||||
return left || format_eval_cond(expr->right, fopts);
|
||||
/* EXPR_NOT */
|
||||
return !left;
|
||||
}
|
||||
|
||||
key = expr->key;
|
||||
if (type == EXPR_STR) {
|
||||
const char *val = str_val(key, fopts, buf);
|
||||
int res;
|
||||
|
||||
if (!val)
|
||||
val = "";
|
||||
res = glob_match(&expr->estr.glob_head, val);
|
||||
if (expr->estr.op == SOP_EQ)
|
||||
return res;
|
||||
return !res;
|
||||
} else if (type == EXPR_INT) {
|
||||
int val = int_val(key, fopts, buf);
|
||||
int res = val - expr->eint.val;
|
||||
if (val == -1 || expr->eint.val == -1) {
|
||||
switch (expr->eid.op) {
|
||||
case KOP_EQ:
|
||||
return res == 0;
|
||||
case KOP_NE:
|
||||
return res != 0;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return expr_op_to_bool(res, expr->eint.op);
|
||||
} else if (type == EXPR_ID) {
|
||||
int a = 0, b = 0;
|
||||
const char *sa, *sb;
|
||||
int res = 0;
|
||||
if ((sa = str_val(key, fopts, buf)) && (sb = str_val(expr->eid.key, fopts, buf))) {
|
||||
res = strcmp(sa, sb);
|
||||
return expr_op_to_bool(res, expr->eid.op);
|
||||
} else {
|
||||
a = int_val(key, fopts, buf);
|
||||
b = int_val(expr->eid.key, fopts, buf);
|
||||
res = a - b;
|
||||
if (a == -1 || b == -1) {
|
||||
switch (expr->eid.op) {
|
||||
case KOP_EQ:
|
||||
return res == 0;
|
||||
case KOP_NE:
|
||||
return res != 0;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return expr_op_to_bool(res, expr->eid.op);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
if (strcmp(key, "stream") == 0) {
|
||||
fo = find_fopt(fopts, "filename");
|
||||
return fo && is_http_url(fo->fo_str);
|
||||
}
|
||||
fo = find_fopt(fopts, key);
|
||||
if (fo)
|
||||
return !fo->empty;
|
||||
opt = option_find_silent(key);
|
||||
if (opt) {
|
||||
opt->get(opt->data, buf, OPTION_MAX_SIZE);
|
||||
if (strcmp(buf, "false") != 0 && strlen(buf) != 0)
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct expr *format_parse_cond(const char* format, int size)
|
||||
{
|
||||
gbuf_clear(&cond_buffer);
|
||||
gbuf_add_bytes(&cond_buffer, format, size);
|
||||
return expr_parse_i(cond_buffer.buffer, "condition contains control characters", 0);
|
||||
}
|
||||
|
||||
static uchar format_skip_cond_expr(const char *format, int *s)
|
||||
{
|
||||
uchar r = 0;
|
||||
while (format[*s]) {
|
||||
uchar u = u_get_char(format, s);
|
||||
if (u == '}' || u == '?') {
|
||||
return u;
|
||||
}
|
||||
if (u != '%') {
|
||||
continue;
|
||||
}
|
||||
u = u_get_char(format, s);
|
||||
if (u == '%' || u == '?' || u == '!' || u == '=') {
|
||||
continue;
|
||||
}
|
||||
if (u == '-') {
|
||||
u = u_get_char(format, s);
|
||||
}
|
||||
if (u == '.')
|
||||
u = u_get_char(format, s);
|
||||
while (isdigit(u)) {
|
||||
u = u_get_char(format, s);
|
||||
}
|
||||
if (u == '{') {
|
||||
unsigned level = 1;
|
||||
while (level) {
|
||||
u = u_get_char(format, s);
|
||||
if (u == 0)
|
||||
return 0;
|
||||
if (u == '}')
|
||||
--level;
|
||||
if (u != '%')
|
||||
continue;
|
||||
u = u_get_char(format, s);
|
||||
if (u == '%' || u == '?' || u == '!' || u == '=')
|
||||
continue;
|
||||
if (u == '-')
|
||||
u = u_get_char(format, s);
|
||||
if (u == '.')
|
||||
u = u_get_char(format, s);
|
||||
while (isdigit(u))
|
||||
u = u_get_char(format, s);
|
||||
if (u == 0)
|
||||
return 0;
|
||||
if (u == '{')
|
||||
++level;
|
||||
}
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
static int format_read_cond(const char *format, int *s, int *a, int *b, int *end)
|
||||
{
|
||||
uchar t = format_skip_cond_expr(format, s);
|
||||
if (t != '?')
|
||||
return 1;
|
||||
*a = *s - 1;
|
||||
t = format_skip_cond_expr(format, s);
|
||||
if (t == 0)
|
||||
return 1;
|
||||
if (t == '?') {
|
||||
*b = *s - 1;
|
||||
t = format_skip_cond_expr(format, s);
|
||||
if (t != '}')
|
||||
return 1;
|
||||
}
|
||||
*end = *s - 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void format_parse(int str_width, const char *format, const struct format_option *fopts, int f_size);
|
||||
|
||||
static void format_parse_if(int str_width, const char *format, const struct format_option *fopts, int *s)
|
||||
{
|
||||
int cond_pos = *s, then_pos = -1, else_pos = -1, end_pos = -1, cond_res = -1;
|
||||
BUG_ON(format_read_cond(format, s, &then_pos, &else_pos, &end_pos) != 0);
|
||||
|
||||
struct expr *cond = format_parse_cond(format + cond_pos, then_pos - cond_pos);
|
||||
cond_res = format_eval_cond(cond, fopts);
|
||||
if (cond)
|
||||
expr_free(cond);
|
||||
|
||||
BUG_ON(cond_res < 0);
|
||||
if (cond_res) {
|
||||
format_parse(str_width, format + then_pos + 1, fopts,
|
||||
(else_pos > 0 ? else_pos : end_pos) - then_pos - 1);
|
||||
} else if (else_pos > 0) {
|
||||
format_parse(str_width, format + else_pos + 1, fopts, end_pos - else_pos - 1);
|
||||
}
|
||||
|
||||
*s = end_pos + 1;
|
||||
}
|
||||
|
||||
static void format_parse(int str_width, const char *format, const struct format_option *fopts, int f_size)
|
||||
{
|
||||
int s = 0;
|
||||
|
||||
while (s < f_size) {
|
||||
const struct format_option *fo;
|
||||
int long_len = 0;
|
||||
const char *long_begin = NULL;
|
||||
uchar u;
|
||||
|
||||
u = u_get_char(format, &s);
|
||||
if (u != '%') {
|
||||
gbuf_add_uchar(str, u);
|
||||
(*len) += u_char_width(u);
|
||||
continue;
|
||||
}
|
||||
u = u_get_char(format, &s);
|
||||
if (u == '%' || u == '?') {
|
||||
gbuf_add_ch(str, u);
|
||||
++(*len);
|
||||
continue;
|
||||
}
|
||||
if (u == '!') {
|
||||
/* middle (priority) text starts */
|
||||
str = &m_str;
|
||||
len = &str_len.mlen;
|
||||
continue;
|
||||
}
|
||||
if (u == '=') {
|
||||
/* right aligned text starts */
|
||||
str = &r_str;
|
||||
len = &str_len.rlen;
|
||||
continue;
|
||||
}
|
||||
align_left = 0;
|
||||
if (u == '-') {
|
||||
align_left = 1;
|
||||
u = u_get_char(format, &s);
|
||||
}
|
||||
width_is_exact = true;
|
||||
if (u == '.') {
|
||||
width_is_exact = false;
|
||||
u = u_get_char(format, &s);
|
||||
}
|
||||
pad = ' ';
|
||||
if (u == '0') {
|
||||
pad = '0';
|
||||
u = u_get_char(format, &s);
|
||||
}
|
||||
width = 0;
|
||||
while (isdigit(u)) {
|
||||
/* minimum length of this field */
|
||||
width *= 10;
|
||||
width += u - '0';
|
||||
u = u_get_char(format, &s);
|
||||
}
|
||||
if (u == '%') {
|
||||
width = (width * str_width) / 100.0 + 0.5;
|
||||
u = u_get_char(format, &s);
|
||||
}
|
||||
if (u == '{') {
|
||||
long_begin = format + s;
|
||||
if (*long_begin == '?') {
|
||||
++s;
|
||||
format_parse_if(str_width, format, fopts, &s);
|
||||
BUG_ON(s > f_size);
|
||||
continue;
|
||||
}
|
||||
while (1) {
|
||||
BUG_ON(s >= f_size);
|
||||
u = u_get_char(format, &s);
|
||||
if (u == '}')
|
||||
break;
|
||||
long_len++;
|
||||
}
|
||||
}
|
||||
for (fo = fopts; ; fo++) {
|
||||
BUG_ON(fo->type == 0);
|
||||
if (long_len ? strnequal(fo->str, long_begin, long_len)
|
||||
: (fo->ch == u)) {
|
||||
|
||||
int type = fo->type;
|
||||
|
||||
if (fo->empty) {
|
||||
gbuf_set(str, ' ', width);
|
||||
*len += width;
|
||||
} else if (type == FO_STR) {
|
||||
print_str(fo->fo_str);
|
||||
} else if (type == FO_INT) {
|
||||
print_num(fo->fo_int);
|
||||
} else if (type == FO_TIME) {
|
||||
print_time(fo->fo_time);
|
||||
} else if (type == FO_DOUBLE) {
|
||||
print_double(fo->fo_double);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void format_read(int str_width, const char *format, const struct format_option *fopts)
|
||||
{
|
||||
gbuf_clear(&l_str);
|
||||
gbuf_clear(&m_str);
|
||||
gbuf_clear(&r_str);
|
||||
str_len.llen = 0;
|
||||
str_len.mlen = 0;
|
||||
str_len.rlen = 0;
|
||||
str = &l_str;
|
||||
len = &str_len.llen;
|
||||
format_parse(str_width, format, fopts, strlen(format));
|
||||
}
|
||||
|
||||
static void format_write(struct gbuf *buf, int str_width)
|
||||
{
|
||||
if (str_width == 0)
|
||||
str_width = str_len.llen + str_len.mlen + str_len.rlen + (str_len.rlen > 0);
|
||||
|
||||
/* NOTE: any invalid UTF-8 bytes have already been converted to <xx>
|
||||
* (ASCII) where x is hex digit
|
||||
*/
|
||||
|
||||
if (str_len.llen + str_len.mlen + str_len.rlen <= str_width) {
|
||||
/* all fit */
|
||||
int ws_len = str_width - (str_len.llen + str_len.mlen + str_len.rlen);
|
||||
|
||||
gbuf_add_bytes(buf, l_str.buffer, l_str.len);
|
||||
gbuf_add_bytes(buf, m_str.buffer, m_str.len);
|
||||
gbuf_set(buf, ' ', ws_len);
|
||||
gbuf_add_bytes(buf, r_str.buffer, r_str.len);
|
||||
} else {
|
||||
/* keep first character since it's almost always padding */
|
||||
int clipped_mark_len = min_u(u_str_width(clipped_text_internal) + 1, str_width);
|
||||
int r_space = str_width - clipped_mark_len;
|
||||
int r_width = min_i(r_space, str_len.rlen);
|
||||
int m_space = r_space - r_width;
|
||||
int m_width = min_i(m_space, str_len.mlen);
|
||||
int l_space = m_space - m_width;
|
||||
int l_width = l_space + clipped_mark_len;
|
||||
int r_idx = 0, ws_pad = 0;
|
||||
|
||||
gbuf_add_ustr(buf, l_str.buffer, &l_width);
|
||||
ws_pad += l_width;
|
||||
gbuf_add_ustr(buf, m_str.buffer, &m_width);
|
||||
ws_pad += m_width;
|
||||
|
||||
int r_skip = str_len.rlen - r_width;
|
||||
r_idx = u_skip_chars(r_str.buffer, &r_skip, true);
|
||||
ws_pad += -r_skip;
|
||||
gbuf_set(buf, ' ', ws_pad);
|
||||
gbuf_add_bytes(buf, r_str.buffer + r_idx, r_str.len - r_idx);
|
||||
}
|
||||
}
|
||||
|
||||
struct fp_len format_print(struct gbuf *buf, int str_width, const char *format, const struct format_option *fopts)
|
||||
{
|
||||
format_read(str_width, format, fopts);
|
||||
|
||||
#if DEBUG > 1
|
||||
if (str_len.llen > 0) {
|
||||
int ul = u_str_width(l_str.buffer);
|
||||
if (ul != str_len.llen)
|
||||
d_print("L %d != %d: size=%zu '%s'\n", ul, str_len.llen, l_str.len, l_str.buffer);
|
||||
}
|
||||
|
||||
if (str_len.rlen > 0) {
|
||||
int ul = u_str_width(r_str.buffer);
|
||||
if (ul != str_len.rlen)
|
||||
d_print("R %d != %d: size=%zu '%s'\n", ul, str_len.rlen, r_str.len, r_str.buffer);
|
||||
}
|
||||
#endif
|
||||
|
||||
format_write(buf, str_width);
|
||||
return str_len;
|
||||
}
|
||||
|
||||
static int format_valid_sub(const char *format, const struct format_option *fopts, int f_size);
|
||||
|
||||
static int format_valid_if(const char *format, const struct format_option *fopts, int *s)
|
||||
{
|
||||
int cond_pos = *s, then_pos = -1, else_pos = -1, end_pos = -1;
|
||||
if (format_read_cond(format, s, &then_pos, &else_pos, &end_pos) != 0)
|
||||
return 0;
|
||||
|
||||
struct expr *cond = format_parse_cond(format + cond_pos, then_pos - cond_pos);
|
||||
if (cond == NULL)
|
||||
return 0;
|
||||
expr_free(cond);
|
||||
|
||||
if (!format_valid_sub(format + then_pos + 1, fopts,
|
||||
(else_pos > 0 ? else_pos : end_pos) - then_pos - 1))
|
||||
return 0;
|
||||
if (else_pos > 0)
|
||||
if (!format_valid_sub(format + else_pos + 1, fopts, end_pos - else_pos - 1))
|
||||
return 0;
|
||||
|
||||
*s = end_pos + 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int format_valid_sub(const char *format, const struct format_option *fopts, int f_size)
|
||||
{
|
||||
int s = 0;
|
||||
|
||||
while (s < f_size) {
|
||||
uchar u;
|
||||
|
||||
u = u_get_char(format, &s);
|
||||
if (u == '%') {
|
||||
int pad_zero = 0, long_len = 0;
|
||||
const struct format_option *fo;
|
||||
const char *long_begin = NULL;
|
||||
|
||||
u = u_get_char(format, &s);
|
||||
if (u == '%' || u == '?' || u == '!' || u == '=')
|
||||
continue;
|
||||
if (u == '-')
|
||||
u = u_get_char(format, &s);
|
||||
if (u == '.')
|
||||
u = u_get_char(format, &s);
|
||||
if (u == '0') {
|
||||
pad_zero = 1;
|
||||
u = u_get_char(format, &s);
|
||||
}
|
||||
while (isdigit(u))
|
||||
u = u_get_char(format, &s);
|
||||
if (u == '%')
|
||||
u = u_get_char(format, &s);
|
||||
if (u == '{') {
|
||||
long_begin = format + s;
|
||||
if (*long_begin == '?') {
|
||||
++s;
|
||||
if (!format_valid_if(format, fopts, &s))
|
||||
return 0;
|
||||
if (s > f_size)
|
||||
return 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
while (1) {
|
||||
if (s >= f_size)
|
||||
return 0;
|
||||
u = u_get_char(format, &s);
|
||||
if (u == '}')
|
||||
break;
|
||||
long_len++;
|
||||
}
|
||||
}
|
||||
for (fo = fopts; fo->type; fo++) {
|
||||
if (long_len ? strnequal(fo->str, long_begin, long_len)
|
||||
: (fo->ch == u)) {
|
||||
if (pad_zero && !fo->pad_zero)
|
||||
return 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (! fo->type)
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int format_valid(const char *format, const struct format_option *fopts)
|
||||
{
|
||||
return format_valid_sub(format, fopts, strlen(format));
|
||||
}
|
||||
65
format_print.h
Normal file
65
format_print.h
Normal file
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2004 Timo Hirvonen
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CMUS_FORMAT_PRINT_H
|
||||
#define CMUS_FORMAT_PRINT_H
|
||||
|
||||
#include "gbuf.h"
|
||||
|
||||
struct format_option {
|
||||
union {
|
||||
/* NULL is treated like "" */
|
||||
const char *fo_str;
|
||||
int fo_int;
|
||||
/* [h:]mm:ss. can be negative */
|
||||
int fo_time;
|
||||
double fo_double;
|
||||
};
|
||||
/* set to 1 if you want to disable printing */
|
||||
unsigned int empty : 1;
|
||||
/* set to 1 if zero padding is allowed */
|
||||
unsigned int pad_zero : 1;
|
||||
enum { FO_STR = 1, FO_INT, FO_TIME, FO_DOUBLE } type;
|
||||
char ch;
|
||||
const char *str;
|
||||
};
|
||||
|
||||
/* gcc < 4.6 and icc < 12.0 can't properly initialize anonymous unions */
|
||||
#if (defined(__GNUC__) && defined(__GNUC_MINOR__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6))) || \
|
||||
(defined(__INTEL_COMPILER) && __INTEL_COMPILER < 1200)
|
||||
#define UNION_INIT(f, v) { .f = v }
|
||||
#else
|
||||
#define UNION_INIT(f, v) .f = v
|
||||
#endif
|
||||
|
||||
#define DEF_FO_STR(c, s, z) { UNION_INIT(fo_str, ""), .type = FO_STR, .pad_zero = z, .ch = c, .str = s }
|
||||
#define DEF_FO_INT(c, s, z) { UNION_INIT(fo_int, 0), .type = FO_INT, .pad_zero = z, .ch = c, .str = s }
|
||||
#define DEF_FO_TIME(c, s, z) { UNION_INIT(fo_time, 0), .type = FO_TIME, .pad_zero = z, .ch = c, .str = s }
|
||||
#define DEF_FO_DOUBLE(c, s, z) { UNION_INIT(fo_double, 0.), .type = FO_DOUBLE, .pad_zero = z, .ch = c, .str = s }
|
||||
#define DEF_FO_END { .type = 0 }
|
||||
|
||||
struct fp_len {
|
||||
int llen;
|
||||
int mlen;
|
||||
int rlen;
|
||||
};
|
||||
|
||||
struct fp_len format_print(struct gbuf *buf, int str_width, const char *format, const struct format_option *fopts);
|
||||
int format_valid(const char *format, const struct format_option *fopts);
|
||||
|
||||
#endif
|
||||
160
gbuf.c
Normal file
160
gbuf.c
Normal file
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2008 Timo Hirvonen
|
||||
*
|
||||
* This code is largely based on strbuf in the GIT version control system.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "gbuf.h"
|
||||
#include "options.h"
|
||||
#include "utils.h"
|
||||
#include "xmalloc.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
char gbuf_empty_buffer[1];
|
||||
|
||||
static inline void gbuf_init(struct gbuf *buf)
|
||||
{
|
||||
buf->buffer = gbuf_empty_buffer;
|
||||
buf->alloc = 0;
|
||||
buf->len = 0;
|
||||
}
|
||||
|
||||
void gbuf_grow(struct gbuf *buf, size_t more)
|
||||
{
|
||||
size_t align = 64 - 1;
|
||||
size_t alloc = (buf->len + more + 1 + align) & ~align;
|
||||
|
||||
if (alloc > buf->alloc) {
|
||||
if (!buf->alloc)
|
||||
buf->buffer = NULL;
|
||||
buf->alloc = alloc;
|
||||
buf->buffer = xrealloc(buf->buffer, buf->alloc);
|
||||
// gbuf is not NUL terminated if this was first alloc
|
||||
buf->buffer[buf->len] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void gbuf_used(struct gbuf *buf, size_t used)
|
||||
{
|
||||
buf->len += used;
|
||||
buf->buffer[buf->len] = 0;
|
||||
}
|
||||
|
||||
void gbuf_free(struct gbuf *buf)
|
||||
{
|
||||
if (buf->alloc)
|
||||
free(buf->buffer);
|
||||
gbuf_init(buf);
|
||||
}
|
||||
|
||||
void gbuf_add_ch(struct gbuf *buf, char ch)
|
||||
{
|
||||
gbuf_grow(buf, 1);
|
||||
buf->buffer[buf->len] = ch;
|
||||
gbuf_used(buf, 1);
|
||||
}
|
||||
|
||||
void gbuf_add_uchar(struct gbuf *buf, uchar u)
|
||||
{
|
||||
size_t uchar_len = 0;
|
||||
gbuf_grow(buf, 4);
|
||||
u_set_char(buf->buffer + buf->len, &uchar_len, u);
|
||||
gbuf_used(buf, uchar_len);
|
||||
}
|
||||
|
||||
void gbuf_add_bytes(struct gbuf *buf, const void *data, size_t len)
|
||||
{
|
||||
gbuf_grow(buf, len);
|
||||
memcpy(buf->buffer + buf->len, data, len);
|
||||
gbuf_used(buf, len);
|
||||
}
|
||||
|
||||
void gbuf_add_str(struct gbuf *buf, const char *str)
|
||||
{
|
||||
int len = strlen(str);
|
||||
|
||||
if (!len)
|
||||
return;
|
||||
gbuf_grow(buf, len);
|
||||
memcpy(buf->buffer + buf->len, str, len);
|
||||
gbuf_used(buf, len);
|
||||
}
|
||||
|
||||
static int gbuf_mark_clipped_text(struct gbuf *buf)
|
||||
{
|
||||
int buf_width = u_str_width(buf->buffer);
|
||||
int clipped_mark_len = min_u(u_str_width(clipped_text_internal), buf_width);
|
||||
int skip = buf_width - clipped_mark_len;
|
||||
buf->len = u_skip_chars(buf->buffer, &skip, false);
|
||||
gbuf_grow(buf, strlen(clipped_text_internal));
|
||||
gbuf_used(buf, u_copy_chars(buf->buffer + buf->len, clipped_text_internal, &clipped_mark_len));
|
||||
return skip;
|
||||
}
|
||||
|
||||
void gbuf_add_ustr(struct gbuf *buf, const char *src, int *width)
|
||||
{
|
||||
int src_bytes = u_str_print_size(src) - 1;
|
||||
gbuf_grow(buf, src_bytes);
|
||||
size_t copy_bytes = u_copy_chars(buf->buffer + buf->len, src, width);
|
||||
gbuf_used(buf, copy_bytes);
|
||||
if (copy_bytes != src_bytes) {
|
||||
gbuf_set(buf, ' ', *width);
|
||||
*width = gbuf_mark_clipped_text(buf);
|
||||
}
|
||||
}
|
||||
|
||||
void gbuf_addf(struct gbuf *buf, const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
gbuf_vaddf(buf, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void gbuf_vaddf(struct gbuf *buf, const char *fmt, va_list ap)
|
||||
{
|
||||
va_list ap2;
|
||||
int slen;
|
||||
|
||||
va_copy(ap2, ap);
|
||||
slen = vsnprintf(buf->buffer + buf->len, buf->alloc - buf->len, fmt, ap);
|
||||
|
||||
if (slen > gbuf_avail(buf)) {
|
||||
gbuf_grow(buf, slen);
|
||||
slen = vsnprintf(buf->buffer + buf->len, buf->alloc - buf->len, fmt, ap2);
|
||||
}
|
||||
va_end(ap2);
|
||||
gbuf_used(buf, slen);
|
||||
}
|
||||
|
||||
void gbuf_set(struct gbuf *buf, int c, size_t count)
|
||||
{
|
||||
gbuf_grow(buf, count);
|
||||
memset(buf->buffer + buf->len, c, count);
|
||||
gbuf_used(buf, count);
|
||||
}
|
||||
|
||||
char *gbuf_steal(struct gbuf *buf)
|
||||
{
|
||||
char *b = buf->buffer;
|
||||
if (!buf->alloc)
|
||||
b = xnew0(char, 1);
|
||||
gbuf_init(buf);
|
||||
return b;
|
||||
}
|
||||
66
gbuf.h
Normal file
66
gbuf.h
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2008 Timo Hirvonen
|
||||
*
|
||||
* This code is largely based on strbuf in the GIT version control system.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CMUS_GBUF_H
|
||||
#define CMUS_GBUF_H
|
||||
|
||||
#include "compiler.h"
|
||||
#include "uchar.h"
|
||||
|
||||
#include <stddef.h> /* size_t */
|
||||
#include <stdarg.h>
|
||||
|
||||
struct gbuf {
|
||||
char *buffer;
|
||||
size_t alloc;
|
||||
size_t len;
|
||||
};
|
||||
|
||||
extern char gbuf_empty_buffer[];
|
||||
|
||||
#define GBUF(name) struct gbuf name = { gbuf_empty_buffer, 0, 0 }
|
||||
|
||||
static inline void gbuf_clear(struct gbuf *buf)
|
||||
{
|
||||
buf->len = 0;
|
||||
buf->buffer[0] = 0;
|
||||
}
|
||||
|
||||
static inline size_t gbuf_avail(struct gbuf *buf)
|
||||
{
|
||||
if (buf->alloc)
|
||||
return buf->alloc - buf->len - 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void gbuf_grow(struct gbuf *buf, size_t more);
|
||||
void gbuf_used(struct gbuf *buf, size_t used);
|
||||
void gbuf_free(struct gbuf *buf);
|
||||
void gbuf_add_ch(struct gbuf *buf, char ch);
|
||||
void gbuf_add_uchar(struct gbuf *buf, uchar u);
|
||||
void gbuf_add_bytes(struct gbuf *buf, const void *data, size_t len);
|
||||
void gbuf_add_str(struct gbuf *buf, const char *str);
|
||||
void gbuf_add_ustr(struct gbuf *buf, const char *src, int *width);
|
||||
void gbuf_addf(struct gbuf *buf, const char *fmt, ...) CMUS_FORMAT(2, 3);
|
||||
void gbuf_vaddf(struct gbuf *buf, const char *fmt, va_list ap);
|
||||
void gbuf_set(struct gbuf *buf, int c, size_t count);
|
||||
char *gbuf_steal(struct gbuf *buf);
|
||||
|
||||
#endif
|
||||
245
glob.c
Normal file
245
glob.c
Normal file
@@ -0,0 +1,245 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2005 Timo Hirvonen
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "glob.h"
|
||||
#include "uchar.h"
|
||||
#include "list.h"
|
||||
#include "xmalloc.h"
|
||||
#include "debug.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
struct glob_item {
|
||||
struct list_head node;
|
||||
enum {
|
||||
GLOB_STAR,
|
||||
GLOB_QMARK,
|
||||
GLOB_TEXT
|
||||
} type;
|
||||
char text[];
|
||||
};
|
||||
|
||||
/* simplification:
|
||||
*
|
||||
* ??*? => ???*
|
||||
* *?* => ?*
|
||||
* *? => ?*
|
||||
* ...
|
||||
*/
|
||||
static void simplify(struct list_head *head)
|
||||
{
|
||||
struct list_head *item;
|
||||
|
||||
item = head->next;
|
||||
while (item != head) {
|
||||
struct list_head *i, *next;
|
||||
int qcount = 0;
|
||||
int scount = 0;
|
||||
|
||||
i = item;
|
||||
do {
|
||||
struct glob_item *gi;
|
||||
|
||||
gi = container_of(i, struct glob_item, node);
|
||||
if (gi->type == GLOB_STAR) {
|
||||
scount++;
|
||||
} else if (gi->type == GLOB_QMARK) {
|
||||
qcount++;
|
||||
} else {
|
||||
i = i->next;
|
||||
break;
|
||||
}
|
||||
i = i->next;
|
||||
} while (i != head);
|
||||
|
||||
next = i;
|
||||
|
||||
if (scount) {
|
||||
/* move all qmarks to front and
|
||||
* if there are >1 stars remove all but the last */
|
||||
struct list_head *insert_after = item->prev;
|
||||
|
||||
i = item;
|
||||
while (qcount) {
|
||||
struct glob_item *gi;
|
||||
|
||||
gi = container_of(i, struct glob_item, node);
|
||||
i = i->next;
|
||||
if (gi->type == GLOB_QMARK) {
|
||||
list_del(&gi->node);
|
||||
list_add(&gi->node, insert_after);
|
||||
qcount--;
|
||||
}
|
||||
}
|
||||
|
||||
i = item;
|
||||
while (scount > 1) {
|
||||
struct glob_item *gi;
|
||||
|
||||
gi = container_of(i, struct glob_item, node);
|
||||
i = i->next;
|
||||
if (gi->type == GLOB_STAR) {
|
||||
list_del(&gi->node);
|
||||
free(gi);
|
||||
scount--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
item = next;
|
||||
}
|
||||
}
|
||||
|
||||
void glob_compile(struct list_head *head, const char *pattern)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
list_init(head);
|
||||
while (pattern[i]) {
|
||||
struct glob_item *item;
|
||||
|
||||
if (pattern[i] == '*') {
|
||||
item = xnew(struct glob_item, 1);
|
||||
item->type = GLOB_STAR;
|
||||
i++;
|
||||
} else if (pattern[i] == '?') {
|
||||
item = xnew(struct glob_item, 1);
|
||||
item->type = GLOB_QMARK;
|
||||
i++;
|
||||
} else {
|
||||
int start, len, j;
|
||||
char *str;
|
||||
|
||||
start = i;
|
||||
len = 0;
|
||||
while (pattern[i]) {
|
||||
if (pattern[i] == '\\') {
|
||||
i++;
|
||||
len++;
|
||||
if (pattern[i])
|
||||
i++;
|
||||
} else if (pattern[i] == '*') {
|
||||
break;
|
||||
} else if (pattern[i] == '?') {
|
||||
break;
|
||||
} else {
|
||||
i++;
|
||||
len++;
|
||||
}
|
||||
}
|
||||
|
||||
item = xmalloc(sizeof(struct glob_item) + len + 1);
|
||||
item->type = GLOB_TEXT;
|
||||
|
||||
str = item->text;
|
||||
i = start;
|
||||
j = 0;
|
||||
while (j < len) {
|
||||
if (pattern[i] == '\\') {
|
||||
i++;
|
||||
if (pattern[i]) {
|
||||
str[j++] = pattern[i++];
|
||||
} else {
|
||||
str[j++] = '\\';
|
||||
}
|
||||
} else {
|
||||
str[j++] = pattern[i++];
|
||||
}
|
||||
}
|
||||
str[j] = 0;
|
||||
}
|
||||
list_add_tail(&item->node, head);
|
||||
}
|
||||
simplify(head);
|
||||
}
|
||||
|
||||
void glob_free(struct list_head *head)
|
||||
{
|
||||
struct list_head *item = head->next;
|
||||
|
||||
while (item != head) {
|
||||
struct glob_item *gi;
|
||||
struct list_head *next = item->next;
|
||||
|
||||
gi = container_of(item, struct glob_item, node);
|
||||
free(gi);
|
||||
item = next;
|
||||
}
|
||||
}
|
||||
|
||||
static int do_glob_match(struct list_head *head, struct list_head *first, const char *text)
|
||||
{
|
||||
struct list_head *item = first;
|
||||
|
||||
while (item != head) {
|
||||
struct glob_item *gitem;
|
||||
|
||||
gitem = container_of(item, struct glob_item, node);
|
||||
if (gitem->type == GLOB_TEXT) {
|
||||
int len = u_strlen(gitem->text);
|
||||
|
||||
if (!u_strncase_equal_base(gitem->text, text, len))
|
||||
return 0;
|
||||
text += strlen(gitem->text);
|
||||
} else if (gitem->type == GLOB_QMARK) {
|
||||
uchar u;
|
||||
int idx = 0;
|
||||
|
||||
u = u_get_char(text, &idx);
|
||||
if (u == 0)
|
||||
return 0;
|
||||
text += idx;
|
||||
} else if (gitem->type == GLOB_STAR) {
|
||||
/* after star there MUST be normal text (or nothing),
|
||||
* question marks have been moved before this star and
|
||||
* other stars have been stripped (see simplify)
|
||||
*/
|
||||
struct list_head *next;
|
||||
struct glob_item *next_gi;
|
||||
const char *t;
|
||||
int tlen;
|
||||
|
||||
next = item->next;
|
||||
if (next == head) {
|
||||
/* this star was the last item => matched */
|
||||
return 1;
|
||||
}
|
||||
next_gi = container_of(next, struct glob_item, node);
|
||||
BUG_ON(next_gi->type != GLOB_TEXT);
|
||||
t = next_gi->text;
|
||||
tlen = strlen(t);
|
||||
while (1) {
|
||||
const char *pos;
|
||||
|
||||
pos = u_strcasestr_base(text, t);
|
||||
if (pos == NULL)
|
||||
return 0;
|
||||
if (do_glob_match(head, next->next, pos + tlen))
|
||||
return 1;
|
||||
text = pos + 1;
|
||||
}
|
||||
}
|
||||
item = item->next;
|
||||
}
|
||||
return text[0] == 0;
|
||||
}
|
||||
|
||||
int glob_match(struct list_head *head, const char *text)
|
||||
{
|
||||
return do_glob_match(head, head->next, text);
|
||||
}
|
||||
28
glob.h
Normal file
28
glob.h
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2005 Timo Hirvonen
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CMUS_GLOB_H
|
||||
#define CMUS_GLOB_H
|
||||
|
||||
#include "list.h"
|
||||
|
||||
void glob_compile(struct list_head *head, const char *pattern);
|
||||
void glob_free(struct list_head *head);
|
||||
int glob_match(struct list_head *head, const char *text);
|
||||
|
||||
#endif
|
||||
327
help.c
Normal file
327
help.c
Normal file
@@ -0,0 +1,327 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2006 <ft@bewatermyfriend.org>
|
||||
*
|
||||
* heavily based on filters.c
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "help.h"
|
||||
#include "window.h"
|
||||
#include "search.h"
|
||||
#include "misc.h"
|
||||
#include "xmalloc.h"
|
||||
#include "keys.h"
|
||||
#include "command_mode.h"
|
||||
#include "ui_curses.h"
|
||||
#include "options.h"
|
||||
#include "cmdline.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
struct window *help_win;
|
||||
struct searchable *help_searchable;
|
||||
|
||||
static LIST_HEAD(help_head);
|
||||
static struct list_head *bound_head;
|
||||
static struct list_head *bound_tail;
|
||||
static struct list_head *unbound_head;
|
||||
static struct list_head *unbound_tail;
|
||||
|
||||
static inline void help_entry_to_iter(struct help_entry *e, struct iter *iter)
|
||||
{
|
||||
iter->data0 = &help_head;
|
||||
iter->data1 = e;
|
||||
iter->data2 = NULL;
|
||||
}
|
||||
|
||||
static GENERIC_ITER_PREV(help_get_prev, struct help_entry, node)
|
||||
static GENERIC_ITER_NEXT(help_get_next, struct help_entry, node)
|
||||
|
||||
static int help_search_get_current(void *data, struct iter *iter, enum search_direction dir)
|
||||
{
|
||||
return window_get_sel(help_win, iter);
|
||||
}
|
||||
|
||||
static int help_search_matches(void *data, struct iter *iter, const char *text)
|
||||
{
|
||||
int matched = 0;
|
||||
char **words = get_words(text);
|
||||
|
||||
if (words[0] != NULL) {
|
||||
struct help_entry *ent;
|
||||
int i;
|
||||
|
||||
ent = iter_to_help_entry(iter);
|
||||
for (i = 0; ; i++) {
|
||||
if (words[i] == NULL) {
|
||||
window_set_sel(help_win, iter);
|
||||
matched = 1;
|
||||
break;
|
||||
}
|
||||
if (ent->type == HE_TEXT) {
|
||||
if (!u_strcasestr(ent->text, words[i]))
|
||||
break;
|
||||
} else if (ent->type == HE_BOUND) {
|
||||
if (!u_strcasestr(ent->binding->cmd, words[i]) &&
|
||||
!u_strcasestr(ent->binding->key->name, words[i]))
|
||||
break;
|
||||
} else if (ent->type == HE_UNBOUND) {
|
||||
if (!u_strcasestr(ent->command->name, words[i]))
|
||||
break;
|
||||
} else if (ent->type == HE_OPTION) {
|
||||
if (!u_strcasestr(ent->option->name, words[i]))
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
free_str_array(words);
|
||||
return matched;
|
||||
}
|
||||
|
||||
static const struct searchable_ops help_search_ops = {
|
||||
.get_prev = help_get_prev,
|
||||
.get_next = help_get_next,
|
||||
.get_current = help_search_get_current,
|
||||
.matches = help_search_matches
|
||||
};
|
||||
|
||||
static void help_add_text(const char *s)
|
||||
{
|
||||
struct help_entry *ent;
|
||||
ent = xnew(struct help_entry, 1);
|
||||
ent->type = HE_TEXT;
|
||||
ent->text = s;
|
||||
list_add_tail(&ent->node, &help_head);
|
||||
}
|
||||
|
||||
static void help_add_defaults(void)
|
||||
{
|
||||
struct cmus_opt *opt;
|
||||
|
||||
help_add_text("Keybindings");
|
||||
help_add_text("-----------");
|
||||
bound_head = help_head.prev;
|
||||
help_add_text("");
|
||||
help_add_text("Unbound Commands");
|
||||
help_add_text("----------------");
|
||||
unbound_head = help_head.prev;
|
||||
help_add_text("");
|
||||
help_add_text("Options");
|
||||
help_add_text("-------");
|
||||
|
||||
list_for_each_entry(opt, &option_head, node) {
|
||||
struct help_entry *ent = xnew(struct help_entry, 1);
|
||||
|
||||
ent->type = HE_OPTION;
|
||||
ent->option = opt;
|
||||
list_add_tail(&ent->node, &help_head);
|
||||
}
|
||||
|
||||
bound_tail = bound_head->next;
|
||||
unbound_tail = unbound_head->next;
|
||||
}
|
||||
|
||||
void help_remove_unbound(struct command *cmd)
|
||||
{
|
||||
struct help_entry *ent;
|
||||
struct iter i;
|
||||
list_for_each_entry(ent, &help_head, node) {
|
||||
if (ent->type != HE_UNBOUND)
|
||||
continue;
|
||||
if (ent->command == cmd) {
|
||||
help_entry_to_iter(ent, &i);
|
||||
window_row_vanishes(help_win, &i);
|
||||
list_del(&ent->node);
|
||||
free(ent);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void list_add_sorted(struct list_head *new, struct list_head *head,
|
||||
struct list_head *tail,
|
||||
int (*cmp)(struct list_head *, struct list_head *))
|
||||
{
|
||||
struct list_head *item = tail->prev;
|
||||
|
||||
while (item != head) {
|
||||
if (cmp(new, item) >= 0)
|
||||
break;
|
||||
item = item->prev;
|
||||
}
|
||||
/* add after item */
|
||||
list_add(new, item);
|
||||
}
|
||||
|
||||
static int bound_cmp(struct list_head *ai, struct list_head *bi)
|
||||
{
|
||||
struct help_entry *a = container_of(ai, struct help_entry, node);
|
||||
struct help_entry *b = container_of(bi, struct help_entry, node);
|
||||
int ret = a->binding->ctx - b->binding->ctx;
|
||||
|
||||
if (!ret)
|
||||
ret = strcmp(a->binding->key->name, b->binding->key->name);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int unbound_cmp(struct list_head *ai, struct list_head *bi)
|
||||
{
|
||||
struct help_entry *a = container_of(ai, struct help_entry, node);
|
||||
struct help_entry *b = container_of(bi, struct help_entry, node);
|
||||
|
||||
return strcmp(a->command->name, b->command->name);
|
||||
}
|
||||
|
||||
void help_add_unbound(struct command *cmd)
|
||||
{
|
||||
struct help_entry *ent;
|
||||
|
||||
ent = xnew(struct help_entry, 1);
|
||||
ent->type = HE_UNBOUND;
|
||||
ent->command = cmd;
|
||||
list_add_sorted(&ent->node, unbound_head, unbound_tail, unbound_cmp);
|
||||
}
|
||||
|
||||
void help_add_all_unbound(void)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; commands[i].name; ++i)
|
||||
if (!commands[i].bc)
|
||||
help_add_unbound(&commands[i]);
|
||||
}
|
||||
|
||||
void help_select(void)
|
||||
{
|
||||
struct iter sel;
|
||||
struct help_entry *ent;
|
||||
char buf[OPTION_MAX_SIZE];
|
||||
|
||||
if (!window_get_sel(help_win, &sel))
|
||||
return;
|
||||
|
||||
ent = iter_to_help_entry(&sel);
|
||||
switch (ent->type) {
|
||||
case HE_BOUND:
|
||||
snprintf(buf, sizeof(buf), "bind -f %s %s %s",
|
||||
key_context_names[ent->binding->ctx],
|
||||
ent->binding->key->name,
|
||||
ent->binding->cmd);
|
||||
cmdline_set_text(buf);
|
||||
enter_command_mode();
|
||||
break;
|
||||
case HE_UNBOUND:
|
||||
snprintf(buf, sizeof(buf), "bind common <key> %s",
|
||||
ent->command->name);
|
||||
cmdline_set_text(buf);
|
||||
enter_command_mode();
|
||||
break;
|
||||
case HE_OPTION:
|
||||
snprintf(buf, sizeof(buf), "set %s=", ent->option->name);
|
||||
size_t len = strlen(buf);
|
||||
ent->option->get(ent->option->data, buf + len, sizeof(buf) - len);
|
||||
cmdline_set_text(buf);
|
||||
enter_command_mode();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void help_toggle(void)
|
||||
{
|
||||
struct iter sel;
|
||||
struct help_entry *ent;
|
||||
|
||||
if (!window_get_sel(help_win, &sel))
|
||||
return;
|
||||
|
||||
ent = iter_to_help_entry(&sel);
|
||||
switch (ent->type) {
|
||||
case HE_OPTION:
|
||||
if (ent->option->toggle) {
|
||||
ent->option->toggle(ent->option->data);
|
||||
help_win->changed = 1;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void help_remove(void)
|
||||
{
|
||||
struct iter sel;
|
||||
struct help_entry *ent;
|
||||
|
||||
if (!window_get_sel(help_win, &sel))
|
||||
return;
|
||||
|
||||
ent = iter_to_help_entry(&sel);
|
||||
switch (ent->type) {
|
||||
case HE_BOUND:
|
||||
if (yes_no_query("Remove selected binding? [y/N]") == UI_QUERY_ANSWER_YES)
|
||||
key_unbind(key_context_names[ent->binding->ctx],
|
||||
ent->binding->key->name, 0);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void help_add_bound(const struct binding *bind)
|
||||
{
|
||||
struct help_entry *ent;
|
||||
ent = xnew(struct help_entry, 1);
|
||||
ent->type = HE_BOUND;
|
||||
ent->binding = bind;
|
||||
list_add_sorted(&ent->node, bound_head, bound_tail, bound_cmp);
|
||||
}
|
||||
|
||||
void help_remove_bound(const struct binding *bind)
|
||||
{
|
||||
struct help_entry *ent;
|
||||
struct iter i;
|
||||
list_for_each_entry(ent, &help_head, node) {
|
||||
if (ent->binding == bind) {
|
||||
help_entry_to_iter(ent, &i);
|
||||
window_row_vanishes(help_win, &i);
|
||||
list_del(&ent->node);
|
||||
free(ent);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void help_init(void)
|
||||
{
|
||||
struct iter iter;
|
||||
|
||||
help_win = window_new(help_get_prev, help_get_next);
|
||||
window_set_contents(help_win, &help_head);
|
||||
window_changed(help_win);
|
||||
help_add_defaults();
|
||||
|
||||
iter.data0 = &help_head;
|
||||
iter.data1 = NULL;
|
||||
iter.data2 = NULL;
|
||||
help_searchable = searchable_new(NULL, &iter, &help_search_ops);
|
||||
}
|
||||
|
||||
void help_exit(void)
|
||||
{
|
||||
searchable_free(help_searchable);
|
||||
window_free(help_win);
|
||||
}
|
||||
66
help.h
Normal file
66
help.h
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2006 <ft@bewatermyfriend.org>
|
||||
*
|
||||
* heavily based on filters.h
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CMUS_HELP_H
|
||||
#define CMUS_HELP_H
|
||||
|
||||
#include "list.h"
|
||||
#include "window.h"
|
||||
#include "search.h"
|
||||
#include "keys.h"
|
||||
|
||||
struct help_entry {
|
||||
struct list_head node;
|
||||
enum {
|
||||
HE_TEXT, /* text entries */
|
||||
HE_BOUND, /* bound keys */
|
||||
HE_UNBOUND, /* unbound commands */
|
||||
HE_OPTION,
|
||||
} type;
|
||||
union {
|
||||
const char *text; /* HE_TEXT */
|
||||
const struct binding *binding; /* HE_BOUND */
|
||||
const struct command *command; /* HE_UNBOUND */
|
||||
const struct cmus_opt *option;
|
||||
};
|
||||
};
|
||||
|
||||
static inline struct help_entry *iter_to_help_entry(struct iter *iter)
|
||||
{
|
||||
return iter->data1;
|
||||
}
|
||||
|
||||
extern struct window *help_win;
|
||||
extern struct searchable *help_searchable;
|
||||
|
||||
void help_select(void);
|
||||
void help_toggle(void);
|
||||
void help_remove(void);
|
||||
|
||||
void help_add_bound(const struct binding *bind);
|
||||
void help_remove_bound(const struct binding *bind);
|
||||
void help_remove_unbound(struct command *cmd);
|
||||
void help_add_unbound(struct command *cmd);
|
||||
void help_add_all_unbound(void);
|
||||
|
||||
void help_init(void);
|
||||
void help_exit(void);
|
||||
|
||||
#endif /* HELP_H */
|
||||
205
history.c
Normal file
205
history.c
Normal file
@@ -0,0 +1,205 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2004-2005 Timo Hirvonen
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "history.h"
|
||||
#include "xmalloc.h"
|
||||
#include "file.h"
|
||||
#include "uchar.h"
|
||||
#include "list.h"
|
||||
#include "prog.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
|
||||
struct history_entry {
|
||||
struct list_head node;
|
||||
char *text;
|
||||
};
|
||||
|
||||
static struct history_entry *history_entry_new(const char *text)
|
||||
{
|
||||
struct history_entry *new;
|
||||
new = xnew(struct history_entry, 1);
|
||||
new->text = xstrdup(text);
|
||||
return new;
|
||||
}
|
||||
|
||||
static void history_entry_free(struct history_entry *history)
|
||||
{
|
||||
free(history->text);
|
||||
free(history);
|
||||
}
|
||||
|
||||
void history_free(struct history *history)
|
||||
{
|
||||
struct list_head *item, *temp;
|
||||
list_for_each_safe(item, temp, &history->head) {
|
||||
struct history_entry *history_entry;
|
||||
history_entry = list_entry(item, struct history_entry, node);
|
||||
history_entry_free(history_entry);
|
||||
}
|
||||
}
|
||||
|
||||
static int history_add_tail(void *data, const char *line)
|
||||
{
|
||||
struct history *history = data;
|
||||
|
||||
if (history->lines < history->max_lines) {
|
||||
struct history_entry *new;
|
||||
|
||||
new = history_entry_new(line);
|
||||
list_add_tail(&new->node, &history->head);
|
||||
history->lines++;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void history_load(struct history *history, char *filename, int max_lines)
|
||||
{
|
||||
list_init(&history->head);
|
||||
history->max_lines = max_lines;
|
||||
history->lines = 0;
|
||||
history->search_pos = NULL;
|
||||
history->filename = filename;
|
||||
file_for_each_line(filename, history_add_tail, history);
|
||||
}
|
||||
|
||||
void history_save(struct history *history)
|
||||
{
|
||||
char filename_tmp[512];
|
||||
struct list_head *item;
|
||||
int fd;
|
||||
ssize_t rc;
|
||||
|
||||
snprintf(filename_tmp, sizeof(filename_tmp), "%s.tmp", history->filename);
|
||||
fd = open(filename_tmp, O_CREAT | O_WRONLY | O_TRUNC, 0666);
|
||||
if (fd == -1)
|
||||
return;
|
||||
list_for_each(item, &history->head) {
|
||||
struct history_entry *history_entry;
|
||||
const char nl = '\n';
|
||||
|
||||
history_entry = list_entry(item, struct history_entry, node);
|
||||
|
||||
rc = write(fd, history_entry->text, strlen(history_entry->text));
|
||||
if (rc == -1)
|
||||
goto out;
|
||||
|
||||
rc = write(fd, &nl, 1);
|
||||
if (rc == -1)
|
||||
goto out;
|
||||
}
|
||||
out:
|
||||
close(fd);
|
||||
|
||||
rc = rename(filename_tmp, history->filename);
|
||||
if (rc)
|
||||
warn_errno("renaming %s to %s", filename_tmp, history->filename);
|
||||
}
|
||||
|
||||
void history_add_line(struct history *history, const char *line)
|
||||
{
|
||||
struct history_entry *new;
|
||||
struct list_head *item;
|
||||
|
||||
new = history_entry_new(line);
|
||||
list_add(&new->node, &history->head);
|
||||
history->lines++;
|
||||
|
||||
/* remove identical */
|
||||
item = history->head.next->next;
|
||||
while (item != &history->head) {
|
||||
struct list_head *next = item->next;
|
||||
struct history_entry *hentry;
|
||||
|
||||
hentry = container_of(item, struct history_entry, node);
|
||||
if (strcmp(hentry->text, new->text) == 0) {
|
||||
list_del(item);
|
||||
history_entry_free(hentry);
|
||||
history->lines--;
|
||||
}
|
||||
item = next;
|
||||
}
|
||||
|
||||
/* remove oldest if history is 'full' */
|
||||
if (history->lines > history->max_lines) {
|
||||
struct list_head *node;
|
||||
struct history_entry *hentry;
|
||||
|
||||
node = history->head.prev;
|
||||
list_del(node);
|
||||
hentry = list_entry(node, struct history_entry, node);
|
||||
history_entry_free(hentry);
|
||||
history->lines--;
|
||||
}
|
||||
}
|
||||
|
||||
void history_reset_search(struct history *history)
|
||||
{
|
||||
history->search_pos = NULL;
|
||||
}
|
||||
|
||||
const char *history_search_forward(struct history *history, const char *text)
|
||||
{
|
||||
struct list_head *item;
|
||||
int search_len;
|
||||
|
||||
if (history->search_pos == NULL) {
|
||||
/* first time to search. set search */
|
||||
item = history->head.next;
|
||||
} else {
|
||||
item = history->search_pos->next;
|
||||
}
|
||||
search_len = strlen(text);
|
||||
while (item != &history->head) {
|
||||
struct history_entry *hentry;
|
||||
|
||||
hentry = list_entry(item, struct history_entry, node);
|
||||
if (strncmp(text, hentry->text, search_len) == 0) {
|
||||
history->search_pos = item;
|
||||
return hentry->text;
|
||||
}
|
||||
item = item->next;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char *history_search_backward(struct history *history, const char *text)
|
||||
{
|
||||
struct list_head *item;
|
||||
int search_len;
|
||||
|
||||
if (history->search_pos == NULL)
|
||||
return NULL;
|
||||
item = history->search_pos->prev;
|
||||
search_len = strlen(text);
|
||||
while (item != &history->head) {
|
||||
struct history_entry *hentry;
|
||||
|
||||
hentry = list_entry(item, struct history_entry, node);
|
||||
if (strncmp(text, hentry->text, search_len) == 0) {
|
||||
history->search_pos = item;
|
||||
return hentry->text;
|
||||
}
|
||||
item = item->prev;
|
||||
}
|
||||
history->search_pos = NULL;
|
||||
return NULL;
|
||||
}
|
||||
40
history.h
Normal file
40
history.h
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2004 Timo Hirvonen
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CMUS_HISTORY_H
|
||||
#define CMUS_HISTORY_H
|
||||
|
||||
#include "list.h"
|
||||
|
||||
struct history {
|
||||
struct list_head head;
|
||||
struct list_head *search_pos;
|
||||
char *filename;
|
||||
int max_lines;
|
||||
int lines;
|
||||
};
|
||||
|
||||
void history_load(struct history *history, char *filename, int max_lines);
|
||||
void history_save(struct history *history);
|
||||
void history_free(struct history *history);
|
||||
void history_add_line(struct history *history, const char *line);
|
||||
void history_reset_search(struct history *history);
|
||||
const char *history_search_forward(struct history *history, const char *text);
|
||||
const char *history_search_backward(struct history *history, const char *text);
|
||||
|
||||
#endif
|
||||
516
http.c
Normal file
516
http.c
Normal file
@@ -0,0 +1,516 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2004-2005 Timo Hirvonen
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "http.h"
|
||||
#include "file.h"
|
||||
#include "debug.h"
|
||||
#include "xmalloc.h"
|
||||
#include "gbuf.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/select.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netdb.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
/*
|
||||
* @uri is http://[user[:pass]@]host[:port][/path][?query]
|
||||
*
|
||||
* uri(7): If the URL supplies a user name but no password, and the remote
|
||||
* server requests a password, the program interpreting the URL should request
|
||||
* one from the user.
|
||||
*/
|
||||
int http_parse_uri(const char *uri, struct http_uri *u)
|
||||
{
|
||||
const char *str, *colon, *at, *slash, *host_start;
|
||||
|
||||
/* initialize all fields */
|
||||
u->uri = xstrdup(uri);
|
||||
u->user = NULL;
|
||||
u->pass = NULL;
|
||||
u->host = NULL;
|
||||
u->path = NULL;
|
||||
u->port = 80;
|
||||
|
||||
if (strncmp(uri, "http://", 7))
|
||||
return -1;
|
||||
str = uri + 7;
|
||||
host_start = str;
|
||||
|
||||
/* [/path] */
|
||||
slash = strchr(str, '/');
|
||||
if (slash) {
|
||||
u->path = xstrdup(slash);
|
||||
} else {
|
||||
u->path = xstrdup("/");
|
||||
}
|
||||
|
||||
/* [user[:pass]@] */
|
||||
at = strchr(str, '@');
|
||||
if (at && (!slash || at < slash)) {
|
||||
/* user[:pass]@ */
|
||||
host_start = at + 1;
|
||||
colon = strchr(str, ':');
|
||||
if (colon == NULL || colon > at) {
|
||||
/* user */
|
||||
u->user = xstrndup(str, at - str);
|
||||
} else {
|
||||
/* user:pass */
|
||||
u->user = xstrndup(str, colon - str);
|
||||
u->pass = xstrndup(colon + 1, at - (colon + 1));
|
||||
}
|
||||
}
|
||||
|
||||
/* host[:port] */
|
||||
colon = strchr(host_start, ':');
|
||||
if (colon && (!slash || colon < slash)) {
|
||||
/* host:port */
|
||||
const char *start;
|
||||
int port;
|
||||
|
||||
u->host = xstrndup(host_start, colon - host_start);
|
||||
colon++;
|
||||
start = colon;
|
||||
|
||||
port = 0;
|
||||
while (*colon >= '0' && *colon <= '9') {
|
||||
port *= 10;
|
||||
port += *colon - '0';
|
||||
colon++;
|
||||
}
|
||||
u->port = port;
|
||||
|
||||
if (colon == start || (*colon != 0 && *colon != '/')) {
|
||||
http_free_uri(u);
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
/* host */
|
||||
if (slash) {
|
||||
u->host = xstrndup(host_start, slash - host_start);
|
||||
} else {
|
||||
u->host = xstrdup(host_start);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void http_free_uri(struct http_uri *u)
|
||||
{
|
||||
free(u->uri);
|
||||
free(u->user);
|
||||
free(u->pass);
|
||||
free(u->host);
|
||||
free(u->path);
|
||||
|
||||
u->uri = NULL;
|
||||
u->user = NULL;
|
||||
u->pass = NULL;
|
||||
u->host = NULL;
|
||||
u->path = NULL;
|
||||
}
|
||||
|
||||
int http_open(struct http_get *hg, int timeout_ms)
|
||||
{
|
||||
const struct addrinfo hints = {
|
||||
.ai_socktype = SOCK_STREAM
|
||||
};
|
||||
struct addrinfo *result;
|
||||
union {
|
||||
struct sockaddr sa;
|
||||
struct sockaddr_storage sas;
|
||||
} addr;
|
||||
size_t addrlen;
|
||||
struct timeval tv;
|
||||
int save, flags, rc;
|
||||
char port[16];
|
||||
|
||||
char *proxy = getenv("http_proxy");
|
||||
if (proxy) {
|
||||
hg->proxy = xnew(struct http_uri, 1);
|
||||
if (http_parse_uri(proxy, hg->proxy)) {
|
||||
d_print("Failed to parse HTTP proxy URI '%s'\n", proxy);
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
hg->proxy = NULL;
|
||||
}
|
||||
|
||||
snprintf(port, sizeof(port), "%d", hg->proxy ? hg->proxy->port : hg->uri.port);
|
||||
rc = getaddrinfo(hg->proxy ? hg->proxy->host : hg->uri.host, port, &hints, &result);
|
||||
if (rc != 0) {
|
||||
d_print("getaddrinfo: %s\n", gai_strerror(rc));
|
||||
return -1;
|
||||
}
|
||||
memcpy(&addr.sa, result->ai_addr, result->ai_addrlen);
|
||||
addrlen = result->ai_addrlen;
|
||||
freeaddrinfo(result);
|
||||
|
||||
hg->fd = socket(addr.sa.sa_family, SOCK_STREAM, 0);
|
||||
if (hg->fd == -1)
|
||||
return -1;
|
||||
|
||||
flags = fcntl(hg->fd, F_GETFL);
|
||||
if (fcntl(hg->fd, F_SETFL, O_NONBLOCK) == -1)
|
||||
goto close_exit;
|
||||
|
||||
tv.tv_sec = timeout_ms / 1000;
|
||||
tv.tv_usec = (timeout_ms % 1000) * 1000;
|
||||
while (1) {
|
||||
fd_set wfds;
|
||||
|
||||
d_print("connecting. timeout=%lld s %lld us\n", (long long)tv.tv_sec, (long long)tv.tv_usec);
|
||||
if (connect(hg->fd, &addr.sa, addrlen) == 0)
|
||||
break;
|
||||
if (errno == EISCONN)
|
||||
break;
|
||||
if (errno != EAGAIN && errno != EINPROGRESS)
|
||||
goto close_exit;
|
||||
|
||||
FD_ZERO(&wfds);
|
||||
FD_SET(hg->fd, &wfds);
|
||||
while (1) {
|
||||
rc = select(hg->fd + 1, NULL, &wfds, NULL, &tv);
|
||||
if (rc == -1) {
|
||||
if (errno != EINTR)
|
||||
goto close_exit;
|
||||
/* signalled */
|
||||
continue;
|
||||
}
|
||||
if (rc == 1) {
|
||||
/* socket ready */
|
||||
break;
|
||||
}
|
||||
if (tv.tv_sec == 0 && tv.tv_usec == 0) {
|
||||
errno = ETIMEDOUT;
|
||||
goto close_exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* restore old flags */
|
||||
if (fcntl(hg->fd, F_SETFL, flags) == -1)
|
||||
goto close_exit;
|
||||
return 0;
|
||||
close_exit:
|
||||
save = errno;
|
||||
close(hg->fd);
|
||||
errno = save;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int http_write(int fd, const char *buf, int count, int timeout_ms)
|
||||
{
|
||||
struct timeval tv;
|
||||
int pos = 0;
|
||||
|
||||
tv.tv_sec = timeout_ms / 1000;
|
||||
tv.tv_usec = (timeout_ms % 1000) * 1000;
|
||||
while (1) {
|
||||
fd_set wfds;
|
||||
int rc;
|
||||
|
||||
d_print("timeout=%lld s %lld us\n", (long long)tv.tv_sec, (long long)tv.tv_usec);
|
||||
|
||||
FD_ZERO(&wfds);
|
||||
FD_SET(fd, &wfds);
|
||||
rc = select(fd + 1, NULL, &wfds, NULL, &tv);
|
||||
if (rc == -1) {
|
||||
if (errno != EINTR)
|
||||
return -1;
|
||||
/* signalled */
|
||||
continue;
|
||||
}
|
||||
if (rc == 1) {
|
||||
rc = write(fd, buf + pos, count - pos);
|
||||
if (rc == -1) {
|
||||
if (errno == EINTR || errno == EAGAIN)
|
||||
continue;
|
||||
return -1;
|
||||
}
|
||||
pos += rc;
|
||||
if (pos == count)
|
||||
return 0;
|
||||
} else if (tv.tv_sec == 0 && tv.tv_usec == 0) {
|
||||
errno = ETIMEDOUT;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int read_timeout(int fd, int timeout_ms)
|
||||
{
|
||||
struct timeval tv;
|
||||
|
||||
tv.tv_sec = timeout_ms / 1000;
|
||||
tv.tv_usec = (timeout_ms % 1000) * 1000;
|
||||
while (1) {
|
||||
fd_set rfds;
|
||||
int rc;
|
||||
|
||||
FD_ZERO(&rfds);
|
||||
FD_SET(fd, &rfds);
|
||||
rc = select(fd + 1, &rfds, NULL, NULL, &tv);
|
||||
if (rc == -1) {
|
||||
if (errno != EINTR)
|
||||
return -1;
|
||||
/* signalled */
|
||||
continue;
|
||||
}
|
||||
if (rc == 1)
|
||||
return 0;
|
||||
if (tv.tv_sec == 0 && tv.tv_usec == 0) {
|
||||
errno = ETIMEDOUT;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* reads response, ignores fscking carriage returns */
|
||||
static int http_read_response(int fd, struct gbuf *buf, int timeout_ms)
|
||||
{
|
||||
char prev = 0;
|
||||
|
||||
if (read_timeout(fd, timeout_ms))
|
||||
return -1;
|
||||
while (1) {
|
||||
int rc;
|
||||
char ch;
|
||||
|
||||
rc = read(fd, &ch, 1);
|
||||
if (rc == -1) {
|
||||
return -1;
|
||||
}
|
||||
if (rc == 0) {
|
||||
return -2;
|
||||
}
|
||||
if (ch == '\r')
|
||||
continue;
|
||||
if (ch == '\n' && prev == '\n')
|
||||
return 0;
|
||||
gbuf_add_ch(buf, ch);
|
||||
prev = ch;
|
||||
}
|
||||
}
|
||||
|
||||
static int http_parse_response(char *str, struct http_get *hg)
|
||||
{
|
||||
/* str is 0 terminated buffer of lines
|
||||
* every line ends with '\n'
|
||||
* no carriage returns
|
||||
* no empty lines
|
||||
*/
|
||||
GROWING_KEYVALS(h);
|
||||
char *end;
|
||||
|
||||
if (strncmp(str, "HTTP/", 5) == 0) {
|
||||
str += 5;
|
||||
while (*str != ' ') {
|
||||
if (*str == '\n') {
|
||||
return -2;
|
||||
}
|
||||
str++;
|
||||
}
|
||||
} else if (strncmp(str, "ICY", 3) == 0) {
|
||||
str += 3;
|
||||
} else {
|
||||
return -2;
|
||||
}
|
||||
while (*str == ' ')
|
||||
str++;
|
||||
|
||||
hg->code = 0;
|
||||
while (*str >= '0' && *str <= '9') {
|
||||
hg->code *= 10;
|
||||
hg->code += *str - '0';
|
||||
str++;
|
||||
}
|
||||
if (!hg->code)
|
||||
return -2;
|
||||
while (*str == ' ')
|
||||
str++;
|
||||
|
||||
end = strchr(str, '\n');
|
||||
hg->reason = xstrndup(str, end - str);
|
||||
str = end + 1;
|
||||
|
||||
/* headers */
|
||||
while (*str) {
|
||||
char *ptr;
|
||||
|
||||
end = strchr(str, '\n');
|
||||
ptr = strchr(str, ':');
|
||||
if (ptr == NULL || ptr > end) {
|
||||
free(hg->reason);
|
||||
hg->reason = NULL;
|
||||
keyvals_terminate(&h);
|
||||
keyvals_free(h.keyvals);
|
||||
return -2;
|
||||
}
|
||||
|
||||
*ptr++ = 0;
|
||||
while (*ptr == ' ')
|
||||
ptr++;
|
||||
|
||||
keyvals_add(&h, str, xstrndup(ptr, end - ptr));
|
||||
str = end + 1;
|
||||
}
|
||||
keyvals_terminate(&h);
|
||||
hg->headers = h.keyvals;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int http_get(struct http_get *hg, struct keyval *headers, int timeout_ms)
|
||||
{
|
||||
GBUF(buf);
|
||||
int i, rc, save;
|
||||
|
||||
gbuf_add_str(&buf, "GET ");
|
||||
gbuf_add_str(&buf, hg->proxy ? hg->uri.uri : hg->uri.path);
|
||||
gbuf_add_str(&buf, " HTTP/1.0\r\n");
|
||||
for (i = 0; headers[i].key; i++) {
|
||||
gbuf_add_str(&buf, headers[i].key);
|
||||
gbuf_add_str(&buf, ": ");
|
||||
gbuf_add_str(&buf, headers[i].val);
|
||||
gbuf_add_str(&buf, "\r\n");
|
||||
}
|
||||
gbuf_add_str(&buf, "\r\n");
|
||||
|
||||
rc = http_write(hg->fd, buf.buffer, buf.len, timeout_ms);
|
||||
if (rc)
|
||||
goto out;
|
||||
|
||||
gbuf_clear(&buf);
|
||||
rc = http_read_response(hg->fd, &buf, timeout_ms);
|
||||
if (rc)
|
||||
goto out;
|
||||
|
||||
rc = http_parse_response(buf.buffer, hg);
|
||||
out:
|
||||
save = errno;
|
||||
gbuf_free(&buf);
|
||||
errno = save;
|
||||
return rc;
|
||||
}
|
||||
|
||||
char *http_read_body(int fd, size_t *size, int timeout_ms)
|
||||
{
|
||||
GBUF(buf);
|
||||
|
||||
if (read_timeout(fd, timeout_ms))
|
||||
return NULL;
|
||||
while (1) {
|
||||
int count = 1023;
|
||||
int rc;
|
||||
|
||||
gbuf_grow(&buf, count);
|
||||
rc = read_all(fd, buf.buffer + buf.len, count);
|
||||
if (rc == -1) {
|
||||
gbuf_free(&buf);
|
||||
return NULL;
|
||||
}
|
||||
buf.len += rc;
|
||||
if (rc == 0) {
|
||||
*size = buf.len;
|
||||
return gbuf_steal(&buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void http_get_free(struct http_get *hg)
|
||||
{
|
||||
http_free_uri(&hg->uri);
|
||||
if (hg->proxy) {
|
||||
http_free_uri(hg->proxy);
|
||||
free(hg->proxy);
|
||||
}
|
||||
if (hg->headers)
|
||||
keyvals_free(hg->headers);
|
||||
free(hg->reason);
|
||||
}
|
||||
|
||||
char *base64_encode(const char *str)
|
||||
{
|
||||
static const char t[64] CMUS_NONSTRING = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
int str_len, buf_len, i, s, d;
|
||||
char *buf;
|
||||
unsigned char b0, b1, b2;
|
||||
|
||||
str_len = strlen(str);
|
||||
buf_len = (str_len + 2) / 3 * 4 + 1;
|
||||
buf = xnew(char, buf_len);
|
||||
s = 0;
|
||||
d = 0;
|
||||
for (i = 0; i < str_len / 3; i++) {
|
||||
b0 = str[s++];
|
||||
b1 = str[s++];
|
||||
b2 = str[s++];
|
||||
|
||||
/* 6 ms bits of b0 */
|
||||
buf[d++] = t[b0 >> 2];
|
||||
|
||||
/* 2 ls bits of b0 . 4 ms bits of b1 */
|
||||
buf[d++] = t[((b0 << 4) | (b1 >> 4)) & 0x3f];
|
||||
|
||||
/* 4 ls bits of b1 . 2 ms bits of b2 */
|
||||
buf[d++] = t[((b1 << 2) | (b2 >> 6)) & 0x3f];
|
||||
|
||||
/* 6 ls bits of b2 */
|
||||
buf[d++] = t[b2 & 0x3f];
|
||||
}
|
||||
switch (str_len % 3) {
|
||||
case 2:
|
||||
b0 = str[s++];
|
||||
b1 = str[s++];
|
||||
|
||||
/* 6 ms bits of b0 */
|
||||
buf[d++] = t[b0 >> 2];
|
||||
|
||||
/* 2 ls bits of b0 . 4 ms bits of b1 */
|
||||
buf[d++] = t[((b0 << 4) | (b1 >> 4)) & 0x3f];
|
||||
|
||||
/* 4 ls bits of b1 */
|
||||
buf[d++] = t[(b1 << 2) & 0x3f];
|
||||
|
||||
buf[d++] = '=';
|
||||
break;
|
||||
case 1:
|
||||
b0 = str[s++];
|
||||
|
||||
/* 6 ms bits of b0 */
|
||||
buf[d++] = t[b0 >> 2];
|
||||
|
||||
/* 2 ls bits of b0 */
|
||||
buf[d++] = t[(b0 << 4) & 0x3f];
|
||||
|
||||
buf[d++] = '=';
|
||||
buf[d++] = '=';
|
||||
break;
|
||||
case 0:
|
||||
break;
|
||||
}
|
||||
buf[d] = 0;
|
||||
return buf;
|
||||
}
|
||||
70
http.h
Normal file
70
http.h
Normal file
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2004-2005 Timo Hirvonen
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CMUS_HTTP_H
|
||||
#define CMUS_HTTP_H
|
||||
|
||||
#include "keyval.h"
|
||||
|
||||
#include <stddef.h> /* size_t */
|
||||
|
||||
/*
|
||||
* 1xx indicates an informational message only
|
||||
* 2xx indicates success of some kind
|
||||
* 3xx redirects the client to another URL
|
||||
* 4xx indicates an error on the client's part
|
||||
* 5xx indicates an error on the server's part
|
||||
*/
|
||||
|
||||
struct http_uri {
|
||||
char *uri;
|
||||
char *user;
|
||||
char *pass;
|
||||
char *host;
|
||||
char *path;
|
||||
int port;
|
||||
};
|
||||
|
||||
struct http_get {
|
||||
struct http_uri uri;
|
||||
struct http_uri *proxy;
|
||||
int fd;
|
||||
struct keyval *headers;
|
||||
char *reason;
|
||||
int code;
|
||||
};
|
||||
|
||||
int http_parse_uri(const char *uri, struct http_uri *u);
|
||||
|
||||
/* frees contents of @u, not @u itself */
|
||||
void http_free_uri(struct http_uri *u);
|
||||
|
||||
int http_open(struct http_get *hg, int timeout_ms);
|
||||
|
||||
/*
|
||||
* returns: 0 success
|
||||
* -1 check errno
|
||||
* -2 parse error
|
||||
*/
|
||||
int http_get(struct http_get *hg, struct keyval *headers, int timeout_ms);
|
||||
void http_get_free(struct http_get *hg);
|
||||
|
||||
char *http_read_body(int fd, size_t *size, int timeout_ms);
|
||||
char *base64_encode(const char *str);
|
||||
|
||||
#endif
|
||||
81
id3.h
Normal file
81
id3.h
Normal file
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2005 Timo Hirvonen
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CMUS_ID3_H
|
||||
#define CMUS_ID3_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* flags for id3_read_tags */
|
||||
#define ID3_V1 (1 << 0)
|
||||
#define ID3_V2 (1 << 1)
|
||||
|
||||
enum id3_key {
|
||||
ID3_ARTIST,
|
||||
ID3_ALBUM,
|
||||
ID3_TITLE,
|
||||
ID3_DATE,
|
||||
ID3_ORIGINALDATE,
|
||||
ID3_GENRE,
|
||||
ID3_DISC,
|
||||
ID3_TRACK,
|
||||
ID3_ALBUMARTIST,
|
||||
ID3_ARTISTSORT,
|
||||
ID3_ALBUMARTISTSORT,
|
||||
ID3_ALBUMSORT,
|
||||
ID3_COMPILATION,
|
||||
ID3_RG_TRACK_GAIN,
|
||||
ID3_RG_TRACK_PEAK,
|
||||
ID3_RG_ALBUM_GAIN,
|
||||
ID3_RG_ALBUM_PEAK,
|
||||
ID3_COMPOSER,
|
||||
ID3_CONDUCTOR,
|
||||
ID3_LYRICIST,
|
||||
ID3_REMIXER,
|
||||
ID3_LABEL,
|
||||
ID3_PUBLISHER,
|
||||
ID3_SUBTITLE,
|
||||
ID3_COMMENT,
|
||||
ID3_MUSICBRAINZ_TRACKID,
|
||||
ID3_MEDIA,
|
||||
ID3_BPM,
|
||||
|
||||
NUM_ID3_KEYS
|
||||
};
|
||||
|
||||
struct id3tag {
|
||||
char v1[128];
|
||||
char *v2[NUM_ID3_KEYS];
|
||||
|
||||
unsigned int has_v1 : 1;
|
||||
unsigned int has_v2 : 1;
|
||||
};
|
||||
|
||||
extern const char * const id3_key_names[NUM_ID3_KEYS];
|
||||
|
||||
int id3_tag_size(const char *buf, int buf_size);
|
||||
|
||||
void id3_init(struct id3tag *id3);
|
||||
void id3_free(struct id3tag *id3);
|
||||
|
||||
int id3_read_tags(struct id3tag *id3, int fd, unsigned int flags);
|
||||
char *id3_get_comment(struct id3tag *id3, enum id3_key key);
|
||||
|
||||
char const *id3_get_genre(uint16_t id);
|
||||
|
||||
#endif
|
||||
86
input.h
Normal file
86
input.h
Normal file
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2005 Timo Hirvonen
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CMUS_INPUT_H
|
||||
#define CMUS_INPUT_H
|
||||
|
||||
#include "keyval.h"
|
||||
#include "sf.h"
|
||||
#include "channelmap.h"
|
||||
|
||||
struct input_plugin;
|
||||
|
||||
void ip_load_plugins(void);
|
||||
|
||||
/*
|
||||
* allocates new struct input_plugin.
|
||||
* never fails. does not check if the file is really playable
|
||||
*/
|
||||
struct input_plugin *ip_new(const char *filename);
|
||||
|
||||
/*
|
||||
* frees struct input_plugin closing it first if necessary
|
||||
*/
|
||||
void ip_delete(struct input_plugin *ip);
|
||||
|
||||
/*
|
||||
* errors: IP_ERROR_{ERRNO, FILE_FORMAT, SAMPLE_FORMAT}
|
||||
*/
|
||||
int ip_open(struct input_plugin *ip);
|
||||
|
||||
void ip_setup(struct input_plugin *ip);
|
||||
|
||||
/*
|
||||
* errors: none?
|
||||
*/
|
||||
int ip_close(struct input_plugin *ip);
|
||||
|
||||
/*
|
||||
* errors: IP_ERROR_{ERRNO, FILE_FORMAT}
|
||||
*/
|
||||
int ip_read(struct input_plugin *ip, char *buffer, int count);
|
||||
|
||||
/*
|
||||
* errors: IP_ERROR_{FUNCTION_NOT_SUPPORTED}
|
||||
*/
|
||||
int ip_seek(struct input_plugin *ip, double offset);
|
||||
|
||||
/*
|
||||
* errors: IP_ERROR_{ERRNO}
|
||||
*/
|
||||
int ip_read_comments(struct input_plugin *ip, struct keyval **comments);
|
||||
|
||||
int ip_duration(struct input_plugin *ip);
|
||||
int ip_bitrate(struct input_plugin *ip);
|
||||
int ip_current_bitrate(struct input_plugin *ip);
|
||||
char *ip_codec(struct input_plugin *ip);
|
||||
char *ip_codec_profile(struct input_plugin *ip);
|
||||
|
||||
sample_format_t ip_get_sf(struct input_plugin *ip);
|
||||
void ip_get_channel_map(struct input_plugin *ip, channel_position_t *channel_map);
|
||||
const char *ip_get_filename(struct input_plugin *ip);
|
||||
const char *ip_get_metadata(struct input_plugin *ip);
|
||||
int ip_is_remote(struct input_plugin *ip);
|
||||
int ip_metadata_changed(struct input_plugin *ip);
|
||||
int ip_eof(struct input_plugin *ip);
|
||||
void ip_add_options(void);
|
||||
char *ip_get_error_msg(struct input_plugin *ip, int rc, const char *arg);
|
||||
char **ip_get_supported_extensions(void);
|
||||
void ip_dump_plugins(void);
|
||||
|
||||
#endif
|
||||
117
ip.h
Normal file
117
ip.h
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2004-2005 Timo Hirvonen
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CMUS_IP_H
|
||||
#define CMUS_IP_H
|
||||
|
||||
#include "keyval.h"
|
||||
#include "sf.h"
|
||||
#include "channelmap.h"
|
||||
|
||||
#ifndef __GNUC__
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#define IP_ABI_VERSION 2
|
||||
|
||||
enum {
|
||||
/* no error */
|
||||
IP_ERROR_SUCCESS,
|
||||
/* system error (error code in errno) */
|
||||
IP_ERROR_ERRNO,
|
||||
/* file type not recognized */
|
||||
IP_ERROR_UNRECOGNIZED_FILE_TYPE,
|
||||
/* file type recognized, but not supported */
|
||||
IP_ERROR_UNSUPPORTED_FILE_TYPE,
|
||||
/* function not supported (usually seek) */
|
||||
IP_ERROR_FUNCTION_NOT_SUPPORTED,
|
||||
/* input plugin detected corrupted file */
|
||||
IP_ERROR_FILE_FORMAT,
|
||||
/* malformed uri */
|
||||
IP_ERROR_INVALID_URI,
|
||||
/* sample format not supported */
|
||||
IP_ERROR_SAMPLE_FORMAT,
|
||||
/* wrong disc inserted */
|
||||
IP_ERROR_WRONG_DISC,
|
||||
/* could not read disc */
|
||||
IP_ERROR_NO_DISC,
|
||||
/* error parsing response line / headers */
|
||||
IP_ERROR_HTTP_RESPONSE,
|
||||
/* usually 404 */
|
||||
IP_ERROR_HTTP_STATUS,
|
||||
/* too many redirections */
|
||||
IP_ERROR_HTTP_REDIRECT_LIMIT,
|
||||
/* plugin does not have this option */
|
||||
IP_ERROR_NOT_OPTION,
|
||||
/* */
|
||||
IP_ERROR_INTERNAL
|
||||
};
|
||||
|
||||
struct input_plugin_data {
|
||||
/* filled by ip-layer */
|
||||
char *filename;
|
||||
int fd;
|
||||
|
||||
unsigned int remote : 1;
|
||||
unsigned int metadata_changed : 1;
|
||||
|
||||
/* shoutcast */
|
||||
int counter;
|
||||
int metaint;
|
||||
char *metadata;
|
||||
char *icy_name;
|
||||
char *icy_genre;
|
||||
char *icy_url;
|
||||
char *icy_br;
|
||||
|
||||
/* filled by plugin */
|
||||
sample_format_t sf;
|
||||
channel_position_t channel_map[CHANNELS_MAX];
|
||||
void *private;
|
||||
};
|
||||
|
||||
struct input_plugin_ops {
|
||||
int (*open)(struct input_plugin_data *ip_data);
|
||||
int (*close)(struct input_plugin_data *ip_data);
|
||||
int (*read)(struct input_plugin_data *ip_data, char *buffer, int count);
|
||||
int (*seek)(struct input_plugin_data *ip_data, double offset);
|
||||
int (*read_comments)(struct input_plugin_data *ip_data,
|
||||
struct keyval **comments);
|
||||
int (*duration)(struct input_plugin_data *ip_data);
|
||||
long (*bitrate)(struct input_plugin_data *ip_data);
|
||||
long (*bitrate_current)(struct input_plugin_data *ip_data);
|
||||
char *(*codec)(struct input_plugin_data *ip_data);
|
||||
char *(*codec_profile)(struct input_plugin_data *ip_data);
|
||||
};
|
||||
|
||||
struct input_plugin_opt {
|
||||
const char *name;
|
||||
int (*set)(const char *val);
|
||||
int (*get)(char **val);
|
||||
};
|
||||
|
||||
/* symbols exported by plugin */
|
||||
extern const struct input_plugin_ops ip_ops;
|
||||
extern const int ip_priority;
|
||||
extern const char * const ip_extensions[];
|
||||
extern const char * const ip_mime_types[];
|
||||
extern const struct input_plugin_opt ip_options[];
|
||||
extern const unsigned ip_abi_version;
|
||||
|
||||
#endif
|
||||
541
ip/aac.c
Normal file
541
ip/aac.c
Normal file
@@ -0,0 +1,541 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2006 dnk <dnk@bjum.net>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "../ip.h"
|
||||
#include "../xmalloc.h"
|
||||
#include "../debug.h"
|
||||
#include "../id3.h"
|
||||
#include "../comment.h"
|
||||
#include "../read_wrapper.h"
|
||||
#include "aac.h"
|
||||
|
||||
#include <neaacdec.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
/* FAAD_MIN_STREAMSIZE == 768, 6 == # of channels */
|
||||
#define BUFFER_SIZE (FAAD_MIN_STREAMSIZE * 6 * 4)
|
||||
|
||||
struct aac_private {
|
||||
char rbuf[BUFFER_SIZE];
|
||||
int rbuf_len;
|
||||
int rbuf_pos;
|
||||
|
||||
unsigned char channels;
|
||||
unsigned long sample_rate;
|
||||
long bitrate;
|
||||
int object_type;
|
||||
|
||||
struct {
|
||||
unsigned long samples;
|
||||
unsigned long bytes;
|
||||
} current;
|
||||
|
||||
char *overflow_buf;
|
||||
int overflow_buf_len;
|
||||
|
||||
NeAACDecHandle decoder; /* typedef void * */
|
||||
};
|
||||
|
||||
static inline int buffer_length(const struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct aac_private *priv = ip_data->private;
|
||||
|
||||
return priv->rbuf_len - priv->rbuf_pos;
|
||||
}
|
||||
|
||||
static inline void *buffer_data(const struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct aac_private *priv = ip_data->private;
|
||||
|
||||
return priv->rbuf + priv->rbuf_pos;
|
||||
}
|
||||
|
||||
static int buffer_fill(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct aac_private *priv = ip_data->private;
|
||||
int32_t n;
|
||||
|
||||
if (priv->rbuf_pos > 0) {
|
||||
priv->rbuf_len = buffer_length(ip_data);
|
||||
memmove(priv->rbuf, priv->rbuf + priv->rbuf_pos, priv->rbuf_len);
|
||||
priv->rbuf_pos = 0;
|
||||
}
|
||||
|
||||
if (priv->rbuf_len == BUFFER_SIZE)
|
||||
return 1;
|
||||
|
||||
n = read_wrapper(ip_data, priv->rbuf + priv->rbuf_len, BUFFER_SIZE - priv->rbuf_len);
|
||||
if (n == -1)
|
||||
return -1;
|
||||
if (n == 0)
|
||||
return 0;
|
||||
|
||||
priv->rbuf_len += n;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static inline void buffer_consume(struct input_plugin_data *ip_data, int n)
|
||||
{
|
||||
struct aac_private *priv = ip_data->private;
|
||||
|
||||
BUG_ON(n > buffer_length(ip_data));
|
||||
|
||||
priv->rbuf_pos += n;
|
||||
}
|
||||
|
||||
static int buffer_fill_min(struct input_plugin_data *ip_data, int len)
|
||||
{
|
||||
int rc;
|
||||
|
||||
BUG_ON(len > BUFFER_SIZE);
|
||||
|
||||
while (buffer_length(ip_data) < len) {
|
||||
rc = buffer_fill(ip_data);
|
||||
if (rc <= 0)
|
||||
return rc;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* 'data' must point to at least 6 bytes of data */
|
||||
static inline int parse_frame(const unsigned char data[6])
|
||||
{
|
||||
int len;
|
||||
|
||||
/* http://www.audiocoding.com/modules/wiki/?page=ADTS */
|
||||
|
||||
/* first 12 bits must be set */
|
||||
if (data[0] != 0xFF)
|
||||
return 0;
|
||||
if ((data[1] & 0xF0) != 0xF0)
|
||||
return 0;
|
||||
|
||||
/* layer is always '00' */
|
||||
if ((data[1] & 0x06) != 0x00)
|
||||
return 0;
|
||||
|
||||
/* frame length is stored in 13 bits */
|
||||
len = data[3] << 11; /* ..1100000000000 */
|
||||
len |= data[4] << 3; /* ..xx11111111xxx */
|
||||
len |= data[5] >> 5; /* ..xxxxxxxxxx111 */
|
||||
len &= 0x1FFF; /* 13 bits */
|
||||
return len;
|
||||
}
|
||||
|
||||
/* scans forward to the next aac frame and makes sure
|
||||
* the entire frame is in the buffer.
|
||||
*/
|
||||
static int buffer_fill_frame(struct input_plugin_data *ip_data)
|
||||
{
|
||||
unsigned char *data;
|
||||
int rc, n, len;
|
||||
int max = 32768;
|
||||
|
||||
while (1) {
|
||||
/* need at least 6 bytes of data */
|
||||
rc = buffer_fill_min(ip_data, 6);
|
||||
if (rc <= 0)
|
||||
return rc;
|
||||
|
||||
len = buffer_length(ip_data);
|
||||
data = buffer_data(ip_data);
|
||||
|
||||
/* scan for a frame */
|
||||
for (n = 0; n < len - 5; n++) {
|
||||
/* give up after 32KB */
|
||||
if (max-- == 0) {
|
||||
d_print("no frame found!\n");
|
||||
/* FIXME: set errno? */
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* see if there's a frame at this location */
|
||||
rc = parse_frame(data + n);
|
||||
if (rc == 0)
|
||||
continue;
|
||||
|
||||
/* found a frame, consume all data up to the frame */
|
||||
buffer_consume(ip_data, n);
|
||||
|
||||
/* rc == frame length */
|
||||
rc = buffer_fill_min(ip_data, rc);
|
||||
if (rc <= 0)
|
||||
return rc;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* consume what we used */
|
||||
buffer_consume(ip_data, n);
|
||||
}
|
||||
/* not reached */
|
||||
}
|
||||
|
||||
static void aac_get_channel_map(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct aac_private *priv = ip_data->private;
|
||||
NeAACDecFrameInfo frame_info;
|
||||
void *buf;
|
||||
int i;
|
||||
|
||||
ip_data->channel_map[0] = CHANNEL_POSITION_INVALID;
|
||||
|
||||
if (buffer_fill_frame(ip_data) <= 0)
|
||||
return;
|
||||
|
||||
buf = NeAACDecDecode(priv->decoder, &frame_info, buffer_data(ip_data), buffer_length(ip_data));
|
||||
NeAACDecPostSeekReset(priv->decoder, 0);
|
||||
if (!buf || frame_info.error != 0 || frame_info.bytesconsumed <= 0
|
||||
|| frame_info.channels > CHANNELS_MAX)
|
||||
return;
|
||||
|
||||
for (i = 0; i < frame_info.channels; i++)
|
||||
ip_data->channel_map[i] = channel_position_aac(frame_info.channel_position[i]);
|
||||
}
|
||||
|
||||
static int aac_open(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct aac_private *priv;
|
||||
NeAACDecConfigurationPtr neaac_cfg;
|
||||
int ret, n;
|
||||
|
||||
/* init private struct */
|
||||
const struct aac_private priv_init = {
|
||||
.decoder = NeAACDecOpen(),
|
||||
.bitrate = -1,
|
||||
.object_type = -1
|
||||
};
|
||||
priv = xnew(struct aac_private, 1);
|
||||
*priv = priv_init;
|
||||
ip_data->private = priv;
|
||||
|
||||
/* set decoder config */
|
||||
neaac_cfg = NeAACDecGetCurrentConfiguration(priv->decoder);
|
||||
neaac_cfg->outputFormat = FAAD_FMT_16BIT; /* force 16 bit audio */
|
||||
neaac_cfg->downMatrix = 0; /* NOT 5.1 -> stereo */
|
||||
neaac_cfg->dontUpSampleImplicitSBR = 0; /* upsample, please! */
|
||||
NeAACDecSetConfiguration(priv->decoder, neaac_cfg);
|
||||
|
||||
/* find a frame */
|
||||
if (buffer_fill_frame(ip_data) <= 0) {
|
||||
ret = -IP_ERROR_FILE_FORMAT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* in case of a bug, make sure there is at least some data
|
||||
* in the buffer for NeAACDecInit() to work with.
|
||||
*/
|
||||
if (buffer_fill_min(ip_data, 256) <= 0) {
|
||||
d_print("not enough data\n");
|
||||
ret = -IP_ERROR_FILE_FORMAT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* init decoder, returns the length of the header (if any) */
|
||||
n = NeAACDecInit(priv->decoder, buffer_data(ip_data), buffer_length(ip_data),
|
||||
&priv->sample_rate, &priv->channels);
|
||||
if (n < 0) {
|
||||
d_print("NeAACDecInit failed\n");
|
||||
ret = -IP_ERROR_FILE_FORMAT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
d_print("sample rate %luhz, channels %u\n", priv->sample_rate, priv->channels);
|
||||
if (!priv->sample_rate || !priv->channels) {
|
||||
ret = -IP_ERROR_FILE_FORMAT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* skip the header */
|
||||
d_print("skipping header (%d bytes)\n", n);
|
||||
|
||||
buffer_consume(ip_data, n);
|
||||
|
||||
/*NeAACDecInitDRM(priv->decoder, priv->sample_rate, priv->channels);*/
|
||||
|
||||
ip_data->sf = sf_rate(priv->sample_rate) | sf_channels(priv->channels) | sf_bits(16) | sf_signed(1);
|
||||
ip_data->sf |= sf_host_endian();
|
||||
aac_get_channel_map(ip_data);
|
||||
|
||||
return 0;
|
||||
out:
|
||||
NeAACDecClose(priv->decoder);
|
||||
free(priv);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int aac_close(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct aac_private *priv = ip_data->private;
|
||||
|
||||
NeAACDecClose(priv->decoder);
|
||||
free(priv);
|
||||
ip_data->private = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* returns -1 on fatal errors
|
||||
* returns -2 on non-fatal errors
|
||||
* 0 on eof
|
||||
* number of bytes put in 'buffer' on success */
|
||||
static int decode_one_frame(struct input_plugin_data *ip_data, void *buffer, int count)
|
||||
{
|
||||
struct aac_private *priv = ip_data->private;
|
||||
unsigned char *aac_data;
|
||||
unsigned int aac_data_size;
|
||||
NeAACDecFrameInfo frame_info;
|
||||
char *sample_buf;
|
||||
int bytes, rc;
|
||||
|
||||
rc = buffer_fill_frame(ip_data);
|
||||
if (rc <= 0)
|
||||
return rc;
|
||||
|
||||
aac_data = buffer_data(ip_data);
|
||||
aac_data_size = buffer_length(ip_data);
|
||||
|
||||
/* aac data -> raw pcm */
|
||||
sample_buf = NeAACDecDecode(priv->decoder, &frame_info, aac_data, aac_data_size);
|
||||
if (frame_info.error == 0 && frame_info.samples > 0) {
|
||||
priv->current.samples += frame_info.samples;
|
||||
priv->current.bytes += frame_info.bytesconsumed;
|
||||
}
|
||||
|
||||
buffer_consume(ip_data, frame_info.bytesconsumed);
|
||||
|
||||
if (!sample_buf || frame_info.bytesconsumed <= 0) {
|
||||
d_print("fatal error: %s\n", NeAACDecGetErrorMessage(frame_info.error));
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (frame_info.error != 0) {
|
||||
d_print("frame error: %s\n", NeAACDecGetErrorMessage(frame_info.error));
|
||||
return -2;
|
||||
}
|
||||
|
||||
if (frame_info.samples <= 0)
|
||||
return -2;
|
||||
|
||||
if (frame_info.channels != priv->channels || frame_info.samplerate != priv->sample_rate) {
|
||||
d_print("invalid channel or sample_rate count\n");
|
||||
return -2;
|
||||
}
|
||||
|
||||
/* 16-bit samples */
|
||||
bytes = frame_info.samples * 2;
|
||||
|
||||
if (bytes > count) {
|
||||
/* decoded too much, keep overflow */
|
||||
priv->overflow_buf = sample_buf + count;
|
||||
priv->overflow_buf_len = bytes - count;
|
||||
memcpy(buffer, sample_buf, count);
|
||||
return count;
|
||||
} else {
|
||||
memcpy(buffer, sample_buf, bytes);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
static int aac_read(struct input_plugin_data *ip_data, char *buffer, int count)
|
||||
{
|
||||
struct aac_private *priv = ip_data->private;
|
||||
int rc;
|
||||
|
||||
/* use overflow from previous call (if any) */
|
||||
if (priv->overflow_buf_len) {
|
||||
int len = priv->overflow_buf_len;
|
||||
|
||||
if (len > count)
|
||||
len = count;
|
||||
|
||||
memcpy(buffer, priv->overflow_buf, len);
|
||||
priv->overflow_buf += len;
|
||||
priv->overflow_buf_len -= len;
|
||||
return len;
|
||||
}
|
||||
|
||||
do {
|
||||
rc = decode_one_frame(ip_data, buffer, count);
|
||||
} while (rc == -2);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int aac_seek(struct input_plugin_data *ip_data, double offset)
|
||||
{
|
||||
return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
static int aac_read_comments(struct input_plugin_data *ip_data,
|
||||
struct keyval **comments)
|
||||
{
|
||||
GROWING_KEYVALS(c);
|
||||
struct id3tag id3;
|
||||
int rc, fd, i;
|
||||
|
||||
fd = open(ip_data->filename, O_RDONLY);
|
||||
if (fd == -1)
|
||||
return -1;
|
||||
|
||||
id3_init(&id3);
|
||||
rc = id3_read_tags(&id3, fd, ID3_V1 | ID3_V2);
|
||||
if (rc == -1) {
|
||||
d_print("error: %s\n", strerror(errno));
|
||||
goto out;
|
||||
}
|
||||
|
||||
for (i = 0; i < NUM_ID3_KEYS; i++) {
|
||||
char *val = id3_get_comment(&id3, i);
|
||||
|
||||
if (val)
|
||||
comments_add(&c, id3_key_names[i], val);
|
||||
}
|
||||
out:
|
||||
close(fd);
|
||||
id3_free(&id3);
|
||||
keyvals_terminate(&c);
|
||||
*comments = c.keyvals;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int aac_duration(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct aac_private *priv = ip_data->private;
|
||||
NeAACDecFrameInfo frame_info;
|
||||
int samples = 0, bytes = 0, frames = 0;
|
||||
off_t file_size;
|
||||
|
||||
file_size = lseek(ip_data->fd, 0, SEEK_END);
|
||||
if (file_size == -1)
|
||||
return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
|
||||
|
||||
/* Seek to the middle of the file. There is almost always silence at
|
||||
* the beginning, which gives wrong results. */
|
||||
if (lseek(ip_data->fd, file_size/2, SEEK_SET) == -1)
|
||||
return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
|
||||
|
||||
priv->rbuf_pos = 0;
|
||||
priv->rbuf_len = 0;
|
||||
|
||||
/* guess track length by decoding the first 10 frames */
|
||||
while (frames < 10) {
|
||||
if (buffer_fill_frame(ip_data) <= 0)
|
||||
break;
|
||||
|
||||
NeAACDecDecode(priv->decoder, &frame_info,
|
||||
buffer_data(ip_data), buffer_length(ip_data));
|
||||
if (frame_info.error == 0 && frame_info.samples > 0) {
|
||||
samples += frame_info.samples;
|
||||
bytes += frame_info.bytesconsumed;
|
||||
frames++;
|
||||
}
|
||||
if (frame_info.bytesconsumed == 0)
|
||||
break;
|
||||
|
||||
buffer_consume(ip_data, frame_info.bytesconsumed);
|
||||
}
|
||||
|
||||
if (frames == 0)
|
||||
return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
|
||||
|
||||
NeAACDecPostSeekReset(priv->decoder, 0);
|
||||
|
||||
samples /= frames;
|
||||
samples /= priv->channels;
|
||||
bytes /= frames;
|
||||
|
||||
/* 8 * file_size / duration */
|
||||
priv->bitrate = (8 * bytes * priv->sample_rate) / samples;
|
||||
|
||||
priv->object_type = frame_info.object_type;
|
||||
|
||||
return ((file_size / bytes) * samples) / priv->sample_rate;
|
||||
}
|
||||
|
||||
static long aac_bitrate(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct aac_private *priv = ip_data->private;
|
||||
return priv->bitrate != -1 ? priv->bitrate : -IP_ERROR_FUNCTION_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
static long aac_current_bitrate(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct aac_private *priv = ip_data->private;
|
||||
long bitrate = -1;
|
||||
if (priv->current.samples > 0) {
|
||||
priv->current.samples /= priv->channels;
|
||||
bitrate = (8 * priv->current.bytes * priv->sample_rate) / priv->current.samples;
|
||||
priv->current.samples = 0;
|
||||
priv->current.bytes = 0;
|
||||
}
|
||||
return bitrate;
|
||||
}
|
||||
|
||||
static char *aac_codec(struct input_plugin_data *ip_data)
|
||||
{
|
||||
return xstrdup("aac");
|
||||
}
|
||||
|
||||
static const char *object_type_to_str(int object_type)
|
||||
{
|
||||
switch (object_type) {
|
||||
case MAIN: return "Main";
|
||||
case LC: return "LC";
|
||||
case SSR: return "SSR";
|
||||
case LTP: return "LTP";
|
||||
case HE_AAC: return "HE";
|
||||
case ER_LC: return "ER-LD";
|
||||
case ER_LTP: return "ER-LTP";
|
||||
case LD: return "LD";
|
||||
case DRM_ER_LC: return "DRM-ER-LC";
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static char *aac_codec_profile(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct aac_private *priv = ip_data->private;
|
||||
const char *profile = object_type_to_str(priv->object_type);
|
||||
|
||||
return profile ? xstrdup(profile) : NULL;
|
||||
}
|
||||
|
||||
const struct input_plugin_ops ip_ops = {
|
||||
.open = aac_open,
|
||||
.close = aac_close,
|
||||
.read = aac_read,
|
||||
.seek = aac_seek,
|
||||
.read_comments = aac_read_comments,
|
||||
.duration = aac_duration,
|
||||
.bitrate = aac_bitrate,
|
||||
.bitrate_current = aac_current_bitrate,
|
||||
.codec = aac_codec,
|
||||
.codec_profile = aac_codec_profile
|
||||
};
|
||||
|
||||
const int ip_priority = 50;
|
||||
const char * const ip_extensions[] = { "aac", NULL };
|
||||
const char * const ip_mime_types[] = { "audio/aac", "audio/aacp", NULL };
|
||||
const struct input_plugin_opt ip_options[] = { { NULL } };
|
||||
const unsigned ip_abi_version = IP_ABI_VERSION;
|
||||
41
ip/aac.h
Normal file
41
ip/aac.h
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2011-2013 Various Authors
|
||||
* Copyright 2011 Johannes Weißl
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CMUS_AAC_H
|
||||
#define CMUS_AAC_H
|
||||
|
||||
#include "../channelmap.h"
|
||||
#include <neaacdec.h>
|
||||
|
||||
static inline channel_position_t channel_position_aac(unsigned char c)
|
||||
{
|
||||
switch (c) {
|
||||
case FRONT_CHANNEL_CENTER: return CHANNEL_POSITION_FRONT_CENTER;
|
||||
case FRONT_CHANNEL_LEFT: return CHANNEL_POSITION_FRONT_LEFT;
|
||||
case FRONT_CHANNEL_RIGHT: return CHANNEL_POSITION_FRONT_RIGHT;
|
||||
case SIDE_CHANNEL_LEFT: return CHANNEL_POSITION_SIDE_LEFT;
|
||||
case SIDE_CHANNEL_RIGHT: return CHANNEL_POSITION_SIDE_RIGHT;
|
||||
case BACK_CHANNEL_LEFT: return CHANNEL_POSITION_REAR_LEFT;
|
||||
case BACK_CHANNEL_RIGHT: return CHANNEL_POSITION_REAR_RIGHT;
|
||||
case BACK_CHANNEL_CENTER: return CHANNEL_POSITION_REAR_CENTER;
|
||||
case LFE_CHANNEL: return CHANNEL_POSITION_LFE;
|
||||
default: return CHANNEL_POSITION_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
213
ip/bass.c
Normal file
213
ip/bass.c
Normal file
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2016 Nic Soudée
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "../ip.h"
|
||||
#include "../xmalloc.h"
|
||||
#include "../comment.h"
|
||||
#include "../bass.h"
|
||||
#include "../uchar.h"
|
||||
|
||||
#define BITS (16)
|
||||
#define FREQ (44100)
|
||||
#define CHANS (2)
|
||||
|
||||
struct bass_private {
|
||||
DWORD chan;
|
||||
};
|
||||
|
||||
static int bass_init(void)
|
||||
{
|
||||
static int inited = 0;
|
||||
|
||||
if (inited)
|
||||
return 1;
|
||||
|
||||
if (!BASS_Init(0, FREQ, 0, 0, NULL))
|
||||
return 0;
|
||||
|
||||
inited = 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int bass_open(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct bass_private *priv;
|
||||
DWORD chan;
|
||||
DWORD flags;
|
||||
|
||||
if (!bass_init())
|
||||
return -IP_ERROR_INTERNAL;
|
||||
|
||||
flags = BASS_MUSIC_DECODE;
|
||||
flags |= BASS_MUSIC_RAMP;
|
||||
flags |= BASS_MUSIC_PRESCAN;
|
||||
flags |= BASS_MUSIC_STOPBACK;
|
||||
|
||||
chan = BASS_MusicLoad(FALSE, ip_data->filename, 0, 0, flags, 0);
|
||||
|
||||
if (!chan) {
|
||||
return -IP_ERROR_ERRNO;
|
||||
}
|
||||
|
||||
priv = xnew(struct bass_private, 1);
|
||||
priv->chan = chan;
|
||||
ip_data->private = priv;
|
||||
ip_data->sf = sf_bits(BITS) | sf_rate(FREQ) | sf_channels(CHANS) | sf_signed(1);
|
||||
ip_data->sf |= sf_host_endian();
|
||||
channel_map_init_stereo(ip_data->channel_map);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bass_close(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct bass_private *priv = ip_data->private;
|
||||
|
||||
BASS_MusicFree(priv->chan);
|
||||
free(priv);
|
||||
ip_data->private = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bass_read(struct input_plugin_data *ip_data, char *buffer, int count)
|
||||
{
|
||||
int length;
|
||||
struct bass_private *priv = ip_data->private;
|
||||
length = BASS_ChannelGetData(priv->chan, buffer, count);
|
||||
if (length < 0) {
|
||||
return 0;
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
static int bass_seek(struct input_plugin_data *ip_data, double offset)
|
||||
{
|
||||
struct bass_private *priv = ip_data->private;
|
||||
QWORD pos = (QWORD)(offset * (FREQ * CHANS * (BITS / 8)) + 0.5);
|
||||
QWORD flags = BASS_POS_BYTE | BASS_POS_DECODE;
|
||||
|
||||
if (!BASS_ChannelSetPosition(priv->chan, pos, flags)) {
|
||||
return -IP_ERROR_INTERNAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static unsigned char *encode_ascii_string(const char *str)
|
||||
{
|
||||
unsigned char *ret;
|
||||
int n;
|
||||
|
||||
ret = xmalloc(strlen(str) + 1);
|
||||
n = u_to_ascii(ret, str, strlen(str));
|
||||
ret[n] = '\0';
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int bass_read_comments(struct input_plugin_data *ip_data,
|
||||
struct keyval **comments)
|
||||
{
|
||||
struct bass_private *priv = ip_data->private;
|
||||
GROWING_KEYVALS(c);
|
||||
const char *val;
|
||||
|
||||
val = BASS_ChannelGetTags(priv->chan, BASS_TAG_MUSIC_NAME);
|
||||
if (val && val[0]) {
|
||||
unsigned char *val_encoded = encode_ascii_string(val);
|
||||
comments_add_const(&c, "title", (char *)val_encoded);
|
||||
free(val_encoded);
|
||||
}
|
||||
keyvals_terminate(&c);
|
||||
*comments = c.keyvals;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bass_duration(struct input_plugin_data *ip_data)
|
||||
{
|
||||
static float length = 0;
|
||||
int pos;
|
||||
struct bass_private *priv = ip_data->private;
|
||||
|
||||
pos = BASS_ChannelGetLength(priv->chan, BASS_POS_BYTE);
|
||||
if (pos && pos != -1) {
|
||||
length = BASS_ChannelBytes2Seconds(priv->chan, pos);
|
||||
}
|
||||
else {
|
||||
length = -IP_ERROR_FUNCTION_NOT_SUPPORTED;
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
static long bass_bitrate(struct input_plugin_data *ip_data)
|
||||
{
|
||||
return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
static const char *bass_type_to_string(int type)
|
||||
{
|
||||
/* from <bass.h> */
|
||||
switch (type) {
|
||||
case 0x20000: return "mod";
|
||||
case 0x20001: return "mtm";
|
||||
case 0x20002: return "s3m";
|
||||
case 0x20003: return "xm";
|
||||
case 0x20004: return "it";
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static char *bass_codec(struct input_plugin_data *ip_data)
|
||||
{
|
||||
const char *codec;
|
||||
int type;
|
||||
BASS_CHANNELINFO info;
|
||||
struct bass_private *priv = ip_data->private;
|
||||
|
||||
if (!(BASS_ChannelGetInfo(priv->chan, &info))) {
|
||||
return NULL;
|
||||
}
|
||||
type = info.ctype;
|
||||
codec = bass_type_to_string(type);
|
||||
return codec ? xstrdup(codec) : NULL;
|
||||
}
|
||||
|
||||
static char *bass_codec_profile(struct input_plugin_data *ip_data)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const struct input_plugin_ops ip_ops = {
|
||||
.open = bass_open,
|
||||
.close = bass_close,
|
||||
.read = bass_read,
|
||||
.seek = bass_seek,
|
||||
.read_comments = bass_read_comments,
|
||||
.duration = bass_duration,
|
||||
.bitrate = bass_bitrate,
|
||||
.bitrate_current = bass_bitrate,
|
||||
.codec = bass_codec,
|
||||
.codec_profile = bass_codec_profile
|
||||
};
|
||||
|
||||
const int ip_priority = 60;
|
||||
const char * const ip_extensions[] = {
|
||||
"xm", "it", "s3m", "mod", "mtm", "umx", NULL
|
||||
};
|
||||
|
||||
const char * const ip_mime_types[] = { NULL };
|
||||
const struct input_plugin_opt ip_options[] = { { NULL } };
|
||||
const unsigned ip_abi_version = IP_ABI_VERSION;
|
||||
|
||||
551
ip/cdio.c
Normal file
551
ip/cdio.c
Normal file
@@ -0,0 +1,551 @@
|
||||
/*
|
||||
* Copyright 2011-2013 Various Authors
|
||||
* Copyright 2011 Johannes Weißl
|
||||
*
|
||||
* Based on cdda.c from XMMS2.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "../ip.h"
|
||||
#include "../file.h"
|
||||
#include "../xmalloc.h"
|
||||
#include "../debug.h"
|
||||
#include "../utils.h"
|
||||
#include "../options.h"
|
||||
#include "../comment.h"
|
||||
#include "../discid.h"
|
||||
|
||||
#include <cdio/cdio.h>
|
||||
#include <cdio/logging.h>
|
||||
#if LIBCDIO_VERSION_NUM >= 90
|
||||
#include <cdio/paranoia/cdda.h>
|
||||
#else
|
||||
#include <cdio/cdda.h>
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#undef HAVE_CDDB
|
||||
|
||||
#ifdef HAVE_CONFIG
|
||||
#include "../config/cdio.h"
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_CDDB
|
||||
#include "../http.h"
|
||||
#include "../xstrjoin.h"
|
||||
#include <cddb/cddb.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_CDDB
|
||||
static char *cddb_url = NULL;
|
||||
#endif
|
||||
|
||||
static struct {
|
||||
CdIo_t *cdio;
|
||||
cdrom_drive_t *drive;
|
||||
const char *disc_id;
|
||||
const char *device;
|
||||
} cached;
|
||||
|
||||
struct cdda_private {
|
||||
CdIo_t *cdio;
|
||||
cdrom_drive_t *drive;
|
||||
char *disc_id;
|
||||
char *device;
|
||||
track_t track;
|
||||
lsn_t first_lsn;
|
||||
lsn_t last_lsn;
|
||||
lsn_t current_lsn;
|
||||
int first_read;
|
||||
|
||||
char read_buf[CDIO_CD_FRAMESIZE_RAW];
|
||||
unsigned long buf_used;
|
||||
};
|
||||
|
||||
static void libcdio_log(cdio_log_level_t level, const char *message)
|
||||
{
|
||||
const char *level_names[] = { "DEBUG", "INFO", "WARN", "ERROR", "ASSERT" };
|
||||
int len = strlen(message);
|
||||
if (len > 0 && message[len-1] == '\n')
|
||||
len--;
|
||||
if (len > 0) {
|
||||
level = clamp(level, 1, N_ELEMENTS(level_names));
|
||||
d_print("%s: %.*s\n", level_names[level-1], len, message);
|
||||
}
|
||||
}
|
||||
|
||||
static int libcdio_open(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct cdda_private *priv, priv_init = {
|
||||
.first_read = 1,
|
||||
.buf_used = CDIO_CD_FRAMESIZE_RAW
|
||||
};
|
||||
CdIo_t *cdio = NULL;
|
||||
cdrom_drive_t *drive = NULL;
|
||||
const char *device = cdda_device;
|
||||
lsn_t first_lsn;
|
||||
int track = -1;
|
||||
char *disc_id = NULL;
|
||||
char *msg = NULL;
|
||||
int rc = 0, save = 0;
|
||||
|
||||
if (!parse_cdda_url(ip_data->filename, &disc_id, &track, NULL)) {
|
||||
rc = -IP_ERROR_INVALID_URI;
|
||||
goto end;
|
||||
}
|
||||
|
||||
if (track == -1) {
|
||||
d_print("invalid or missing track number, aborting!\n");
|
||||
rc = -IP_ERROR_INVALID_URI;
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* In case of cue/toc/nrg, take filename (= disc_id) as device.
|
||||
* A real disc_id is base64 encoded and never contains a slash */
|
||||
if (strchr(disc_id, '/'))
|
||||
device = disc_id;
|
||||
|
||||
ip_data->fd = open(device, O_RDONLY);
|
||||
if (ip_data->fd == -1) {
|
||||
save = errno;
|
||||
d_print("could not open device %s\n", device);
|
||||
rc = -IP_ERROR_ERRNO;
|
||||
goto end;
|
||||
}
|
||||
|
||||
if (cached.cdio && strcmp(disc_id, cached.disc_id) == 0 && strcmp(device, cached.device) == 0) {
|
||||
cdio = cached.cdio;
|
||||
drive = cached.drive;
|
||||
} else {
|
||||
cdio_log_set_handler(libcdio_log);
|
||||
cdio = cdio_open(device, DRIVER_UNKNOWN);
|
||||
if (!cdio) {
|
||||
d_print("failed to open device %s\n", device);
|
||||
rc = -IP_ERROR_NO_DISC;
|
||||
goto end;
|
||||
}
|
||||
cdio_set_speed(cdio, 1);
|
||||
|
||||
drive = cdio_cddap_identify_cdio(cdio, CDDA_MESSAGE_LOGIT, &msg);
|
||||
if (!drive) {
|
||||
d_print("failed to identify drive, aborting!\n");
|
||||
rc = -IP_ERROR_NO_DISC;
|
||||
goto end;
|
||||
}
|
||||
d_print("%s", msg);
|
||||
cdio_cddap_verbose_set(drive, CDDA_MESSAGE_LOGIT, CDDA_MESSAGE_LOGIT);
|
||||
drive->b_swap_bytes = 1;
|
||||
|
||||
if (cdio_cddap_open(drive)) {
|
||||
d_print("unable to open disc, aborting!\n");
|
||||
rc = -IP_ERROR_NO_DISC;
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
|
||||
first_lsn = cdio_cddap_track_firstsector(drive, track);
|
||||
if (first_lsn == -1) {
|
||||
d_print("no such track: %d, aborting!\n", track);
|
||||
rc = -IP_ERROR_INVALID_URI;
|
||||
goto end;
|
||||
}
|
||||
|
||||
priv = xnew(struct cdda_private, 1);
|
||||
*priv = priv_init;
|
||||
priv->cdio = cdio;
|
||||
priv->drive = drive;
|
||||
priv->disc_id = xstrdup(disc_id);
|
||||
priv->device = xstrdup(device);
|
||||
priv->track = track;
|
||||
priv->first_lsn = first_lsn;
|
||||
priv->last_lsn = cdio_cddap_track_lastsector(drive, priv->track);
|
||||
priv->current_lsn = first_lsn;
|
||||
|
||||
cached.cdio = priv->cdio;
|
||||
cached.drive = priv->drive;
|
||||
cached.disc_id = priv->disc_id;
|
||||
cached.device = priv->device;
|
||||
|
||||
ip_data->private = priv;
|
||||
ip_data->sf = sf_bits(16) | sf_rate(44100) | sf_channels(2) | sf_signed(1);
|
||||
ip_data->sf |= sf_host_endian();
|
||||
|
||||
end:
|
||||
free(disc_id);
|
||||
|
||||
if (rc < 0) {
|
||||
if (ip_data->fd != -1)
|
||||
close(ip_data->fd);
|
||||
ip_data->fd = -1;
|
||||
}
|
||||
|
||||
if (rc == -IP_ERROR_ERRNO)
|
||||
errno = save;
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int libcdio_close(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct cdda_private *priv = ip_data->private;
|
||||
|
||||
if (ip_data->fd != -1)
|
||||
close(ip_data->fd);
|
||||
ip_data->fd = -1;
|
||||
|
||||
if (strcmp(priv->disc_id, cached.disc_id) != 0 || strcmp(priv->device, cached.device) != 0) {
|
||||
cdio_cddap_close_no_free_cdio(priv->drive);
|
||||
cdio_destroy(priv->cdio);
|
||||
free(priv->disc_id);
|
||||
free(priv->device);
|
||||
}
|
||||
|
||||
free(priv);
|
||||
ip_data->private = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int libcdio_read(struct input_plugin_data *ip_data, char *buffer, int count)
|
||||
{
|
||||
struct cdda_private *priv = ip_data->private;
|
||||
int rc = 0;
|
||||
|
||||
if (priv->first_read || cdio_get_media_changed(priv->cdio)) {
|
||||
char *disc_id;
|
||||
priv->first_read = 0;
|
||||
if (!get_disc_id(priv->device, &disc_id, NULL))
|
||||
return -IP_ERROR_NO_DISC;
|
||||
if (strcmp(disc_id, priv->disc_id) != 0) {
|
||||
free(disc_id);
|
||||
return -IP_ERROR_WRONG_DISC;
|
||||
}
|
||||
free(disc_id);
|
||||
}
|
||||
|
||||
if (priv->current_lsn >= priv->last_lsn)
|
||||
return 0;
|
||||
|
||||
if (priv->buf_used == CDIO_CD_FRAMESIZE_RAW) {
|
||||
cdio_cddap_read(priv->drive, priv->read_buf, priv->current_lsn, 1);
|
||||
priv->current_lsn++;
|
||||
priv->buf_used = 0;
|
||||
}
|
||||
|
||||
if (count >= CDIO_CD_FRAMESIZE_RAW) {
|
||||
rc = CDIO_CD_FRAMESIZE_RAW - priv->buf_used;
|
||||
memcpy(buffer, priv->read_buf + priv->buf_used, rc);
|
||||
} else {
|
||||
unsigned long buf_left = CDIO_CD_FRAMESIZE_RAW - priv->buf_used;
|
||||
|
||||
if (buf_left < count) {
|
||||
memcpy(buffer, priv->read_buf + priv->buf_used, buf_left);
|
||||
rc = buf_left;
|
||||
} else {
|
||||
memcpy(buffer, priv->read_buf + priv->buf_used, count);
|
||||
rc = count;
|
||||
}
|
||||
}
|
||||
priv->buf_used += rc;
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int libcdio_seek(struct input_plugin_data *ip_data, double offset)
|
||||
{
|
||||
struct cdda_private *priv = ip_data->private;
|
||||
lsn_t new_lsn;
|
||||
int64_t samples = offset * 44100;
|
||||
|
||||
/* Magic number 42... really should think of a better way to do this but
|
||||
* it seemed that the lsn is off by about 42 everytime...
|
||||
*/
|
||||
new_lsn = samples / 441.0 * CDIO_CD_FRAMES_PER_SEC / 100 + 42;
|
||||
|
||||
if ((priv->first_lsn + new_lsn) > priv->last_lsn) {
|
||||
d_print("trying to seek past the end of track.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
priv->current_lsn = priv->first_lsn + new_lsn;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef HAVE_CDDB
|
||||
static int parse_cddb_url(const char *url, struct http_uri *http_uri, int *use_http)
|
||||
{
|
||||
char *full_url;
|
||||
int rc;
|
||||
|
||||
if (is_http_url(url)) {
|
||||
*use_http = 1;
|
||||
full_url = xstrdup(url);
|
||||
} else {
|
||||
*use_http = 0;
|
||||
full_url = xstrjoin("http://", url);
|
||||
}
|
||||
|
||||
rc = http_parse_uri(full_url, http_uri);
|
||||
free(full_url);
|
||||
return rc == 0;
|
||||
}
|
||||
|
||||
static void setup_cddb_conn(cddb_conn_t *cddb_conn)
|
||||
{
|
||||
struct http_uri http_uri, http_proxy_uri;
|
||||
const char *proxy;
|
||||
int use_http;
|
||||
|
||||
parse_cddb_url(cddb_url, &http_uri, &use_http);
|
||||
|
||||
proxy = getenv("http_proxy");
|
||||
if (proxy && http_parse_uri(proxy, &http_proxy_uri) == 0) {
|
||||
cddb_http_proxy_enable(cddb_conn);
|
||||
cddb_set_http_proxy_server_name(cddb_conn, http_proxy_uri.host);
|
||||
cddb_set_http_proxy_server_port(cddb_conn, http_proxy_uri.port);
|
||||
if (http_proxy_uri.user)
|
||||
cddb_set_http_proxy_username(cddb_conn, http_proxy_uri.user);
|
||||
if (http_proxy_uri.pass)
|
||||
cddb_set_http_proxy_password(cddb_conn, http_proxy_uri.pass);
|
||||
http_free_uri(&http_proxy_uri);
|
||||
} else
|
||||
cddb_http_proxy_disable(cddb_conn);
|
||||
|
||||
if (use_http)
|
||||
cddb_http_enable(cddb_conn);
|
||||
else
|
||||
cddb_http_disable(cddb_conn);
|
||||
|
||||
cddb_set_server_name(cddb_conn, http_uri.host);
|
||||
cddb_set_email_address(cddb_conn, "me@home");
|
||||
cddb_set_server_port(cddb_conn, http_uri.port);
|
||||
if (strcmp(http_uri.path, "/") != 0)
|
||||
cddb_set_http_path_query(cddb_conn, http_uri.path);
|
||||
#ifdef DEBUG_CDDB
|
||||
cddb_cache_disable(cddb_conn);
|
||||
#endif
|
||||
|
||||
http_free_uri(&http_uri);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#define add_comment(c, x) do { if (x) comments_add_const(c, #x, x); } while (0)
|
||||
|
||||
static int libcdio_read_comments(struct input_plugin_data *ip_data, struct keyval **comments)
|
||||
{
|
||||
struct cdda_private *priv = ip_data->private;
|
||||
GROWING_KEYVALS(c);
|
||||
const char *artist = NULL, *albumartist = NULL, *album = NULL,
|
||||
*title = NULL, *genre = NULL, *comment = NULL;
|
||||
const cdtext_t *cdt;
|
||||
#ifdef HAVE_CDDB
|
||||
bool track_comments_found = false;
|
||||
cddb_conn_t *cddb_conn = NULL;
|
||||
cddb_disc_t *cddb_disc = NULL;
|
||||
#endif
|
||||
char buf[64];
|
||||
|
||||
#if LIBCDIO_VERSION_NUM >= 90
|
||||
cdt = cdio_get_cdtext(priv->cdio);
|
||||
if (cdt) {
|
||||
artist = cdtext_get(cdt, CDTEXT_FIELD_PERFORMER, priv->track);
|
||||
title = cdtext_get(cdt, CDTEXT_FIELD_TITLE, priv->track);
|
||||
genre = cdtext_get(cdt, CDTEXT_FIELD_GENRE, priv->track);
|
||||
comment = cdtext_get(cdt, CDTEXT_FIELD_MESSAGE, priv->track);
|
||||
|
||||
#ifdef HAVE_CDDB
|
||||
if (title)
|
||||
track_comments_found = true;
|
||||
#endif
|
||||
|
||||
album = cdtext_get(cdt, CDTEXT_FIELD_TITLE, 0);
|
||||
albumartist = cdtext_get(cdt, CDTEXT_FIELD_PERFORMER, 0);
|
||||
if (!artist)
|
||||
artist = albumartist;
|
||||
if (!genre)
|
||||
genre = cdtext_get(cdt, CDTEXT_FIELD_GENRE, 0);
|
||||
if (!comment)
|
||||
comment = cdtext_get(cdt, CDTEXT_FIELD_MESSAGE, 0);
|
||||
}
|
||||
#else
|
||||
cdt = cdio_get_cdtext(priv->cdio, priv->track);
|
||||
if (cdt) {
|
||||
char * const *field = cdt->field;
|
||||
artist = field[CDTEXT_PERFORMER];
|
||||
title = field[CDTEXT_TITLE];
|
||||
genre = field[CDTEXT_GENRE];
|
||||
comment = field[CDTEXT_MESSAGE];
|
||||
#ifdef HAVE_CDDB
|
||||
track_comments_found = true;
|
||||
#endif
|
||||
}
|
||||
cdt = cdio_get_cdtext(priv->cdio, 0);
|
||||
if (cdt) {
|
||||
char * const *field = cdt->field;
|
||||
album = field[CDTEXT_TITLE];
|
||||
albumartist = field[CDTEXT_PERFORMER];
|
||||
if (!artist)
|
||||
artist = field[CDTEXT_PERFORMER];
|
||||
if (!genre)
|
||||
genre = field[CDTEXT_GENRE];
|
||||
if (!comment)
|
||||
comment = field[CDTEXT_MESSAGE];
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_CDDB
|
||||
if (!track_comments_found && cddb_url && cddb_url[0]) {
|
||||
cddb_track_t *cddb_track;
|
||||
track_t i_tracks = cdio_get_num_tracks(priv->cdio);
|
||||
track_t i_first_track = cdio_get_first_track_num(priv->cdio);
|
||||
unsigned int year;
|
||||
int i;
|
||||
|
||||
cddb_conn = cddb_new();
|
||||
if (!cddb_conn)
|
||||
malloc_fail();
|
||||
|
||||
setup_cddb_conn(cddb_conn);
|
||||
|
||||
cddb_disc = cddb_disc_new();
|
||||
if (!cddb_disc)
|
||||
malloc_fail();
|
||||
for (i = 0; i < i_tracks; i++) {
|
||||
cddb_track = cddb_track_new();
|
||||
if (!cddb_track)
|
||||
malloc_fail();
|
||||
cddb_track_set_frame_offset(cddb_track,
|
||||
cdio_get_track_lba(priv->cdio, i+i_first_track));
|
||||
cddb_disc_add_track(cddb_disc, cddb_track);
|
||||
}
|
||||
|
||||
cddb_disc_set_length(cddb_disc, cdio_get_track_lba(priv->cdio,
|
||||
CDIO_CDROM_LEADOUT_TRACK) / CDIO_CD_FRAMES_PER_SEC);
|
||||
if (cddb_query(cddb_conn, cddb_disc) == 1 && cddb_read(cddb_conn, cddb_disc)) {
|
||||
albumartist = cddb_disc_get_artist(cddb_disc);
|
||||
album = cddb_disc_get_title(cddb_disc);
|
||||
genre = cddb_disc_get_genre(cddb_disc);
|
||||
year = cddb_disc_get_year(cddb_disc);
|
||||
if (year) {
|
||||
sprintf(buf, "%u", year);
|
||||
comments_add_const(&c, "date", buf);
|
||||
}
|
||||
cddb_track = cddb_disc_get_track(cddb_disc, priv->track - 1);
|
||||
artist = cddb_track_get_artist(cddb_track);
|
||||
if (!artist)
|
||||
artist = albumartist;
|
||||
title = cddb_track_get_title(cddb_track);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
add_comment(&c, artist);
|
||||
add_comment(&c, albumartist);
|
||||
add_comment(&c, album);
|
||||
add_comment(&c, title);
|
||||
add_comment(&c, genre);
|
||||
add_comment(&c, comment);
|
||||
|
||||
sprintf(buf, "%02d", priv->track);
|
||||
comments_add_const(&c, "tracknumber", buf);
|
||||
|
||||
#ifdef HAVE_CDDB
|
||||
if (cddb_disc)
|
||||
cddb_disc_destroy(cddb_disc);
|
||||
if (cddb_conn)
|
||||
cddb_destroy(cddb_conn);
|
||||
#endif
|
||||
|
||||
keyvals_terminate(&c);
|
||||
*comments = c.keyvals;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int libcdio_duration(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct cdda_private *priv = ip_data->private;
|
||||
|
||||
return (priv->last_lsn - priv->first_lsn) / CDIO_CD_FRAMES_PER_SEC;
|
||||
}
|
||||
|
||||
static long libcdio_bitrate(struct input_plugin_data *ip_data)
|
||||
{
|
||||
return 44100 * 16 * 2;
|
||||
}
|
||||
|
||||
static char *libcdio_codec(struct input_plugin_data *ip_data)
|
||||
{
|
||||
return xstrdup("cdda");
|
||||
}
|
||||
|
||||
static char *libcdio_codec_profile(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct cdda_private *priv = ip_data->private;
|
||||
discmode_t cd_discmode = cdio_get_discmode(priv->cdio);
|
||||
|
||||
return xstrdup(discmode2str[cd_discmode]);
|
||||
}
|
||||
|
||||
#ifdef HAVE_CDDB
|
||||
static int libcdio_set_cddb_url(const char *val)
|
||||
{
|
||||
struct http_uri http_uri;
|
||||
int use_http;
|
||||
if (!parse_cddb_url(val, &http_uri, &use_http))
|
||||
return -IP_ERROR_INVALID_URI;
|
||||
http_free_uri(&http_uri);
|
||||
free(cddb_url);
|
||||
cddb_url = xstrdup(val);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int libcdio_get_cddb_url(char **val)
|
||||
{
|
||||
if (!cddb_url)
|
||||
cddb_url = xstrdup("freedb.freedb.org:8880");
|
||||
*val = xstrdup(cddb_url);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
const struct input_plugin_ops ip_ops = {
|
||||
.open = libcdio_open,
|
||||
.close = libcdio_close,
|
||||
.read = libcdio_read,
|
||||
.seek = libcdio_seek,
|
||||
.read_comments = libcdio_read_comments,
|
||||
.duration = libcdio_duration,
|
||||
.bitrate = libcdio_bitrate,
|
||||
.codec = libcdio_codec,
|
||||
.codec_profile = libcdio_codec_profile,
|
||||
};
|
||||
|
||||
const struct input_plugin_opt ip_options[] = {
|
||||
#ifdef HAVE_CDDB
|
||||
{ "cddb_url", libcdio_set_cddb_url, libcdio_get_cddb_url },
|
||||
#endif
|
||||
{ NULL },
|
||||
};
|
||||
|
||||
const int ip_priority = 50;
|
||||
const char * const ip_extensions[] = { NULL };
|
||||
const char * const ip_mime_types[] = { "x-content/audio-cdda", NULL };
|
||||
const unsigned ip_abi_version = IP_ABI_VERSION;
|
||||
346
ip/cue.c
Normal file
346
ip/cue.c
Normal file
@@ -0,0 +1,346 @@
|
||||
/*
|
||||
* Copyright (C) 2008-2013 Various Authors
|
||||
* Copyright (C) 2011 Gregory Petrosyan
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "../ip.h"
|
||||
#include "../debug.h"
|
||||
#include "../input.h"
|
||||
#include "../utils.h"
|
||||
#include "../comment.h"
|
||||
#include "../xmalloc.h"
|
||||
#include "../cue_utils.h"
|
||||
#include "../cue.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <fcntl.h>
|
||||
#include <math.h>
|
||||
|
||||
struct cue_private {
|
||||
struct input_plugin *child;
|
||||
|
||||
char *cue_filename;
|
||||
int track_n;
|
||||
|
||||
double start_offset;
|
||||
double current_offset;
|
||||
double end_offset;
|
||||
};
|
||||
|
||||
|
||||
static int _parse_cue_url(const char *url, char **filename, int *track_n)
|
||||
{
|
||||
const char *slash;
|
||||
long n;
|
||||
|
||||
if (!is_cue_url(url))
|
||||
return 1;
|
||||
|
||||
url += 6;
|
||||
|
||||
slash = strrchr(url, '/');
|
||||
if (!slash)
|
||||
return 1;
|
||||
|
||||
if (str_to_int(slash + 1, &n) != 0)
|
||||
return 1;
|
||||
|
||||
*filename = xstrndup(url, slash - url);
|
||||
*track_n = n;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static char *_make_absolute_path(const char *abs_filename, const char *rel_filename)
|
||||
{
|
||||
char *s;
|
||||
const char *slash;
|
||||
char buf[4096] = {0};
|
||||
|
||||
slash = strrchr(abs_filename, '/');
|
||||
if (slash == NULL)
|
||||
return xstrdup(rel_filename);
|
||||
|
||||
s = xstrndup(abs_filename, slash - abs_filename);
|
||||
snprintf(buf, sizeof(buf), "%s/%s", s, rel_filename);
|
||||
|
||||
free(s);
|
||||
return xstrdup(buf);
|
||||
}
|
||||
|
||||
|
||||
static int cue_open(struct input_plugin_data *ip_data)
|
||||
{
|
||||
int rc;
|
||||
char *child_filename;
|
||||
struct cue_sheet *cd;
|
||||
struct cue_track *t;
|
||||
struct cue_private *priv;
|
||||
|
||||
priv = xnew(struct cue_private, 1);
|
||||
|
||||
rc = _parse_cue_url(ip_data->filename, &priv->cue_filename, &priv->track_n);
|
||||
if (rc) {
|
||||
rc = -IP_ERROR_INVALID_URI;
|
||||
goto url_parse_failed;
|
||||
}
|
||||
|
||||
cd = cue_from_file(priv->cue_filename);
|
||||
if (cd == NULL) {
|
||||
rc = -IP_ERROR_FILE_FORMAT;
|
||||
goto cue_parse_failed;
|
||||
}
|
||||
|
||||
t = cue_get_track(cd, priv->track_n);
|
||||
if (!t) {
|
||||
rc = -IP_ERROR_FILE_FORMAT;
|
||||
goto cue_read_failed;
|
||||
}
|
||||
|
||||
child_filename = _make_absolute_path(priv->cue_filename, t->file);
|
||||
priv->child = ip_new(child_filename);
|
||||
free(child_filename);
|
||||
|
||||
rc = ip_open(priv->child);
|
||||
if (rc)
|
||||
goto ip_open_failed;
|
||||
|
||||
ip_setup(priv->child);
|
||||
|
||||
priv->start_offset = t->offset;
|
||||
priv->current_offset = t->offset;
|
||||
|
||||
rc = ip_seek(priv->child, priv->start_offset);
|
||||
if (rc)
|
||||
goto ip_open_failed;
|
||||
|
||||
if (t->length >= 0)
|
||||
priv->end_offset = priv->start_offset + t->length;
|
||||
else
|
||||
priv->end_offset = -1;
|
||||
|
||||
ip_data->fd = open(ip_get_filename(priv->child), O_RDONLY);
|
||||
if (ip_data->fd == -1)
|
||||
goto ip_open_failed;
|
||||
|
||||
ip_data->private = priv;
|
||||
ip_data->sf = ip_get_sf(priv->child);
|
||||
ip_get_channel_map(priv->child, ip_data->channel_map);
|
||||
|
||||
cue_free(cd);
|
||||
return 0;
|
||||
|
||||
ip_open_failed:
|
||||
ip_delete(priv->child);
|
||||
|
||||
cue_read_failed:
|
||||
cue_free(cd);
|
||||
|
||||
cue_parse_failed:
|
||||
free(priv->cue_filename);
|
||||
|
||||
url_parse_failed:
|
||||
free(priv);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
static int cue_close(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct cue_private *priv = ip_data->private;
|
||||
|
||||
close(ip_data->fd);
|
||||
ip_data->fd = -1;
|
||||
|
||||
ip_delete(priv->child);
|
||||
free(priv->cue_filename);
|
||||
|
||||
free(priv);
|
||||
ip_data->private = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int cue_read(struct input_plugin_data *ip_data, char *buffer, int count)
|
||||
{
|
||||
int rc;
|
||||
struct cue_private *priv = ip_data->private;
|
||||
|
||||
if (priv->end_offset >= 0.0 && priv->current_offset >= priv->end_offset)
|
||||
return 0;
|
||||
|
||||
rc = ip_read(priv->child, buffer, count);
|
||||
if (rc <= 0)
|
||||
return rc;
|
||||
|
||||
if (priv->end_offset >= 0.0) {
|
||||
sample_format_t sf = ip_get_sf(priv->child);
|
||||
double len = (double)rc / sf_get_second_size(sf);
|
||||
|
||||
double rem_len = priv->end_offset - priv->current_offset;
|
||||
priv->current_offset += len;
|
||||
|
||||
if (priv->current_offset >= priv->end_offset)
|
||||
rc = lround(rem_len * sf_get_rate(sf)) * sf_get_frame_size(sf);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
static int cue_seek(struct input_plugin_data *ip_data, double offset)
|
||||
{
|
||||
struct cue_private *priv = ip_data->private;
|
||||
double new_offset = priv->start_offset + offset;
|
||||
|
||||
if (priv->end_offset >= 0.0 && new_offset > priv->end_offset)
|
||||
new_offset = priv->end_offset;
|
||||
|
||||
priv->current_offset = new_offset;
|
||||
|
||||
return ip_seek(priv->child, new_offset);
|
||||
}
|
||||
|
||||
|
||||
static int cue_read_comments(struct input_plugin_data *ip_data, struct keyval **comments)
|
||||
{
|
||||
struct cue_private *priv = ip_data->private;
|
||||
struct cue_sheet *cd = cue_from_file(priv->cue_filename);
|
||||
struct cue_track *t;
|
||||
int rc;
|
||||
char buf[32] = { 0 };
|
||||
GROWING_KEYVALS(c);
|
||||
|
||||
if (cd == NULL) {
|
||||
rc = -IP_ERROR_FILE_FORMAT;
|
||||
goto cue_parse_failed;
|
||||
}
|
||||
|
||||
t = cue_get_track(cd, priv->track_n);
|
||||
if (!t) {
|
||||
rc = -IP_ERROR_FILE_FORMAT;
|
||||
goto get_track_failed;
|
||||
}
|
||||
|
||||
snprintf(buf, sizeof(buf), "%d", priv->track_n);
|
||||
comments_add_const(&c, "tracknumber", buf);
|
||||
|
||||
if (t->meta.title)
|
||||
comments_add_const(&c, "title", t->meta.title);
|
||||
if (cd->meta.title)
|
||||
comments_add_const(&c, "album", cd->meta.title);
|
||||
if (t->meta.performer)
|
||||
comments_add_const(&c, "artist", t->meta.performer);
|
||||
if (cd->meta.performer)
|
||||
comments_add_const(&c, "albumartist", cd->meta.performer);
|
||||
if (t->meta.date)
|
||||
comments_add_const(&c, "date", t->meta.date);
|
||||
else if (cd->meta.date)
|
||||
comments_add_const(&c, "date", cd->meta.date);
|
||||
if (cd->meta.compilation)
|
||||
comments_add_const(&c, "compilation", cd->meta.compilation);
|
||||
if (cd->meta.discnumber)
|
||||
comments_add_const(&c, "discnumber", cd->meta.discnumber);
|
||||
if (t->meta.genre)
|
||||
comments_add_const(&c, "genre", t->meta.genre);
|
||||
else if (cd->meta.genre)
|
||||
comments_add_const(&c, "genre", cd->meta.genre);
|
||||
|
||||
if (cd->meta.rg_gain)
|
||||
comments_add_const(&c, "replaygain_album_gain", cd->meta.rg_gain);
|
||||
if (cd->meta.rg_peak)
|
||||
comments_add_const(&c, "replaygain_album_peak", cd->meta.rg_peak);
|
||||
if (t->meta.rg_gain)
|
||||
comments_add_const(&c, "replaygain_track_gain", t->meta.rg_gain);
|
||||
if (t->meta.rg_peak)
|
||||
comments_add_const(&c, "replaygain_track_peak", t->meta.rg_peak);
|
||||
|
||||
keyvals_terminate(&c);
|
||||
*comments = c.keyvals;
|
||||
|
||||
cue_free(cd);
|
||||
return 0;
|
||||
|
||||
get_track_failed:
|
||||
cue_free(cd);
|
||||
|
||||
cue_parse_failed:
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
static int cue_duration(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct cue_private *priv = ip_data->private;
|
||||
if (priv->end_offset < 0.0)
|
||||
return ip_duration(priv->child) - priv->start_offset;
|
||||
else
|
||||
return priv->end_offset - priv->start_offset;
|
||||
}
|
||||
|
||||
|
||||
static long cue_bitrate(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct cue_private *priv = ip_data->private;
|
||||
|
||||
return ip_bitrate(priv->child);
|
||||
}
|
||||
|
||||
|
||||
static long cue_current_bitrate(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct cue_private *priv = ip_data->private;
|
||||
|
||||
return ip_current_bitrate(priv->child);
|
||||
}
|
||||
|
||||
|
||||
static char *cue_codec(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct cue_private *priv = ip_data->private;
|
||||
|
||||
return ip_codec(priv->child);
|
||||
}
|
||||
|
||||
|
||||
static char *cue_codec_profile(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct cue_private *priv = ip_data->private;
|
||||
|
||||
return ip_codec_profile(priv->child);
|
||||
}
|
||||
|
||||
|
||||
const struct input_plugin_ops ip_ops = {
|
||||
.open = cue_open,
|
||||
.close = cue_close,
|
||||
.read = cue_read,
|
||||
.seek = cue_seek,
|
||||
.read_comments = cue_read_comments,
|
||||
.duration = cue_duration,
|
||||
.bitrate = cue_bitrate,
|
||||
.bitrate_current = cue_current_bitrate,
|
||||
.codec = cue_codec,
|
||||
.codec_profile = cue_codec_profile,
|
||||
};
|
||||
|
||||
const int ip_priority = 50;
|
||||
const char * const ip_extensions[] = { "cue", NULL };
|
||||
const char * const ip_mime_types[] = { "application/x-cue", NULL };
|
||||
const struct input_plugin_opt ip_options[] = { { NULL } };
|
||||
const unsigned ip_abi_version = IP_ABI_VERSION;
|
||||
552
ip/ffmpeg.c
Normal file
552
ip/ffmpeg.c
Normal file
@@ -0,0 +1,552 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2007 Kevin Ko <kevin.s.ko@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "../ip.h"
|
||||
#include "../xmalloc.h"
|
||||
#include "../debug.h"
|
||||
#include "../utils.h"
|
||||
#include "../comment.h"
|
||||
#ifdef HAVE_CONFIG
|
||||
#include "../config/ffmpeg.h"
|
||||
#endif
|
||||
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavformat/avio.h>
|
||||
#include <libswresample/swresample.h>
|
||||
#include <libavutil/opt.h>
|
||||
#include <libavutil/channel_layout.h>
|
||||
#ifndef AVUTIL_MATHEMATICS_H
|
||||
#include <libavutil/mathematics.h>
|
||||
#endif
|
||||
|
||||
struct ffmpeg_private {
|
||||
AVCodecContext *codec_ctx;
|
||||
AVFormatContext *format_ctx;
|
||||
AVCodec const *codec;
|
||||
SwrContext *swr;
|
||||
int stream_index;
|
||||
|
||||
AVPacket *pkt;
|
||||
AVFrame *frame;
|
||||
double seek_ts;
|
||||
int64_t skip_samples;
|
||||
|
||||
/* A buffer to hold swr_convert()-ed samples */
|
||||
AVFrame *swr_frame;
|
||||
int swr_frame_samples_cap;
|
||||
int swr_frame_start;
|
||||
|
||||
/* Bitrate estimation */
|
||||
unsigned long curr_size;
|
||||
unsigned long curr_duration;
|
||||
};
|
||||
|
||||
static const char *ffmpeg_errmsg(int err)
|
||||
{
|
||||
static char errstr[AV_ERROR_MAX_STRING_SIZE];
|
||||
av_strerror(err, errstr, AV_ERROR_MAX_STRING_SIZE);
|
||||
return errstr;
|
||||
}
|
||||
|
||||
static void ffmpeg_init(void)
|
||||
{
|
||||
static int inited = 0;
|
||||
|
||||
if (inited != 0)
|
||||
return;
|
||||
inited = 1;
|
||||
|
||||
av_log_set_level(AV_LOG_QUIET);
|
||||
|
||||
#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(58, 9, 100)
|
||||
/* We could register decoders explicitly to save memory, but we have to
|
||||
* be careful about compatibility. */
|
||||
av_register_all();
|
||||
#endif
|
||||
}
|
||||
|
||||
static int ffmpeg_open_input(struct input_plugin_data *ip_data,
|
||||
struct ffmpeg_private *priv)
|
||||
{
|
||||
AVFormatContext *ic = NULL;
|
||||
AVCodecContext *cc = NULL;
|
||||
AVCodecParameters *cp = NULL;
|
||||
AVCodec const *codec = NULL;
|
||||
int stream_index = -1;
|
||||
|
||||
int err;
|
||||
int res = avformat_open_input(&ic, ip_data->filename, NULL, NULL);
|
||||
if (res < 0) {
|
||||
err = -IP_ERROR_FILE_FORMAT;
|
||||
goto err;
|
||||
}
|
||||
|
||||
res = avformat_find_stream_info(ic, NULL);
|
||||
if (res < 0) {
|
||||
d_print("unable to find stream info\n");
|
||||
err = -IP_ERROR_FILE_FORMAT;
|
||||
goto err;
|
||||
}
|
||||
|
||||
for (int i = 0; i < ic->nb_streams; i++) {
|
||||
cp = ic->streams[i]->codecpar;
|
||||
if (cp->codec_type == AVMEDIA_TYPE_AUDIO) {
|
||||
stream_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (stream_index == -1) {
|
||||
d_print("could not find audio stream\n");
|
||||
err = -IP_ERROR_FILE_FORMAT;
|
||||
goto err_silent;
|
||||
}
|
||||
|
||||
codec = avcodec_find_decoder(cp->codec_id);
|
||||
if (!codec) {
|
||||
d_print("codec (id: %d, name: %s) not found\n",
|
||||
cc->codec_id, avcodec_get_name(cc->codec_id));
|
||||
err = -IP_ERROR_UNSUPPORTED_FILE_TYPE;
|
||||
goto err_silent;
|
||||
}
|
||||
cc = avcodec_alloc_context3(codec);
|
||||
avcodec_parameters_to_context(cc, cp);
|
||||
|
||||
res = avcodec_open2(cc, codec, NULL);
|
||||
if (res < 0) {
|
||||
d_print("could not open codec (id: %d, name: %s)\n",
|
||||
cc->codec_id, avcodec_get_name(cc->codec_id));
|
||||
err = -IP_ERROR_UNSUPPORTED_FILE_TYPE;
|
||||
goto err;
|
||||
}
|
||||
|
||||
priv->format_ctx = ic;
|
||||
priv->codec_ctx = cc;
|
||||
priv->codec = codec;
|
||||
priv->stream_index = stream_index;
|
||||
return 0;
|
||||
err:
|
||||
d_print("%s\n", ffmpeg_errmsg(res));
|
||||
err_silent:
|
||||
avcodec_free_context(&cc);
|
||||
avformat_close_input(&ic);
|
||||
return err;
|
||||
}
|
||||
|
||||
static void ffmpeg_set_sf_and_swr_opts(SwrContext *swr, AVCodecContext *cc,
|
||||
sample_format_t *sf_out, enum AVSampleFormat *out_sample_fmt)
|
||||
{
|
||||
int out_sample_rate = min_u(cc->sample_rate, 384000);
|
||||
sample_format_t sf = sf_rate(out_sample_rate) | sf_host_endian();
|
||||
av_opt_set_int(swr, "in_sample_rate", cc->sample_rate, 0);
|
||||
av_opt_set_int(swr, "out_sample_rate", out_sample_rate, 0);
|
||||
|
||||
switch (cc->sample_fmt) {
|
||||
case AV_SAMPLE_FMT_FLT: case AV_SAMPLE_FMT_FLTP:
|
||||
case AV_SAMPLE_FMT_S32: case AV_SAMPLE_FMT_S32P:
|
||||
sf |= sf_bits(32) | sf_signed(1);
|
||||
*out_sample_fmt = AV_SAMPLE_FMT_S32;
|
||||
break;
|
||||
default:
|
||||
sf |= sf_bits(16) | sf_signed(1);
|
||||
*out_sample_fmt = AV_SAMPLE_FMT_S16;
|
||||
}
|
||||
av_opt_set_sample_fmt(swr, "in_sample_fmt", cc->sample_fmt, 0);
|
||||
av_opt_set_sample_fmt(swr, "out_sample_fmt", *out_sample_fmt, 0);
|
||||
|
||||
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59, 24, 100)
|
||||
sf |= sf_channels(cc->ch_layout.nb_channels);
|
||||
|
||||
if (cc->ch_layout.order == AV_CHANNEL_ORDER_UNSPEC)
|
||||
av_channel_layout_default(&cc->ch_layout, cc->ch_layout.nb_channels);
|
||||
av_opt_set_chlayout(swr, "in_chlayout", &cc->ch_layout, 0);
|
||||
av_opt_set_chlayout(swr, "out_chlayout", &cc->ch_layout, 0);
|
||||
#else
|
||||
sf |= sf_channels(cc->channels);
|
||||
|
||||
av_opt_set_int(swr, "in_channel_layout",
|
||||
av_get_default_channel_layout(cc->channels), 0);
|
||||
av_opt_set_int(swr, "out_channel_layout",
|
||||
av_get_default_channel_layout(cc->channels), 0);
|
||||
#endif
|
||||
|
||||
*sf_out = sf;
|
||||
}
|
||||
|
||||
static int ffmpeg_init_swr_frame(struct ffmpeg_private *priv,
|
||||
sample_format_t sf, enum AVSampleFormat out_sample_fmt)
|
||||
{
|
||||
AVCodecContext *cc = priv->codec_ctx;
|
||||
AVFrame *frame = av_frame_alloc();
|
||||
|
||||
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59, 24, 100)
|
||||
av_channel_layout_copy(&frame->ch_layout, &cc->ch_layout);
|
||||
#else
|
||||
frame->channel_layout = av_get_default_channel_layout(cc->channels);
|
||||
#endif
|
||||
|
||||
frame->sample_rate = sf_get_rate(sf);
|
||||
frame->format = out_sample_fmt;
|
||||
|
||||
/* NOTE: 10 sec is probably too much, but the amount of space
|
||||
* needed for swr_convert() is unpredictable */
|
||||
frame->nb_samples = 10 * sf_get_rate(sf);
|
||||
int res = av_frame_get_buffer(frame, 0);
|
||||
if (res < 0) {
|
||||
d_print("av_frame_get_buffer(): %s\n", ffmpeg_errmsg(res));
|
||||
return -IP_ERROR_INTERNAL;
|
||||
}
|
||||
priv->swr_frame_samples_cap = frame->nb_samples;
|
||||
frame->nb_samples = 0;
|
||||
|
||||
priv->swr_frame = frame;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ffmpeg_free(struct ffmpeg_private *priv)
|
||||
{
|
||||
avcodec_free_context(&priv->codec_ctx);
|
||||
avformat_close_input(&priv->format_ctx);
|
||||
|
||||
swr_free(&priv->swr);
|
||||
|
||||
av_frame_free(&priv->frame);
|
||||
av_packet_free(&priv->pkt);
|
||||
av_frame_free(&priv->swr_frame);
|
||||
}
|
||||
|
||||
static int ffmpeg_open(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct ffmpeg_private priv;
|
||||
enum AVSampleFormat out_sample_fmt;
|
||||
memset(&priv, 0, sizeof(struct ffmpeg_private));
|
||||
|
||||
ffmpeg_init();
|
||||
|
||||
int err = ffmpeg_open_input(ip_data, &priv);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
priv.pkt = av_packet_alloc();
|
||||
priv.frame = av_frame_alloc();
|
||||
priv.seek_ts = -1;
|
||||
|
||||
priv.swr = swr_alloc();
|
||||
ffmpeg_set_sf_and_swr_opts(priv.swr, priv.codec_ctx,
|
||||
&ip_data->sf, &out_sample_fmt);
|
||||
swr_init(priv.swr);
|
||||
|
||||
err = ffmpeg_init_swr_frame(&priv, ip_data->sf, out_sample_fmt);
|
||||
if (err < 0) {
|
||||
ffmpeg_free(&priv);
|
||||
return err;
|
||||
}
|
||||
|
||||
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59, 24, 100)
|
||||
channel_map_init_waveex(priv.codec_ctx->ch_layout.nb_channels,
|
||||
priv.codec_ctx->ch_layout.u.mask, ip_data->channel_map);
|
||||
#else
|
||||
channel_map_init_waveex(priv.codec_ctx->channels,
|
||||
priv.codec_ctx->channel_layout, ip_data->channel_map);
|
||||
#endif
|
||||
|
||||
ip_data->private = xnew(struct ffmpeg_private, 1);
|
||||
memcpy(ip_data->private, &priv, sizeof(struct ffmpeg_private));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ffmpeg_close(struct input_plugin_data *ip_data)
|
||||
{
|
||||
ffmpeg_free(ip_data->private);
|
||||
free(ip_data->private);
|
||||
ip_data->private = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int64_t ffmpeg_calc_skip_samples(struct ffmpeg_private *priv)
|
||||
{
|
||||
int64_t ts;
|
||||
if (priv->frame->pts >= 0) {
|
||||
ts = priv->frame->pts;
|
||||
} else if (priv->frame->pkt_dts >= 0) {
|
||||
ts = priv->frame->pkt_dts;
|
||||
} else {
|
||||
d_print("AVFrame.pts and AVFrame.pkt_dts are unset\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
AVStream *s = priv->format_ctx->streams[priv->stream_index];
|
||||
double frame_ts = ts * av_q2d(s->time_base);
|
||||
|
||||
d_print("seek_ts: %.6fs, frame_ts: %.6fs\n", priv->seek_ts, frame_ts);
|
||||
|
||||
if (frame_ts >= priv->seek_ts)
|
||||
return 0;
|
||||
return (priv->seek_ts - frame_ts) * priv->frame->sample_rate;
|
||||
}
|
||||
|
||||
static void ffmpeg_skip_frame_part(struct ffmpeg_private *priv)
|
||||
{
|
||||
if (priv->skip_samples >= priv->frame->nb_samples) {
|
||||
d_print("skipping frame: %d samples\n",
|
||||
priv->frame->nb_samples);
|
||||
priv->skip_samples -= priv->frame->nb_samples;
|
||||
priv->frame->nb_samples = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
int bps = av_get_bytes_per_sample(priv->frame->format);
|
||||
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59, 24, 100)
|
||||
int channels = priv->codec_ctx->ch_layout.nb_channels;
|
||||
#else
|
||||
int channels = priv->codec_ctx->channels;
|
||||
#endif
|
||||
|
||||
priv->frame->nb_samples -= priv->skip_samples;
|
||||
|
||||
/* Just modify frame's data pointer because it's throw-away */
|
||||
if (av_sample_fmt_is_planar(priv->frame->format)) {
|
||||
for (int i = 0; i < channels; i++)
|
||||
priv->frame->extended_data[i] += priv->skip_samples * bps;
|
||||
} else {
|
||||
priv->frame->extended_data[0] += priv->skip_samples * channels * bps;
|
||||
}
|
||||
d_print("skipping %lld samples\n", (long long)priv->skip_samples);
|
||||
priv->skip_samples = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* return:
|
||||
* <0 - error
|
||||
* 0 - eof
|
||||
* >0 - ok
|
||||
*/
|
||||
static int ffmpeg_get_frame(struct ffmpeg_private *priv)
|
||||
{
|
||||
int res;
|
||||
retry:
|
||||
res = avcodec_receive_frame(priv->codec_ctx, priv->frame);
|
||||
if (res == AVERROR(EAGAIN)) {
|
||||
av_packet_unref(priv->pkt);
|
||||
res = av_read_frame(priv->format_ctx, priv->pkt);
|
||||
if (res < 0)
|
||||
goto err;
|
||||
|
||||
if (priv->pkt->stream_index != priv->stream_index)
|
||||
goto retry;
|
||||
|
||||
priv->curr_size += priv->pkt->size;
|
||||
priv->curr_duration += priv->pkt->duration;
|
||||
|
||||
res = avcodec_send_packet(priv->codec_ctx, priv->pkt);
|
||||
if (res == 0 || res == AVERROR(EAGAIN))
|
||||
goto retry;
|
||||
}
|
||||
if (res < 0)
|
||||
goto err;
|
||||
|
||||
if (priv->seek_ts > 0) {
|
||||
priv->skip_samples = ffmpeg_calc_skip_samples(priv);
|
||||
if (priv->skip_samples >= 0)
|
||||
priv->seek_ts = -1;
|
||||
}
|
||||
|
||||
if (priv->skip_samples > 0) {
|
||||
ffmpeg_skip_frame_part(priv);
|
||||
if (priv->frame->nb_samples == 0)
|
||||
goto retry;
|
||||
}
|
||||
return 1;
|
||||
err:
|
||||
if (res == AVERROR_EOF)
|
||||
return 0;
|
||||
d_print("%s\n", ffmpeg_errmsg(res));
|
||||
return -IP_ERROR_INTERNAL;
|
||||
}
|
||||
|
||||
static int ffmpeg_convert_frame(struct ffmpeg_private *priv)
|
||||
{
|
||||
int res = swr_convert(priv->swr,
|
||||
priv->swr_frame->extended_data,
|
||||
priv->swr_frame_samples_cap,
|
||||
(const uint8_t **)priv->frame->extended_data,
|
||||
priv->frame->nb_samples);
|
||||
if (res >= 0) {
|
||||
priv->swr_frame->nb_samples = res;
|
||||
priv->swr_frame_start = 0;
|
||||
return res;
|
||||
}
|
||||
d_print("%s\n", ffmpeg_errmsg(res));
|
||||
return -IP_ERROR_INTERNAL;
|
||||
}
|
||||
|
||||
static int ffmpeg_read(struct input_plugin_data *ip_data, char *buffer, int count)
|
||||
{
|
||||
struct ffmpeg_private *priv = ip_data->private;
|
||||
int written = 0;
|
||||
int res;
|
||||
|
||||
count /= sf_get_frame_size(ip_data->sf);
|
||||
|
||||
while (count) {
|
||||
if (priv->swr_frame->nb_samples == 0) {
|
||||
res = ffmpeg_get_frame(priv);
|
||||
if (res == 0)
|
||||
break;
|
||||
else if (res < 0)
|
||||
return res;
|
||||
|
||||
res = ffmpeg_convert_frame(priv);
|
||||
if (res < 0)
|
||||
return res;
|
||||
}
|
||||
|
||||
int copy_frames = min_i(count, priv->swr_frame->nb_samples);
|
||||
int copy_bytes = copy_frames * sf_get_frame_size(ip_data->sf);
|
||||
void *dst = priv->swr_frame->extended_data[0] + priv->swr_frame_start;
|
||||
memcpy(buffer + written, dst, copy_bytes);
|
||||
|
||||
priv->swr_frame->nb_samples -= copy_frames;
|
||||
priv->swr_frame_start += copy_bytes;
|
||||
count -= copy_frames;
|
||||
written += copy_bytes;
|
||||
}
|
||||
return written;
|
||||
}
|
||||
|
||||
static int ffmpeg_seek(struct input_plugin_data *ip_data, double offset)
|
||||
{
|
||||
struct ffmpeg_private *priv = ip_data->private;
|
||||
AVStream *st = priv->format_ctx->streams[priv->stream_index];
|
||||
|
||||
priv->seek_ts = offset;
|
||||
priv->skip_samples = 0;
|
||||
int64_t ts = offset / av_q2d(st->time_base);
|
||||
|
||||
int ret = avformat_seek_file(priv->format_ctx,
|
||||
priv->stream_index, 0, ts, ts, 0);
|
||||
if (ret < 0)
|
||||
return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
|
||||
|
||||
priv->swr_frame->nb_samples = 0;
|
||||
priv->swr_frame_start = 0;
|
||||
avcodec_flush_buffers(priv->codec_ctx);
|
||||
swr_convert(priv->swr, NULL, 0, NULL, 0); /* flush swr buffer */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ffmpeg_read_metadata(struct growing_keyvals *c, AVDictionary *metadata)
|
||||
{
|
||||
AVDictionaryEntry *tag = NULL;
|
||||
|
||||
while ((tag = av_dict_get(metadata, "", tag, AV_DICT_IGNORE_SUFFIX))) {
|
||||
if (tag->value[0])
|
||||
comments_add_const(c, tag->key, tag->value);
|
||||
}
|
||||
}
|
||||
|
||||
static int ffmpeg_read_comments(struct input_plugin_data *ip_data,
|
||||
struct keyval **comments)
|
||||
{
|
||||
struct ffmpeg_private *priv = ip_data->private;
|
||||
AVFormatContext *ic = priv->format_ctx;
|
||||
|
||||
GROWING_KEYVALS(c);
|
||||
|
||||
ffmpeg_read_metadata(&c, ic->metadata);
|
||||
for (unsigned i = 0; i < ic->nb_streams; i++) {
|
||||
ffmpeg_read_metadata(&c, ic->streams[i]->metadata);
|
||||
}
|
||||
|
||||
keyvals_terminate(&c);
|
||||
*comments = c.keyvals;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ffmpeg_duration(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct ffmpeg_private *priv = ip_data->private;
|
||||
return priv->format_ctx->duration / AV_TIME_BASE;
|
||||
}
|
||||
|
||||
static long ffmpeg_bitrate(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct ffmpeg_private *priv = ip_data->private;
|
||||
long bitrate = priv->format_ctx->bit_rate;
|
||||
return bitrate ? bitrate : -IP_ERROR_FUNCTION_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
static long ffmpeg_current_bitrate(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct ffmpeg_private *priv = ip_data->private;
|
||||
AVStream *st = priv->format_ctx->streams[priv->stream_index];
|
||||
long bitrate = -1;
|
||||
/* ape codec returns silly numbers */
|
||||
if (priv->codec->id == AV_CODEC_ID_APE)
|
||||
return -1;
|
||||
if (priv->curr_duration > 0) {
|
||||
double seconds = priv->curr_duration * av_q2d(st->time_base);
|
||||
bitrate = (8 * priv->curr_size) / seconds;
|
||||
priv->curr_size = 0;
|
||||
priv->curr_duration = 0;
|
||||
}
|
||||
return bitrate;
|
||||
}
|
||||
|
||||
static char *ffmpeg_codec(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct ffmpeg_private *priv = ip_data->private;
|
||||
return xstrdup(priv->codec->name);
|
||||
}
|
||||
|
||||
static char *ffmpeg_codec_profile(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct ffmpeg_private *priv = ip_data->private;
|
||||
const char *profile;
|
||||
profile = av_get_profile_name(priv->codec, priv->codec_ctx->profile);
|
||||
return profile ? xstrdup(profile) : NULL;
|
||||
}
|
||||
|
||||
const struct input_plugin_ops ip_ops = {
|
||||
.open = ffmpeg_open,
|
||||
.close = ffmpeg_close,
|
||||
.read = ffmpeg_read,
|
||||
.seek = ffmpeg_seek,
|
||||
.read_comments = ffmpeg_read_comments,
|
||||
.duration = ffmpeg_duration,
|
||||
.bitrate = ffmpeg_bitrate,
|
||||
.bitrate_current = ffmpeg_current_bitrate,
|
||||
.codec = ffmpeg_codec,
|
||||
.codec_profile = ffmpeg_codec_profile
|
||||
};
|
||||
|
||||
const int ip_priority = 30;
|
||||
const char *const ip_extensions[] = {
|
||||
"aa", "aac", "ac3", "aif", "aifc", "aiff", "ape", "au", "dsf", "fla",
|
||||
"flac", "m4a", "m4b", "mka", "mkv", "mp+", "mp2", "mp3", "mp4", "mpc",
|
||||
"mpp", "ogg", "opus", "shn", "tak", "tta", "wav", "webm", "wma", "wv",
|
||||
#ifdef USE_FALLBACK_IP
|
||||
"*",
|
||||
#endif
|
||||
NULL
|
||||
};
|
||||
const char *const ip_mime_types[] = { NULL };
|
||||
const struct input_plugin_opt ip_options[] = { { NULL } };
|
||||
const unsigned ip_abi_version = IP_ABI_VERSION;
|
||||
519
ip/flac.c
Normal file
519
ip/flac.c
Normal file
@@ -0,0 +1,519 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2005 Timo Hirvonen
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "../ip.h"
|
||||
#include "../comment.h"
|
||||
#include "../xmalloc.h"
|
||||
#include "../debug.h"
|
||||
#include "../utils.h"
|
||||
|
||||
#include <FLAC/export.h>
|
||||
#include <FLAC/stream_decoder.h>
|
||||
#include <FLAC/metadata.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
|
||||
#ifndef UINT64_MAX
|
||||
#define UINT64_MAX ((uint64_t)-1)
|
||||
#endif
|
||||
|
||||
/* Reduce typing. Namespaces are nice but FLAC API is fscking ridiculous. */
|
||||
|
||||
/* functions, types, enums */
|
||||
#define F(s) FLAC__stream_decoder_ ## s
|
||||
#define T(s) FLAC__StreamDecoder ## s
|
||||
#define Dec FLAC__StreamDecoder
|
||||
#define E(s) FLAC__STREAM_DECODER_ ## s
|
||||
|
||||
#define FLAC_MAX_CHANNELS 8
|
||||
|
||||
struct flac_private {
|
||||
/* file/stream position and length */
|
||||
uint64_t pos;
|
||||
uint64_t len;
|
||||
|
||||
Dec *dec;
|
||||
|
||||
/* PCM data */
|
||||
char *buf;
|
||||
unsigned int buf_size;
|
||||
unsigned int buf_wpos;
|
||||
unsigned int buf_rpos;
|
||||
|
||||
struct keyval *comments;
|
||||
double duration;
|
||||
long bitrate;
|
||||
int bps;
|
||||
};
|
||||
|
||||
static T(ReadStatus) read_cb(const Dec *dec, unsigned char *buf, size_t *size, void *data)
|
||||
{
|
||||
struct input_plugin_data *ip_data = data;
|
||||
struct flac_private *priv = ip_data->private;
|
||||
int rc;
|
||||
|
||||
if (priv->pos == priv->len) {
|
||||
*size = 0;
|
||||
return E(READ_STATUS_END_OF_STREAM);
|
||||
}
|
||||
if (*size == 0)
|
||||
return E(READ_STATUS_CONTINUE);
|
||||
|
||||
rc = read(ip_data->fd, buf, *size);
|
||||
if (rc == -1) {
|
||||
*size = 0;
|
||||
if (errno == EINTR || errno == EAGAIN) {
|
||||
/* FIXME: not sure how the flac decoder handles this */
|
||||
d_print("interrupted\n");
|
||||
return E(READ_STATUS_CONTINUE);
|
||||
}
|
||||
return E(READ_STATUS_ABORT);
|
||||
}
|
||||
|
||||
priv->pos += rc;
|
||||
*size = rc;
|
||||
if (rc == 0) {
|
||||
/* should not happen */
|
||||
return E(READ_STATUS_END_OF_STREAM);
|
||||
}
|
||||
return E(READ_STATUS_CONTINUE);
|
||||
}
|
||||
|
||||
static T(SeekStatus) seek_cb(const Dec *dec, uint64_t offset, void *data)
|
||||
{
|
||||
struct input_plugin_data *ip_data = data;
|
||||
struct flac_private *priv = ip_data->private;
|
||||
off_t off;
|
||||
|
||||
if (priv->len == UINT64_MAX)
|
||||
return E(SEEK_STATUS_ERROR);
|
||||
off = lseek(ip_data->fd, offset, SEEK_SET);
|
||||
if (off == -1) {
|
||||
return E(SEEK_STATUS_ERROR);
|
||||
}
|
||||
priv->pos = off;
|
||||
return E(SEEK_STATUS_OK);
|
||||
}
|
||||
|
||||
static T(TellStatus) tell_cb(const Dec *dec, uint64_t *offset, void *data)
|
||||
{
|
||||
struct input_plugin_data *ip_data = data;
|
||||
struct flac_private *priv = ip_data->private;
|
||||
|
||||
*offset = priv->pos;
|
||||
return E(TELL_STATUS_OK);
|
||||
}
|
||||
|
||||
static T(LengthStatus) length_cb(const Dec *dec, uint64_t *len, void *data)
|
||||
{
|
||||
struct input_plugin_data *ip_data = data;
|
||||
struct flac_private *priv = ip_data->private;
|
||||
|
||||
if (ip_data->remote) {
|
||||
return E(LENGTH_STATUS_ERROR);
|
||||
}
|
||||
*len = priv->len;
|
||||
return E(LENGTH_STATUS_OK);
|
||||
}
|
||||
|
||||
static int eof_cb(const Dec *dec, void *data)
|
||||
{
|
||||
struct input_plugin_data *ip_data = data;
|
||||
struct flac_private *priv = ip_data->private;
|
||||
|
||||
return priv->pos == priv->len;;
|
||||
}
|
||||
|
||||
#if defined(WORDS_BIGENDIAN)
|
||||
|
||||
#define LE32(x) swap_uint32(x)
|
||||
|
||||
#else
|
||||
|
||||
#define LE32(x) (x)
|
||||
|
||||
#endif
|
||||
|
||||
static FLAC__StreamDecoderWriteStatus write_cb(const Dec *dec, const FLAC__Frame *frame,
|
||||
const int32_t * const *buf, void *data)
|
||||
{
|
||||
struct input_plugin_data *ip_data = data;
|
||||
struct flac_private *priv = ip_data->private;
|
||||
int frames, bytes, size, channels, bits, depth;
|
||||
int ch, nch, i = 0;
|
||||
char *dest; int32_t src;
|
||||
|
||||
frames = frame->header.blocksize;
|
||||
channels = sf_get_channels(ip_data->sf);
|
||||
bits = sf_get_bits(ip_data->sf);
|
||||
bytes = frames * bits / 8 * channels;
|
||||
size = priv->buf_size;
|
||||
|
||||
if (size - priv->buf_wpos < bytes) {
|
||||
if (size < bytes)
|
||||
size = bytes;
|
||||
size *= 2;
|
||||
priv->buf = xrenew(char, priv->buf, size);
|
||||
priv->buf_size = size;
|
||||
}
|
||||
|
||||
depth = frame->header.bits_per_sample;
|
||||
if (!depth)
|
||||
depth = priv->bps;
|
||||
nch = frame->header.channels;
|
||||
dest = priv->buf + priv->buf_wpos;
|
||||
for (i = 0; i < frames; i++) {
|
||||
for (ch = 0; ch < channels; ch++) {
|
||||
src = LE32(buf[ch % nch][i] << (bits - depth));
|
||||
memcpy(dest, &src, bits / 8);
|
||||
dest += bits / 8;
|
||||
}
|
||||
}
|
||||
|
||||
priv->buf_wpos += bytes;
|
||||
return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
|
||||
}
|
||||
|
||||
/* You should make a copy of metadata with FLAC__metadata_object_clone() if you will
|
||||
* need it elsewhere. Since metadata blocks can potentially be large, by
|
||||
* default the decoder only calls the metadata callback for the STREAMINFO
|
||||
* block; you can instruct the decoder to pass or filter other blocks with
|
||||
* FLAC__stream_decoder_set_metadata_*() calls.
|
||||
*/
|
||||
static void metadata_cb(const Dec *dec, const FLAC__StreamMetadata *metadata, void *data)
|
||||
{
|
||||
struct input_plugin_data *ip_data = data;
|
||||
struct flac_private *priv = ip_data->private;
|
||||
|
||||
switch (metadata->type) {
|
||||
case FLAC__METADATA_TYPE_STREAMINFO:
|
||||
{
|
||||
const FLAC__StreamMetadata_StreamInfo *si = &metadata->data.stream_info;
|
||||
int bits = 0;
|
||||
|
||||
if (si->bits_per_sample >= 4 && si->bits_per_sample <= 32) {
|
||||
bits = priv->bps = si->bits_per_sample;
|
||||
bits = 8 * ((bits + 7) / 8);
|
||||
}
|
||||
|
||||
ip_data->sf = sf_rate(si->sample_rate) |
|
||||
sf_bits(bits) |
|
||||
sf_signed(1) |
|
||||
sf_channels(si->channels);
|
||||
if (!ip_data->remote && si->total_samples) {
|
||||
priv->duration = (double) si->total_samples / si->sample_rate;
|
||||
if (priv->duration >= 1 && priv->len >= 1)
|
||||
priv->bitrate = priv->len * 8 / priv->duration;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case FLAC__METADATA_TYPE_VORBIS_COMMENT:
|
||||
if (priv->comments) {
|
||||
d_print("Ignoring VORBISCOMMENT\n");
|
||||
} else {
|
||||
GROWING_KEYVALS(c);
|
||||
int i, nr;
|
||||
|
||||
nr = metadata->data.vorbis_comment.num_comments;
|
||||
for (i = 0; i < nr; i++) {
|
||||
const char *str = (const char *)metadata->data.vorbis_comment.comments[i].entry;
|
||||
char *key, *val;
|
||||
|
||||
val = strchr(str, '=');
|
||||
if (!val)
|
||||
continue;
|
||||
key = xstrndup(str, val - str);
|
||||
val = xstrdup(val + 1);
|
||||
comments_add(&c, key, val);
|
||||
free(key);
|
||||
}
|
||||
keyvals_terminate(&c);
|
||||
priv->comments = c.keyvals;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void error_cb(const Dec *dec, FLAC__StreamDecoderErrorStatus status, void *data)
|
||||
{
|
||||
d_print("FLAC error: %s\n", FLAC__StreamDecoderErrorStatusString[status]);
|
||||
}
|
||||
|
||||
static void free_priv(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct flac_private *priv = ip_data->private;
|
||||
int save = errno;
|
||||
|
||||
F(finish)(priv->dec);
|
||||
F(delete)(priv->dec);
|
||||
if (priv->comments)
|
||||
keyvals_free(priv->comments);
|
||||
free(priv->buf);
|
||||
free(priv);
|
||||
ip_data->private = NULL;
|
||||
errno = save;
|
||||
}
|
||||
|
||||
/* http://flac.sourceforge.net/format.html#frame_header */
|
||||
static void channel_map_init_flac(int channels, channel_position_t *map)
|
||||
{
|
||||
if (channels == 1) {
|
||||
map[0] = CHANNEL_POSITION_MONO;
|
||||
} else if (channels == 2) {
|
||||
map[0] = CHANNEL_POSITION_FRONT_LEFT;
|
||||
map[1] = CHANNEL_POSITION_FRONT_RIGHT;
|
||||
} else if (channels == 3) {
|
||||
map[0] = CHANNEL_POSITION_FRONT_LEFT;
|
||||
map[1] = CHANNEL_POSITION_FRONT_RIGHT;
|
||||
map[2] = CHANNEL_POSITION_FRONT_CENTER;
|
||||
} else if (channels == 4) {
|
||||
map[0] = CHANNEL_POSITION_FRONT_LEFT;
|
||||
map[1] = CHANNEL_POSITION_FRONT_RIGHT;
|
||||
map[2] = CHANNEL_POSITION_REAR_LEFT;
|
||||
map[3] = CHANNEL_POSITION_REAR_RIGHT;
|
||||
} else if (channels == 5) {
|
||||
map[0] = CHANNEL_POSITION_FRONT_LEFT;
|
||||
map[1] = CHANNEL_POSITION_FRONT_RIGHT;
|
||||
map[2] = CHANNEL_POSITION_FRONT_CENTER;
|
||||
map[3] = CHANNEL_POSITION_REAR_LEFT;
|
||||
map[4] = CHANNEL_POSITION_REAR_RIGHT;
|
||||
} else if (channels == 6) {
|
||||
map[0] = CHANNEL_POSITION_FRONT_LEFT;
|
||||
map[1] = CHANNEL_POSITION_FRONT_RIGHT;
|
||||
map[2] = CHANNEL_POSITION_FRONT_CENTER;
|
||||
map[3] = CHANNEL_POSITION_LFE;
|
||||
map[4] = CHANNEL_POSITION_REAR_LEFT;
|
||||
map[5] = CHANNEL_POSITION_REAR_RIGHT;
|
||||
} else if (channels == 7) {
|
||||
map[0] = CHANNEL_POSITION_FRONT_LEFT;
|
||||
map[1] = CHANNEL_POSITION_FRONT_RIGHT;
|
||||
map[2] = CHANNEL_POSITION_FRONT_CENTER;
|
||||
map[3] = CHANNEL_POSITION_LFE;
|
||||
map[4] = CHANNEL_POSITION_REAR_LEFT;
|
||||
map[5] = CHANNEL_POSITION_REAR_RIGHT;
|
||||
map[6] = CHANNEL_POSITION_REAR_CENTER;
|
||||
} else if (channels >= 8) {
|
||||
if (channels > 8) {
|
||||
d_print("Flac file with %d channels?!", channels);
|
||||
}
|
||||
map[0] = CHANNEL_POSITION_FRONT_LEFT;
|
||||
map[1] = CHANNEL_POSITION_FRONT_RIGHT;
|
||||
map[2] = CHANNEL_POSITION_FRONT_CENTER;
|
||||
map[3] = CHANNEL_POSITION_LFE;
|
||||
map[4] = CHANNEL_POSITION_REAR_LEFT;
|
||||
map[5] = CHANNEL_POSITION_REAR_RIGHT;
|
||||
map[6] = CHANNEL_POSITION_SIDE_LEFT;
|
||||
map[7] = CHANNEL_POSITION_SIDE_RIGHT;
|
||||
}
|
||||
}
|
||||
|
||||
static int flac_open(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct flac_private *priv;
|
||||
|
||||
Dec *dec = F(new)();
|
||||
|
||||
const struct flac_private priv_init = {
|
||||
.dec = dec,
|
||||
.duration = -1,
|
||||
.bitrate = -1,
|
||||
.bps = 0
|
||||
};
|
||||
|
||||
if (!dec)
|
||||
return -IP_ERROR_INTERNAL;
|
||||
|
||||
priv = xnew(struct flac_private, 1);
|
||||
*priv = priv_init;
|
||||
if (ip_data->remote) {
|
||||
priv->len = UINT64_MAX;
|
||||
} else {
|
||||
off_t off = lseek(ip_data->fd, 0, SEEK_END);
|
||||
|
||||
if (off == -1 || lseek(ip_data->fd, 0, SEEK_SET) == -1) {
|
||||
int save = errno;
|
||||
|
||||
F(delete)(dec);
|
||||
free(priv);
|
||||
errno = save;
|
||||
return -IP_ERROR_ERRNO;
|
||||
}
|
||||
priv->len = off;
|
||||
}
|
||||
ip_data->private = priv;
|
||||
|
||||
FLAC__stream_decoder_set_metadata_respond_all(dec);
|
||||
if (FLAC__stream_decoder_init_stream(dec, read_cb, seek_cb, tell_cb,
|
||||
length_cb, eof_cb, write_cb, metadata_cb,
|
||||
error_cb, ip_data) != E(INIT_STATUS_OK)) {
|
||||
int save = errno;
|
||||
|
||||
d_print("init failed\n");
|
||||
F(delete)(priv->dec);
|
||||
free(priv);
|
||||
ip_data->private = NULL;
|
||||
errno = save;
|
||||
return -IP_ERROR_ERRNO;
|
||||
}
|
||||
|
||||
ip_data->sf = 0;
|
||||
if (!F(process_until_end_of_metadata)(priv->dec)) {
|
||||
free_priv(ip_data);
|
||||
return -IP_ERROR_ERRNO;
|
||||
}
|
||||
|
||||
if (!ip_data->sf) {
|
||||
free_priv(ip_data);
|
||||
return -IP_ERROR_FILE_FORMAT;
|
||||
}
|
||||
int bits = sf_get_bits(ip_data->sf);
|
||||
if (!bits) {
|
||||
free_priv(ip_data);
|
||||
return -IP_ERROR_SAMPLE_FORMAT;
|
||||
}
|
||||
|
||||
int channels = sf_get_channels(ip_data->sf);
|
||||
if (channels > 8) {
|
||||
free_priv(ip_data);
|
||||
return -IP_ERROR_FILE_FORMAT;
|
||||
}
|
||||
|
||||
channel_map_init_flac(sf_get_channels(ip_data->sf), ip_data->channel_map);
|
||||
d_print("sr: %d, ch: %d, bits: %d\n",
|
||||
sf_get_rate(ip_data->sf),
|
||||
channels,
|
||||
bits);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int flac_close(struct input_plugin_data *ip_data)
|
||||
{
|
||||
free_priv(ip_data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int flac_read(struct input_plugin_data *ip_data, char *buffer, int count)
|
||||
{
|
||||
struct flac_private *priv = ip_data->private;
|
||||
int avail;
|
||||
|
||||
while (1) {
|
||||
avail = priv->buf_wpos - priv->buf_rpos;
|
||||
BUG_ON(avail < 0);
|
||||
if (avail > 0)
|
||||
break;
|
||||
FLAC__bool internal_error = !F(process_single)(priv->dec);
|
||||
FLAC__StreamDecoderState state = F(get_state)(priv->dec);
|
||||
if (state == E(END_OF_STREAM))
|
||||
return 0;
|
||||
if (state == E(ABORTED) || state == E(OGG_ERROR) || internal_error) {
|
||||
d_print("process_single failed\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
if (count > avail)
|
||||
count = avail;
|
||||
memcpy(buffer, priv->buf + priv->buf_rpos, count);
|
||||
priv->buf_rpos += count;
|
||||
BUG_ON(priv->buf_rpos > priv->buf_wpos);
|
||||
if (priv->buf_rpos == priv->buf_wpos) {
|
||||
priv->buf_rpos = 0;
|
||||
priv->buf_wpos = 0;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/* Flush the input and seek to an absolute sample. Decoding will resume at the
|
||||
* given sample.
|
||||
*/
|
||||
static int flac_seek(struct input_plugin_data *ip_data, double offset)
|
||||
{
|
||||
struct flac_private *priv = ip_data->private;
|
||||
priv->buf_rpos = 0;
|
||||
priv->buf_wpos = 0;
|
||||
uint64_t sample;
|
||||
|
||||
sample = (uint64_t)(offset * (double)sf_get_rate(ip_data->sf) + 0.5);
|
||||
if (!F(seek_absolute)(priv->dec, sample)) {
|
||||
if (F(get_state(priv->dec)) == FLAC__STREAM_DECODER_SEEK_ERROR) {
|
||||
if (!F(flush)(priv->dec))
|
||||
d_print("failed to flush\n");
|
||||
}
|
||||
return -IP_ERROR_ERRNO;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int flac_read_comments(struct input_plugin_data *ip_data, struct keyval **comments)
|
||||
{
|
||||
struct flac_private *priv = ip_data->private;
|
||||
|
||||
if (priv->comments) {
|
||||
*comments = keyvals_dup(priv->comments);
|
||||
} else {
|
||||
*comments = keyvals_new(0);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int flac_duration(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct flac_private *priv = ip_data->private;
|
||||
|
||||
return priv->duration;
|
||||
}
|
||||
|
||||
static long flac_bitrate(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct flac_private *priv = ip_data->private;
|
||||
return priv->bitrate;
|
||||
}
|
||||
|
||||
static char *flac_codec(struct input_plugin_data *ip_data)
|
||||
{
|
||||
return xstrdup("flac");
|
||||
}
|
||||
|
||||
static char *flac_codec_profile(struct input_plugin_data *ip_data)
|
||||
{
|
||||
/* maybe identify compression-level over min/max blocksize/framesize */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const struct input_plugin_ops ip_ops = {
|
||||
.open = flac_open,
|
||||
.close = flac_close,
|
||||
.read = flac_read,
|
||||
.seek = flac_seek,
|
||||
.read_comments = flac_read_comments,
|
||||
.duration = flac_duration,
|
||||
.bitrate = flac_bitrate,
|
||||
.bitrate_current = flac_bitrate,
|
||||
.codec = flac_codec,
|
||||
.codec_profile = flac_codec_profile
|
||||
};
|
||||
|
||||
const int ip_priority = 50;
|
||||
const char * const ip_extensions[] = { "flac", "fla", NULL };
|
||||
const char * const ip_mime_types[] = { NULL };
|
||||
const struct input_plugin_opt ip_options[] = { { NULL } };
|
||||
const unsigned ip_abi_version = IP_ABI_VERSION;
|
||||
287
ip/mad.c
Normal file
287
ip/mad.c
Normal file
@@ -0,0 +1,287 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2004 Timo Hirvonen
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "../ip.h"
|
||||
#include "nomad.h"
|
||||
#include "../id3.h"
|
||||
#include "../ape.h"
|
||||
#include "../xmalloc.h"
|
||||
#include "../read_wrapper.h"
|
||||
#include "../debug.h"
|
||||
#include "../utils.h"
|
||||
#include "../comment.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
#include <errno.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
static ssize_t read_func(void *datasource, void *buffer, size_t count)
|
||||
{
|
||||
struct input_plugin_data *ip_data = datasource;
|
||||
|
||||
return read_wrapper(ip_data, buffer, count);
|
||||
}
|
||||
|
||||
static off_t lseek_func(void *datasource, off_t offset, int whence)
|
||||
{
|
||||
struct input_plugin_data *ip_data = datasource;
|
||||
|
||||
return lseek(ip_data->fd, offset, whence);
|
||||
}
|
||||
|
||||
static int close_func(void *datasource)
|
||||
{
|
||||
struct input_plugin_data *ip_data = datasource;
|
||||
int rc;
|
||||
|
||||
rc = ip_data->fd != -1 ? close(ip_data->fd) : 0;
|
||||
ip_data->fd = -1;
|
||||
return rc;
|
||||
}
|
||||
|
||||
static struct nomad_callbacks callbacks = {
|
||||
.read = read_func,
|
||||
.lseek = lseek_func,
|
||||
.close = close_func
|
||||
};
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
static int mad_open(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct nomad *nomad;
|
||||
const struct nomad_info *info;
|
||||
int rc;
|
||||
|
||||
rc = nomad_open_callbacks(&nomad, ip_data, &callbacks);
|
||||
switch (rc) {
|
||||
case -NOMAD_ERROR_ERRNO:
|
||||
return -1;
|
||||
case -NOMAD_ERROR_FILE_FORMAT:
|
||||
return -IP_ERROR_FILE_FORMAT;
|
||||
}
|
||||
ip_data->private = nomad;
|
||||
|
||||
info = nomad_info(nomad);
|
||||
|
||||
/* always 16-bit signed little-endian */
|
||||
ip_data->sf = sf_rate(info->sample_rate) | sf_channels(info->channels) |
|
||||
sf_bits(16) | sf_signed(1);
|
||||
channel_map_init_waveex(info->channels, 0, ip_data->channel_map);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mad_close(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct nomad *nomad;
|
||||
|
||||
nomad = ip_data->private;
|
||||
nomad_close(nomad);
|
||||
ip_data->fd = -1;
|
||||
ip_data->private = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mad_read(struct input_plugin_data *ip_data, char *buffer, int count)
|
||||
{
|
||||
struct nomad *nomad;
|
||||
|
||||
nomad = ip_data->private;
|
||||
return nomad_read(nomad, buffer, count);
|
||||
}
|
||||
|
||||
static int mad_seek(struct input_plugin_data *ip_data, double offset)
|
||||
{
|
||||
struct nomad *nomad;
|
||||
|
||||
nomad = ip_data->private;
|
||||
return nomad_time_seek(nomad, offset);
|
||||
}
|
||||
|
||||
static int mad_read_comments(struct input_plugin_data *ip_data,
|
||||
struct keyval **comments)
|
||||
{
|
||||
struct nomad *nomad = ip_data->private;
|
||||
const struct nomad_lame *lame = nomad_lame(nomad);
|
||||
struct id3tag id3;
|
||||
int fd, rc, save, i;
|
||||
APETAG(ape);
|
||||
GROWING_KEYVALS(c);
|
||||
|
||||
fd = open(ip_data->filename, O_RDONLY);
|
||||
if (fd == -1) {
|
||||
return -1;
|
||||
}
|
||||
d_print("filename: %s\n", ip_data->filename);
|
||||
|
||||
id3_init(&id3);
|
||||
rc = id3_read_tags(&id3, fd, ID3_V1 | ID3_V2);
|
||||
save = errno;
|
||||
close(fd);
|
||||
errno = save;
|
||||
if (rc) {
|
||||
if (rc == -1) {
|
||||
d_print("error: %s\n", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
d_print("corrupted tag?\n");
|
||||
goto next;
|
||||
}
|
||||
|
||||
for (i = 0; i < NUM_ID3_KEYS; i++) {
|
||||
char *val = id3_get_comment(&id3, i);
|
||||
|
||||
if (val)
|
||||
comments_add(&c, id3_key_names[i], val);
|
||||
}
|
||||
|
||||
next:
|
||||
id3_free(&id3);
|
||||
|
||||
rc = ape_read_tags(&ape, ip_data->fd, 0);
|
||||
if (rc < 0)
|
||||
goto out;
|
||||
|
||||
for (i = 0; i < rc; i++) {
|
||||
char *k, *v;
|
||||
k = ape_get_comment(&ape, &v);
|
||||
if (!k)
|
||||
break;
|
||||
comments_add(&c, k, v);
|
||||
free(k);
|
||||
}
|
||||
|
||||
out:
|
||||
ape_free(&ape);
|
||||
|
||||
/* add last so the other tags get preference */
|
||||
if (lame && !isnan(lame->trackGain)) {
|
||||
char buf[64];
|
||||
|
||||
if (!isnan(lame->peak)) {
|
||||
sprintf(buf, "%f", lame->peak);
|
||||
comments_add_const(&c, "replaygain_track_peak", buf);
|
||||
}
|
||||
sprintf(buf, "%+.1f dB", lame->trackGain);
|
||||
comments_add_const(&c, "replaygain_track_gain", buf);
|
||||
}
|
||||
|
||||
keyvals_terminate(&c);
|
||||
*comments = c.keyvals;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mad_duration(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct nomad *nomad = ip_data->private;
|
||||
|
||||
return nomad_info(nomad)->duration;
|
||||
}
|
||||
|
||||
static long mad_bitrate(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct nomad *nomad = ip_data->private;
|
||||
long bitrate = nomad_info(nomad)->avg_bitrate;
|
||||
|
||||
return bitrate != -1 ? bitrate : -IP_ERROR_FUNCTION_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
static long mad_current_bitrate(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct nomad *nomad = ip_data->private;
|
||||
return nomad_current_bitrate(nomad);
|
||||
}
|
||||
|
||||
static char *mad_codec(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct nomad *nomad = ip_data->private;
|
||||
|
||||
switch (nomad_info(nomad)->layer) {
|
||||
case 3:
|
||||
return xstrdup("mp3");
|
||||
case 2:
|
||||
return xstrdup("mp2");
|
||||
case 1:
|
||||
return xstrdup("mp1");
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static char *mad_codec_profile(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct nomad *nomad = ip_data->private;
|
||||
const struct nomad_lame *lame = nomad_lame(nomad);
|
||||
const char *mode = nomad_info(nomad)->vbr ? "VBR" : "CBR";
|
||||
|
||||
if (lame) {
|
||||
/* LAME:
|
||||
* 0: unknown
|
||||
* 1: cbr
|
||||
* 2: abr
|
||||
* 3: vbr rh (--vbr-old)
|
||||
* 4: vbr mtrh (--vbr-new)
|
||||
* 5: vbr mt (obsolete)
|
||||
*/
|
||||
int method = lame->vbr_method;
|
||||
if (method == 2)
|
||||
mode = "ABR";
|
||||
else if (method >= 3 && method <= 5) {
|
||||
const struct nomad_xing *xing = nomad_xing(nomad);
|
||||
|
||||
if (xing && xing->flags & XING_SCALE && xing->scale && xing->scale <= 100) {
|
||||
char buf[16];
|
||||
int v = 10 - (xing->scale + 9) / 10;
|
||||
/* quality (-q): 10 - (xing->scale - ((9 - v) * 10)) */
|
||||
|
||||
sprintf(buf, "VBR V%d", v);
|
||||
return xstrdup(buf);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return xstrdup(mode);
|
||||
}
|
||||
|
||||
const struct input_plugin_ops ip_ops = {
|
||||
.open = mad_open,
|
||||
.close = mad_close,
|
||||
.read = mad_read,
|
||||
.seek = mad_seek,
|
||||
.read_comments = mad_read_comments,
|
||||
.duration = mad_duration,
|
||||
.bitrate = mad_bitrate,
|
||||
.bitrate_current = mad_current_bitrate,
|
||||
.codec = mad_codec,
|
||||
.codec_profile = mad_codec_profile
|
||||
};
|
||||
|
||||
const int ip_priority = 55;
|
||||
const char * const ip_extensions[] = { "mp3", "mp2", NULL };
|
||||
const char * const ip_mime_types[] = {
|
||||
"audio/mpeg", "audio/x-mp3", "audio/x-mpeg", NULL
|
||||
};
|
||||
const struct input_plugin_opt ip_options[] = { { NULL } };
|
||||
const unsigned ip_abi_version = IP_ABI_VERSION;
|
||||
176
ip/mikmod.c
Normal file
176
ip/mikmod.c
Normal file
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
* Adapted from AlsaPlayer 0.99.76
|
||||
*
|
||||
* mikmod_engine.c
|
||||
* Copyright (C) 1999 Paul N. Fisher <rao@gnu.org>
|
||||
* Copyright (C) 2002 Andy Lo A Foe <andy@alsaplayer.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "../ip.h"
|
||||
#include "../xmalloc.h"
|
||||
#include <mikmod.h>
|
||||
#include "../debug.h"
|
||||
#include "../comment.h"
|
||||
|
||||
struct mik_private {
|
||||
MODULE *file;
|
||||
};
|
||||
|
||||
static int mikmod_init(void)
|
||||
{
|
||||
static int inited = 0;
|
||||
|
||||
if (inited)
|
||||
return 1;
|
||||
|
||||
MikMod_RegisterAllDrivers();
|
||||
MikMod_RegisterAllLoaders();
|
||||
|
||||
md_reverb = 0;
|
||||
/* we should let the user decide which one is better... */
|
||||
md_mode = DMODE_SOFT_MUSIC | DMODE_SOFT_SNDFX | DMODE_STEREO |
|
||||
DMODE_16BITS | DMODE_INTERP;
|
||||
|
||||
if (MikMod_Init(NULL)) {
|
||||
d_print("Could not initialize mikmod, reason: %s\n",
|
||||
MikMod_strerror(MikMod_errno));
|
||||
return 0;
|
||||
}
|
||||
|
||||
inited = 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int mik_open(struct input_plugin_data *ip_data)
|
||||
{
|
||||
MODULE *mf = NULL;
|
||||
struct mik_private *priv;
|
||||
int mi = mikmod_init();
|
||||
|
||||
if (!mi)
|
||||
return -IP_ERROR_INTERNAL;
|
||||
|
||||
mf = Player_Load(ip_data->filename, 255, 0);
|
||||
|
||||
if (!mf)
|
||||
return -IP_ERROR_ERRNO;
|
||||
|
||||
priv = xnew(struct mik_private, 1);
|
||||
priv->file = mf;
|
||||
|
||||
ip_data->private = priv;
|
||||
ip_data->sf = sf_bits(16) | sf_rate(44100) | sf_channels(2) | sf_signed(1);
|
||||
ip_data->sf |= sf_host_endian();
|
||||
channel_map_init_stereo(ip_data->channel_map);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mik_close(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct mik_private *priv = ip_data->private;
|
||||
|
||||
Player_Stop();
|
||||
Player_Free(priv->file);
|
||||
free(ip_data->private);
|
||||
ip_data->private = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mik_read(struct input_plugin_data *ip_data, char *buffer, int count)
|
||||
{
|
||||
int length;
|
||||
struct mik_private *priv = ip_data->private;
|
||||
|
||||
if (!Player_Active())
|
||||
Player_Start(priv->file);
|
||||
|
||||
if (!Player_Active())
|
||||
return 0;
|
||||
|
||||
length = VC_WriteBytes(buffer, count);
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
static int mik_seek(struct input_plugin_data *ip_data, double offset)
|
||||
{
|
||||
/* cannot seek in modules properly */
|
||||
return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
static int mik_read_comments(struct input_plugin_data *ip_data, struct keyval **comments)
|
||||
{
|
||||
struct mik_private *priv = ip_data->private;
|
||||
GROWING_KEYVALS(c);
|
||||
const char *val;
|
||||
|
||||
val = priv->file->songname;
|
||||
if (val && val[0])
|
||||
comments_add_const(&c, "title", val);
|
||||
|
||||
val = priv->file->comment;
|
||||
if (val && val[0])
|
||||
comments_add_const(&c, "comment", val);
|
||||
|
||||
keyvals_terminate(&c);
|
||||
*comments = c.keyvals;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mik_duration(struct input_plugin_data *ip_data)
|
||||
{
|
||||
return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
static long mik_bitrate(struct input_plugin_data *ip_data)
|
||||
{
|
||||
return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
static char *mik_codec(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct mik_private *priv = ip_data->private;
|
||||
const char *codec = priv->file->modtype;
|
||||
return (codec && codec[0]) ? xstrdup(codec) : NULL;
|
||||
}
|
||||
|
||||
static char *mik_codec_profile(struct input_plugin_data *ip_data)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const struct input_plugin_ops ip_ops = {
|
||||
.open = mik_open,
|
||||
.close = mik_close,
|
||||
.read = mik_read,
|
||||
.seek = mik_seek,
|
||||
.read_comments = mik_read_comments,
|
||||
.duration = mik_duration,
|
||||
.bitrate = mik_bitrate,
|
||||
.bitrate_current = mik_bitrate,
|
||||
.codec = mik_codec,
|
||||
.codec_profile = mik_codec_profile
|
||||
};
|
||||
|
||||
const int ip_priority = 40;
|
||||
const char * const ip_extensions[] = {
|
||||
"mod", "s3m", "xm", "it", "669", "amf", "dsm",
|
||||
"far", "med", "mtm", "stm", "ult",
|
||||
NULL
|
||||
};
|
||||
const char * const ip_mime_types[] = { NULL };
|
||||
const struct input_plugin_opt ip_options[] = { { NULL } };
|
||||
const unsigned ip_abi_version = IP_ABI_VERSION;
|
||||
244
ip/modplug.c
Normal file
244
ip/modplug.c
Normal file
@@ -0,0 +1,244 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2005 Timo Hirvonen
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "../ip.h"
|
||||
#include "../file.h"
|
||||
#include "../xmalloc.h"
|
||||
#include "../comment.h"
|
||||
#ifdef HAVE_CONFIG
|
||||
#include "../config/modplug.h"
|
||||
#endif
|
||||
|
||||
#include <libmodplug/modplug.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
|
||||
struct mod_private {
|
||||
ModPlugFile *file;
|
||||
};
|
||||
|
||||
static int mod_open(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct mod_private *priv;
|
||||
char *contents;
|
||||
off_t size;
|
||||
ssize_t rc;
|
||||
ModPlugFile *file;
|
||||
ModPlug_Settings settings;
|
||||
|
||||
size = lseek(ip_data->fd, 0, SEEK_END);
|
||||
if (size == -1)
|
||||
return -IP_ERROR_ERRNO;
|
||||
if (lseek(ip_data->fd, 0, SEEK_SET) == -1)
|
||||
return -IP_ERROR_ERRNO;
|
||||
|
||||
contents = xnew(char, size);
|
||||
rc = read_all(ip_data->fd, contents, size);
|
||||
if (rc == -1) {
|
||||
int save = errno;
|
||||
|
||||
free(contents);
|
||||
errno = save;
|
||||
return -IP_ERROR_ERRNO;
|
||||
}
|
||||
if (rc != size) {
|
||||
free(contents);
|
||||
return -IP_ERROR_FILE_FORMAT;
|
||||
}
|
||||
errno = 0;
|
||||
file = ModPlug_Load(contents, size);
|
||||
if (file == NULL) {
|
||||
int save = errno;
|
||||
|
||||
free(contents);
|
||||
errno = save;
|
||||
if (errno == 0) {
|
||||
/* libmodplug never sets errno? */
|
||||
return -IP_ERROR_FILE_FORMAT;
|
||||
}
|
||||
return -IP_ERROR_ERRNO;
|
||||
}
|
||||
free(contents);
|
||||
|
||||
priv = xnew(struct mod_private, 1);
|
||||
priv->file = file;
|
||||
|
||||
ModPlug_GetSettings(&settings);
|
||||
settings.mFlags = MODPLUG_ENABLE_OVERSAMPLING | MODPLUG_ENABLE_NOISE_REDUCTION;
|
||||
/* settings.mFlags |= MODPLUG_ENABLE_REVERB; */
|
||||
/* settings.mFlags |= MODPLUG_ENABLE_MEGABASS; */
|
||||
/* settings.mFlags |= MODPLUG_ENABLE_SURROUND; */
|
||||
settings.mChannels = 2;
|
||||
settings.mBits = 16;
|
||||
settings.mFrequency = 44100;
|
||||
settings.mResamplingMode = MODPLUG_RESAMPLE_FIR;
|
||||
ModPlug_SetSettings(&settings);
|
||||
|
||||
ip_data->private = priv;
|
||||
ip_data->sf = sf_bits(16) | sf_rate(44100) | sf_channels(2) | sf_signed(1);
|
||||
ip_data->sf |= sf_host_endian();
|
||||
channel_map_init_stereo(ip_data->channel_map);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mod_close(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct mod_private *priv = ip_data->private;
|
||||
|
||||
ModPlug_Unload(priv->file);
|
||||
free(priv);
|
||||
ip_data->private = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mod_read(struct input_plugin_data *ip_data, char *buffer, int count)
|
||||
{
|
||||
struct mod_private *priv = ip_data->private;
|
||||
int rc;
|
||||
|
||||
errno = 0;
|
||||
rc = ModPlug_Read(priv->file, buffer, count);
|
||||
if (rc < 0) {
|
||||
if (errno == 0)
|
||||
return -IP_ERROR_INTERNAL;
|
||||
return -IP_ERROR_ERRNO;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int mod_seek(struct input_plugin_data *ip_data, double offset)
|
||||
{
|
||||
struct mod_private *priv = ip_data->private;
|
||||
int ms = (int)(offset * 1000.0 + 0.5);
|
||||
|
||||
/* void */
|
||||
ModPlug_Seek(priv->file, ms);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mod_read_comments(struct input_plugin_data *ip_data, struct keyval **comments)
|
||||
{
|
||||
struct mod_private *priv = ip_data->private;
|
||||
GROWING_KEYVALS(c);
|
||||
const char *val;
|
||||
|
||||
val = ModPlug_GetName(priv->file);
|
||||
if (val && val[0])
|
||||
comments_add_const(&c, "title", val);
|
||||
|
||||
#if MODPLUG_API_8
|
||||
val = ModPlug_GetMessage(priv->file);
|
||||
if (val && val[0])
|
||||
comments_add_const(&c, "comment", val);
|
||||
#endif
|
||||
|
||||
keyvals_terminate(&c);
|
||||
*comments = c.keyvals;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mod_duration(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct mod_private *priv = ip_data->private;
|
||||
|
||||
return (ModPlug_GetLength(priv->file) + 500) / 1000;
|
||||
}
|
||||
|
||||
static long mod_bitrate(struct input_plugin_data *ip_data)
|
||||
{
|
||||
return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
#if MODPLUG_API_8
|
||||
static const char *mod_type_to_string(int type)
|
||||
{
|
||||
/* from <libmodplug/sndfile.h>, which is C++ */
|
||||
switch (type) {
|
||||
case 0x01: return "mod";
|
||||
case 0x02: return "s3m";
|
||||
case 0x04: return "xm";
|
||||
case 0x08: return "med";
|
||||
case 0x10: return "mtm";
|
||||
case 0x20: return "it";
|
||||
case 0x40: return "699";
|
||||
case 0x80: return "ult";
|
||||
case 0x100: return "stm";
|
||||
case 0x200: return "far";
|
||||
case 0x800: return "amf";
|
||||
case 0x1000: return "ams";
|
||||
case 0x2000: return "dsm";
|
||||
case 0x4000: return "mdl";
|
||||
case 0x8000: return "okt";
|
||||
case 0x10000: return "midi";
|
||||
case 0x20000: return "dmf";
|
||||
case 0x40000: return "ptm";
|
||||
case 0x80000: return "dbm";
|
||||
case 0x100000: return "mt2";
|
||||
case 0x200000: return "amf0";
|
||||
case 0x400000: return "psm";
|
||||
case 0x80000000:return "umx";
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
static char *mod_codec(struct input_plugin_data *ip_data)
|
||||
{
|
||||
#if MODPLUG_API_8
|
||||
struct mod_private *priv = ip_data->private;
|
||||
const char *codec;
|
||||
int type;
|
||||
|
||||
type = ModPlug_GetModuleType(priv->file);
|
||||
codec = mod_type_to_string(type);
|
||||
|
||||
return codec ? xstrdup(codec) : NULL;
|
||||
#else
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
static char *mod_codec_profile(struct input_plugin_data *ip_data)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const struct input_plugin_ops ip_ops = {
|
||||
.open = mod_open,
|
||||
.close = mod_close,
|
||||
.read = mod_read,
|
||||
.seek = mod_seek,
|
||||
.read_comments = mod_read_comments,
|
||||
.duration = mod_duration,
|
||||
.bitrate = mod_bitrate,
|
||||
.bitrate_current = mod_bitrate,
|
||||
.codec = mod_codec,
|
||||
.codec_profile = mod_codec_profile
|
||||
};
|
||||
|
||||
const int ip_priority = 50;
|
||||
const char * const ip_extensions[] = {
|
||||
"mod", "s3m", "xm", "it", "669", "amf", "ams", "dbm", "dmf", "dsm",
|
||||
"far", "mdl", "med", "mtm", "okt", "ptm", "stm", "ult", "umx", "mt2",
|
||||
"psm", NULL
|
||||
};
|
||||
const char * const ip_mime_types[] = { NULL };
|
||||
const struct input_plugin_opt ip_options[] = { { NULL } };
|
||||
const unsigned ip_abi_version = IP_ABI_VERSION;
|
||||
699
ip/mp4.c
Normal file
699
ip/mp4.c
Normal file
@@ -0,0 +1,699 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2006 dnk <dnk@bjum.net>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "../ip.h"
|
||||
#include "../xmalloc.h"
|
||||
#include "../debug.h"
|
||||
#include "../id3.h"
|
||||
#include "../file.h"
|
||||
#ifdef HAVE_CONFIG
|
||||
#include "../config/mp4.h"
|
||||
#endif
|
||||
#include "../comment.h"
|
||||
#include "../utils.h"
|
||||
#include "aac.h"
|
||||
|
||||
#if USE_MPEG4IP
|
||||
#include <mp4.h>
|
||||
#else
|
||||
#include <mp4v2/mp4v2.h>
|
||||
#endif
|
||||
|
||||
#include <neaacdec.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <strings.h>
|
||||
|
||||
/* no perfect fallback, for example faac uses only 1024 samples to prime */
|
||||
#define ENCODER_DELAY_DEFAULT 2112
|
||||
|
||||
struct mp4_private {
|
||||
char *overflow_buf;
|
||||
int overflow_buf_len;
|
||||
|
||||
unsigned char channels;
|
||||
unsigned long sample_rate;
|
||||
unsigned long frame_size;
|
||||
unsigned long encoder_delay;
|
||||
unsigned long drop_samples;
|
||||
unsigned long decoded_samples;
|
||||
|
||||
NeAACDecHandle decoder; /* typedef void * */
|
||||
|
||||
struct {
|
||||
MP4FileHandle handle; /* typedef void * */
|
||||
|
||||
MP4TrackId track;
|
||||
MP4SampleId sample; /* "media sample" (AAC access unit) id */
|
||||
MP4SampleId num_samples;
|
||||
MP4Duration duration; /* audio samples, including encoder delay */
|
||||
uint32_t scale; /* sample_rate but from mp4 header */
|
||||
} mp4;
|
||||
|
||||
struct {
|
||||
unsigned long samples;
|
||||
unsigned long bytes;
|
||||
} current;
|
||||
};
|
||||
|
||||
static bool try_iTunSMPB(struct mp4_private *priv)
|
||||
{
|
||||
/* SMPB == seamless playback, older tag for gapless playback */
|
||||
MP4ItmfItemList *itmf_list = MP4ItmfGetItemsByMeaning(priv->mp4.handle, "com.apple.iTunes", "iTunSMPB");
|
||||
if (!itmf_list)
|
||||
return false;
|
||||
|
||||
MP4ItmfItem* item = itmf_list->elements;
|
||||
if (item == NULL || item->dataList.size == 0) {
|
||||
MP4ItmfItemListFree(itmf_list);
|
||||
return false;
|
||||
}
|
||||
|
||||
char *pos = item->dataList.elements[0].value;
|
||||
(void)strtol(pos, &pos, 16); // pad
|
||||
unsigned long delay = strtol(pos, &pos, 16);
|
||||
(void)strtol(pos, &pos, 16); // remainder
|
||||
unsigned long samples = strtol(pos, &pos, 16);
|
||||
priv->encoder_delay = delay;
|
||||
priv->mp4.duration = delay + samples;
|
||||
|
||||
MP4ItmfItemListFree(itmf_list);
|
||||
return true;
|
||||
}
|
||||
|
||||
static MP4TrackId mp4_get_track(MP4FileHandle *handle)
|
||||
{
|
||||
int i, num_tracks;
|
||||
const char *track_type;
|
||||
uint8_t obj_type;
|
||||
MP4TrackId id;
|
||||
|
||||
num_tracks = MP4GetNumberOfTracks(handle, NULL, 0);
|
||||
|
||||
for (i = 0; i < num_tracks; i++) {
|
||||
id = MP4FindTrackId(handle, i, NULL, 0);
|
||||
|
||||
track_type = MP4GetTrackType(handle, id);
|
||||
if (!track_type)
|
||||
continue;
|
||||
|
||||
if (!MP4_IS_AUDIO_TRACK_TYPE(track_type))
|
||||
continue;
|
||||
|
||||
/* MP4GetTrackAudioType */
|
||||
obj_type = MP4GetTrackEsdsObjectTypeId(handle, id);
|
||||
if (obj_type == MP4_INVALID_AUDIO_TYPE)
|
||||
continue;
|
||||
|
||||
if (obj_type == MP4_MPEG4_AUDIO_TYPE) {
|
||||
obj_type = MP4GetTrackAudioMpeg4Type(handle, id);
|
||||
|
||||
if (MP4_IS_MPEG4_AAC_AUDIO_TYPE(obj_type))
|
||||
return id;
|
||||
} else {
|
||||
if (MP4_IS_AAC_AUDIO_TYPE(obj_type))
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
return MP4_INVALID_TRACK_ID;
|
||||
}
|
||||
|
||||
static void mp4_get_channel_map(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct mp4_private *priv = ip_data->private;
|
||||
unsigned char *aac_data = NULL;
|
||||
unsigned int aac_data_len = 0;
|
||||
NeAACDecFrameInfo frame_info;
|
||||
int i;
|
||||
|
||||
ip_data->channel_map[0] = CHANNEL_POSITION_INVALID;
|
||||
|
||||
if (MP4ReadSample(priv->mp4.handle, priv->mp4.track, priv->mp4.sample,
|
||||
&aac_data, &aac_data_len, NULL, NULL, NULL, NULL) == 0)
|
||||
return;
|
||||
|
||||
if (!aac_data)
|
||||
return;
|
||||
|
||||
NeAACDecDecode(priv->decoder, &frame_info, aac_data, aac_data_len);
|
||||
free(aac_data);
|
||||
|
||||
/* avoid squash of first frame by starting at 1 */
|
||||
NeAACDecPostSeekReset(priv->decoder, 1);
|
||||
|
||||
if (frame_info.error != 0 || frame_info.bytesconsumed <= 0
|
||||
|| frame_info.channels > CHANNELS_MAX)
|
||||
return;
|
||||
|
||||
for (i = 0; i < frame_info.channels; i++)
|
||||
ip_data->channel_map[i] = channel_position_aac(frame_info.channel_position[i]);
|
||||
}
|
||||
|
||||
static void mp4_close_handle(MP4FileHandle handle)
|
||||
{
|
||||
#ifdef MP4_CLOSE_DO_NOT_COMPUTE_BITRATE
|
||||
MP4Close(handle, 0);
|
||||
#else
|
||||
MP4Close(handle);
|
||||
#endif
|
||||
}
|
||||
|
||||
static int mp4_open(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct mp4_private *priv;
|
||||
NeAACDecConfigurationPtr neaac_cfg;
|
||||
unsigned char *buf;
|
||||
unsigned int buf_size;
|
||||
int rc = -IP_ERROR_FILE_FORMAT;
|
||||
|
||||
const struct mp4_private priv_init = {
|
||||
.frame_size = 1024,
|
||||
.decoded_samples = 0,
|
||||
.decoder = NULL
|
||||
};
|
||||
|
||||
/* http://sourceforge.net/forum/message.php?msg_id=3578887 */
|
||||
if (ip_data->remote)
|
||||
return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
|
||||
|
||||
/* kindly ask mp4v2 to not spam stderr */
|
||||
MP4LogSetLevel(MP4_LOG_NONE);
|
||||
|
||||
/* init private struct */
|
||||
priv = xnew(struct mp4_private, 1);
|
||||
*priv = priv_init;
|
||||
ip_data->private = priv;
|
||||
|
||||
priv->decoder = NeAACDecOpen();
|
||||
|
||||
/* set decoder config */
|
||||
neaac_cfg = NeAACDecGetCurrentConfiguration(priv->decoder);
|
||||
neaac_cfg->outputFormat = FAAD_FMT_16BIT; /* force 16 bit audio */
|
||||
neaac_cfg->downMatrix = 0; /* NOT 5.1 -> stereo */
|
||||
NeAACDecSetConfiguration(priv->decoder, neaac_cfg);
|
||||
|
||||
/* open mpeg-4 file */
|
||||
#ifdef MP4_DETAILS_ALL
|
||||
priv->mp4.handle = MP4Read(ip_data->filename, 0);
|
||||
#else
|
||||
priv->mp4.handle = MP4Read(ip_data->filename);
|
||||
#endif
|
||||
if (!priv->mp4.handle) {
|
||||
d_print("MP4Read failed\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* find aac audio track */
|
||||
priv->mp4.track = mp4_get_track(priv->mp4.handle);
|
||||
if (priv->mp4.track == MP4_INVALID_TRACK_ID) {
|
||||
d_print("MP4FindTrackId failed\n");
|
||||
if (MP4GetNumberOfTracks(priv->mp4.handle, MP4_AUDIO_TRACK_TYPE, 0) > 0)
|
||||
rc = -IP_ERROR_UNSUPPORTED_FILE_TYPE;
|
||||
goto out;
|
||||
}
|
||||
|
||||
priv->mp4.num_samples = MP4GetTrackNumberOfSamples(priv->mp4.handle, priv->mp4.track);
|
||||
priv->mp4.sample = 1;
|
||||
priv->mp4.duration = MP4GetTrackDuration(priv->mp4.handle, priv->mp4.track);
|
||||
priv->mp4.scale = MP4GetTrackTimeScale(priv->mp4.handle, priv->mp4.track);
|
||||
if (priv->mp4.scale == 0) {
|
||||
rc = -IP_ERROR_INTERNAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (MP4GetTrackNumberOfEdits(priv->mp4.handle, priv->mp4.track) == 1) {
|
||||
priv->encoder_delay = MP4GetTrackEditMediaStart(priv->mp4.handle, priv->mp4.track, 1); // usually 2048
|
||||
|
||||
/* The Edit List reliably gives the original duration excluding encoder delay (but
|
||||
* typically only in ms precision). We can convert this to samples and check whether
|
||||
* the sample accurate TrackDuration includes encoder delay. By my understanding it
|
||||
* should not, but in the files with an Edit List I looked at it did.
|
||||
*
|
||||
* Could just skip the rest of this block and still be mostly compatible...
|
||||
*/
|
||||
uint32_t movie_scale = MP4GetTimeScale(priv->mp4.handle); // usually 1000
|
||||
MP4Duration movie_duration = MP4GetTrackEditDuration(priv->mp4.handle, priv->mp4.track, 1); // usually in ms
|
||||
MP4Duration orig_samples_low_prec = movie_duration * priv->mp4.scale / movie_scale;
|
||||
MP4Duration total_samples_low_prec = priv->encoder_delay + orig_samples_low_prec;
|
||||
|
||||
if (abs_delta(priv->mp4.duration, orig_samples_low_prec) < 100)
|
||||
priv->mp4.duration += priv->encoder_delay;
|
||||
else if (abs_delta(priv->mp4.duration, total_samples_low_prec) > 100)
|
||||
priv->mp4.duration = total_samples_low_prec;
|
||||
} else if (try_iTunSMPB(priv)) {
|
||||
// nothing
|
||||
} else {
|
||||
priv->encoder_delay = ENCODER_DELAY_DEFAULT;
|
||||
priv->mp4.duration += ENCODER_DELAY_DEFAULT;
|
||||
}
|
||||
priv->drop_samples = priv->encoder_delay;
|
||||
|
||||
buf = NULL;
|
||||
buf_size = 0;
|
||||
mp4AudioSpecificConfig mp4ASC = {0};
|
||||
if (!MP4GetTrackESConfiguration(priv->mp4.handle, priv->mp4.track, &buf, &buf_size)) {
|
||||
/* failed to get mpeg-4 audio config... this is ok.
|
||||
* NeAACDecInit2() will simply use default values instead.
|
||||
*/
|
||||
buf = NULL;
|
||||
buf_size = 0;
|
||||
} else if (NeAACDecAudioSpecificConfig(buf, buf_size, &mp4ASC) >= 0) {
|
||||
if (mp4ASC.frameLengthFlag == 1)
|
||||
priv->frame_size = 960;
|
||||
if (mp4ASC.sbr_present_flag == 1 || mp4ASC.forceUpSampling)
|
||||
priv->frame_size *= 2;
|
||||
}
|
||||
|
||||
/* init decoder according to mpeg-4 audio config */
|
||||
if (NeAACDecInit2(priv->decoder, buf, buf_size, &priv->sample_rate, &priv->channels) < 0) {
|
||||
free(buf);
|
||||
goto out;
|
||||
}
|
||||
|
||||
free(buf);
|
||||
|
||||
d_print("sample rate %luhz, channels %u\n", priv->sample_rate, priv->channels);
|
||||
|
||||
ip_data->sf = sf_rate(priv->sample_rate) | sf_channels(priv->channels) | sf_bits(16) | sf_signed(1);
|
||||
ip_data->sf |= sf_host_endian();
|
||||
mp4_get_channel_map(ip_data);
|
||||
|
||||
return 0;
|
||||
|
||||
out:
|
||||
if (priv->mp4.handle)
|
||||
mp4_close_handle(priv->mp4.handle);
|
||||
if (priv->decoder)
|
||||
NeAACDecClose(priv->decoder);
|
||||
free(priv);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int mp4_close(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct mp4_private *priv;
|
||||
|
||||
priv = ip_data->private;
|
||||
|
||||
if (priv->mp4.handle)
|
||||
mp4_close_handle(priv->mp4.handle);
|
||||
|
||||
if (priv->decoder)
|
||||
NeAACDecClose(priv->decoder);
|
||||
|
||||
free(priv);
|
||||
ip_data->private = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* returns -1 on fatal errors
|
||||
* returns -2 on non-fatal errors
|
||||
* 0 on eof
|
||||
* number of bytes put in 'buffer' on success */
|
||||
static int decode_one_frame(struct input_plugin_data *ip_data, void *buffer, int count)
|
||||
{
|
||||
struct mp4_private *priv;
|
||||
unsigned char *aac_data = NULL;
|
||||
unsigned int aac_data_len = 0;
|
||||
NeAACDecFrameInfo frame_info;
|
||||
char *sample_buf;
|
||||
int bytes;
|
||||
|
||||
priv = ip_data->private;
|
||||
|
||||
BUG_ON(priv->overflow_buf_len);
|
||||
|
||||
if (priv->mp4.sample > priv->mp4.num_samples)
|
||||
return 0; /* EOF */
|
||||
|
||||
if (MP4ReadSample(priv->mp4.handle, priv->mp4.track, priv->mp4.sample,
|
||||
&aac_data, &aac_data_len, NULL, NULL, NULL, NULL) == 0) {
|
||||
d_print("error reading mp4 sample %d\n", priv->mp4.sample);
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
priv->mp4.sample++;
|
||||
|
||||
if (!aac_data) {
|
||||
d_print("aac_data == NULL\n");
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
sample_buf = NeAACDecDecode(priv->decoder, &frame_info, aac_data, aac_data_len);
|
||||
free(aac_data);
|
||||
|
||||
if (!sample_buf || frame_info.bytesconsumed <= 0) {
|
||||
d_print("fatal error: %s\n", NeAACDecGetErrorMessage(frame_info.error));
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (frame_info.error != 0) {
|
||||
d_print("frame error: %s\n", NeAACDecGetErrorMessage(frame_info.error));
|
||||
return -2;
|
||||
}
|
||||
|
||||
priv->current.samples += frame_info.samples;
|
||||
priv->current.bytes += frame_info.bytesconsumed;
|
||||
|
||||
/* gapless handling */
|
||||
|
||||
int frame_samples = frame_info.samples / frame_info.channels;
|
||||
priv->decoded_samples += frame_samples;
|
||||
|
||||
if (priv->drop_samples) {
|
||||
int skip = min_u(priv->drop_samples, frame_samples);
|
||||
priv->drop_samples -= skip;
|
||||
frame_samples -= skip;
|
||||
frame_info.samples = frame_samples * frame_info.channels;
|
||||
memmove(sample_buf, sample_buf + (skip * frame_info.channels * 2), frame_info.samples * 2);
|
||||
}
|
||||
if (priv->decoded_samples > priv->mp4.duration) {
|
||||
int extra_samples = (priv->decoded_samples - priv->mp4.duration) * frame_info.channels;
|
||||
if (frame_info.samples >= extra_samples)
|
||||
frame_info.samples -= extra_samples;
|
||||
}
|
||||
|
||||
if (frame_info.samples <= 0)
|
||||
return -2;
|
||||
|
||||
if (frame_info.channels != priv->channels || frame_info.samplerate != priv->sample_rate) {
|
||||
d_print("invalid channel or sample_rate count\n");
|
||||
return -2;
|
||||
}
|
||||
|
||||
/* 16-bit samples */
|
||||
bytes = frame_info.samples * 2;
|
||||
|
||||
if (bytes > count) {
|
||||
/* decoded too much; keep overflow. */
|
||||
priv->overflow_buf = sample_buf + count;
|
||||
priv->overflow_buf_len = bytes - count;
|
||||
memcpy(buffer, sample_buf, count);
|
||||
return count;
|
||||
} else {
|
||||
memcpy(buffer, sample_buf, bytes);
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
static int mp4_read(struct input_plugin_data *ip_data, char *buffer, int count)
|
||||
{
|
||||
struct mp4_private *priv;
|
||||
int rc;
|
||||
|
||||
priv = ip_data->private;
|
||||
|
||||
/* use overflow from previous call (if any) */
|
||||
if (priv->overflow_buf_len > 0) {
|
||||
int len = priv->overflow_buf_len;
|
||||
|
||||
if (len > count)
|
||||
len = count;
|
||||
|
||||
memcpy(buffer, priv->overflow_buf, len);
|
||||
priv->overflow_buf += len;
|
||||
priv->overflow_buf_len -= len;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
do {
|
||||
rc = decode_one_frame(ip_data, buffer, count);
|
||||
} while (rc == -2);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int mp4_seek(struct input_plugin_data *ip_data, double offset)
|
||||
{
|
||||
struct mp4_private *priv;
|
||||
MP4SampleId sample;
|
||||
|
||||
priv = ip_data->private;
|
||||
|
||||
sample = MP4GetSampleIdFromTime(priv->mp4.handle, priv->mp4.track,
|
||||
(MP4Timestamp)(offset * (double)priv->mp4.scale), 1);
|
||||
if (sample == MP4_INVALID_SAMPLE_ID)
|
||||
return -IP_ERROR_INTERNAL;
|
||||
|
||||
priv->mp4.sample = sample;
|
||||
priv->decoded_samples = (sample - 1) * priv->frame_size;
|
||||
|
||||
if (priv->decoded_samples < priv->encoder_delay)
|
||||
priv->drop_samples = priv->encoder_delay - priv->decoded_samples;
|
||||
else
|
||||
priv->drop_samples = 0;
|
||||
|
||||
NeAACDecPostSeekReset(priv->decoder, sample);
|
||||
|
||||
d_print("seeking to sample %d\n", sample);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mp4_read_comments(struct input_plugin_data *ip_data,
|
||||
struct keyval **comments)
|
||||
{
|
||||
struct mp4_private *priv;
|
||||
#if USE_MPEG4IP
|
||||
uint16_t meta_num, meta_total;
|
||||
uint8_t val;
|
||||
char *str;
|
||||
/*uint8_t *ustr;
|
||||
uint32_t size;*/
|
||||
#else
|
||||
const MP4Tags *tags;
|
||||
MP4ItmfItemList* itmf_list;
|
||||
#endif
|
||||
GROWING_KEYVALS(c);
|
||||
|
||||
priv = ip_data->private;
|
||||
|
||||
#if USE_MPEG4IP
|
||||
/* MP4GetMetadata* provides malloced pointers, and the data
|
||||
* is in UTF-8 (or at least it should be). */
|
||||
if (MP4GetMetadataArtist(priv->mp4.handle, &str))
|
||||
comments_add(&c, "artist", str);
|
||||
if (MP4GetMetadataAlbum(priv->mp4.handle, &str))
|
||||
comments_add(&c, "album", str);
|
||||
if (MP4GetMetadataName(priv->mp4.handle, &str))
|
||||
comments_add(&c, "title", str);
|
||||
if (MP4GetMetadataGenre(priv->mp4.handle, &str))
|
||||
comments_add(&c, "genre", str);
|
||||
if (MP4GetMetadataYear(priv->mp4.handle, &str))
|
||||
comments_add(&c, "date", str);
|
||||
|
||||
if (MP4GetMetadataCompilation(priv->mp4.handle, &val))
|
||||
comments_add_const(&c, "compilation", val ? "yes" : "no");
|
||||
#if 0
|
||||
if (MP4GetBytesProperty(priv->mp4.handle, "moov.udta.meta.ilst.aART.data", &ustr, &size)) {
|
||||
char *xstr;
|
||||
|
||||
/* What's this?
|
||||
* This is the result from lack of documentation.
|
||||
* It's supposed to return just a string, but it
|
||||
* returns an additional 16 bytes of junk at the
|
||||
* beginning. Could be a bug. Could be intentional.
|
||||
* Hopefully this works around it:
|
||||
*/
|
||||
if (ustr[0] == 0 && size > 16) {
|
||||
ustr += 16;
|
||||
size -= 16;
|
||||
}
|
||||
xstr = xnew(char, size + 1);
|
||||
memcpy(xstr, ustr, size);
|
||||
xstr[size] = 0;
|
||||
comments_add(&c, "albumartist", xstr);
|
||||
free(xstr);
|
||||
}
|
||||
#endif
|
||||
if (MP4GetMetadataTrack(priv->mp4.handle, &meta_num, &meta_total)) {
|
||||
char buf[6];
|
||||
snprintf(buf, 6, "%u", meta_num);
|
||||
comments_add_const(&c, "tracknumber", buf);
|
||||
}
|
||||
if (MP4GetMetadataDisk(priv->mp4.handle, &meta_num, &meta_total)) {
|
||||
char buf[6];
|
||||
snprintf(buf, 6, "%u", meta_num);
|
||||
comments_add_const(&c, "discnumber", buf);
|
||||
}
|
||||
|
||||
#else /* !USE_MPEG4IP, new interface */
|
||||
|
||||
tags = MP4TagsAlloc();
|
||||
|
||||
MP4TagsFetch(tags, priv->mp4.handle);
|
||||
|
||||
if (tags->artist)
|
||||
comments_add_const(&c, "artist", tags->artist);
|
||||
if (tags->albumArtist)
|
||||
comments_add_const(&c, "albumartist", tags->albumArtist);
|
||||
if (tags->sortArtist)
|
||||
comments_add_const(&c, "artistsort", tags->sortArtist);
|
||||
if (tags->sortAlbumArtist)
|
||||
comments_add_const(&c, "albumartistsort", tags->sortAlbumArtist);
|
||||
if (tags->sortAlbum)
|
||||
comments_add_const(&c, "albumsort", tags->sortAlbum);
|
||||
if (tags->album)
|
||||
comments_add_const(&c, "album", tags->album);
|
||||
if (tags->name)
|
||||
comments_add_const(&c, "title", tags->name);
|
||||
if (tags->genre) {
|
||||
comments_add_const(&c, "genre", tags->genre);
|
||||
} else if (tags->genreType) {
|
||||
char const *genre = id3_get_genre(*tags->genreType - 1);
|
||||
if (genre)
|
||||
comments_add_const(&c, "genre", genre);
|
||||
}
|
||||
if (tags->releaseDate && strcmp(tags->releaseDate, "0") != 0)
|
||||
comments_add_const(&c, "date", tags->releaseDate);
|
||||
if (tags->compilation)
|
||||
comments_add_const(&c, "compilation", *tags->compilation ? "yes" : "no");
|
||||
if (tags->track) {
|
||||
char buf[6];
|
||||
snprintf(buf, 6, "%u", tags->track->index);
|
||||
comments_add_const(&c, "tracknumber", buf);
|
||||
}
|
||||
if (tags->disk) {
|
||||
char buf[6];
|
||||
snprintf(buf, 6, "%u", tags->disk->index);
|
||||
comments_add_const(&c, "discnumber", buf);
|
||||
}
|
||||
if (tags->tempo) {
|
||||
char buf[6];
|
||||
snprintf(buf, 6, "%u", *tags->tempo);
|
||||
comments_add_const(&c, "bpm", buf);
|
||||
}
|
||||
|
||||
MP4TagsFree(tags);
|
||||
|
||||
itmf_list = MP4ItmfGetItemsByMeaning(priv->mp4.handle, "com.apple.iTunes", NULL);
|
||||
if (itmf_list) {
|
||||
int i;
|
||||
for (i = 0; i < itmf_list->size; i++) {
|
||||
MP4ItmfItem* item = &itmf_list->elements[i];
|
||||
if (item->dataList.size < 1)
|
||||
continue;
|
||||
if (item->dataList.size > 1)
|
||||
d_print("ignore multiple values for tag %s\n", item->name);
|
||||
else {
|
||||
MP4ItmfData* data = &item->dataList.elements[0];
|
||||
char *val = xstrndup(data->value, data->valueSize);
|
||||
comments_add(&c, item->name, val);
|
||||
}
|
||||
}
|
||||
MP4ItmfItemListFree(itmf_list);
|
||||
}
|
||||
#endif
|
||||
|
||||
keyvals_terminate(&c);
|
||||
*comments = c.keyvals;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mp4_duration(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct mp4_private *priv;
|
||||
|
||||
priv = ip_data->private;
|
||||
|
||||
return priv->mp4.duration / priv->mp4.scale;
|
||||
}
|
||||
|
||||
static long mp4_bitrate(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct mp4_private *priv = ip_data->private;
|
||||
long bitrate = MP4GetTrackBitRate(priv->mp4.handle, priv->mp4.track);
|
||||
return bitrate ? bitrate : -IP_ERROR_FUNCTION_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
static long mp4_current_bitrate(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct mp4_private *priv = ip_data->private;
|
||||
long bitrate = -1;
|
||||
if (priv->current.samples > 0) {
|
||||
priv->current.samples /= priv->channels;
|
||||
bitrate = (8 * priv->current.bytes * priv->sample_rate) / priv->current.samples;
|
||||
priv->current.samples = 0;
|
||||
priv->current.bytes = 0;
|
||||
}
|
||||
return bitrate;
|
||||
}
|
||||
|
||||
static char *mp4_codec(struct input_plugin_data *ip_data)
|
||||
{
|
||||
return xstrdup("aac");
|
||||
}
|
||||
|
||||
static const char *object_type_to_str(uint8_t obj_type)
|
||||
{
|
||||
switch (obj_type) {
|
||||
case MP4_MPEG4_AAC_MAIN_AUDIO_TYPE: return "Main";
|
||||
case MP4_MPEG4_AAC_LC_AUDIO_TYPE: return "LC";
|
||||
case MP4_MPEG4_AAC_SSR_AUDIO_TYPE: return "SSR";
|
||||
case MP4_MPEG4_AAC_LTP_AUDIO_TYPE: return "LTP";
|
||||
#ifdef MP4_MPEG4_AAC_HE_AUDIO_TYPE
|
||||
case MP4_MPEG4_AAC_HE_AUDIO_TYPE: return "HE";
|
||||
#endif
|
||||
case MP4_MPEG4_AAC_SCALABLE_AUDIO_TYPE: return "Scalable";
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static char *mp4_codec_profile(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct mp4_private *priv = ip_data->private;
|
||||
const char *profile;
|
||||
uint8_t obj_type;
|
||||
|
||||
obj_type = MP4GetTrackEsdsObjectTypeId(priv->mp4.handle, priv->mp4.track);
|
||||
if (obj_type == MP4_MPEG4_AUDIO_TYPE)
|
||||
obj_type = MP4GetTrackAudioMpeg4Type(priv->mp4.handle, priv->mp4.track);
|
||||
|
||||
profile = object_type_to_str(obj_type);
|
||||
|
||||
return profile ? xstrdup(profile) : NULL;
|
||||
}
|
||||
|
||||
const struct input_plugin_ops ip_ops = {
|
||||
.open = mp4_open,
|
||||
.close = mp4_close,
|
||||
.read = mp4_read,
|
||||
.seek = mp4_seek,
|
||||
.read_comments = mp4_read_comments,
|
||||
.duration = mp4_duration,
|
||||
.bitrate = mp4_bitrate,
|
||||
.bitrate_current = mp4_current_bitrate,
|
||||
.codec = mp4_codec,
|
||||
.codec_profile = mp4_codec_profile
|
||||
};
|
||||
|
||||
const int ip_priority = 50;
|
||||
const char * const ip_extensions[] = { "mp4", "m4a", "m4b", NULL };
|
||||
const char * const ip_mime_types[] = { /*"audio/mp4", "audio/mp4a-latm",*/ NULL };
|
||||
const struct input_plugin_opt ip_options[] = { { NULL } };
|
||||
const unsigned ip_abi_version = IP_ABI_VERSION;
|
||||
464
ip/mpc.c
Normal file
464
ip/mpc.c
Normal file
@@ -0,0 +1,464 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2006 Chun-Yu Shei <cshei AT cs.indiana.edu>
|
||||
*
|
||||
* Cleaned up by Timo Hirvonen <tihirvon@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "../ip.h"
|
||||
#include "../ape.h"
|
||||
#include "../comment.h"
|
||||
#include "../file.h"
|
||||
#include "../xmalloc.h"
|
||||
#include "../read_wrapper.h"
|
||||
|
||||
#ifdef HAVE_CONFIG
|
||||
#include "../config/mpc.h"
|
||||
#endif
|
||||
|
||||
#if MPC_SV8
|
||||
#include <mpc/mpcdec.h>
|
||||
#define callback_t mpc_reader
|
||||
#define get_ip_data(d) (d)->data
|
||||
#else
|
||||
#include <mpcdec/mpcdec.h>
|
||||
#define MPC_FALSE FALSE
|
||||
#define MPC_TRUE TRUE
|
||||
#define callback_t void
|
||||
#define get_ip_data(d) (d)
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
|
||||
struct mpc_private {
|
||||
#if MPC_SV8
|
||||
mpc_demux *decoder;
|
||||
#else
|
||||
mpc_decoder decoder;
|
||||
#endif
|
||||
mpc_reader reader;
|
||||
mpc_streaminfo info;
|
||||
|
||||
off_t file_size;
|
||||
|
||||
int samples_pos;
|
||||
int samples_avail;
|
||||
|
||||
/* mpcdec/mpcdec.h
|
||||
*
|
||||
* the api doc says this is pcm samples per mpc frame
|
||||
* but it's really pcm _frames_ per mpc frame
|
||||
* MPC_FRAME_LENGTH = 36 * 32 (1152)
|
||||
*
|
||||
* this is wrong, it should be 2 * MPC_FRAME_LENGTH (2304)
|
||||
* MPC_DECODER_BUFFER_LENGTH = 4 * MPC_FRAME_LENGTH (4608)
|
||||
*
|
||||
* use MPC_DECODER_BUFFER_LENGTH just to be sure it works
|
||||
*/
|
||||
MPC_SAMPLE_FORMAT samples[MPC_DECODER_BUFFER_LENGTH];
|
||||
|
||||
struct {
|
||||
unsigned long samples;
|
||||
unsigned long bits;
|
||||
} current;
|
||||
};
|
||||
|
||||
/* callbacks */
|
||||
static mpc_int32_t read_impl(callback_t *data, void *ptr, mpc_int32_t size)
|
||||
{
|
||||
struct input_plugin_data *ip_data = get_ip_data(data);
|
||||
int rc;
|
||||
|
||||
rc = read_wrapper(ip_data, ptr, size);
|
||||
if (rc == -1)
|
||||
return -1;
|
||||
if (rc == 0) {
|
||||
errno = 0;
|
||||
return 0;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static mpc_bool_t seek_impl(callback_t *data, mpc_int32_t offset)
|
||||
{
|
||||
struct input_plugin_data *ip_data = get_ip_data(data);
|
||||
|
||||
if (lseek(ip_data->fd, offset, SEEK_SET) == -1)
|
||||
return MPC_FALSE;
|
||||
return MPC_TRUE;
|
||||
}
|
||||
|
||||
static mpc_int32_t tell_impl(callback_t *data)
|
||||
{
|
||||
struct input_plugin_data *ip_data = get_ip_data(data);
|
||||
|
||||
return lseek(ip_data->fd, 0, SEEK_CUR);
|
||||
}
|
||||
|
||||
static mpc_int32_t get_size_impl(callback_t *data)
|
||||
{
|
||||
struct input_plugin_data *ip_data = get_ip_data(data);
|
||||
struct mpc_private *priv = ip_data->private;
|
||||
|
||||
return priv->file_size;
|
||||
}
|
||||
|
||||
static mpc_bool_t canseek_impl(callback_t *data)
|
||||
{
|
||||
struct input_plugin_data *ip_data = get_ip_data(data);
|
||||
|
||||
return !ip_data->remote;
|
||||
}
|
||||
|
||||
static int mpc_open(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct mpc_private *priv;
|
||||
|
||||
const struct mpc_private priv_init = {
|
||||
.file_size = -1,
|
||||
/* set up an mpc_reader linked to our function implementations */
|
||||
.reader = {
|
||||
.read = read_impl,
|
||||
.seek = seek_impl,
|
||||
.tell = tell_impl,
|
||||
.get_size = get_size_impl,
|
||||
.canseek = canseek_impl,
|
||||
.data = ip_data
|
||||
}
|
||||
};
|
||||
|
||||
priv = xnew(struct mpc_private, 1);
|
||||
*priv = priv_init;
|
||||
|
||||
if (!ip_data->remote) {
|
||||
priv->file_size = lseek(ip_data->fd, 0, SEEK_END);
|
||||
lseek(ip_data->fd, 0, SEEK_SET);
|
||||
}
|
||||
|
||||
/* must be before mpc_streaminfo_read() */
|
||||
ip_data->private = priv;
|
||||
|
||||
/* read file's streaminfo data */
|
||||
#if MPC_SV8
|
||||
priv->decoder = mpc_demux_init(&priv->reader);
|
||||
if (!priv->decoder) {
|
||||
#else
|
||||
mpc_streaminfo_init(&priv->info);
|
||||
if (mpc_streaminfo_read(&priv->info, &priv->reader) != ERROR_CODE_OK) {
|
||||
#endif
|
||||
free(priv);
|
||||
return -IP_ERROR_FILE_FORMAT;
|
||||
}
|
||||
|
||||
#if MPC_SV8
|
||||
mpc_demux_get_info(priv->decoder, &priv->info);
|
||||
#else
|
||||
/* instantiate a decoder with our file reader */
|
||||
mpc_decoder_setup(&priv->decoder, &priv->reader);
|
||||
if (!mpc_decoder_initialize(&priv->decoder, &priv->info)) {
|
||||
free(priv);
|
||||
return -IP_ERROR_FILE_FORMAT;
|
||||
}
|
||||
#endif
|
||||
|
||||
priv->samples_avail = 0;
|
||||
priv->samples_pos = 0;
|
||||
|
||||
ip_data->sf = sf_rate(priv->info.sample_freq) | sf_channels(priv->info.channels) |
|
||||
sf_bits(16) | sf_signed(1);
|
||||
channel_map_init_waveex(priv->info.channels, 0, ip_data->channel_map);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mpc_close(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct mpc_private *priv = ip_data->private;
|
||||
|
||||
#if MPC_SV8
|
||||
mpc_demux_exit(priv->decoder);
|
||||
#endif
|
||||
free(priv);
|
||||
ip_data->private = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int scale(struct input_plugin_data *ip_data, char *buffer, int count)
|
||||
{
|
||||
struct mpc_private *priv = ip_data->private;
|
||||
const MPC_SAMPLE_FORMAT *samples;
|
||||
const int clip_min = (unsigned)-1 << (16 - 1);
|
||||
const int clip_max = (1 << (16 - 1)) - 1;
|
||||
const int float_scale = 1 << (16 - 1);
|
||||
int i, sample_count;
|
||||
|
||||
/* number of bytes to 16-bit samples */
|
||||
sample_count = count / 2;
|
||||
if (sample_count > priv->samples_avail)
|
||||
sample_count = priv->samples_avail;
|
||||
|
||||
/* scale 32-bit samples to 16-bit */
|
||||
samples = priv->samples + priv->samples_pos;
|
||||
for (i = 0; i < sample_count; i++) {
|
||||
int val;
|
||||
|
||||
val = samples[i] * float_scale;
|
||||
if (val < clip_min) {
|
||||
val = clip_min;
|
||||
} else if (val > clip_max) {
|
||||
val = clip_max;
|
||||
}
|
||||
|
||||
buffer[i * 2 + 0] = val & 0xff;
|
||||
buffer[i * 2 + 1] = val >> 8;
|
||||
}
|
||||
|
||||
priv->samples_pos += sample_count;
|
||||
priv->samples_avail -= sample_count;
|
||||
if (priv->samples_avail == 0)
|
||||
priv->samples_pos = 0;
|
||||
|
||||
/* number of 16-bit samples to bytes */
|
||||
return sample_count * 2;
|
||||
}
|
||||
|
||||
static int mpc_read(struct input_plugin_data *ip_data, char *buffer, int count)
|
||||
{
|
||||
struct mpc_private *priv = ip_data->private;
|
||||
|
||||
#if MPC_SV8
|
||||
mpc_status status;
|
||||
mpc_frame_info frame;
|
||||
int samples;
|
||||
|
||||
frame.buffer = priv->samples;
|
||||
|
||||
while (priv->samples_avail == 0) {
|
||||
status = mpc_demux_decode(priv->decoder, &frame);
|
||||
|
||||
if (status != MPC_STATUS_OK) {
|
||||
return -IP_ERROR_ERRNO;
|
||||
}
|
||||
if (frame.bits == -1) {
|
||||
/* EOF */
|
||||
return 0;
|
||||
}
|
||||
|
||||
samples = frame.samples;
|
||||
priv->samples_avail = samples * priv->info.channels;
|
||||
|
||||
priv->current.samples += frame.samples;
|
||||
priv->current.bits += frame.bits;
|
||||
}
|
||||
#else
|
||||
|
||||
if (priv->samples_avail == 0) {
|
||||
uint32_t acc = 0, bits = 0;
|
||||
uint32_t status = mpc_decoder_decode(&priv->decoder, priv->samples, &acc, &bits);
|
||||
|
||||
if (status == (uint32_t)(-1)) {
|
||||
/* right ret val? */
|
||||
return -IP_ERROR_ERRNO;
|
||||
}
|
||||
if (status == 0) {
|
||||
/* EOF */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* status seems to be number of _frames_
|
||||
* the api documentation is wrong
|
||||
*/
|
||||
priv->samples_avail = status * priv->info.channels;
|
||||
|
||||
priv->current.samples += status;
|
||||
priv->current.bits += bits;
|
||||
}
|
||||
#endif
|
||||
|
||||
return scale(ip_data, buffer, count);
|
||||
}
|
||||
|
||||
static int mpc_seek(struct input_plugin_data *ip_data, double offset)
|
||||
{
|
||||
struct mpc_private *priv = ip_data->private;
|
||||
|
||||
priv->samples_pos = 0;
|
||||
priv->samples_avail = 0;
|
||||
|
||||
#if MPC_SV8
|
||||
if (mpc_demux_seek_second(priv->decoder, offset) == MPC_STATUS_OK)
|
||||
#else
|
||||
if (mpc_decoder_seek_seconds(&priv->decoder, offset))
|
||||
#endif
|
||||
return 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static const char *gain_to_str(int gain)
|
||||
{
|
||||
static char buf[16];
|
||||
#if MPC_SV8
|
||||
float g = MPC_OLD_GAIN_REF - gain / 256.f;
|
||||
sprintf(buf, "%.2f", g);
|
||||
#else
|
||||
int b, a = gain / 100;
|
||||
|
||||
if (gain < 0) {
|
||||
b = -gain % 100;
|
||||
} else {
|
||||
b = gain % 100;
|
||||
}
|
||||
sprintf(buf, "%d.%02d", a, b);
|
||||
#endif
|
||||
return buf;
|
||||
}
|
||||
|
||||
static const char *peak_to_str(unsigned int peak)
|
||||
{
|
||||
static char buf[16];
|
||||
#if MPC_SV8
|
||||
sprintf(buf, "%.5f", peak / 256.f / 100.f);
|
||||
#else
|
||||
sprintf(buf, "%d.%05d", peak / 32767, peak % 32767);
|
||||
#endif
|
||||
return buf;
|
||||
}
|
||||
|
||||
static int mpc_read_comments(struct input_plugin_data *ip_data, struct keyval **comments)
|
||||
{
|
||||
struct mpc_private *priv = ip_data->private;
|
||||
GROWING_KEYVALS(c);
|
||||
int count, i;
|
||||
APETAG(ape);
|
||||
|
||||
count = ape_read_tags(&ape, ip_data->fd, 1);
|
||||
if (count < 0)
|
||||
goto out;
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
char *k, *v;
|
||||
k = ape_get_comment(&ape, &v);
|
||||
if (!k)
|
||||
break;
|
||||
comments_add(&c, k, v);
|
||||
free(k);
|
||||
}
|
||||
|
||||
out:
|
||||
if (priv->info.gain_title && priv->info.peak_title) {
|
||||
comments_add_const(&c, "replaygain_track_gain", gain_to_str(priv->info.gain_title));
|
||||
comments_add_const(&c, "replaygain_track_peak", peak_to_str(priv->info.peak_title));
|
||||
}
|
||||
if (priv->info.gain_album && priv->info.peak_album) {
|
||||
comments_add_const(&c, "replaygain_album_gain", gain_to_str(priv->info.gain_album));
|
||||
comments_add_const(&c, "replaygain_album_peak", peak_to_str(priv->info.peak_album));
|
||||
}
|
||||
keyvals_terminate(&c);
|
||||
|
||||
*comments = c.keyvals;
|
||||
ape_free(&ape);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mpc_duration(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct mpc_private *priv = ip_data->private;
|
||||
|
||||
/* priv->info.pcm_samples seems to be number of frames
|
||||
* priv->info.frames is _not_ pcm frames
|
||||
*/
|
||||
#if MPC_SV8
|
||||
return mpc_streaminfo_get_length(&priv->info);
|
||||
#else
|
||||
return priv->info.pcm_samples / priv->info.sample_freq;
|
||||
#endif
|
||||
}
|
||||
|
||||
static long mpc_bitrate(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct mpc_private *priv = ip_data->private;
|
||||
if (priv->info.average_bitrate)
|
||||
return (long) (priv->info.average_bitrate + 0.5);
|
||||
if (priv->info.bitrate)
|
||||
return priv->info.bitrate;
|
||||
return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
static long mpc_current_bitrate(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct mpc_private *priv = ip_data->private;
|
||||
long bitrate = -1;
|
||||
if (priv->current.samples > 0) {
|
||||
bitrate = (priv->info.sample_freq * priv->current.bits) / priv->current.samples;
|
||||
priv->current.samples = 0;
|
||||
priv->current.bits = 0;
|
||||
}
|
||||
return bitrate;
|
||||
|
||||
}
|
||||
|
||||
static char *mpc_codec(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct mpc_private *priv = ip_data->private;
|
||||
switch (priv->info.stream_version) {
|
||||
case 7:
|
||||
return xstrdup("mpc7");
|
||||
case 8:
|
||||
return xstrdup("mpc8");
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static char *mpc_codec_profile(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct mpc_private *priv = ip_data->private;
|
||||
const char *profile = priv->info.profile_name;
|
||||
char *s = NULL;
|
||||
|
||||
if (profile && profile[0]) {
|
||||
int i;
|
||||
|
||||
/* remove single quotes */
|
||||
while (*profile == '\'')
|
||||
profile++;
|
||||
s = xstrdup(profile);
|
||||
for (i = strlen(s) - 1; s[i] == '\'' && i >= 0; i--)
|
||||
s[i] = '\0';
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
const struct input_plugin_ops ip_ops = {
|
||||
.open = mpc_open,
|
||||
.close = mpc_close,
|
||||
.read = mpc_read,
|
||||
.seek = mpc_seek,
|
||||
.read_comments = mpc_read_comments,
|
||||
.duration = mpc_duration,
|
||||
.bitrate = mpc_bitrate,
|
||||
.bitrate_current = mpc_current_bitrate,
|
||||
.codec = mpc_codec,
|
||||
.codec_profile = mpc_codec_profile
|
||||
};
|
||||
|
||||
const int ip_priority = 50;
|
||||
const char *const ip_extensions[] = { "mpc", "mpp", "mp+", NULL };
|
||||
const char *const ip_mime_types[] = { "audio/x-musepack", NULL };
|
||||
const struct input_plugin_opt ip_options[] = { { NULL } };
|
||||
const unsigned ip_abi_version = IP_ABI_VERSION;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user