This commit is contained in:
2026-03-29 14:01:52 +03:00
commit 0611279128
210 changed files with 60454 additions and 0 deletions

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

File diff suppressed because it is too large Load Diff

883
Doc/ttman.c Normal file
View 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
View 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
View File

@@ -0,0 +1,102 @@
# cmus — C\* Music Player
https://cmus.github.io/
[![Build Status](https://github.com/cmus/cmus/actions/workflows/build.yml/badge.svg)](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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

79
command_mode.h Normal file
View 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
View 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
View 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
View 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
View 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, &param);
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
View 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
View 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

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

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

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

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

1054
expr.c Normal file

File diff suppressed because it is too large Load Diff

91
expr.h Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

1294
id3.c Normal file

File diff suppressed because it is too large Load Diff

81
id3.h Normal file
View 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

1067
input.c Normal file

File diff suppressed because it is too large Load Diff

86
input.h Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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