MP3Diags-1.2.02/0000755000175000001440000000000012477147652012166 5ustar ciobiusersMP3Diags-1.2.02/license.boost.txt0000644000175000001440000000247211203001725015453 0ustar ciobiusersBoost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. MP3Diags-1.2.02/Install.sh0000755000175000001440000000061712266761466014140 0ustar ciobiusers#!/bin/bash # # Tested on several systems only # ttt1 Quite likely this needs changes to work with other distros and / or versions # # by passing the param "QMAKE_CXX=clang" the project will be compiled with clang MP3_DIAGS_STATIC="" . ./Build.sh transl=/usr/local/share/mp3diags"$BranchDash"/translations sudo cp bin/$MP3DiagsExe /usr/local/bin sudo mkdir -p "$transl" sudo cp bin/*.qm "$transl" MP3Diags-1.2.02/MP3DiagsCLI.cmd0000644000175000001440000000031012477147652014544 0ustar ciobiusers@echo off rem this file is overwritten by the NSIS script, using the full dir for the exe MP3DiagsWindows.exe %* > %TEMP%\Mp3DiagsOut.txt type %TEMP%\Mp3DiagsOut.txt del %TEMP%\Mp3DiagsOut.txt MP3Diags-1.2.02/pad_file.xml0000644000175000001440000001417712477147652014465 0ustar ciobiusers 3.10 Online PAD Generator 1.33 - http://www.padbuilder.com Portable Application Description, or PAD for short, is a data set that is used by shareware authors to disseminate information to anyone interested in their software products. To find out more go to http://www.asp-shareware.org/pad Marian Ciobanu Iasi Romania http://sourceforge.net/users/ciobi07 Marian Ciobanu ciobi@inbox.com Marian Ciobanu ciobi@inbox.com ciobi@inbox.com ciobi@inbox.com MP3 Diags 1.2.02 03 09 2015 Freeware Minor Update Install and Uninstall Linux,Win7 x32,Win7 x64,WinVista,WinXP English Audio Audio & Multimedia::Other N MP3, diagnosis, tag editor, tagger, normalize, repair, VBR Find and fix Problems in MP3 Files Find and fix Problems in MP3 Files Find and fix Problems in MP3 Files Find and fix Problems in MP3 Files MP3 Diags finds problems in MP3 files and helps the user to fix many of them. It looks at both the audio part (VBR info, quality, normalization) and the tags containing track information (ID3.) It has a tag editor, which can download album information (including cover art) from MusicBrainz and Discogs, as well as paste data from the clipboard. Track information can also be extracted from a file's name. Another component is the file renamer, which can rename files based on the fields in their ID3V2 tag (artist, track number, album, genre, ...) http://mp3diags.sourceforge.net/ http://mp3diags.sourceforge.net/010_getting_the_program.html http://mp3diags.sourceforge.net/004_screenshot01.html http://mp3diags.sourceforge.net/icon.png http://mp3diags.sourceforge.net/pad_file.xml http://sourceforge.net/projects/mp3diags/files/mp3diags-windows-setup/MP3DiagsSetup.exe http://sourceforge.net/projects/mp3diags/files/mp3diags-src/MP3Diags.tar.gz MP3 Diags can be freely distributed under the terms of the GNU General Public License Version 2 - http://www.gnu.org/licenses/gpl-2.0.html MP3Diags-1.2.02/MakeTranslations.sh0000755000175000001440000000066312012264212015762 0ustar ciobiusers#!/bin/bash # if [[ "$#" != "0" ]]; then echo "Usage: `basename $0`" echo "" exit fi LRelease=lrelease if [ -f /etc/fedora-release ] ; then LRelease=lrelease-qt4 fi #lupdate mp3diags.pro #lrelease src/src.pro $LRelease src/translations/mp3diags_*.ts if [[ "$?" != "0" ]] ; then echo -e "\nThere was an error trying to build the translations. This shouldn't impact other parts of the build process.\n" >&2 fi MP3Diags-1.2.02/license.gplv2.txt0000644000175000001440000004235211107241710015363 0ustar ciobiusersGNU 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. 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 an idea of what it does. Copyright (C) yyyy 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. MP3Diags-1.2.02/BuildBz2.sh0000755000175000001440000000126212266762452014140 0ustar ciobiusers#!/bin/bash # # Builds MP3 Diags linking Boost libraries statically, if possible, for creation of "generic" binaries # # Tested on several systems only # ttt1 Quite likely this needs changes to work with other distros and / or versions # # by passing the param "QMAKE_CXX=clang" the project will be compiled with clang if [ -d bin ] ; then rm bin/* fi MP3_DIAGS_STATIC=STATIC_SER . ./Build.sh Cpu=`uname -m` NewName=$MP3DiagsExe-Linux-$Cpu-1.2.02 mv bin/$MP3DiagsExe bin/$NewName # force gnu format because some not very old tools have trouble understanding the newer pax format tar -H gnu -c bin | bzip2 > $NewName.tar.bz2 mv $NewName.tar.bz2 bin mv bin/$NewName bin/$MP3DiagsExe MP3Diags-1.2.02/README.TXT0000644000175000001440000000126611261156642013516 0ustar ciobiusersIn most cases it's easier to use pre-built binaries, which are available for Windows and for several major Linux distributions from the main download page: http://mp3diags.sourceforge.net/010_getting_the_program.html Build instructions for Windows are at: http://mp3diags.sourceforge.net/010_getting_the_program.html#sourceWindows Build instructions for Linux / others are at: http://mp3diags.sourceforge.net/010_getting_the_program.html#sourceGeneric Basically, you should run BuildMp3Diags.hta on Windows and Install.sh elsewhere. Note that even if there is a CMakeLists.txt, CMake isn't officially supported. If it works for you - fine. If it doesn't - you're on your own. MP3Diags-1.2.02/CMakeLists.txt0000644000175000001440000000550711257151552014722 0ustar ciobiuserscmake_minimum_required(VERSION 2.6.0) # Avoid to include the CMakeLists.txt files themselves to the project to get rid # of the custom build step in Visual Studio that regenerates the project. set(CMAKE_SUPPRESS_REGENERATION TRUE) # Use relative instead of absolute paths in generated project files. set(CMAKE_USE_RELATIVE_PATHS TRUE) # Do not use whitespaces in the project name. set(name "MP3Diags") project(${name}) # Search for Qt in QTDIR first, then in C:\Qt\** (on Windows only). list(APPEND qt_glob_paths $ENV{QTDIR}) if(WIN32) file(GLOB dirs "C:/Qt/*") foreach(dir ${dirs}) if(IS_DIRECTORY ${dir}) list(APPEND qt_glob_paths ${dir}) endif() endforeach() # Search paths with higher version numbers first. list(SORT qt_glob_paths) list(REVERSE qt_glob_paths) endif() set(QT_SEARCH_PATHS ${qt_glob_paths} CACHE PATH "Paths where the Qt library is searched in.") set(CMAKE_PREFIX_PATH ${QT_SEARCH_PATHS}) # Try to find Qt, which on success provides QT_USE_FILE, which in turn provides # QT_LIBRARIES. find_package(Qt4 REQUIRED) file(GLOB_RECURSE sources "src/*.cpp") file(GLOB_RECURSE sources_h "src/*.h") file(GLOB_RECURSE sources_ui "src/*.ui") file(GLOB_RECURSE sources_qrc "src/*.qrc") # For some more info see e.g. http://qtnode.net/wiki/Qt_with_cmake qt4_wrap_cpp(sources_moc ${sources_h}) qt4_wrap_ui(sources_uic ${sources_ui}) qt4_add_resources(source_rcc ${sources_qrc}) # Enable the required Qt libraries. set(QT_USE_QTNETWORK 1) set(QT_USE_QTXML 1) include(${QT_USE_FILE}) # CMake places the ui_*.h files here. include_directories(${CMAKE_BINARY_DIR}) # Try to find Boost, probably requires BOOST_ROOT to be set. set(BOOST_SEARCH_PATHS "$ENV{BOOST_ROOT}" CACHE PATH "Paths where the Boost library is searched in.") set(ENV{BOOST_ROOT} ${BOOST_SEARCH_PATHS}) find_package(Boost REQUIRED) include_directories(${Boost_INCLUDE_DIRS}) link_directories(${Boost_LIBRARY_DIRS}) add_executable(${name} ${sources} ${sources_h} ${sources_moc} ${sources_uic} ${sources_qrc} ${source_rcc}) target_link_libraries(${name} ${QT_LIBRARIES} ${Boost_LIBRARIES}) if(WIN32) # We want to use the Unicode Windows API, dynamically link against Boost and suppress deprecation / security warnings. add_definitions(-D_UNICODE -DUNICODE -DBOOST_ALL_DYN_LINK -D_CRT_SECURE_NO_WARNINGS -D_SCL_SECURE_NO_WARNINGS) # Do not treat wchar_t as a built-in type (for compatibility with Qt). set_target_properties(${name} PROPERTIES COMPILE_FLAGS "/Zc:wchar_t-") # For "GetProcessMemoryInfo()". target_link_libraries(${name} psapi.lib) endif() # Hide a few variables from the CMake GUI. set(CMAKE_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX} CACHE INTERNAL "") set(QT_QMAKE_EXECUTABLE ${QT_QMAKE_EXECUTABLE} CACHE INTERNAL "") set(Boost_LIB_DIAGNOSTIC_DEFINITIONS ${Boost_LIB_DIAGNOSTIC_DEFINITIONS} CACHE INTERNAL "") MP3Diags-1.2.02/changelog.txt0000644000175000001440000005277212477145520014663 0ustar ciobiusers--------------------------------------------------------------------------- 08.03.2015 - 1.2.02 - integrated changes from 1.3.01: - fixed incorrect message occurring some times when start after a crash - better logging and retries for write errors - added offset to the output created via the command line - fixed crash caused by saving very small images - build fix - disabled Discogs integration --------------------------------------------------------------------------- 17.01.2014 - 1.2.01 - 1.2 as the new stable branch - build fix for clang and for Solaris - fixed broken links in documentation and moved alternate downloads from Dropbox to my ISP --------------------------------------------------------------------------- 31.03.2013 - 1.2.00 - new version number to support the new "stable" branch --------------------------------------------------------------------------- 01.12.2012 - 1.1.21.080 - fixed bug that caused a "%s" to be shown instead of the list of files about to be changed --------------------------------------------------------------------------- 20.10.2012 - 1.1.20.077 - Discogs fix --------------------------------------------------------------------------- 29.04.2012 - 1.1.19.075 - French translation --------------------------------------------------------------------------- 06.04.2012 - 1.1.18.074 - German translation - made code compilable on GCC 4.7 - several translation fixes --------------------------------------------------------------------------- 17.03.2012 - 1.1.17.073 - fixed crash introduced in 1.1.16 with translation changes --------------------------------------------------------------------------- 11.03.2012 - 1.1.16.072 - made program translatable - added Czech translation --------------------------------------------------------------------------- 01.02.2012 - 1.1.12.068 - external tools - possibility of applying transformation lists in CLI mode - "Open containing folder" in the main window - delete files from the main window - close buttons for Gnome 3 - APE remover - non-audio remover - CLI analyzer uses session settings to determine quality thresholds - fixed menu tooltips that disappeared too quickly --------------------------------------------------------------------------- 03.12.2011 - 1.1.09.064 - moved to Discogs API V2 - added codepage support when copying ID3V1 to ID3V2 - fixed crash caused by ID3V2.4 tag with invalid flags - fixed splitting of unsupported ID3V2 tags - fixed Xing generation for small bitrates --------------------------------------------------------------------------- 28.08.2011 - 1.1.08.062 - made Discogs queries work again, after a Discogs API change - fixed track numbering when querying multi-volume albums from Discogs - made Linux shell integration accept file names containing spaces - added close button for dialogs on Gnome 3 --------------------------------------------------------------------------- 30.07.2011 - 1.1.07.061 - shell integration for Linux - replaced MP3Diags with MP3Diags-unstable in places where this wasn't done - restructured the .spec file - added close button on Gnome 3 --------------------------------------------------------------------------- 22.07.2011 - 1.1.06.059 - shell integration fixes --------------------------------------------------------------------------- 21.07.2011 - 1.1.05.058 - shell integration (Windows only) --------------------------------------------------------------------------- 17.07.2011 - 1.1.04.057 - folder-based sessions based on command-line parameters --------------------------------------------------------------------------- 16.06.2011 - 1.1.03.056 - integrated command-line mode by Michael Elsdörfer --------------------------------------------------------------------------- 30.05.2011 - 1.1.01.054 - created unstable branch --------------------------------------------------------------------------- 28.05.2011 - 1.0.08.053 - fixed crash when trying to save after error at startup - added branch support to build process --------------------------------------------------------------------------- 29.12.2010 - 1.0.07.052 - fixed crash in folder filter dialog - made project compilable on Fedora 13 and 14 --------------------------------------------------------------------------- 04.07.2010 - 1.0.06.051 - fixed crash caused by invalid Lyrics tag - fixed assertion failure when opening the folder filter in Windows --------------------------------------------------------------------------- 17.03.2010 - 1.0.05.050 - fixed issue #23 (assertion failure) --------------------------------------------------------------------------- 07.03.2010 - 1.0.04.049 - fixed crash triggered by invalid frame lengths in ID3V2 - fixed crash that occured when the backup directory couldn't be created --------------------------------------------------------------------------- 29.01.2010 - 1.0.03.048 - fixed crash triggered by GEOB frames containing UTF-16 text --------------------------------------------------------------------------- 12.01.2010 - 1.0.02.047 - fixed crash triggered by trying to repair broken ID3V2 tags that contain images - fixed crash that occurred when changing file names manually in the file renamer, without defining a pattern - fixed crash caused by unsynchronized ID3V2.4.0 frames that end with 0xff --------------------------------------------------------------------------- 11.12.2009 - 1.0.01.046 - fixed crash that occurred when running on Qt 4.6 - fixed crash that occurred when changing song info in the tag editor if songs had images stored as BMP or anything else besides JPG or PNG --------------------------------------------------------------------------- 29.11.2009 - 1.0.00.045 - wording changes to reflect non-beta status - pressing CTRL+C when viewing full-size images in the tag editor or in "Tag details" in the main window copies the image to the clipboard - added "Rating" and changed field order in "Tag details" to match the Tag editor --------------------------------------------------------------------------- 04.11.2009 - 0.99.06.044 - fixed a crash in folder filter - fixed bug causing non-normalized files having any TXXX frame to appear normalized - case is ignored for file extension, so .Mp3 or .mP3 files are recognized - better support and more consistent handling for TXXX and text frames in ID3V2 - reduced number of locales by eliminating redundant ones - disabled CTRL+A selection in the main window - static link for serialization - added trace details for web downloads --------------------------------------------------------------------------- 27.10.2009 - 0.99.06.043 - "Simple view" in file configuration - fixed crash on empty text frames in ID3V2 - generic binaries for Linux - BuildMp3Diags.hta detects VS version - documentation updates --------------------------------------------------------------------------- 20.10.2009 - 0.99.06.042 - better support for binary frames in ID2V2.4.0 - fixed bug resulting in crash when files were modified in external tools - fixed bug resulting in crash when renaming files if a filter is applied - fixed bug resulting in crash when going to "Tag details" for files using Unicode in USLT (issue 40) - added option to include styles in Discogs info - added case-change option to the tag editor - better detection and notification for changed files before applying transformations or saving from the tag editor - file renamer allows file names to be changed manually - file renamer can use "duplicate" label for unrated songs - images shown for Lyrics tags - improved case-change transformation - removing images from the tag editor now works even for non-cover images --------------------------------------------------------------------------- 09.10.2009 - 0.99.06.041 - brought documentation up to date - added test for ReplayGain info stored inside Id3V2 - failing to read text frames from files no longer causes crashes - fixed bug resulting in crash when 2 ID3V2 tags are present and "Discard invalid ID3V2 data" gets called - XML export now works when names contain double quotes - locale in export dialog - locale lists are now sorted - long text frames are now truncated when shown in the "File info" area - changed names and order for tabs under Config/Files - made transformation options work correctly in MSVC --------------------------------------------------------------------------- 30.09.2009 - 0.99.06.040 - "Various Artists" support - all pictures are shown and can be viewed in full size in "Tag details" - all pictures from a file are shown in the tag editor - patterns in the tag editor may now be disabled - export as M3U or XML - better handling of text frames containing null characters - auto-size for the tag editor's "current file" area - better column widths in the tag editor - improved HTA for Windows build --------------------------------------------------------------------------- 23.09.2009 - 0.99.05.038 - fixed bug introduced in 0.99.05.037 causing crashes when finding empty ID3V2 frames - new build process for Windows - 4th custom transf list now defaults to a "fix-all" approach --------------------------------------------------------------------------- 17.09.2009 - 0.99.05.037 - fixes on right-click - UTF-8 strings recognized in ID3V2.3.0 - fixed bug 35 (assertion failure) - faster tracer - code compilable with VS 2008 (port by Sebastian Schuberth) --------------------------------------------------------------------------- 07.09.2009 - 0.99.05.034 - drive labels shown in Windows - mp3gain can be started now if it's in a directory containing spaces - configurable invalid characters and replacement for file renamer - automatic check for new versions - text inside square, curly, and angle brackets removed from web queries - improved tracing code - no longer rescan the files if exiting tag editor without changes - replace non-alphanumeric chars with spaces in web queries (issue 2) - better sorting in the tag editor for albums with unusual track numbers - a default .ini name is generated in most cases - (probably) fixed an assert (not sure because couldn't reproduce it) - tracks without a track number are put at the end in the tag editor - let the user know about reporting support notes and about patterns - better detection of exceptions in threads - exceptions that propagate from slots are now caught - default "actions to be taken" no longer shown when applying transforms - file info for StreamWriter --------------------------------------------------------------------------- 02.09.2009 - 0.99.05.033 - fixed crash in Windows when checking a whole drive - fixed crash when saving data from the tag editor - improved trace speed on Windows - fixed potential crash at startup - fixed crash when changing a file that is being used by other program - more details and better formatting in assert messages and trace files - MPEG2 Layer3 streams no longer show Support note - improved temporary file generation, which can result in faster transforms - F1 help now works for the first session dialog - smaller TABs in the "Tag details" area make "Other info" more readable - better HTML paragraph formatting --------------------------------------------------------------------------- 23.08.2009 - 0.99.05.032 - new Qt (4.5.2) and MinGW (3.4.5) for the Windows version - restructured crash detector - fixed Windows issue with rectangles being shown instead of letters - Lyrics partial support - warning that may corrupt data - note about how to change selected files - HTML clean up --------------------------------------------------------------------------- 18.08.2009 - 0.99.05.031 - crash detection - fixed small memory leak in config dialog - fixed small memory leak in the tag editor - content is shown for GEOB frames - .ID3 files are now loaded in addition to .MP3 - made the counter shown when applying transforms increment on new file (until now it was incremented for each transform) - "Various artists" no longer set as "artist" when downloading track info from MusicBrainz - fixed "current cell" in the tag editor (until now, when dragging the mouse to select several cells, the current cell was wrong, leading to setting values incorrectly - fixed a bug that didn't allow removal of the track number - slightly improved the normalizer, so a "busy" cursor is shown when the connection to the underlying process is lost (the program seems frozen, but it resumes after about 30 seconds) --------------------------------------------------------------------------- 28.07.2009 - 0.99.05.030 - fixed a bug that caused the tag editor to reserve more space than needed even if the "fast save" option was turned off - fixed a bug that prevented removal of elements from lists - fixed a bug in the ID3V2 tag writer that prevented "Discard invalid ID3V2 data" and other transformations to properly work with ID3V2.4.0 tags that contain UTF8-encoded strings, resulting in a broken ID3V2.3.0 tag - file renamer now accepts patterns with no directory separators, in which case the renamed files are placed in the source directory - pattern dialogs now show the current line and column - fixed an assertion in the code that determines the file list - added tooltips for all the notes in the main file table - improved speed for "Discard invalid ID3V2 data" when no changes are done - transformation name included in the dialog that shows which file is currently processed - broken ID3V2 streams get removed when saving from the tag editor - the tag editor no longer triggers an assertion failure if non-default settings in the file section of the configuration dialog; (e.g. until now saving from the tag editor while original files weren't deleted resulted in this assertion failure) - fixed a bug in the "Change case for ID3V2 text frames" that resulted in a program crash if some fields were missing from the ID3V2 tag --------------------------------------------------------------------------- 26.07.2009 - 0.99.05.029 - improved paste in tag editor (it is possible to paste to multiple cells or to paste file names copied from file browsers) - configurable max image size - button to remove image files - fixed loading images from current dir - fixed a bug in tag editor patterns that prevented patterns ending with a static text from working - Windows only: fixed pasting image file that was copied from Windows Explorer --------------------------------------------------------------------------- 23.07.2009 - 0.99.05.028 - fast save in the tag editor --------------------------------------------------------------------------- 21.07.2009 - 0.99.04.027 - file renamer can work with the list of visible files instead of the current album if the user holds CTRL down when pressing the button - new transform for keeping a single image, as front cover - new transform for removing ID3V1 - configurable visible transformations - tooltips for the transformations menu - use of backslash for path separator on Windows - fixed some bugs in the directory filter - made F1 work on Windows - fixed normalization status on Windows - directory filter no longer shows some directories that don't make sense - more checks + fixed error reporting in file renamer - "sessions" button visible by default for second and later sessions --------------------------------------------------------------------------- 17.07.2009 - 0.99.04.026 - size grip on most dialogs - F1 help --------------------------------------------------------------------------- 15.07.2009 - 0.99.03.025 - restructured Unicode stream classes - Lyrics inside ID3V2 are now shown --------------------------------------------------------------------------- 15.07.2009 - 0.99.03.024 - switched to fstream_utf8 --------------------------------------------------------------------------- 13.07.2009 - 0.99.03.023 - tag editor now looks at filter - files changed in the tag editor no longer show up in the main window if a filter is applied and they don't match the filter - better alignment for text in note column header on Ubuntu - replaced C / POSIX calls with Qt calls --------------------------------------------------------------------------- 09.07.2009 - 0.99.03.022 - configurable colors --------------------------------------------------------------------------- 09.07.2009 - 0.99.03.021 - 2-letter labels - gradient grouping of notes - more consistent font handling - detecting files changed by external tools - tooltips are shown for the column headers in the file table --------------------------------------------------------------------------- 05.07.2009 - 0.99.02.020 - added sysinfo to "about" dialog - more details in sysinfo - made file renamer work on Windows - file renamer now replaces invalid characters in file names - made logger work on Windows 7 (and probably Vista) --------------------------------------------------------------------------- 29.06.2009 - 0.99.02.019 - downloaded images are no longer lost when saving in the tag editor - duration is now shown for audio streams - fixed assertion caused by files with too many streams - assertion instructs reports to be made on forum instead of email - number of files that might get changed is shown before processing multiple files - assert message includes more info about the OS - multiple ID3 stream remover no longer included by default in second list - some changes to .spec file in the hope it will work on Mandriva --------------------------------------------------------------------------- 24.06.2009 - 0.99.02.018 - made -mt suffix default for Boost Serialization --------------------------------------------------------------------------- 24.06.2009 - 0.99.02.017 - always use multithreaded libraries - improved assert dialog; now it has more data, which can be copied and even emailed directly - the tag editor shows a warning in some cases when a user action would result in discarded images --------------------------------------------------------------------------- 21.06.2009 - 0.99.02.016 - fixed an assertion failure that was triggered by an unsupported text encoding in APIC - added support for UTF8 text encoding in APIC - made the documentation look slightly better on IE6 --------------------------------------------------------------------------- 20.06.2009 - 0.99.02.015 - made compilable on Fedora 11 and added Fedora 11 build - copying missing ID3V1 fields to ID3V2 no longer part of the default custom transformation list 2 --------------------------------------------------------------------------- 18.06.2009 - 0.99.02.014 - the tag editor loads albums much faster than before; this is most visible when navigating to the next / previous album --------------------------------------------------------------------------- 16.06.2009 - 0.99.02.013 - Windows-only: new Boost Serialization library, compiled with multithreading support - Windows-only: new install script, so now the setup has a more modern look and allows the user to run the program when the install completes --------------------------------------------------------------------------- 14.06.2009 - 0.99.02.012 - added support for UTF8 in ID3V240 - added maximize button to most windows in Windows (but could not get this to work with Gnome) - removed "What's this" button from most windows - now the main window shows the session name if more than 1 session was defined - now the main window shows up maximized in Gnome and Windows if it was maximized when it was closed - changed documentation links to point to new site, at SourceForge - minor documentation improvements --------------------------------------------------------------------------- 06.06.2009 - 0.99.02.011 - added dependency to guarantee that SVG icons can be displayed on non-KDE desktops - fixed an assertion in "Sessions / Save as" - doc comment changes to prepare for moving to SourceForge --------------------------------------------------------------------------- 04.06.2009 - 0.99.02.010 - fixed a bug that could result in removal of audio data when using the "Remove inner non-audio" transformation - first Windows version - fixed incorrect handling of the option to keep a single image in an ID3V2 tag --------------------------------------------------------------------------- 03.06.2009 - 0.99.01.009 - added scripts for Ubuntu builds - made compilable on Windows - added some Windows-specific icons, to replace those that are displayed incorrectly - improved ColumnResizer - switched text in SVGs to "path", to avoid issues related to missing fonts - fixed Config / Files so it's no longer possible to only to uncheck all radio buttons - better handling of light background colors in the Notes table - fixed a bug that could result in erased files when the disk becomes full - change header / footer layout in the documentation --------------------------------------------------------------------------- 25.05.2009 - 0.99.0.008 - increased cell width for notes, to look OK with more fonts - added an #include so gcc 4.4 can compile the project - now using black instead of "selection" color for notes if the contrast with the background is too low - improved the workaround that is needed for multiline text, so now it can handle all fonts (previously a message used to show up telling the user to change the font) - added horizontal scrollbar to directory trees - install.sh now exits errors --------------------------------------------------------------------------- 22.05.2009 - 0.99.0.007 - first public version; most of the 1.0 features implemented and functional (what's left is an option to write ID3V2 tags "in place", to be used if the tag editor seems too slow; this may lead to data loss in cases of program / computer crash)MP3Diags-1.2.02/CMake-VS2008-Win32.cmd0000644000175000001440000000040111256451406015353 0ustar ciobiusers@echo off : (Re-)create an empty output directory. rmdir /s /q VS2008-Win32 2> nul mkdir VS2008-Win32 2> nul : Generate the project files in the output directory. pushd VS2008-Win32 cmake -G "Visual Studio 9 2008" .. if errorlevel 1 pause popd MP3Diags-1.2.02/COPYING0000644000175000001440000004313111200232050013165 0ustar ciobiusers GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 Library 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. Copyright (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, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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. , 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 Library General Public License instead of this License. MP3Diags-1.2.02/license.lgplv3.txt0000644000175000001440000001645411107241731015547 0ustar ciobiusersGNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, “this License†refers to version 3 of the GNU Lesser General Public License, and the “GNU GPL†refers to version 3 of the GNU General Public License. “The Library†refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An “Application†is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A “Combined Work†is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the “Linked Versionâ€. The “Minimal Corresponding Source†for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The “Corresponding Application Code†for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: * a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or * b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: * a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. * b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: * a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. * b) Accompany the Combined Work with a copy of the GNU GPL and this license document. * c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. * d) Do one of the following: o 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. o 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. * e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: * a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. * b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License “or any later version†applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library.MP3Diags-1.2.02/license.lgpl-2.1.txt0000644000175000001440000005664411203001247015572 0ustar ciobiusersGNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: * a) The modified work must itself be a software library. * b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. * c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. * d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: * a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) * b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. * c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. * d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. * e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: * a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. * b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. MP3Diags-1.2.02/AdjustMt.sh0000755000175000001440000000237611576206013014252 0ustar ciobiusers#!/bin/bash # # corrects src/src.pro, so it uses the right serialization library # if $1 is STATIC_SER, it tries to do link Boost Ser statically; if it can't, it reverts to dynamic linking #if lib $1 exists changes src/src.pro and terminates the script function tryLib { rm -f -r tstMt mkdir tstMt echo "int main() {}" > tstMt/a.cpp g++ tstMt/a.cpp -l$1 -o tstMt/a.out 2> /dev/null libExists=$? #echo $noMt rm -f -r tstMt if [ $libExists -eq 0 ] ; then cat src/src.pro | sed -e 's%.*boost_serialization[^\]*% -l'$1% -e 's%.*boost_program_options.*% -l'$2% > src/src.pro1 mv -f src/src.pro1 src/src.pro echo Serialization Library set as $1 exit 0 fi echo Serialization Library $1 not found #return $libExists } if [[ "STATIC_SER" == $1 ]] ; then tryLib :libboost_serialization-mt.a :libboost_program_options-mt.a tryLib :libboost_serialization.a :libboost_program_options.a # ttt0 not sure if this should be considered tryLib boost_serialization-mt boost_program_options-mt tryLib boost_serialization boost_program_options else tryLib boost_serialization-mt boost_program_options-mt tryLib boost_serialization boost_program_options fi echo Boost Serialization not found MP3Diags-1.2.02/license.gplv3.txt0000644000175000001440000010360611107241715015371 0ustar ciobiusersGNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright © 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. 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 them 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 prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. 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. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. “This License†refers to version 3 of the GNU General Public License. “Copyright†also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. “The Program†refers to any copyrightable work licensed under this License. Each licensee is addressed as “youâ€. “Licensees†and “recipients†may be individuals or organizations. To “modify†a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a “modified version†of the earlier work or a work “based on†the earlier work. A “covered work†means either the unmodified Program or a work based on the Program. To “propagate†a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To “convey†a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays “Appropriate Legal Notices†to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The “source code†for a work means the preferred form of the work for making modifications to it. “Object code†means any non-source form of a work. A “Standard Interface†means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The “System Libraries†of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A “Major Componentâ€, in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The “Corresponding Source†for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey 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; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: * a) The work must carry prominent notices stating that you modified it, and giving a relevant date. * b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to “keep intact all noticesâ€. * c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. * d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an “aggregate†if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: * a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. * b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. * c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. * d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. * e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A “User Product†is either (1) a “consumer productâ€, which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, “normally used†refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. “Installation Information†for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. “Additional permissions†are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: * a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or * b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or * c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or * d) Limiting the use for publicity purposes of names of licensors or authors of the material; or * e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or * f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered “further restrictions†within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An “entity transaction†is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A “contributor†is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's “contributor versionâ€. A contributor's “essential patent claims†are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, “control†includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a “patent license†is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To “grant†such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. “Knowingly relying†means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is “discriminatory†if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If 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 convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU 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 that a certain numbered version of the GNU General Public License “or any later version†applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. 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. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 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. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. 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 state the exclusion of warranty; and each file should have at least the “copyright†line and a pointer to where the full notice is found. Copyright (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 3 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 . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program 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, your program's commands might be different; for a GUI interface, you would use an “about boxâ€. You should also get your employer (if you work as a programmer) or school, if any, to sign a “copyright disclaimer†for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU 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. But first, please read .MP3Diags-1.2.02/Uninstall.sh0000755000175000001440000000121311727155373014467 0ustar ciobiusers#!/bin/bash # #ttt1 only works for one user, but it's likely that this is the case when it's most needed BranchSlash=`cat branch.txt` BranchDash=`echo "$BranchSlash" | sed 's#/#-#'` exe=MP3Diags$BranchDash /usr/local/bin/$exe -u sudo rm /usr/local/bin/$exe rm ~/.config/Ciobi/$exe.conf #transl=/usr/local/share/mp3diags"$BranchDash"/translations share=/usr/local/share/mp3diags"$BranchDash" sudo rm -rf "$share" echo echo "If other users started MP3 Diags as well, they will have to remove their configuration file manually (it\'s ~/.config/Ciobi/Mp3Diags.conf)" echo "Sessions files (.ini and associated .dat) can only be removed manually" echo MP3Diags-1.2.02/branch.txt0000644000175000001440000000000011570452726014144 0ustar ciobiusersMP3Diags-1.2.02/mp3diags.pro0000644000175000001440000000025311713511277014405 0ustar ciobiusersSUBDIRS += src TEMPLATE = subdirs CONFIG += warn_on \ qt \ thread #TRANSLATIONS = src/translations/MP3Diags_cs.ts #RESOURCES += Mp3Diags-root.qrc MP3Diags-1.2.02/src/0000755000175000001440000000000012477147651012754 5ustar ciobiusersMP3Diags-1.2.02/src/TagWriter.h0000644000175000001440000005613111720137552015030 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef TagWriterH #define TagWriterH #include #include #include #include "DataStream.h" #include "CommonTypes.h" class QToolButton; class TagWriter; class ImageInfoPanelWdgImpl; // wrapper for the 3-state "Assign" button in the tag editor struct AssgnBtnWrp { enum State { ALL_ASSGN, SOME_ASSGN, NONE_ASSGN }; void setState(State eState); // sets m_eState and changes the button icon accordingly State getState() const { return m_eState; } AssgnBtnWrp(QToolButton* pButton) : m_pButton(pButton) { setState(NONE_ASSGN); } private: State m_eState; QToolButton* m_pButton; }; struct TagWrtImageInfo { ImageInfo m_imageInfo; std::set m_sstrFiles; bool operator==(const ImageInfo& imgInf) const { return m_imageInfo == imgInf; } TagWrtImageInfo(const ImageInfo& imageInfo, const std::string& strFile) : m_imageInfo(imageInfo) { if (!strFile.empty()) { m_sstrFiles.insert(strFile); } } }; class ImageColl { std::vector m_vTagWrtImageInfo; std::vector m_vpWidgets; int m_nCurrent; public: ImageColl(); int addImage(const ImageInfo& img, const std::string& strFile = ""); // returns the index of the image; if it already exists it's not added again; if it's invalid returns -1 void addWidget(ImageInfoPanelWdgImpl*); // first addImage gets called by TagWriter and after it's done it tells MainFormDlgImpl to create widgets, which calls this; void clear(); // clears both m_vTagWrtImageInfo and m_vpWidgets void select(int n); // -1 deselects all const TagWrtImageInfo& operator[](int n) const { return m_vTagWrtImageInfo.at(n); } int find(const ImageInfo& img) const; int size() const { return (int)m_vTagWrtImageInfo.size(); } const TagWrtImageInfo& back() const { return m_vTagWrtImageInfo.at(size() - 1); } }; namespace SongInfoParser { class TrackTextParser; } // There's one of these for each pair of in the current album (or rather , because patterns are not seen directly). They merely "use" the corresponding TrackTextParser to set up the data in the constructor, so the hierarchy of Reader objects is kept only once, in TagWriter. A TrackTextReader extracts track information from a string, which is either a file name or a single line from a multi-line string pasted from the clipboard. class TrackTextReader : public TagReader { std::string m_strTitle; std::string m_strArtist; std::string m_strTrackNumber; TagTimestamp m_timeStamp; std::string m_strGenre; std::string m_strAlbumName; double m_dRating; std::string m_strComposer; bool m_bHasTitle; bool m_bHasArtist; bool m_bHasTrackNumber; bool m_bHasTimeStamp; bool m_bHasGenre; bool m_bHasAlbumName; bool m_bHasRating; bool m_bHasComposer; const char* m_szType; public: /*override*/ std::string getTitle(bool* pbFrameExists = 0) const { if (0 != pbFrameExists) { *pbFrameExists = m_bHasTitle; } return m_strTitle; } /*override*/ std::string getArtist(bool* pbFrameExists = 0) const { if (0 != pbFrameExists) { *pbFrameExists = m_bHasArtist; } return m_strArtist; } /*override*/ std::string getTrackNumber(bool* pbFrameExists = 0) const { if (0 != pbFrameExists) { *pbFrameExists = m_bHasTrackNumber; } return m_strTrackNumber; } /*override*/ TagTimestamp getTime(bool* pbFrameExists = 0) const { if (0 != pbFrameExists) { *pbFrameExists = m_bHasTimeStamp; } return m_timeStamp; } /*override*/ std::string getGenre(bool* pbFrameExists = 0) const { if (0 != pbFrameExists) { *pbFrameExists = m_bHasGenre; } return m_strGenre; } /*override*/ ImageInfo getImage(bool* /*pbFrameExists*/ = 0) const { throw NotSupportedOp(); } /*override*/ std::string getAlbumName(bool* pbFrameExists = 0) const { if (0 != pbFrameExists) { *pbFrameExists = m_bHasAlbumName; } return m_strAlbumName; } /*override*/ double getRating(bool* pbFrameExists = 0) const { if (0 != pbFrameExists) { *pbFrameExists = m_bHasRating; } return m_dRating; } /*override*/ std::string getComposer(bool* pbFrameExists = 0) const { if (0 != pbFrameExists) { *pbFrameExists = m_bHasComposer; } return m_strComposer; } /*override*/ SuportLevel getSupport(Feature) const; DECL_RD_NAME(QT_TRANSLATE_NOOP("TagReader", "Pattern")) const char* getType() const { return m_szType; } TrackTextReader(SongInfoParser::TrackTextParser* pTrackTextParser, const std::string& s); /*override*/ ~TrackTextReader(); }; // information downloaded from sites and passed in AlbumInfo class WebReader : public TagReader { std::string m_strTitle; std::string m_strArtist; std::string m_strTrackNumber; TagTimestamp m_timeStamp; std::string m_strGenre; ImageInfo m_imageInfo; std::string m_strAlbumName; double m_dRating; std::string m_strComposer; AlbumInfo::VarArtists m_eVarArtists; //bool m_bSuppTitle; //bool m_bSuppArtist; //bool m_bSuppTrackNumber; //bool m_bSuppTimeStamp; bool m_bSuppGenre; //bool m_bSuppAlbumName; //bool m_bSuppRating; bool m_bSuppComposer; bool m_bSuppVarArtists; std::string m_strType; int convVarArtists() const; // converts m_eVarArtists to an int is either 0 or contains all VA-enabled values, based on configuration public: /*override*/ std::string getTitle(bool* pbFrameExists = 0) const { if (0 != pbFrameExists) { *pbFrameExists = !m_strTitle.empty(); } return m_strTitle; } /*override*/ std::string getArtist(bool* pbFrameExists = 0) const { if (0 != pbFrameExists) { *pbFrameExists = !m_strArtist.empty(); } return m_strArtist; } /*override*/ std::string getTrackNumber(bool* pbFrameExists = 0) const { if (0 != pbFrameExists) { *pbFrameExists = !m_strTrackNumber.empty(); } return m_strTrackNumber; } /*override*/ TagTimestamp getTime(bool* pbFrameExists = 0) const { if (0 != pbFrameExists) { *pbFrameExists = 0 != *m_timeStamp.asString(); } return m_timeStamp; } /*override*/ std::string getGenre(bool* pbFrameExists = 0) const { if (0 != pbFrameExists) { *pbFrameExists = !m_strGenre.empty(); } return m_strGenre; } /*override*/ ImageInfo getImage(bool* pbFrameExists = 0) const { if (0 != pbFrameExists) { *pbFrameExists = !m_imageInfo.isNull(); } return m_imageInfo; } /*override*/ std::string getAlbumName(bool* pbFrameExists = 0) const { if (0 != pbFrameExists) { *pbFrameExists = !m_strAlbumName.empty(); } return m_strAlbumName; } /*override*/ double getRating(bool* pbFrameExists = 0) const { if (0 != pbFrameExists) { *pbFrameExists = m_dRating >= 0; } return m_dRating; } /*override*/ std::string getComposer(bool* pbFrameExists = 0) const { if (0 != pbFrameExists) { *pbFrameExists = !m_strComposer.empty(); } return m_strComposer; } /*override*/ int getVariousArtists(bool* pbFrameExists = 0) const { if (0 != pbFrameExists) { *pbFrameExists = AlbumInfo::VA_NOT_SUPP != m_eVarArtists; } return convVarArtists(); } /*override*/ SuportLevel getSupport(Feature) const; DECL_RD_NAME(QT_TRANSLATE_NOOP("TagReader", "Web")) const std::string& getType() const { return m_strType; } WebReader(const AlbumInfo& albumInfo, int nTrackNo); // nTrackNo is 0-based, just an index in albumInfo.m_vTracks ~WebReader(); }; class Mp3Handler; class CommonData; // tag data for a file/Mp3Handler // fields are in the order given by TagReader::Feature, and not FEATURE_ON_POS, so UI components must use FEATURE_ON_POS themselves to determine the "row" they are passing class Mp3HandlerTagData { Mp3HandlerTagData(const Mp3HandlerTagData&); Mp3HandlerTagData& operator=(const Mp3HandlerTagData&); public: Mp3HandlerTagData(TagWriter* pTagWriter, const Mp3Handler* pMp3Handler, int nCrtPos, int nOrigPos, const std::string& strPastedVal); ~Mp3HandlerTagData(); enum Status { EMPTY, ID3V2_VAL, NON_ID3V2_VAL, ASSIGNED }; std::string getData(int nField) const { return m_vValueInfo[nField].m_strValue; } int getImage() const; // 0-based; -1 if there-s no image; double getRating() const; Status getStatus(int nField) const { return m_vValueInfo[nField].m_eStatus; } void setData(int nField, const std::string& s); // may throw InvalidValue void setStatus(int nField, Status); std::string getData(int nField, int k) const; // returns the data corresponding to the k-th element in m_pTagWriter->m_vTagReaderInfo; returns "\n" if it doesn't have a corresponding stream (e.g. 2nd ID3V1 tag) or if the given feature is not supported (e.g. picture in ID3V1) // nField is the "internal" row for which data is retrieved, so TagReader::FEATURE_ON_POS[] has to be used by the UI caller const Mp3Handler* getMp3Handler() const { return m_pMp3Handler; } void refreshReaders(); // updates m_vpTagReader to reflect m_pTagWriter->m_vTagReaderInfo, then updates unassigned values void print(std::ostream&) const; int getOrigPos() const { return m_nOrigPos; } const TagReader* getMatchingReader(int i) const; // returns 0 if i is out of range void adjustVarArtists(bool b); // if VARIOUS_ARTISTS is not ASSIGNED, sets m_strValue and m_eStatus struct InvalidValue {}; private: void setUp(); // to be called initially and each time the priority of tag readers changes TagWriter* m_pTagWriter; const Mp3Handler* m_pMp3Handler; int m_nCrtPos; int m_nOrigPos; // m_nOrigSong gives the position in m_pCommonData->m_vpViewHandlers; the current position may change after sorting by track number std::vector m_vpTagReaders; // needed for the "current album" grid; tag readers used to get the data for track, title, ... ; doesn't own the pointers; there are 2 related differences between it and m_vpMatchingTagReaders: 1) m_vpTagReaders doesn't contain null elements; 2) some of the null entries in m_vpMatchingTagReaders are replaced with other entries in m_vpTagReaders /* there is a file that has 2 ID3V1 tags in the current album, so 2 ID3V1 columns appear, along with an ID3V2 and an Ape; the user chooses this order "ID3V1 2", "ID3V2", "Ape", "ID3V1 1" (which internally are seen as "ID3V1 1", "ID3V2 0", "Ape 0", "ID3V1 0", because counting starts at 0) the current file only has an ID3V1 and an ID3V2 m_vpMatchingTagReaders will end up with: , "ID3V2 0", , "ID3V1 0" (matching m_pTagWriter->m_vTagReaderInfo's size of 4) m_vpTagReaders will end up with: "ID3V1 0", "ID3V2 0", "ID3V1 0" (size is 3) see also the big comment in refreshReaders() */ std::vector m_vpMatchingTagReaders; // needed for the "current file" grid; has the same size as m_pTagWriter->m_vTagReaderInfo; some elements are null, if there is no corresponding reader; doesn't own the pointers; std::vector m_vTrackTextReaders; // pointers to TrackTextReader need to be owned, so it's easiest to put the objects in a vector, while also putting the pointers in m_vpTagReaders std::vector m_vWebReaders; // pointers to TrackTextReader need to be owned, so it's easiest to put the objects in a vector, while also putting the pointers in m_vpTagReaders struct ValueInfo { std::string m_strValue; Status m_eStatus; ValueInfo() : m_eStatus(EMPTY) {} }; std::vector m_vValueInfo; // PictureIndex is stored as a string std::string m_strPastedVal; // cleared on reload(), assigned when pasting multi-line content from the clipboard mutable std::vector m_vstrImgCache; }; struct TagReaderInfo { std::string m_strName; int m_nPos; // usually this is 0; has other values if there are more than 1 instance of a TagReader type (e.g. TrackTextReader, Id3V230Stream, ...), to tell them apart bool m_bAlone; // if it's the only TagReaderInfo with its name enum { ONE_OF_MANY, ALONE }; TagReaderInfo(const std::string& strName, int nPos, bool bAlone) : m_strName(strName), m_nPos(nPos), m_bAlone(bAlone) {} bool operator==(const TagReaderInfo& other) const { return m_nPos == other.m_nPos && m_strName == other.m_strName; } }; class TagWriter : public QObject { Q_OBJECT std::vector m_vSortedKnownTagReaders; // used to remember the sort order between albums and/or sessions; after m_vTagReaderInfo is populated, it should be sorted so that it matches the order in m_vSortedKnownTagReaders, if possible; items not found are to be added to the end void sortTagReaders(); // sorts m_vTagReaderInfo so that it matches m_vSortedKnownTagReaders std::vector m_vpTrackTextParsers; // one TrackTextParser for each pattern std::set m_snActivePatterns; std::vector m_vAlbumInfo; // one AlbumInfo for every album data downloaded from structured web sites // "original value" of a selected field; not necessarily related to the fields in a file's tags, but merely holds whatever happened to be in a given field when it gets selected; the "original value" from Mp3HandlerTagData's point of view is not stored anywhere, but it is recovered as needed, because toggleAssigned() calls "reloadAll("", DONT_CLEAR)" struct OrigValue { int m_nSong, m_nField; // nField is an index in TagReader::Feature, not affected by TagReader::FEATURE_ON_POS std::string m_strVal; Mp3HandlerTagData::Status m_eStatus; OrigValue(int nSong, int nField, const std::string& strVal, Mp3HandlerTagData::Status eStatus) : m_nSong(nSong), m_nField(nField), m_strVal(strVal), m_eStatus(eStatus) {} bool operator<(const OrigValue& other) const { if (m_nSong < other.m_nSong) { return true; } if (other.m_nSong < m_nSong) { return false; } return m_nField < other.m_nField; } }; friend std::ostream& operator<<(std::ostream& out, const TagWriter::OrigValue& val); std::set m_sSelOrigVal; // original values for selected fields (so it can also be used to determine which fields are selected) bool m_bSomeSel; bool m_bNonStandardTrackNo; std::vector m_vnMovedTo; // the position on screen corresponding to m_pCommonData->m_vpViewHandlers elements after they are sorted by track number void sortSongs(); // sorts by track number; shows a warning if issues are detected (should be exactly one track number, from 1 to the track count) bool addImgFromFile(const QString& qs, bool bConsiderAssigned); // see also addImage() std::vector m_vstrPastedValues; CommonData* m_pCommonData; QWidget* m_pParentWnd; // for QMessageBox int m_nCurrentFile; ImageColl m_imageColl; bool m_bShowedNonSeqWarn; std::set m_snUnassignedImages; const bool& m_bIsFastSaving; bool m_bShouldShowPatternsNote; int m_nFileToErase; bool m_bVariousArtists; bool m_bAutoVarArtists; // true at first, until the "toggle" button is clicked void adjustVarArtists(); bool m_bDelayedAdjVarArtists; bool m_bWaitingChangeNotif; public: TagWriter(CommonData* pCommonData, QWidget* pParentWnd, const bool& bIsFastSaving, const TextCaseOptions& eArtistCase, const TextCaseOptions& eTitleCase); ~TagWriter(); enum ClearData { DONT_CLEAR_DATA, CLEAR_DATA }; enum ClearAssigned { DONT_CLEAR_ASSGN, CLEAR_ASSGN }; // called in 3 cases: 1) when going to a new album; 2) when changing the order of readers; 3) when adding/changing/removing TrackTextReaders; all require the reader list to be updated (there are 3 kinds of readers: 1) those that the current album uses, which are stored in Mp3Handler; 2) TrackTextReader instances, which are built manually from m_vpTrackTextParsers; and 3) those with data read from the web); // nPos tells which should be the current file; if nPos is <0 it is ignored and the current file remains the same; a value <0 should only be used when changing reader priorities; // if eReloadOption is CLEAR, everyting is reloaded; if it's something else, the call is supposed to be for the same album, after changing tag priorities, so ASSIGNED values shouldn't change; if it's UPDATE_SEL, the selection is changed to the first cell for the current song; if it's DONT_UPDATE_SEL the selection isn't changed // if strCrt is empty and eReloadOption is DONT_CLEAR, the current position is kept; if strCrt is empty and eReloadOption is CLEAR, the current position is first song; //void reloadAll(std::string strCrt, ReloadOption eReloadOption/*, bool bKeepUnassgnImg*/); // bKeepUnassgnImg matters only if eReloadOption is CLEAR void reloadAll(std::string strCrt, bool bClearData, bool bClearAssgn); void setCrt(const std::string& strCrt); // makes current a file with a given name; if name is not found (may also be empty), makes current the first file; doesn't cause the grid selection to change; void setCrt(int nCrt); // asserts nCrt is valid; doesn't cause the grid selection to change; std::vector m_vpMp3HandlerTagData; // one for each file in the current album std::vector m_vTagReaderInfo; // has only readers that correspond to the current album, so it's a subset of m_vSortedKnownTagReaders; (sortTagReaders() adds to m_vSortedKnownTagReaders whatever new Readers are found in m_vTagReaderInfo) SongInfoParser::TrackTextParser* getTrackTextParser(int n) { return m_vpTrackTextParsers.at(n); } int getTrackTextParsersCnt() const { return (int)m_vpTrackTextParsers.size(); } const AlbumInfo& getAlbumInfo(int n) const { return m_vAlbumInfo.at(n); } int getAlbumInfoCnt() const { return (int)m_vAlbumInfo.size(); } int getIndex(const ImageInfo&) const; // asserts that the picture exists const Mp3Handler* getCurrentHndl() const; // returns 0 if there's no current handler std::string getCurrentName() const; // returns "" if there's no current handler const Mp3HandlerTagData* getCrtMp3HandlerTagData() const; // returns 0 if there's no current handler void moveReader(int nOldVisualIndex, int nNewVisualIndex); void addKnownInf(const std::vector& v); // should be called on startup by MainFormDlgImpl, to get config data; asserts that m_vSortedKnownTagReaders is empty; const std::vector& getSortedKnownTagReaders() const { return m_vSortedKnownTagReaders; } // the int tells which position a given pattern occupied before; (it's -1 for new patterns); // doesn't throw, but invalid patterns are discarded; it returns false if at least one pattern was discarded; bool updatePatterns(const std::vector >&); std::vector getPatterns() const; void setActivePatterns(const std::set&); std::set getActivePatterns() const { return m_snActivePatterns; } // model-based nField; UI components should pass the UI index through TagReader::FEATURE_ON_POS[] before calling this Mp3HandlerTagData::Status getStatus(int nSong, int nField) const { return m_vpMp3HandlerTagData[nSong]->getStatus(nField); } std::string getData(int nSong, int nField) const { return m_vpMp3HandlerTagData[nSong]->getData(nField); } void setData(int nSong, int nField, const std::string& s) { m_vpMp3HandlerTagData[nSong]->setData(nField, s); } // may throw InvalidValue void setStatus(int nSong, int nField, Mp3HandlerTagData::Status eStatus) { m_vpMp3HandlerTagData[nSong]->setStatus(nField, eStatus); } void hasUnsaved(int nSong, bool& bAssigned, bool& bNonId3V2); // sets bAssigned and bNonId3V2 if at least one field has the corresponding status; void hasUnsaved(bool& bAssigned, bool& bNonId3V2); // sets bAssigned and bNonId3V2 if at least one field in at least a song has the corresponding status; void getAlbumInfo(std::string& strArtist, std::string& strAlbum); // artist and album for the current song; empty if they don't exist void addAlbumInfo(const AlbumInfo&); // should be called when the selection changes; updates m_sSelOrigVal and returns the new state of m_pAssignedB; AssgnBtnWrp::State updateAssigned(const std::vector >& vFields); // should be called when the user clicks on the assign button; changes status of selected cells and returns the new state of m_pAssignedB AssgnBtnWrp::State toggleAssigned(AssgnBtnWrp::State eCrtState); void copyFirst(); void paste(); void sort(); void eraseFields(const std::vector >& vFields); bool isFastSaving() const { return m_bIsFastSaving; } bool shouldShowPatternsNote() const { return m_bShouldShowPatternsNote; } enum ConsiderAssigned { CONSIDER_UNASSIGNED, CONSIDER_ASSIGNED }; int addImage(const ImageInfo& img, bool bConsiderAssigned); // returns the index of the image; if it already exists it's not added again; if it's invalid returns -1 // "unassigned" images cause warnings when going to another album void addImgWidget(ImageInfoPanelWdgImpl*); const ImageColl& getImageColl() const { return m_imageColl; } void selectImg(int n); void clearShowedNonSeqWarn() { m_bShowedNonSeqWarn = false; } int getUnassignedImagesCount() const { return int(m_snUnassignedImages.size()); } void toggleVarArtists(); void delayedAdjVarArtists(); const TextCaseOptions& m_eArtistCase; const TextCaseOptions& m_eTitleCase; private slots: void onAssignImage(int); void onEraseFile(int); void onEraseFileDelayed(); void onDelayedTrackSeqWarn(); void onDelayedAdjVarArtists(); void onDelayedChangeNotif(); signals: void albumChanged(/*bool bContentOnly*/); // the selection may be kept iff bContentOnly is true void fileChanged(); void imagesChanged(); void requestSave(); void varArtistsUpdated(bool bVarArtists); }; #endif // #ifndef TagWriterH MP3Diags-1.2.02/src/Id3V2Stream.cpp0000644000175000001440000014440111724634702015457 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include "fstream_unicode.h" #include "Id3V2Stream.h" #include "MpegStream.h" #include "Helpers.h" #include "CommonData.h" #include "Widgets.h" // for GlobalTranslHlp using namespace std; using namespace pearl; /* ID3V2.3 +-----------------------------+ | Header (10 bytes) | +-----------------------------+ | Extended Header | | (variable length, OPTIONAL) | +-----------------------------+ | Frames (variable length) | +-----------------------------+ | Padding | | (variable length, OPTIONAL) | +-----------------------------+ | Footer (10 bytes, OPTIONAL) | +-----------------------------+ http://osdir.com/ml/multimedia.id3v2/2007-08/msg00008.html : For v2.3 tags, you would read the first ten bytes (tag header) and then, if there is an extended header, read 4 bytes to get its size and then skip that many bytes. (The padding that is given in the extended header comes AFTER the frames.) For v2.4 tags, you would read the first ten bytes (tag header) and then, if there is an extended header, read 4 bytes to get its size and then skip (size - 4) bytes ahead. (I believe in v2.4 tags, the size given in the extended header includes the 4 bytes you would have already read.) And don't forget that in v2.4 tags, the extended header size is stored as a syncsafe integer. */ // the total size, including the 10-byte header int getId3V2Size(char* pId3Header) { unsigned char* p (reinterpret_cast(pId3Header)); return (p[6] << 21) + (p[7] << 14) + (p[8] << 7) + (p[9] << 0) + 10; } // reads nCount bytes into pDest; // if bHasUnsynch is true, it actually reads more bytes, applying the unsynchronization algorithm, so pDest gets nCount bytes; // returns the number of bytes it could read; // posNext is the position where the next block begins (might be EOF); nothing starting at that position should be read; needed to take care of an ID3V2 tag whose last frame ends with 0xff and has no padding; // asserts that posNext is not before the current position in the stream streamsize readID3V2(bool bHasUnsynch, istream& in, char* pDest, streamsize nCount, streampos posNext, int& nBytesSkipped) { nBytesSkipped = 0; if (0 == nCount) { return 0; } streampos posCrt (in.tellg()); STRM_ASSERT (posNext >= posCrt); // not always right if (nCount > posNext - posCrt) { nCount = posNext - posCrt; } if (!bHasUnsynch) { return read(in, pDest, nCount); } const int BFR_SIZE (256); char bfr [BFR_SIZE]; char cPrev (0); char* q (pDest); for (;;) { streamsize nTarget (min(streamsize(BFR_SIZE), streamsize(posNext - posCrt))); streamsize nRead (read(in, bfr, nTarget)); if (0 == nRead) { // doesn't matter what was read before; EOF reached goto e1; } char* p (bfr); for (;;) { if (posCrt >= posNext) { goto e1; } if (p >= bfr + nRead) { break; } if (0 == *p && char(0xff) == cPrev) { ++p; posCrt += 1; ++nBytesSkipped; cPrev = 0; if (posCrt >= posNext) { goto e1; } if (p >= bfr + nRead) { break; } } if (q >= pDest + nCount) { goto e1; } cPrev = *q++ = *p++; posCrt += 1; } } e1: in.clear(); in.seekg(posCrt); return streamsize(q - pDest); } //============================================================================================================ //============================================================================================================ //============================================================================================================ Id3V2Frame::Id3V2Frame(streampos pos, bool bHasUnsynch, StringWrp* pFileName) : m_nMemDataSize(-1), m_nDiskDataSize(-1), m_pos(pos), m_pFileName(pFileName), m_bHasUnsynch(bHasUnsynch), m_bHasLatin1NonAscii(false), m_eApicStatus(NO_APIC), m_nPictureType(-1), m_nImgOffset(-1), m_nImgSize(-1), m_eCompr(ImageInfo::INVALID), m_nWidth(-1), m_nHeight(-1) { } /*virtual*/ Id3V2Frame::~Id3V2Frame() { //qDebug("Id3V2Frame::~Id3V2Frame(%p)", this); } static bool isReadable(char c) { return c > 32 && c < 127; } // normally returns m_szName, but if it has invalid characters (<=32 or >=127), returns a hex representation string Id3V2Frame::getReadableName() const { if (isReadable(m_szName[0]) && isReadable(m_szName[1]) && isReadable(m_szName[2]) && isReadable(m_szName[3])) { return m_szName; } char a [32]; sprintf(a, "0x%02x 0x%02x 0x%02x 0x%02x", (unsigned)(unsigned char)m_szName[0], (unsigned)(unsigned char)m_szName[1], (unsigned)(unsigned char)m_szName[2], (unsigned)(unsigned char)m_szName[3]); return a; } static const char* findAfter(const char* p, unsigned char cEnc, const char* pLast) { //inspect(p, pLast - p); if (0 == p) { return 0; } switch (cEnc) { case 0: case 3: { while (p < pLast - 1 && 0 != *p) { ++p; } if (p + 1 < pLast) { return p + 1; } return 0; } case 1: case 2: { while (p < pLast - 2 && (0 != *p || 0 != *(p + 1))) { p += 2; //inspect(p, pLast - p); } if (p + 2 < pLast) { return p + 2; } return 0; } default: CB_ASSERT(false); } } void Id3V2Frame::print(ostream& out, bool bFullInfo) const { out << m_szName; if ('T' == m_szName[0]) { string s (getUtf8String()); if (!bFullInfo && cSize(s) > 100) { s.erase(90); s += " ..."; } out << "=\"" << s << "\""; //ttt2 probably specific to particular versions of Linux and GCC //cout << " value=\"" << getUtf8String() << "\""; //ttt2 probably specific to particular versions of Linux and GCC //out << " value=\"" << "RRRRRRRR" << "\""; } else if (bFullInfo && string("USLT") == m_szName) { Id3V2FrameDataLoader wrp (*this); const char* pData (wrp.getData()); //const char* q (pData + 1); out << ": "; int nBeg (1); { // skip description if (0 == pData[0] || 3 == pData[0]) { for (; nBeg < m_nMemDataSize && 0 != pData[nBeg]; ++nBeg) {} ++nBeg; } else { for (; nBeg < m_nMemDataSize - 1 && (0 != pData[nBeg] || 0 != pData[nBeg + 1]); ++nBeg) {} nBeg += 2; } } QString qs; switch (pData[0]) { case 0: // Latin-1 qs = QString::fromLatin1(pData + nBeg, m_nMemDataSize - nBeg); break; case 1: try { qs = QString::fromUtf8(utf8FromBomUtf16(pData + nBeg, m_nMemDataSize - nBeg).c_str()); } catch (const NotId3V2Frame&) { qs = tr("<< error decoding string >>"); } break; case 2: qs = tr("<< unsupported encoding >>"); break; case 3: qs = QString::fromUtf8(pData + nBeg, m_nMemDataSize - nBeg); // ttt3 not quite OK for ID3V2.3.0, but it's probably better this way break; } //qs.replace('\n', " / "); qs.replace('\r', ""); qs = "\n" + qs + "\n"; out << qs.toUtf8().constData(); } else { out << " " << convStr(tr("size=")) << m_nMemDataSize; } if (bFullInfo && string("GEOB") == m_szName) { // !!! "size" is already written //ttt2 perhaps try and guess the data type Id3V2FrameDataLoader wrp (*this); const char* pData (wrp.getData()); unsigned char cEnc (*pData); const char* pMime (0); const char* pFile (0); const char* pDescr (0); const char* pBinData (0); QString qstrMime, qstrFile, qstrDescr; if (cEnc > 3) { out << " " << convStr(tr("invalid text encoding")); } else if (2 == cEnc) { out << " " << convStr(tr("unsupported text encoding")); } else { const char* pLast (pData + m_nMemDataSize); // actually first after last pMime = pData + 1; pFile = findAfter(pMime, 0, pLast); // !!! mime is always UTF-8 pDescr = findAfter(pFile, cEnc, pLast); pBinData = findAfter(pDescr, cEnc, pLast); if (0 != pBinData) { qstrMime = QString::fromLatin1(pMime); switch (cEnc) { case 0: // Latin-1 qstrFile = QString::fromLatin1(pFile); qstrDescr = QString::fromLatin1(pDescr); break; case 1: try { qstrFile = QString::fromUtf8(utf8FromBomUtf16(pFile, pDescr - pFile).c_str()); } catch (const NotId3V2Frame&) { // invalid encoding qstrFile = tr(""); } try { qstrDescr = QString::fromUtf8(utf8FromBomUtf16(pDescr, pBinData - pDescr).c_str()); } catch (const NotId3V2Frame&) { // invalid encoding qstrDescr = tr(""); } break; /*case 2: qs = tr("<< unsupported encoding >>"); break;*/ case 3: // ttt3 not quite OK for ID3V2.3.0, but it's probably better this way qstrFile = QString::fromUtf8(pFile); qstrDescr = QString::fromUtf8(pDescr); break; default: CB_ASSERT1 (false, m_pFileName->s); } } if (0 == pBinData) { out << " " << convStr(tr("invalid data")); } else { int nBinSize (pLast - pBinData); int nPrintedBinSize (min(1024, nBinSize)); //out << " MIME=\"" << convStr(qstrMime) << "\" File=\"" << convStr(qstrFile) << "\" Descr=\"" << convStr(qstrDescr) << "\" Binary data size=" << pLast - pBinData << (nPrintedBinSize != nBinSize ? " Begins with: " : " Content: ") << asHex(pBinData, nPrintedBinSize); out << " " << convStr(tr("MIME=\"%1\" File=\"%2\" Descr=\"%3\" Binary data size=%4").arg(qstrMime).arg(qstrFile).arg(qstrDescr).arg(pLast - pBinData)); out << " " << convStr((nPrintedBinSize != nBinSize ? tr("Begins with: %1") : tr("Content: %1")).arg(convStr(asHex(pBinData, nPrintedBinSize)))); } } } if (Id3V2Frame::NO_APIC != m_eApicStatus) { out << " " << convStr(tr("status=%1").arg(tr(getImageStatus()))); } } const char* Id3V2Frame::getImageType() const { return ImageInfo::getImageType(m_nPictureType); } const char* Id3V2Frame::getImageStatus() const { switch(m_eApicStatus) { case USES_LINK: return QT_TR_NOOP("link"); case NON_COVER: return QT_TR_NOOP("non-cover"); case ERR: return QT_TR_NOOP("error"); case COVER: return QT_TR_NOOP("cover"); default: CB_ASSERT1 (false, m_pFileName->s); } } double Id3V2Frame::getRating() const // asserts it's POPM { CB_ASSERT (0 == strcmp(KnownFrames::LBL_RATING(), m_szName)); Id3V2FrameDataLoader wrp (*this); const char* pData (wrp.getData()); int k (0); while (k < m_nMemDataSize && 0 != pData[k]) { ++k; } // skip email addr ++k; if (k >= m_nMemDataSize) { // error //ttt2 add warning on constructor return -1; } unsigned char c (pData[k]); return 5*(double(c) - 1)/254; // ttt2 not sure this is the best mapping } /*static*/ string Id3V2Frame::utf8FromBomUtf16(const char* pData, int nSize) { CB_CHECK1 (nSize > 1, NotId3V2Frame()); // UNICODE string entries must have a size of 3 or more." if (2 == nSize && 0 == *pData && 0 == *(pData + 1)) { return ""; } // not quite correct, but seems to happen; even if a string is null, it must begin with BOM //ttt1 add a note const unsigned char* p (reinterpret_cast (pData)); CB_CHECK1 ((0xff == p[0] && 0xfe == p[1]) || (0xff == p[1] && 0xfe == p[0]), NotId3V2Frame()); //ttt2 perhaps use other exception #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN bool bIsFffeOk (true); // x86 #else bool bIsFffeOk (false); #endif pData += 2; vector v; int nUSize ((nSize - 2)/2); // ttt3 maybe check that nSize is an even number, but not sure what to do if it isn't if ((0xff == *p && !bIsFffeOk) || (0xff != *p && bIsFffeOk)) { // swap bytes so QString would understand them; it might seem useful for QString to understand BOM, but it doesn't; so ... v.resize(nUSize*2); for (int i = 0; i < nUSize; ++i) { v[i*2] = pData[i*2 + 1]; v[i*2 + 1] = pData[i*2]; } pData = &v[0]; } const ushort* pUs (reinterpret_cast(pData)); QString qs (QString::fromUtf16(pUs, nUSize)); string s (convStr(qs)); return s; } // for display / export; for frames in KnownFrames::getKnownFrames only the data up to the first 0 is used (effectively removing comments in 2.3.0), while for the others, including TXXX, nulls and all other characters with codes below 32 are replaced with spaces (as a result, both description and value are shown for TXXX), so in either case the return value doesn't contain nulls; // whitespaces at the end of the string are removed; string Id3V2Frame::getUtf8String() const { try { string s (getUtf8StringImpl()); if (0 == KnownFrames::getKnownFrames().count(m_szName) || isTxxx()) { // text frames may contain null characters, and what's after a null char isn't supposed to be displayed; however, for frames that aren't used we may want to see what's after the null // ttt2 null characters mean different things inside ID3V2.3.0 (comment follows) vs. ID3V2.4.0 (multiple values separated by 0); however, this doesn't matter in current implementation, because Id3V240Frame::getUtf8StringImpl() replaces nulls with commas for (int i = 0; i < cSize(s); ++i) { unsigned char c (s[i]); if (c < 32) // ttt3 ASCII only { s[i] = 32; } } } s = s.c_str(); // !!! so now s doesn't contain null chars rtrim(s); return s; } catch (const Id3V2FrameDataLoader::LoadFailure&) { return convStr(TagReader::tr("")); //ttt2 not sure if this is the best thing to do, but at least avoids crashes; } catch (const Id3V2Frame::NotId3V2Frame&) { return convStr(TagReader::tr("")); //ttt2 not sure if this is the best thing to do, but at least avoids crashes; } } // for internal processing; similar to getUtf8String() but doesn't replace internal null characters with spaces; // removes trailing nulls and whitespaces, as well as 2.3.0 comments; string Id3V2Frame::getRawUtf8String() const { try { string s (getUtf8StringImpl()); int i (cSize(s) - 1); for (; i >= 0; --i) { unsigned char c (s[i]); if (0 != c && (c >= ' ' || (c < ' ' && QChar(c).isSpace()))) { break; } } ++i; if (i < cSize(s)) { s.erase(i); } return s; } catch (const Id3V2FrameDataLoader::LoadFailure&) { return convStr(TagReader::tr("")); //ttt2 not sure if this is the best thing to do, but at least avoids crashes; } catch (const Id3V2Frame::NotId3V2Frame&) { return convStr(TagReader::tr("")); //ttt2 not sure if this is the best thing to do, but at least avoids crashes; } } bool Id3V2Frame::isTxxx() const { return 0 == strcmp(KnownFrames::LBL_TXXX(), m_szName); } //============================================================================================================ //============================================================================================================ //============================================================================================================ Id3V2FrameDataLoader::Id3V2FrameDataLoader(const Id3V2Frame& frame) : m_frame(frame) { if (cSize(frame.m_vcData) < m_frame.m_nMemDataSize) { int nDiscard (frame.getOffset()); // for DataLengthIndicator //m_bOwnsData = true; CB_ASSERT1 (frame.m_vcData.empty(), m_frame.m_pFileName->s); CB_ASSERT1 (0 != frame.m_pFileName, m_frame.m_pFileName->s); m_vcOwnData.resize(m_frame.m_nMemDataSize); ifstream_utf8 in (m_frame.m_pFileName->s.c_str(), ios::binary); in.seekg(m_frame.m_pos); in.seekg(m_frame.m_nDiskHdrSize, ios_base::cur); streampos posNext (m_frame.m_pos); posNext += m_frame.m_nDiskDataSize + m_frame.m_nDiskHdrSize; int nContentBytesSkipped (0); int nRead (0); in.seekg(nDiscard, ios_base::cur); nRead = readID3V2(m_frame.m_bHasUnsynch, in, &m_vcOwnData[0], cSize(m_vcOwnData), posNext, nContentBytesSkipped); //qDebug("nRead %d ; m_frame.m_nMemDataSize %d ; nContentBytesSkipped %d ", nRead, m_frame.m_nMemDataSize, nContentBytesSkipped); if (cSize(m_vcOwnData) != nRead) { throw LoadFailure(); } m_pData = &m_vcOwnData[0]; } else { if (frame.m_vcData.empty()) { static char c (0); m_pData = &c; } else { m_pData = &frame.m_vcData[0]; } } } Id3V2FrameDataLoader::~Id3V2FrameDataLoader() { } //============================================================================================================ //============================================================================================================ //============================================================================================================ /*static*/ const char* KnownFrames::LBL_TITLE() { return "TIT2"; } /*static*/ const char* KnownFrames::LBL_ARTIST() { return "TPE1"; } /*static*/ const char* KnownFrames::LBL_TRACK_NUMBER() { return "TRCK"; } /*static*/ const char* KnownFrames::LBL_TIME_YEAR_230() { return "TYER"; } /*static*/ const char* KnownFrames::LBL_TIME_DATE_230() { return "TDAT"; } /*static*/ const char* KnownFrames::LBL_TIME_240() { return "TDRC"; } /*static*/ const char* KnownFrames::LBL_GENRE() { return "TCON"; } /*static*/ const char* KnownFrames::LBL_IMAGE() { return "APIC"; } /*static*/ const char* KnownFrames::LBL_ALBUM() { return "TALB"; } /*static*/ const char* KnownFrames::LBL_RATING() { return "POPM"; } /*static*/ const char* KnownFrames::LBL_COMPOSER() { return "TCOM"; } /*static*/ const char* KnownFrames::LBL_WMP_VAR_ART() { return "TPE2"; } /*static*/ const char* KnownFrames::LBL_ITUNES_VAR_ART() { return "TCMP"; } /*static*/ const char* KnownFrames::LBL_TXXX() { return "TXXX"; } //============================================================================================================ //============================================================================================================ //============================================================================================================ Id3V2StreamBase::Id3V2StreamBase(int nIndex, istream& in, StringWrp* pFileName) : DataStream(nIndex), m_nPaddingSize(0), m_pos(in.tellg()), m_pFileName(pFileName), m_eImageStatus(ImageInfo::NO_PICTURE_FOUND), m_pPicFrame(0) { } /*override*/ Id3V2StreamBase::~Id3V2StreamBase() { clearPtrContainer(m_vpFrames); } bool Id3V2StreamBase::hasUnsynch() const { return 0 != (m_cFlags & 0x80); } void Id3V2StreamBase::printFrames(ostream& out) const { for (vector::const_iterator it = m_vpFrames.begin(), end = m_vpFrames.end(); it != end; ++it) { (*it)->print(out, Id3V2Frame::FULL_INFO); //(*it)->print(cout); } } /*override*/ void Id3V2StreamBase::copy(std::istream& in, std::ostream& out) { appendFilePart(in, out, m_pos, m_nTotalSize); //ttt2 } /*override*/ std::string Id3V2StreamBase::getInfo() const { ostringstream out; out << convStr(DataStream::tr("padding=")) << m_nPaddingSize << ", " << convStr(DataStream::tr("unsynch=")) << convStr(GlobalTranslHlp::tr(boolAsYesNo(hasUnsynch()))) << "; " << convStr(DataStream::tr("frames")) << ": "; bool bFirst (true); for (vector::const_iterator it = m_vpFrames.begin(), end = m_vpFrames.end(); it != end; ++it) { if (!bFirst) { out << ", "; } bFirst = false; (*it)->print(out, Id3V2Frame::SHORT_INFO); } string s (out.str()); //cout << s << endl; //printHex(s, cout, false); return s; } Id3V2Frame* Id3V2StreamBase::findFrame(const char* szFrameName) //ttt2 finds the first frame, but doesn't care about duplicates { for (int i = 0, n = cSize(m_vpFrames); i < n; ++i) { Id3V2Frame* p = m_vpFrames[i]; if (0 == strcmp(szFrameName, p->m_szName)) { return p; } } return 0; } const Id3V2Frame* Id3V2StreamBase::findFrame(const char* szFrameName) const //ttt2 finds the first frame, but doesn't care about duplicates { for (int i = 0, n = cSize(m_vpFrames); i < n; ++i) { const Id3V2Frame* p = m_vpFrames[i]; if (0 == strcmp(szFrameName, p->m_szName)) { return p; } } return 0; } /*override*/ std::string Id3V2StreamBase::getTitle(bool* pbFrameExists /* = 0*/) const { const Id3V2Frame* p (findFrame(KnownFrames::LBL_TITLE())); if (0 != pbFrameExists) { *pbFrameExists = 0 != p; } if (0 == p) { return ""; } return p->getUtf8String(); } /*override*/ std::string Id3V2StreamBase::getArtist(bool* pbFrameExists /* = 0*/) const { const Id3V2Frame* p (findFrame(KnownFrames::LBL_ARTIST())); if (0 != pbFrameExists) { *pbFrameExists = 0 != p; } if (0 == p) { return ""; } return p->getUtf8String(); } /*override*/ std::string Id3V2StreamBase::getTrackNumber(bool* pbFrameExists /* = 0*/) const { const Id3V2Frame* p (findFrame(KnownFrames::LBL_TRACK_NUMBER())); if (0 != pbFrameExists) { *pbFrameExists = 0 != p; } if (0 == p) { return ""; } return p->getUtf8String(); } /*static bool isNum(const string& s) { if (s.empty()) { return false; } for (int i = 0, n = cSize(s); i < n; ++i) { if (!isdigit(s[i])) { return false; } } return true; }*/ static string decodeGenre(const string& s) { string strRes; const char* q (s.c_str()); while ('(' == *q && '(' != *(q + 1)) { const char* q1 (q + 1); if (!isdigit(*q1)) { return s; } // error for (; isdigit(*q1); ++q1) {} if (')' != *q1) { return s; } // error if (!strRes.empty()) { strRes += " / "; } strRes += getId3V1Genre(atoi(q + 1)); q = q1 + 1; } if ('(' == *q && '(' == *(q + 1)) { ++q; } if (0 != *q) { if (!strRes.empty()) { strRes += " / "; } strRes += q; } return strRes; } /*override*/ std::string Id3V2StreamBase::getGenre(bool* pbFrameExists /* = 0*/) const { const Id3V2Frame* p (findFrame(KnownFrames::LBL_GENRE())); // for valid formats see tstGenre() and http://www.id3.org/id3v2.3.0#head-42b02d20fb8bf48e38ec5415e34909945dd849dc if (0 != pbFrameExists) { *pbFrameExists = 0 != p; } if (0 == p) { return ""; } string s (p->getUtf8String()); /*int n (cSize(s)); if (n > 2 && '(' == s[0] && ')' == s[n - 1] && isNum(s.substr(1, n - 2))) { return getId3V1Genre(atoi(s.c_str() + 1)); } if (isNum(s)) { return getId3V1Genre(atoi(s.c_str())); }*/ return decodeGenre(s); } /* void tstGenre() //ttt2 remove { cout << "\nGenre test\n"; { string s ("gaga"); cout << "*" << s << "*" << decodeGenre(s) << "*\n"; } { string s ("(10)gaga"); cout << "*" << s << "*" << decodeGenre(s) << "*\n"; } { string s ("(10)(83)gaga"); cout << "*" << s << "*" << decodeGenre(s) << "*\n"; } { string s ("(10)(83)((gaga)"); cout << "*" << s << "*" << decodeGenre(s) << "*\n"; } { string s ("(10a)gaga"); cout << "*" << s << "*" << decodeGenre(s) << "*\n"; } { string s ("(b10)gaga"); cout << "*" << s << "*" << decodeGenre(s) << "*\n"; } } */ void Id3V2StreamBase::checkFrames(NoteColl& notes) // various checks to be called from derived class' constructor { const Id3V2Frame* p (findFrame(KnownFrames::LBL_GENRE())); if (0 != p) { string s (p->getUtf8String()); /*int n (cSize(s)); if (n > 2 && '(' == s[0] && ')' == s[n - 1] && isNum(s.substr(1, n - 2))) { MP3_NOTE (p->m_pos, "Numerical value between parantheses found as track genre. The standard specifies a numerical value, but most applications use a descriptive name instead."); } else if (isNum(s)) { MP3_NOTE (p->m_pos, "Numerical value found as track genre. While this is consistent with the standard, most applications use a descriptive name instead."); } else */if (s.empty()) { MP3_NOTE (p->m_pos, id3v2EmptyTcon); //ttt2 perhaps replace this with something more generic } } //ttt2 add other checks } //ttt2 perhaps use links to pictures in crt dir /*override*/ ImageInfo Id3V2StreamBase::getImage(bool* pbFrameExists /* = 0*/) const { //if (0 != pbFrameExists) { *pbFrameExists = false; } return ImageInfo(ImageInfo::NO_PICTURE_FOUND); if (0 != pbFrameExists) { const Id3V2Frame* p (findFrame(KnownFrames::LBL_IMAGE())); *pbFrameExists = 0 != p; } //ImageInfo res; //res.m_eStatus = m_eImageStatus; if (ImageInfo::OK != m_eImageStatus && ImageInfo::LOADED_NOT_COVER != m_eImageStatus) { CB_ASSERT1 (0 == m_pPicFrame, m_pFileName->s); return ImageInfo(-1, m_eImageStatus); } CB_ASSERT1 (0 != m_pPicFrame, m_pFileName->s); try { Id3V2FrameDataLoader wrp (*m_pPicFrame); const char* pCrtData (wrp.getData()); const char* pBinData (pCrtData + m_pPicFrame->m_nImgOffset); //CB_CHECK (pixmap.loadFromData(pBinData, m_nImgSize)); // make sure the data is still available and correct (the file might have been modified externally) if (-1 == m_pPicFrame->m_nWidth) { QPixmap pic; if (!pic.loadFromData(reinterpret_cast(pBinData), m_pPicFrame->m_nImgSize)) // this takes a lot of time { goto e1; } m_pPicFrame->m_nWidth = short(pic.width()); m_pPicFrame->m_nHeight = short(pic.height()); } QByteArray b (QByteArray::fromRawData(pBinData, m_pPicFrame->m_nImgSize)); b.append('x'); b.resize(b.size() - 1); // !!! these are needed because fromRawData() doesn't create copies of the memory used for the byte array return ImageInfo(m_pPicFrame->m_nPictureType, m_eImageStatus, m_pPicFrame->m_eCompr, b, m_pPicFrame->m_nWidth, m_pPicFrame->m_nHeight); //QBuffer bfr (&res.m_compressedImg); //bfr. //res.m_compressedImg = QByteArray(fromRawData //delete pPictureInfo; } catch (const Id3V2FrameDataLoader::LoadFailure&) { //eImageStatus = ImageInfo::ERROR_LOADING; } e1: trace("The picture could be loaded before but now this is no longer possible. The most likely reason is that the file was moved or changed by an external application."); //ttt0 is there a macro? return ImageInfo(-1, ImageInfo::ERROR_LOADING); } /*override*/ vector Id3V2StreamBase::getImages() const { vector v; for (int i = 0; i < cSize(m_vpFrames); ++i) { Id3V2Frame* pFrame (m_vpFrames[i]); if (Id3V2Frame::NON_COVER == pFrame->m_eApicStatus || Id3V2Frame::COVER == pFrame->m_eApicStatus) { try { Id3V2FrameDataLoader wrp (*pFrame); const char* pCrtData (wrp.getData()); const char* pBinData (pCrtData + pFrame->m_nImgOffset); //CB_CHECK (pixmap.loadFromData(pBinData, m_nImgSize)); // make sure the data is still available and correct (the file might have been modified externally) if (-1 == pFrame->m_nWidth) { QPixmap pic; if (!pic.loadFromData(reinterpret_cast(pBinData), pFrame->m_nImgSize)) // this takes a lot of time { v.push_back(ImageInfo(pFrame->m_nPictureType, ImageInfo::ERROR_LOADING)); continue; } pFrame->m_nWidth = short(pic.width()); pFrame->m_nHeight = short(pic.height()); } QByteArray b (QByteArray::fromRawData(pBinData, pFrame->m_nImgSize)); b.append('x'); b.resize(b.size() - 1); // !!! these are needed because fromRawData() doesn't create copies of the memory used for the byte array v.push_back(ImageInfo(pFrame->m_nPictureType, Id3V2Frame::COVER == pFrame->m_eApicStatus ? ImageInfo::OK : ImageInfo::LOADED_NOT_COVER, pFrame->m_eCompr, b, pFrame->m_nWidth, pFrame->m_nHeight)); } catch (const Id3V2FrameDataLoader::LoadFailure&) { v.push_back(ImageInfo(pFrame->m_nPictureType, ImageInfo::ERROR_LOADING)); } continue; } if (Id3V2Frame::ERR == pFrame->m_eApicStatus) { v.push_back(ImageInfo(pFrame->m_nPictureType, ImageInfo::ERROR_LOADING)); } if (Id3V2Frame::USES_LINK == pFrame->m_eApicStatus) { v.push_back(ImageInfo(pFrame->m_nPictureType, ImageInfo::USES_LINK)); } } return v; } /*override*/ std::string Id3V2StreamBase::getImageData(bool* pbFrameExists /* = 0*/) const { if (0 != pbFrameExists) { *pbFrameExists = 0 != m_pPicFrame; } if (0 == m_pPicFrame) { return ""; } ostringstream out; // !!! don't translate, as this is used by XML export only //ttt1 review exporting as XML out << "type:" << m_pPicFrame->getImageType() << ", status:" << m_pPicFrame->getImageStatus() << ", size:" << m_pPicFrame->m_nWidth << "x" << m_pPicFrame->m_nHeight << ", compr:" << ImageInfo::getComprStr(m_pPicFrame->m_eCompr); return out.str(); } /*static*/ const char* Id3V2StreamBase::decodeApic(NoteColl& notes, int nDataSize, streampos pos, const char* const pData, const char*& szMimeType, int& nPictureType, const char*& szDescription) { MP3_CHECK (0 == pData[0] || 3 == pData[0], pos, id3v2UnsupApicTextEnc, NotSupTextEnc()); // !!! there's no need for StreamIsUnsupported here, because this error is not fatal, and it isn't allowed to propagate, therefore doesn't cause a stream to be Unsupported; //ttt2 review, support szMimeType = pData + 1; // ttt3 type 0 is Latin1, while type 3 is UTF8, so this isn't quite right; however, MIME types should probably be plain ASCII, so it's the same; and anyway, we only recognize JPEG and PNG, which are ASCII //int nMimeSize (strnlen(pData, nDataSize)); const char* p (pData + 1); for (; p < pData + nDataSize && 0 != *p; ++p) {} MP3_CHECK (p < pData + nDataSize - 1, pos, id3v2UnsupApicTextEnc, ErrorDecodingApic()); // "-1" to account for szDescription CB_ASSERT (0 == *p); ++p; nPictureType = *p++; szDescription = p; for (; p < pData + nDataSize; ++p) { if (0 == *p) { return p + 1; } } throw ErrorDecodingApic(); } void Id3V2StreamBase::preparePictureHlp(NoteColl& notes, Id3V2Frame* pFrame, const char* pFrameData, const char* pImgData, const char* szMimeType) { if (0 == strcmp("-->", szMimeType)) { MP3_NOTE (pFrame->m_pos, id3v2LinkInApic); pFrame->m_eApicStatus = Id3V2Frame::USES_LINK; return; } //QPixmap img; // !!! QPixmap can only be used in GUI threads, so QImage must be used instead: http://lists.trolltech.com/qt-interest/2005-02/thread00008-0.html or http://lists.trolltech.com/qt-interest/2006-11/thread00045-0.html QImage img; const unsigned char* pBinData (reinterpret_cast(pImgData)); int nSize (pFrame->m_nMemDataSize - (pImgData - pFrameData)); if (img.loadFromData(pBinData, nSize)) { pFrame->m_nImgSize = nSize; pFrame->m_nImgOffset = pImgData - pFrameData; pFrame->m_eApicStatus = pFrame->m_nPictureType == Id3V2Frame::PT_COVER ? Id3V2Frame::COVER : Id3V2Frame::NON_COVER; pFrame->m_nWidth = short(img.width()); pFrame->m_nHeight = short(img.height()); if (0 == strcmp("image/jpeg", szMimeType) || 0 == strcmp("image/jpg", szMimeType)) { pFrame->m_eCompr = ImageInfo::JPG; } else if (0 == strcmp("image/png", szMimeType)) { pFrame->m_eCompr = ImageInfo::PNG; } else { pFrame->m_eCompr = ImageInfo::INVALID; } //ttt2 perhaps support GIF or other formats; (well, GIFs can be loaded, but are recompressed when saving) return; } pFrame->m_eApicStatus = Id3V2Frame::ERR; if (pFrame->m_nMemDataSize > 100) { MP3_NOTE (pFrame->m_pos, id3v2ErrorLoadingApic); } else { MP3_NOTE (pFrame->m_pos, id3v2ErrorLoadingApicTooShort); } } void Id3V2StreamBase::preparePicture(NoteColl& notes) // initializes fields used by the APIC frame { const char* szMimeType; const char* szDescription; //Id3V2Frame* pFirstLoadableCover (0); Id3V2Frame* pFirstLoadableNonCover (0); Id3V2Frame* pFirstApic (0); // might have errors, might be link, ... for (int i = 0, n = cSize(m_vpFrames); i < n; ++i) { // go through the frame list and set m_eApicStatus Id3V2Frame* p = m_vpFrames[i]; if (0 == strcmp(KnownFrames::LBL_IMAGE(), p->m_szName)) { if (0 == pFirstApic) { pFirstApic = p; } // !!! regardless of what exceptions might get thrown, p will have an m_eApicStatus other than NO_APIC; that happens either in preparePictureHlp() or with explicit assignments try { Id3V2FrameDataLoader wrp (*p); const char* pData (wrp.getData()); const char* pCrtData (0); try { pCrtData = decodeApic(notes, p->m_nMemDataSize, p->m_pos, pData, szMimeType, p->m_nPictureType, szDescription); } catch (const NotSupTextEnc&) { p->m_eApicStatus = Id3V2Frame::ERR; continue; } catch (const ErrorDecodingApic&) { p->m_eApicStatus = Id3V2Frame::ERR; continue; } if (0 != *szDescription) { MP3_NOTE (p->m_pos, id3v2PictDescrIgnored); } preparePictureHlp(notes, p, pData, pCrtData, szMimeType); if (Id3V2Frame::COVER == p->m_eApicStatus) { if (0 == m_pPicFrame) { m_eImageStatus = ImageInfo::OK; m_pPicFrame = p; } } if (0 == pFirstLoadableNonCover && Id3V2Frame::NON_COVER == p->m_eApicStatus) { pFirstLoadableNonCover = p; } } catch (const Id3V2FrameDataLoader::LoadFailure&) { MP3_NOTE (p->m_pos, fileWasChanged); p->m_eApicStatus = Id3V2Frame::ERR; } } } if (ImageInfo::OK == m_eImageStatus) { return; } // no cover frame was found; just pick the first loadable APIC frame and use it CB_ASSERT (ImageInfo::NO_PICTURE_FOUND == m_eImageStatus); if (0 != pFirstLoadableNonCover) { m_eImageStatus = ImageInfo::LOADED_NOT_COVER; m_pPicFrame = pFirstLoadableNonCover; return; } if (0 == pFirstApic) { return; } switch (pFirstApic->m_eApicStatus) { case Id3V2Frame::USES_LINK: m_eImageStatus = ImageInfo::USES_LINK; return; case Id3V2Frame::ERR: m_eImageStatus = ImageInfo::ERROR_LOADING; return; default: CB_ASSERT1 (false, m_pFileName->s); // all cases should have been covered } } /*override*/ std::string Id3V2StreamBase::getAlbumName(bool* pbFrameExists /* = 0*/) const { const Id3V2Frame* p (findFrame(KnownFrames::LBL_ALBUM())); if (0 != pbFrameExists) { *pbFrameExists = 0 != p; } if (0 == p) { return ""; } return p->getUtf8String(); } /*override*/ std::string Id3V2StreamBase::getComposer(bool* pbFrameExists /* = 0*/) const { const Id3V2Frame* p (findFrame(KnownFrames::LBL_COMPOSER())); if (0 != pbFrameExists) { *pbFrameExists = 0 != p; } if (0 == p) { return ""; } return p->getUtf8String(); } // *pbFrameExists gets set if at least one frame exists /*override*/ int Id3V2StreamBase::getVariousArtists(bool* pbFrameExists /* = 0*/) const { int nRes (0); if (0 != pbFrameExists) { *pbFrameExists = false; } try { { const Id3V2Frame* p (findFrame(KnownFrames::LBL_WMP_VAR_ART())); if (0 != p) { if (0 != pbFrameExists) { *pbFrameExists = true; } if (0 == convStr(p->getUtf8String()).compare("VaRiOuS Artists", Qt::CaseInsensitive)) { nRes += TagReader::VA_WMP; } } } { const Id3V2Frame* p (findFrame(KnownFrames::LBL_ITUNES_VAR_ART())); if (0 != p) { if (0 != pbFrameExists) { *pbFrameExists = true; } if ("1" == p->getUtf8String()) { nRes += TagReader::VA_ITUNES; } } } } catch (const Id3V2Frame::NotId3V2Frame&) { // !!! nothing } catch (const Id3V2Frame::UnsupportedId3V2Frame&) { // !!! nothing } return nRes; } /*override*/ double Id3V2StreamBase::getRating(bool* pbFrameExists /* = 0*/) const { const Id3V2Frame* p (findFrame(KnownFrames::LBL_RATING())); if (0 != pbFrameExists) { *pbFrameExists = 0 != p; } if (0 == p) { return -1; } return p->getRating(); } static const char* REPLAY_GAIN_ROOT ("replaygain"); //ttt1 mp3gain 1.5.1 can use ID3V2 instead of APE, so that should be tested as well; see TXXX="MP3GAIN_MINMAX[...]" in "Arctic Monkeys - 01 - My Propeller.mp3" (which also has TXXX="replaygain_track_gain[...]") - also review Id3V2Cleaner::processId3V2Stream bool Id3V2StreamBase::hasReplayGain() const { for (int i = 0; i < cSize(m_vpFrames); ++i) { const Id3V2Frame* pFrame (m_vpFrames[i]); if (pFrame->isTxxx()) { QString qs (convStr(pFrame->getUtf8String())); if (qs.startsWith(REPLAY_GAIN_ROOT, Qt::CaseInsensitive)) { return true; } } } return false; } static const char* getId3V2ClassDisplayName() // needed so pointer comparison can be performed for Id3V2StreamBase::getClassDisplayName() regardless of the template param { return "ID3V2"; } /*static*/ const char* Id3V2StreamBase::getClassDisplayName() { return getId3V2ClassDisplayName(); } // returns a frame with the given name; normally it returns the first such frame, but it may return another if there's a good reason; returns 0 if no frame was found; const Id3V2Frame* Id3V2StreamBase::getFrame(const char* szName) const { if (0 == strcmp(szName, KnownFrames::LBL_IMAGE())) { return m_pPicFrame; } return findFrame(szName); } /*static*/ const set& KnownFrames::getExcludeFromInfoFrames() // frames that shouldn't be part of "other info"; doesn't include TXXX and "Various Artists" frames { static bool bFirstTime (true); static set s; if (bFirstTime) { s.insert(KnownFrames::LBL_TITLE()); s.insert(KnownFrames::LBL_ARTIST()); s.insert(KnownFrames::LBL_TRACK_NUMBER()); s.insert(KnownFrames::LBL_TIME_YEAR_230()); s.insert(KnownFrames::LBL_TIME_DATE_230()); s.insert(KnownFrames::LBL_TIME_240()); //ttt2 perhaps this shouldn't be used for 2.3.0, but it covers cases like reading 2.4.0 and writing 2.3.0 or bugs by some tools s.insert(KnownFrames::LBL_GENRE()); s.insert(KnownFrames::LBL_IMAGE()); s.insert(KnownFrames::LBL_ALBUM()); s.insert(KnownFrames::LBL_RATING()); s.insert(KnownFrames::LBL_COMPOSER()); bFirstTime = false; } return s; } // includes "Various Artists" frames; doesn't include TXXX /*static*/ const set& KnownFrames::getKnownFrames() { static bool bFirstTime (true); static set sKnownFrames (getExcludeFromInfoFrames()); if (bFirstTime) { sKnownFrames.insert(KnownFrames::LBL_WMP_VAR_ART()); sKnownFrames.insert(KnownFrames::LBL_ITUNES_VAR_ART()); bFirstTime = false; } return sKnownFrames; } /*override*/ std::string Id3V2StreamBase::getOtherInfo() const { set sUsedFrames; //string strRes; ostringstream out; bool b (false); for (int i = 0, n = cSize(m_vpFrames); i < n; ++i) { Id3V2Frame* p = m_vpFrames[i]; if (KnownFrames::getExcludeFromInfoFrames().count(p->m_szName) > 0 && sUsedFrames.count(p->m_szName) == 0) { sUsedFrames.insert(p->m_szName); } else { if (Id3V2Frame::NON_COVER != p->m_eApicStatus && Id3V2Frame::COVER != p->m_eApicStatus) // images that can be loaded are shown, so there shouldn't be another entry for them { if (b) { out << ", "; } b = true; p->print(out, Id3V2Frame::FULL_INFO); } } } return out.str(); } void Id3V2StreamBase::checkDuplicates(NoteColl& notes) const { // for some it's OK to be duplicated, e.g. for APIC and various "Picture type" pictures; set > sUsedFrames; int nImgCnt (0); streampos secondImgPos (-1); for (int i = 0, n = cSize(m_vpFrames); i < n; ++i) { Id3V2Frame* p = m_vpFrames[i]; //if (0 == strcmp("TDOR", p->m_szName)) { MP3_NOTE (p->m_pos, "TDOR found. See if it should be processed."); } //ttt remove //if (0 == strcmp("TDRC", p->m_szName)) { MP3_NOTE (p->m_pos, "TDRC found. See if it should be processed."); } //if (0 == strcmp("TDRL", p->m_szName)) { MP3_NOTE (p->m_pos, "TDRL found. See if it should be processed."); } if (KnownFrames::getKnownFrames().count(p->m_szName) > 0) { if (0 == strcmp(KnownFrames::LBL_IMAGE(), p->m_szName)) { ++nImgCnt; if (2 == nImgCnt) { secondImgPos = p->m_pos; } } if (sUsedFrames.count(make_pair(p->m_szName, p->m_nPictureType)) == 0) { sUsedFrames.insert(make_pair(p->m_szName, p->m_nPictureType)); } else { if (0 == strcmp(KnownFrames::LBL_IMAGE(), p->m_szName)) { MP3_NOTE (p->m_pos, id3v2DuplicatePic); } else if (0 == strcmp(KnownFrames::LBL_RATING(), p->m_szName)) { MP3_NOTE (p->m_pos, id3v2DuplicatePopm); } else { MP3_NOTE_D (p->m_pos, id3v2MultipleFramesWithSameName, tr("%1 (Frame: %2)").arg(noteTr(id3v2MultipleFramesWithSameName)).arg(p->m_szName)); //ttt2 m_pos should be replaced with the position of the second frame with this ID } } } } if (nImgCnt > 1) { MP3_NOTE (secondImgPos, id3v2MultipleApic); } } TagTimestamp Id3V2StreamBase::get230TrackTime(bool* pbFrameExists) const { const Id3V2Frame* p (findFrame(KnownFrames::LBL_TIME_YEAR_230())); if (0 != pbFrameExists) { *pbFrameExists = 0 != p; } if (0 == p) { return TagTimestamp(""); } string strYear (p->getUtf8String()); if (4 != cSize(strYear)) { return TagTimestamp(""); } p = findFrame(KnownFrames::LBL_TIME_DATE_230()); try { if (0 == p) { return TagTimestamp(strYear); } string strDate (p->getUtf8String()); if (4 != cSize(strDate)) { return TagTimestamp(strYear); } return TagTimestamp(strYear + "-" + strDate.substr(2, 2) + "-" + strDate.substr(0, 2)); } catch (const TagTimestamp::InvalidTime&) { return TagTimestamp(""); } } vector Id3V2StreamBase::getKnownFrames() const // to be used by Id3V2Cleaner; { vector v; for (int i = 0; i < cSize(m_vpFrames); ++i) { const Id3V2Frame* p (m_vpFrames[i]); if (KnownFrames::getKnownFrames().count(p->m_szName) > 0 || p->isTxxx()) { // add if known except if a picture with the same type was already added; don't add duplicates even if Id3V230StreamWriter could take care of them, because it keeps the last value and we want the first one bool bAdd (true); for (int j = 0; j < cSize(v); ++j) { if (p->m_nPictureType == v[j]->m_nPictureType && 0 == strcmp(v[j]->m_szName, p->m_szName)) { // !!! important for both image and non-image frames; while Id3V230StreamWriter would remove duplicates as configured, we want to get rid of them now, so the first value is kept; Id3V230StreamWriter removes old values as new ones are added, which would keep the last value //ttt2 not always right for multiple pictures if "keep one" is checked; bAdd = false; break; } } if (p->isTxxx()) { string s (p->getUtf8String()); QString qs (convStr(s)); if (qs.startsWith(REPLAY_GAIN_ROOT, Qt::CaseInsensitive)) { bAdd = true; //ttt2 perhaps check it's no duplicate; not sure about case } } if (bAdd && 0 == strcmp(KnownFrames::LBL_IMAGE(), p->m_szName) && Id3V2Frame::COVER != p->m_eApicStatus && Id3V2Frame::NON_COVER != p->m_eApicStatus) { // !!! get rid of broken pictures and links bAdd = false; } if (bAdd) { v.push_back(p); } } } return v; } //============================================================================================================ //============================================================================================================ //============================================================================================================ // explicit instantiation //template class Id3V2Stream; //template class Id3V2Stream; //============================================================================================================ //============================================================================================================ //============================================================================================================ /*static*/ const char* KnownFrames::getFrameName (int n) { switch (n) { case 0: return LBL_TITLE(); case 1: return LBL_ARTIST(); case 2: return LBL_TRACK_NUMBER(); case 3: return LBL_TIME_YEAR_230(); case 4: return LBL_TIME_DATE_230(); case 5: return LBL_TIME_240(); case 6: return LBL_GENRE(); case 7: return LBL_IMAGE(); case 8: return LBL_ALBUM(); case 9: return LBL_RATING(); case 10: return LBL_COMPOSER(); } CB_THROW1 (InvalidIndex()); } /*static*/ bool KnownFrames::canHaveDuplicates(const char* szName) { //ttt2 make this more sophisticated; maybe allow multiple LBL_RATING, each with its own email; //if (0 == strcmp(szName, LBL_IMAGE())) { return true; } if (1 == getKnownFrames().count(szName)) { return false; } // !!! OK for LBL_IMAGE, because when actually using this the image type should be compared as well return true; } //ttt2 note for mismatch between file name and rating MP3Diags-1.2.02/src/FilesModel.h0000644000175000001440000001120511261073057015133 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef FilesModelH #define FilesModelH #include #include #include #include //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== class CommonData; // all files class FilesModel : public QAbstractTableModel { Q_OBJECT int m_nPrevCurrentRow; // the "previous current" while processing the "current changed" event; used to determine if the current notes (m_vpCrtNotes) and streams need to be updated public: FilesModel(CommonData* pCommonData); /*override*/ int rowCount(const QModelIndex&) const; /*override*/ int columnCount(const QModelIndex&) const; /*override*/ QVariant data(const QModelIndex&, int nRole) const; /*override*/ QVariant headerData(int nSection, Qt::Orientation eOrientation, int nRole = Qt::DisplayRole) const; CommonData* m_pCommonData; //void selectTopLeft(); // makes current and selects the element in the top-left corner and emits a change signal regardless of the element that was selected before; makes current the default invalid index (-1,-1) if the table is empty void selectRow(int nCrtRow, const std::vector& vnSel = std::vector()); // makes current and selects the specified row and emits a change signal regardless of the element that was selected before; makes current the default invalid index (-1,-1) if the table is empty //void matchSelToStreams(); void matchSelToNotes(); void fixSelection(); // deselects cells that are selected but are on a different row from the "current" cell and selects the file name signals: void currentFileChanged(); public slots: void onFilesGSelChanged(); }; //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== //class FilesGDelegate : public QAbstractItemDelegate class FilesGDelegate : public QItemDelegate { Q_OBJECT public: FilesGDelegate(CommonData* pCommonData, QObject* pParent) : QItemDelegate(pParent), m_pCommonData(pCommonData) {} /*override*/ void paint(QPainter* pPainter, const QStyleOptionViewItem& option, const QModelIndex& index) const; /*override*/ QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const; CommonData* m_pCommonData; }; class FileHeaderView : public QHeaderView { Q_OBJECT /*override*/ void paintSection(QPainter* pPainter, const QRect& rect, int nLogicalIndex) const; /*override*/ void mouseMoveEvent(QMouseEvent* pEvent); CommonData* m_pCommonData; public: FileHeaderView(CommonData* pCommonData, QWidget* pParent); }; #endif // #ifndef FilesModelH MP3Diags-1.2.02/src/FullSizeImgDlg.h0000644000175000001440000000351211304150114015717 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef FullSizeImgDlgH #define FullSizeImgDlgH #include #include "CommonTypes.h" class FullSizeImgDlg : public QDialog { Q_OBJECT const ImageInfo& m_imageInfo; public: FullSizeImgDlg(QWidget* pParent, const ImageInfo& imageInfo); public slots: void onCopy(); }; #endif // #ifndef WidgetsH MP3Diags-1.2.02/src/ScanDlgImpl.h0000644000175000001440000000435011230111215015231 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef ScanDlgImplH #define ScanDlgImplH #include #include "ui_Scan.h" class CommonData; class CheckedDirModel; class ScanDlgImpl : public QDialog, private Ui::ScanDlg { Q_OBJECT CommonData* m_pCommonData; CheckedDirModel* m_pDirModel; public: ScanDlgImpl(QWidget* pParent, CommonData* pCommonData); bool run(bool& bForce); // if returning true, it also calls CommonData::setDirs() ~ScanDlgImpl(); /*$PUBLIC_FUNCTIONS$*/ public slots: /*$PUBLIC_SLOTS$*/ protected: /*$PROTECTED_FUNCTIONS$*/ protected slots: /*$PROTECTED_SLOTS$*/ void on_m_pScanB_clicked() { accept(); } void on_m_pCancelB_clicked() { reject(); } void onShow(); void onHelp(); }; #endif MP3Diags-1.2.02/src/DirFilterDlgImpl.cpp0000644000175000001440000002541011724615670016612 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include "DirFilterDlgImpl.h" #include "CommonData.h" #include "Helpers.h" #include "StoredSettings.h" using namespace std; using namespace pearl; //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== class DirListElem : public ListElem { /*override*/ string getText(int /*nCol*/) const { return m_strDir; } string m_strDir; // uses native separators //CommonData* m_pCommonData; public: DirListElem(const string& strDir) : m_strDir(strDir) {} const string& getDir() const { return m_strDir; } }; //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== static string addSepToRoot(const string& s) { #ifndef WIN32 return s; #else //ttt2 if (s.size() != 2) { return s; } return s + "\\"; #endif } static string removeSepFromRoot(const string& s) { #ifndef WIN32 return s; #else //ttt2 if (s.size() != 3) { return s; } return s.substr(0, 2); #endif } #ifndef WIN32 static char getDrive(const string&) { return '.'; } #else //ttt2 static char getDrive(const string& s) { CB_ASSERT(s.size() >= 2); return s[0]; } #endif void DirFilterDlgImpl::populateLists() { TRACER("DirFilterDlgImpl::populateLists"); const set& sDirs (m_pCommonData->getAllDirs()); if (sDirs.empty()) { return; } map > mCommonDirs; for (set::const_iterator it = sDirs.begin(), begin = sDirs.begin(), end = sDirs.end(); it != end; ++it) { const string strDir (*it + getPathSepAsStr()); char d (getDrive(strDir)); if (0 == mCommonDirs.count(d)) { mCommonDirs[d] = make_pair(strDir, 0); } else { string s (mCommonDirs[d].first); int j (0); for (int n = min(cSize(s), cSize(strDir)); j < n; ++j) { if (strDir[j] != s[j]) { break; } } for (; j >= 0 && (getPathSep() != strDir[j] || getPathSep() != s[j]); --j) {} // a bug in this line was fixed by Mario Schwalbe on December 16 2010 s.erase(j + 1); // it's OK for j==-1 mCommonDirs[d] = make_pair(s, 0); // "0" doesn't matter //if (cSize(strCommonDir) <= 1) { break; } } } //ttt2 strCommonDir should be drive-specific on Wnd set sDirsAndParents (sDirs); for (map >::iterator it = mCommonDirs.begin(), end = mCommonDirs.end(); it != end; ++it) { string s (it->second.first); string::size_type nCommonSize (s.size()); if (nCommonSize > 0) { CB_ASSERT (getPathSep() == s[nCommonSize - 1]); s.erase(nCommonSize - 1); --nCommonSize; mCommonDirs[getDrive(s)] = make_pair(s, nCommonSize); if (nCommonSize > 0) // this is always true on Windows { sDirsAndParents.insert(s); } } } for (set::const_iterator it = sDirs.begin(), end = sDirs.end(); it != end; ++it) { const string& strDir(*it); CB_ASSERT(beginsWith(strDir, mCommonDirs[getDrive(strDir)].first)); int nCommonSize (mCommonDirs[getDrive(strDir)].second); string::size_type k (nCommonSize + 1); for (;;) { k = strDir.find(getPathSep(), k); if (string::npos == k) { break; } sDirsAndParents.insert(strDir.substr(0, k)); ++k; } } for (set::iterator it = sDirsAndParents.begin(), end = sDirsAndParents.end(); it != end; ++it) { m_vpOrigAll.push_back(new DirListElem(addSepToRoot(toNativeSeparators(*it)))); } vector vAll; vAll.insert(vAll.end(), sDirsAndParents.begin(), sDirsAndParents.end()); for (int i = 0, n = cSize(m_pCommonData->m_filter.getDirs()); i < n; ++i) { const string& s (m_pCommonData->m_filter.getDirs()[i]); int k (lower_bound(vAll.begin(), vAll.end(), s) - vAll.begin()); // CB_ASSERT (k < cSize(sDirsAndParents) && vAll[k] == s); // !!! wrong assert - m_pCommonData->m_filter is serialized, so it can keep old values, which don't get actualized as the scanned directories change if (k < cSize(sDirsAndParents) && vAll[k] == s) { m_vOrigSel.push_back(k); } // else - ignore; this old dir is no longer part of the scanned dirs } m_vSel = m_vOrigSel; } //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== DirFilterDlgImpl::DirFilterDlgImpl(CommonData* pCommonData, QWidget* pParent /* =0*/) : QDialog(pParent, getDialogWndFlags()), ListPainter(convStr(tr(""))), m_pCommonData(pCommonData) { TRACER("DirFilterDlgImpl constr"); setupUi(this); populateLists(); m_pListHldr->setLayout(new QHBoxLayout()); m_pDoubleList = new DoubleList( *this, DoubleList::RESTORE_OPEN, DoubleList::SINGLE_UNSORTABLE, convStr(tr("Available folders")), convStr(tr("Include folders")), this); m_pListHldr->layout()->addWidget(m_pDoubleList); m_pListHldr->layout()->setContentsMargins(0, 0, 0, 0); int nWidth, nHeight; m_pCommonData->m_settings.loadDirFilterSettings(nWidth, nHeight); if (nWidth > 400 && nHeight > 400) { resize(nWidth, nHeight); } else { defaultResize(*this); } connect(m_pDoubleList, SIGNAL(avlDoubleClicked(int)), this, SLOT(onAvlDoubleClicked(int))); { QAction* p (new QAction(this)); p->setShortcut(QKeySequence("F1")); connect(p, SIGNAL(triggered()), this, SLOT(onHelp())); addAction(p); } } DirFilterDlgImpl::~DirFilterDlgImpl() { clearPtrContainer(m_vpOrigAll); } #if 0 void DirFilterDlgImpl::logState(const char* /*szPlace*/) const { cout << szPlace << /*": m_filter.m_vSelDirs=" << m_pCommonData->m_filter.m_vSelDirs.size() << " m_availableDirs.m_vstrDirs=" << m_availableDirs.m_vstrDirs.size() << " m_selectedDirs.m_vSelDirs=" << m_selectedDirs.m_vstrDirs.size() <<*/ endl; } #endif //--------------------------------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------------------------------- void DirFilterDlgImpl::on_m_pOkB_clicked() { vector v; for (int i = 0, n = cSize(m_vSel); i < n; ++i) { v.push_back(fromNativeSeparators(removeSepFromRoot(dynamic_cast(m_vpOrigAll[m_vSel[i]])->getDir()))); } m_pCommonData->m_filter.setDirs(v); m_pCommonData->m_settings.saveDirFilterSettings(width(), height()); accept(); } void DirFilterDlgImpl::on_m_pCancelB_clicked() { reject(); } void DirFilterDlgImpl::onAvlDoubleClicked(int nRow) { vector v; const DirListElem* p (dynamic_cast(m_vpOrigAll[getAvailable()[nRow]])); CB_ASSERT(0 != p); v.push_back(fromNativeSeparators(removeSepFromRoot(p->getDir()))); m_pCommonData->m_filter.setDirs(v); accept(); } //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== /*override*/ string DirFilterDlgImpl::getTooltip(TooltipKey eTooltipKey) const { switch (eTooltipKey) { case SELECTED_G: return "";//"Notes to be included"; case AVAILABLE_G: return "";//"Available notes"; case ADD_B: return convStr(tr("Add selected folders")); case DELETE_B: return convStr(tr("Remove selected folders")); case ADD_ALL_B: return "";//"Add all folders"; case DELETE_ALL_B: return "";//"Remove all folders"; case RESTORE_DEFAULT_B: return ""; case RESTORE_OPEN_B: return convStr(tr("Restore lists to the configuration they had when the window was open")); default: CB_ASSERT(false); } } /*override*/ Qt::Alignment DirFilterDlgImpl::getAlignment(int /*nCol*/) const { return Qt::AlignTop | Qt::AlignLeft; } /*override*/ void DirFilterDlgImpl::reset() { CB_ASSERT(false); } /*override*/ int DirFilterDlgImpl::getHdrHeight() const { return CELL_HEIGHT; } void DirFilterDlgImpl::onHelp() { openHelp("180_folder_filter.html"); } MP3Diags-1.2.02/src/NotesModel.cpp0000644000175000001440000002321511724340425015520 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include "NotesModel.h" #include "FilesModel.h" #include "StreamsModel.h" #include "DataStream.h" #include "CommonData.h" using namespace std; //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== NotesModel::NotesModel(CommonData* pCommonData) : QAbstractTableModel(pCommonData->m_pNotesG), m_pCommonData(pCommonData) {} /*override*/ int NotesModel::rowCount(const QModelIndex&) const { return cSize(m_pCommonData->getCrtNotes()); } /*override*/ int NotesModel::columnCount(const QModelIndex&) const { return 3; } /*override*/ QVariant NotesModel::data(const QModelIndex& index, int nRole) const { LAST_STEP("NotesModel::data()"); int j (index.column()); //if (nRole == Qt::SizeHintRole && j > 0) { return QSize(CELL_WIDTH - 1, CELL_HEIGHT - 1); } // !!! "-1" so one pixel can be used to draw the grid //if (nRole == Qt::SizeHintRole) { return QSize(CELL_WIDTH - 10, CELL_HEIGHT - 10); } // !!! "-1" so one pixel can be used to draw the grid if (!index.isValid() || nRole != Qt::DisplayRole) { return QVariant(); } int nRow (index.row()); int nSize (cSize(m_pCommonData->getCrtNotes())); CB_ASSERT (nRow >= 0 || nRow < nSize); const Note* pNote (m_pCommonData->getCrtNotes()[nRow]); //printf("Note at %p\n", pNote); switch (j) { case 0: return getNoteLabel(pNote); case 1: { const string& s (pNote->getDetail()); if (s.empty()) { return Notes::tr(pNote->getDescription()); } return convStr(s); } case 2: return convStr(pNote->getPosHex()); //return "mmmiiiWWWlll"; //return "ABCDEFGHIJKLM"; default: CB_ASSERT(false); } } /*override*/ QVariant NotesModel::headerData(int nSection, Qt::Orientation eOrientation, int nRole /* = Qt::DisplayRole*/) const { LAST_STEP("NotesModel::headerData"); if (nRole == Qt::SizeHintRole) { /*QVariant v (QAbstractTableModel::headerData(nSection, eOrientation, nRole)); // !!! doesn't work because QAbstractTableModel::headerData always returns an invalid value [...] */ return getNumVertHdrSize(cSize(m_pCommonData->getCrtNotes()), eOrientation); } if (nRole != Qt::DisplayRole) { return QVariant(); } if (Qt::Horizontal == eOrientation) { switch (nSection) { case 0: return tr("L"); case 1: return tr("Note"); case 2: return tr("Address"); default: CB_ASSERT (false); } } return nSection + 1; } void NotesModel::matchSelToMain() { QModelIndexList lSelFiles (m_pCommonData->m_pFilesG->selectionModel()->selection().indexes()); set sSel; for (QModelIndexList::iterator it = lSelFiles.begin(), end = lSelFiles.end(); it != end; ++it) { int nCol (it->column()); if (nCol > 0) // skip file name { sSel.insert(nCol - 1); } } QItemSelectionModel* pNotesSelModel (m_pCommonData->m_pNotesG->selectionModel()); pNotesSelModel->clearSelection(); bool bFirstFound (false); for (int i = 0, nNoteCnt = cSize(m_pCommonData->getCrtNotes()); i < nNoteCnt; ++i) { const Note* pNote (m_pCommonData->getCrtNotes()[i]); int nPos (m_pCommonData->findPos(pNote)); if (sSel.count(nPos) > 0) { if (!bFirstFound) { bFirstFound = true; m_pCommonData->m_pNotesG->setCurrentIndex(index(i, 0)); } pNotesSelModel->select(index(i, 0), QItemSelectionModel::Select | QItemSelectionModel::Rows); } } } void NotesModel::onNotesGSelChanged() { NonblockingGuard g (m_pCommonData->m_bChangeGuard); if (!g) { return; } m_pCommonData->m_pFilesModel->matchSelToNotes(); m_pCommonData->m_pStreamsModel->matchSelToNotes(); } static bool isInList(const Note* pNote, vector& vStreams) { if (-1 == pNote->getPos()) { return false; } for (int i = 0, n = cSize(vStreams); i < n; ++i) // ttt2 use a binary search; the streams are sorted; { if (pNote->getPos() >= vStreams[i]->getPos() && pNote->getPos() < vStreams[i]->getPos() + vStreams[i]->getSize()) { //cout << "accepted " << pNote->getPosHex() << " " << pNote->getDescription() << endl; return true; } } return false; } void NotesModel::matchSelToStreams() { QModelIndexList lSelStreams (m_pCommonData->m_pStreamsG->selectionModel()->selection().indexes()); const vector& vCrtStreams (m_pCommonData->getViewHandlers()[m_pCommonData->getFilesGCrtRow()]->getStreams()); vector vSelStreams; for (QModelIndexList::iterator it = lSelStreams.begin(), end = lSelStreams.end(); it != end; ++it) { int nRow (it->row()); vSelStreams.push_back(vCrtStreams[nRow]); } QItemSelectionModel* pNotesSelModel (m_pCommonData->m_pNotesG->selectionModel()); pNotesSelModel->clearSelection(); bool bFirstFound (false); for (int i = 0, nNoteCnt = cSize(m_pCommonData->getCrtNotes()); i < nNoteCnt; ++i) { const Note* pNote (m_pCommonData->getCrtNotes()[i]); if (isInList(pNote, vSelStreams)) { if (!bFirstFound) { bFirstFound = true; m_pCommonData->m_pNotesG->setCurrentIndex(index(i, 0)); } pNotesSelModel->select(index(i, 0), QItemSelectionModel::Select | QItemSelectionModel::Rows); } } } //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== NotesGDelegate::NotesGDelegate(CommonData* pCommonData) : MultiLineTvDelegate(pCommonData->m_pNotesG), m_pCommonData(pCommonData) { } /*override*/ void NotesGDelegate::paint(QPainter* pPainter, const QStyleOptionViewItem& option, const QModelIndex& index) const { int nRow (index.row()); //f1(); pPainter->save(); CB_ASSERT (nRow >= 0 && nRow < cSize(m_pCommonData->getCrtNotes())); const Note* pNote (m_pCommonData->getCrtNotes().at(nRow)); QStyleOptionViewItemV2 myOption (option); //myOption.palette.setColor(QPalette::Base, pal.color(QPalette::Disabled, QPalette::Window)); // !!! the palette doesn't matter; fillRect() should be called QColor col; double d1, d2; m_pCommonData->getNoteColor(*pNote, vector(), col, d1, d2); pPainter->fillRect(myOption.rect, QBrush(col)); if (0 == index.column()) { myOption.displayAlignment |= Qt::AlignHCenter; //myOption.font = m_pCommonData->getGeneralFont(); //myOption.font.setPixelSize(9); if (Note::ERR == pNote->getSeverity()) { myOption.palette.setColor(QPalette::Text, ERROR_PEN_COLOR()); } else if (Note::SUPPORT == pNote->getSeverity()) { myOption.palette.setColor(QPalette::Text, SUPPORT_PEN_COLOR()); } } if (2 == index.column()) { myOption.displayAlignment |= Qt::AlignRight; myOption.font = m_pCommonData->getFixedFont(); } //myOption.displayAlignment = Qt::AlignRight | Qt::AlignVCenter; #if 0 //myOption.palette.setColor(QPalette::Base, pal.color(QPalette::Disabled, QPalette::Window)); QString qstrText (index.model()->data(index, Qt::DisplayRole).toString()); //qstrText = "QQQ"; drawDisplay(pPainter, myOption, myOption.rect, qstrText); // ttt3 see why selecting cells in column 0 or 1 doesn't make column 2 selected unless there's an address for the note; drawFocus(pPainter, myOption, myOption.rect); #else MultiLineTvDelegate::paint(pPainter, myOption, index); #endif pPainter->restore(); } MP3Diags-1.2.02/src/FilesModel.cpp0000644000175000001440000005120111727124254015471 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include "FilesModel.h" #include "CommonData.h" #include "NotesModel.h" #include "StreamsModel.h" using namespace std; //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== FilesModel::FilesModel(CommonData* pCommonData) : QAbstractTableModel(pCommonData->m_pFilesG), m_nPrevCurrentRow(-1), m_pCommonData(pCommonData) { } //static int RR (0); //#define USE_10000 // not quite OK to use, but allows some quick testing /*override*/ int FilesModel::rowCount(const QModelIndex&) const { #ifdef USE_10000 return 10000; #else return cSize(m_pCommonData->getViewHandlers()); #endif } /*override*/ int FilesModel::columnCount(const QModelIndex&) const { return m_pCommonData->getUniqueNotes().getFltCount() + 1; } /*extern*/ int CELL_WIDTH (1); // these get calculated based on the current font; however, they are also used /*extern*/ int CELL_HEIGHT (1); /*override*/ QVariant FilesModel::data(const QModelIndex& index, int nRole) const { LAST_STEP("FilesModel::headerData"); if (!index.isValid()) { return QVariant(); } int j (index.column()); if (nRole == Qt::SizeHintRole && j > 0) { return QSize(CELL_WIDTH - 1, CELL_HEIGHT - 1); } // !!! "-1" so one pixel can be used to draw the grid //qDebug("cw %d", CELL_WIDTH); #ifdef USE_10000 if (index.row() >= cSize(m_pCommonData->getFltHandlers())) { return QVariant(); } #endif const Mp3Handler* pHndl (m_pCommonData->getViewHandlers()[index.row()]); if (nRole == Qt::ToolTipRole && 0 == j) { QString s (pHndl->getUiName()); //QFontMetrics fm (QApplication::fontMetrics()); QFontMetrics fm (m_pCommonData->m_pFilesG->fontMetrics()); // !!! no need to use "QApplication::fontMetrics()" int nWidth (fm.width(s)); if (nWidth + 10 < m_pCommonData->m_pFilesG->horizontalHeader()->sectionSize(0)) // ttt2 "10" is hard-coded { //return QVariant(); return ""; // !!! with "return QVariant()" the previous tooltip remains until the cursor moves over another cell that has a tooltip }//*/ return s; } if (nRole != Qt::DisplayRole && nRole != Qt::ToolTipRole) { return QVariant(); } if (0 == j) { return pHndl->getUiName(); } const NoteColl& notes (pHndl->getNotes()); for (int i = 0, n = cSize(notes.getList()); i < n; ++i) // ttt2 poor performance { const Note* pNote (notes.getList()[i]); if (j - 1 == m_pCommonData->findPos(pNote)) { return nRole == Qt::ToolTipRole ? makeMultiline(Notes::tr(m_pCommonData->getUniqueNotes().getFlt(j - 1)->getDescription())) : "x"; } } return ""; } /*override*/ QVariant FilesModel::headerData(int nSection, Qt::Orientation eOrientation, int nRole /* = Qt::DisplayRole*/) const { LAST_STEP("FilesModel::headerData"); #if 0 // 2008.07.28: no longer used because resizeColumnsToContents() cannot be called (it takes too long) if (nRole == Qt::SizeHintRole) { /*QVariant v (QAbstractTableModel::headerData(nSection, eOrientation, nRole)); // !!! doesn't work because QAbstractTableModel::headerData always returns an invalid value [...] */ if (eOrientation == Qt::Vertical) { /*QFontMetrics fm (m_pCommonData->m_pFilesG->fontMetrics()); double d (1.01 + log10(double(m_pCommonData->getFltHandlers().size()))); int n (d); QString s (n, QChar('9')); //QSize size (fm.boundingRect(r, Qt::AlignTop | Qt::TextWordWrap, s).size()); int nWidth (fm.width(s)); //return QSize(50, CELL_HEIGHT); return QSize(nWidth + 10, CELL_HEIGHT); //tttx perhaps use data cell height of 17 (fontMetrics().height()) (so header cell height is 18 ("+1" to account for the grid) //ttt2 hard-coded "10"*/ getNumVertHdrSize ... } return QVariant(); } #endif if (nRole != Qt::DisplayRole) { return QVariant(); } if (Qt::Horizontal == eOrientation) { if (0 == nSection) { return tr("File name"); } const vector& v (m_pCommonData->getUniqueNotes().getFltVec()); int nSize (cSize(v)); if (nSection > nSize) { return ""; //ttt2 see why is this needed; it wasn't so until version 134 (handling of MainFormDlgImpl::resizeEvent() or of MainFormDlgImpl::on_m_pMainTabWidget_currentChanged() seem to have something to do with this) } return getNoteLabel(v[nSection - 1]); } return nSection + 1; } // makes current and selects the specified row and emits a change signal regardless of the element that was selected before; makes current the default invalid index (-1,-1) if the table is empty; void FilesModel::selectRow(int nRow, const vector& vnSel /* = std::vector()*/) { QItemSelectionModel* pSelModel (m_pCommonData->m_pFilesG->selectionModel()); //m_pCommonData->printFilesCrt(); { NonblockingGuard g (m_pCommonData->m_bChangeGuard); if (!g) { return; } pSelModel->clear(); // 2008.07.08: this doesn't work quite as expected: it does trigger SelectionChanged, but it changes the current item after emitting the signal, so whoever catches it, doesn't know what the current item is (it's probably in the second param of CurrentChanged, but that doesn't help SelectionChanged, which has QItemSelection params, which don't seem to offer any way to get at the "current" index) } //m_pCommonData->printFilesCrt(); emit layoutChanged(); m_nPrevCurrentRow = -2; // to make sure that onFilesGSelChanged() updates the notes and streams regardless of whether there are any files in m_pFilesG or not if (!m_pCommonData->getViewHandlers().empty()) { //pSelModel->select(index(0, 0), QItemSelectionModel::Current); //pSelModel->select(index(0, 0), QItemSelectionModel::Select); //m_pCommonData->printFilesCrt(); m_pCommonData->m_pFilesG->setCurrentIndex(index(nRow, 0)); // this sometimes calls onFilesGSelChanged(), but not always; //m_pCommonData->printFilesCrt(); } if (-2 == m_nPrevCurrentRow) { onFilesGSelChanged(); } for (int i = 0, n = cSize(vnSel); i < n; ++i) { pSelModel->select(pSelModel->model()->index(vnSel[i], 0), QItemSelectionModel::Select); } } // this gets called more often than expected: besides the cases when it gets called recursively and exits immediately, it is called on MousePressed, MouseReleased and Clicked (which is sent at the later stage of MouseReleased processing) void FilesModel::onFilesGSelChanged() { NonblockingGuard g (m_pCommonData->m_bChangeGuard); if (!g) { return; } //m_pCommonData->printFilesCrt(); int nGridCrt (m_pCommonData->getFilesGCrtRow()); if (m_nPrevCurrentRow != nGridCrt) { //m_pCommonData->m_pNotesModel->updateCurrentNotes(); //m_pCommonData->m_pStreamsModel->updateCurrentStreams(); emit currentFileChanged(); m_nPrevCurrentRow = nGridCrt; } fixSelection(); m_pCommonData->m_pNotesModel->matchSelToMain(); m_pCommonData->m_pStreamsModel->matchSelToNotes(); } void FilesModel::fixSelection() // deselects cells that are selected but are on a different row from the "current" cell and selects the file name { // it works OK when getFltHandlers() is empty QItemSelectionModel* pSelModel (m_pCommonData->m_pFilesG->selectionModel()); QModelIndexList lstSel (pSelModel->selection().indexes()); QModelIndex crt (m_pCommonData->m_pFilesG->selectionModel()->currentIndex()); int nCrtRow (crt.row()); int nCrtCol (crt.column()); if (0 == nCrtCol) { for (QModelIndexList::iterator it = lstSel.begin(), end = lstSel.end(); it != end; ++it) { if (0 != it->column()) { pSelModel->select(*it, QItemSelectionModel::Deselect); } } } else { set sSelectableColumns; sSelectableColumns.insert(0); for (int i = 0, n = cSize(m_pCommonData->getCrtNotes()); i < n; ++i) // ttt2 poor performance { const Note* pNote (m_pCommonData->getCrtNotes()[i]); sSelectableColumns.insert(m_pCommonData->findPos(pNote) + 1); } for (QModelIndexList::iterator it = lstSel.begin(), end = lstSel.end(); it != end; ++it) { if ((it->row() != nCrtRow && 0 != it->column()) || 0 == sSelectableColumns.count(it->column())) { pSelModel->select(*it, QItemSelectionModel::Deselect); } } if (nCrtRow >= 0) { pSelModel->select(index(nCrtRow, 0), QItemSelectionModel::Select); } } } void FilesModel::matchSelToNotes() { QItemSelectionModel* pNotesSelModel (m_pCommonData->m_pNotesG->selectionModel()); QModelIndexList notesLstSel (pNotesSelModel->selection().indexes()); set sSel; for (QModelIndexList::iterator it = notesLstSel.begin(), end = notesLstSel.end(); it != end; ++it) { //cout << "cell sel at " << it->row() << "x" << it->column() << endl; const Note* pNote (m_pCommonData->getCrtNotes()[it->row()]); int nPos (m_pCommonData->findPos(pNote)); sSel.insert(nPos); } m_pCommonData->m_pFilesG->selectionModel()->clearSelection(); for (set::const_iterator it = sSel.begin(), end = sSel.end(); it != end; ++it) { m_pCommonData->m_pFilesG->selectionModel()->select(index(m_pCommonData->getFilesGCrtRow(), *it + 1), QItemSelectionModel::Select); //cout << "selecting " << m_pCommonData->getFilesGCrtRow() << "x" << *it + 1 << endl; } m_pCommonData->m_pFilesG->selectionModel()->select(index(m_pCommonData->getFilesGCrtRow(), 0), QItemSelectionModel::Select); } //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== /*override*/ void FilesGDelegate::paint(QPainter* pPainter, const QStyleOptionViewItem& option, const QModelIndex& index) const { // adapted from Trolltech's Pixelator (GPL V2 or V3) http://doc.trolltech.com/4.3/itemviews-pixelator.html int nCol (index.column()); if (0 == nCol) { return QItemDelegate::paint(pPainter, option, index); } //int nRow (index.row()); double r ((index.model()->data(index, Qt::DisplayRole).toString() == "" ? 0 : (CELL_HEIGHT - 0)/5)); pPainter->save(); pPainter->setRenderHint(QPainter::Antialiasing, true); pPainter->setPen(Qt::NoPen); const Note* pNote (m_pCommonData->getUniqueNotes().getFlt(nCol - 1)); bool bSel (0 != (QStyle::State_Selected & option.state)); bool bCrt (0 != (QStyle::State_HasFocus & option.state)); bool bActive (0 != (QStyle::State_Active & option.state)); // "active" is true if the parent window has keyboard focus /*QColor colSev (getNoteColor(*pNote)); //ttt2 perhaps try to derive all these colors from the global pallette (e.g. option.palette.highlight(), option.palette.highlightedText(), ...) QColor colBkg (colSev); QColor colFg (option.palette.color(QPalette::Active, QPalette::Highlight)); //ttt3 not necessarily "Active" //QColor colFg (Qt::black); //ttt3 not necessarily "Active" if (colFg.green() >= 160 && colFg.red() >= 160) { colFg = QColor(0, 0, 0); } if (bSel) { QColor c (colBkg); colBkg = colFg; colFg = c; }*/ //ttt2 perhaps try to derive all these colors from the global pallette (e.g. option.palette.highlight(), option.palette.highlightedText(), ...) QColor colNote; double dGradStart, dGradEnd; m_pCommonData->getNoteColor(*pNote, m_pCommonData->getUniqueNotes().getFltVec(), colNote, dGradStart, dGradEnd); QColor colSel (option.palette.color(QPalette::Active, QPalette::Highlight)); //ttt3 not necessarily "Active" //qDebug("gr %f %f", dGradStart, dGradEnd); QColor colFg, colBkg; colBkg = bSel ? colSel : colNote; if (colSel.green() >= 160 && colSel.red() >= 160) { // for better contrast we use something dark if the "highlight" color is light //colFg = QColor(0, 0, 0); colFg = option.palette.color(QPalette::Active, QPalette::HighlightedText); } else { colFg = bSel ? colNote : colSel; } //colBkg = QColor(220, 255, 230); QLinearGradient grad (option.rect.x(), 0, option.rect.x() + option.rect.width(), 0); /*switch(nCol % 4) { case 0: grad.setColorAt(0, colBkg.lighter(120)); grad.setColorAt(0.4, colBkg); grad.setColorAt(0.6, colBkg); grad.setColorAt(1, colBkg.darker(120)); break; case 1: grad.setColorAt(0, colBkg.lighter(120)); grad.setColorAt(0.4, colBkg); grad.setColorAt(0.6, colBkg); grad.setColorAt(1, colBkg); break; case 2: grad.setColorAt(0, colBkg); grad.setColorAt(0.4, colBkg); grad.setColorAt(0.6, colBkg); grad.setColorAt(1, colBkg); break; case 3: grad.setColorAt(0, colBkg); grad.setColorAt(0.4, colBkg); grad.setColorAt(0.6, colBkg); grad.setColorAt(1, colBkg.darker(120)); break; }*/ /* switch(nCol % 4) { case 0: configureGradient(grad, colBkg, 0, 1); break; case 1: configureGradient(grad, colBkg, 0, 0.33); break; case 2: configureGradient(grad, colBkg, 0.33, 0.67); break; case 3: configureGradient(grad, colBkg, 0.67, 1); break; } */ configureGradient(grad, colBkg, dGradStart, dGradEnd); //pPainter->fillRect(option.rect, colBkg); pPainter->fillRect(option.rect, grad); if (0 != r) { pPainter->setBrush(QBrush(colFg)); pPainter->drawEllipse( QRectF(option.rect.x() + option.rect.width()/2.0 - r, option.rect.y() + option.rect.height()/2.0 - r, 2*r, 2*r)); } if (bCrt && bActive) { pPainter->setRenderHint(QPainter::Antialiasing, false); const int ADJ (0); QRect r (option.rect); r.adjust(ADJ, ADJ, -ADJ - 1, -ADJ - 1); pPainter->setBrush(QBrush(Qt::NoBrush)); QPen pen (pPainter->pen()); pen.setStyle(Qt::DotLine); //pen.setColor(Qt::black); pen.setColor(colFg); pPainter->setPen(pen); pPainter->drawRect(r); } pPainter->restore(); } /*override*/ QSize FilesGDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const { if (!index.isValid()) { return QSize(); } if (index.column() > 0) { return QItemDelegate::sizeHint(option, index); } int nMargin (QApplication::style()->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1); int j (index.column()); int nColWidth (((const QTableView*)parent())->horizontalHeader()->sectionSize(j)); QRect r (0, 0, nColWidth - 2*nMargin - 1, 10000); QSize res (option.fontMetrics.boundingRect(r, Qt::AlignTop | Qt::TextWordWrap, index.data(Qt::DisplayRole).toString()).size()); res.setWidth(nColWidth); return res; } //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== FileHeaderView::FileHeaderView(CommonData* pCommonData, QWidget* pParent) : QHeaderView(Qt::Horizontal, pParent), m_pCommonData(pCommonData) { } /*override*/ void FileHeaderView::mouseMoveEvent(QMouseEvent* pEvent) { int k (logicalIndexAt(pEvent->x(), pEvent->y())); if (k <= 0) { setToolTip(""); return; } --k; setToolTip(makeMultiline(Notes::tr(m_pCommonData->getUniqueNotes().getFlt(k)->getDescription()))); } static int s_nCut (0); int getHeaderDrawOffset() { return s_nCut; } /*override*/ void FileHeaderView::paintSection(QPainter* pPainter, const QRect& r, int nLogicalIndex) const { if (0 == nLogicalIndex) { pPainter->save(); QFont f (pPainter->font()); f.setWeight(QFont::Bold); pPainter->setFont(f); QHeaderView::paintSection(pPainter, r, nLogicalIndex); pPainter->restore(); return; } pPainter->save(); // partial copy from Qt's implementation of QHeaderView (qheaderview.cpp) QStyleOptionHeader opt; initStyleOption(&opt); opt.rect = r; opt.section = nLogicalIndex; int nVisual (visualIndex(nLogicalIndex)); if (count() == 1) opt.position = QStyleOptionHeader::OnlyOneSection; else if (nVisual == 0) opt.position = QStyleOptionHeader::Beginning; else if (nVisual == count() - 1) opt.position = QStyleOptionHeader::End; else opt.position = QStyleOptionHeader::Middle; opt.selectedPosition = QStyleOptionHeader::NotAdjacent; style()->drawControl(QStyle::CE_Header, &opt, pPainter, this); static bool s_bCutInit (false); if (!s_bCutInit) { s_bCutInit = true; int n (r.width()), m (r.height()); QRect r1 (0, 0, n, m); QImage img (n, m, QImage::Format_RGB32); QPainter pntr (&img); pntr.fillRect(r1, QColor(255, 255, 255)); opt.rect = r1; style()->drawControl(QStyle::CE_Header, &opt, &pntr); //img.save("/home/ciobi/tmp/3/hdr1.png"); m /= 2; double v1 (QColor(img.pixel(n - 3, m)).valueF()); double v2 (QColor(img.pixel(n - 2, m)).valueF()); double v3 (QColor(img.pixel(n - 1, m)).valueF()); //qDebug("%f %f %f", v1, v2, v3); if ((v1 > v2 + 0.07 && v3 > v2 + 0.07) || (v1 < v2 - 0.07 && v3 < v2 - 0.07)) { s_nCut = 3; //ttt2 hard-coded, must be kept in synch with CELL_WIDTH } // qDebug("cut: %d", s_nCut); } pPainter->restore(); pPainter->save(); pPainter->setFont(m_pCommonData->getLabelFont()); { // bold for selected QModelIndexList l (m_pCommonData->m_pFilesG->selectionModel()->selection().indexes()); for (QModelIndexList::iterator it = l.begin(); it != l.end(); ++it) { const QModelIndex& ndx (*it); if (ndx.column() == nLogicalIndex) { QFont f (pPainter->font()); f.setWeight(QFont::Bold); pPainter->setFont(f); break; } } } const Note* p (m_pCommonData->getUniqueNotes().getFltVec().at(nLogicalIndex - 1)); if (Note::ERR == p->getSeverity()) { pPainter->setPen(ERROR_PEN_COLOR()); } else if (Note::SUPPORT == p->getSeverity()) { pPainter->setPen(SUPPORT_PEN_COLOR()); } QRect r1 (r); r1.adjust(0, 0, -s_nCut, 0); pPainter->drawText(r1, Qt::AlignCenter, getNoteLabel(p)); pPainter->restore(); } MP3Diags-1.2.02/src/TagEditor.ui0000644000175000001440000005552011700300536015161 0ustar ciobiusers TagEditorDlg 0 0 993 679 MP3 Diags - Tag editor true 0 0 40 40 40 40 Qt::NoFocus Save S :/images/document-save.svg:/images/document-save.svg 36 36 true 40 40 40 40 Qt::NoFocus Reset R :/images/edit-undo.svg:/images/edit-undo.svg 36 36 true Qt::Horizontal QSizePolicy::Fixed 15 15 40 40 40 40 Qt::NoFocus Previous [Ctrl+P] < :/images/go-previous.svg:/images/go-previous.svg 36 36 Ctrl+P true Qt::NoArrow Qt::Horizontal QSizePolicy::Fixed 4 15 Folder true Qt::Horizontal QSizePolicy::Fixed 4 15 40 40 40 40 Qt::NoFocus Next [Ctrl+N] > :/images/go-next.svg:/images/go-next.svg 36 36 Ctrl+N true Qt::NoArrow Qt::Horizontal QSizePolicy::Fixed 15 15 40 40 40 40 Qt::NoFocus Query Discogs Q :/images/discogs.png:/images/discogs.png 36 36 true 40 40 40 40 Qt::NoFocus Query MusicBrainz ... :/images/musicbrainz_logo1.svg:/images/musicbrainz_logo1.svg 36 36 true Qt::Horizontal QSizePolicy::Fixed 15 15 40 40 40 40 Qt::NoFocus Toggle "Various Artists" V :/images/va_va.svg:/images/va_va.svg 36 36 true 40 40 40 40 Qt::NoFocus Change case automatically ... :/images/change-case.svg:/images/change-case.svg 36 36 true 40 40 40 40 Qt::NoFocus Copy field(s) from first line C :/images/duplicate_first.svg:/images/duplicate_first.svg 36 36 true 40 40 40 40 Qt::NoFocus Sort by track number ... :/images/sort_asc.svg:/images/sort_asc.svg 36 36 true 40 40 40 40 Qt::NoFocus Toggle "assigned" state A :/images/assgn-some.svg:/images/assgn-some.svg 36 36 true 40 40 40 40 Qt::NoFocus Paste ... :/images/edit-paste.svg:/images/edit-paste.svg 36 36 Ctrl+V true 40 40 40 40 Qt::NoFocus Edit file patterns F :/images/patterns.svg:/images/patterns.svg 36 36 true Qt::Horizontal QSizePolicy::Fixed 15 15 40 40 40 40 Qt::NoFocus Background colors ... :/images/palette.svg:/images/palette.svg 36 36 true 40 40 40 40 Qt::NoFocus Configuration ... :/images/configure.svg:/images/configure.svg 36 36 true 40 40 40 40 Qt::NoFocus Close ... :/images/dialog-close.svg:/images/dialog-close.svg 36 36 true 0 4 0 0 TextLabel 0 0 0 QAbstractItemView::NoSelection 0 TextLabel MP3Diags-1.2.02/src/Id3Transf.cpp0000644000175000001440000011027411724477005015253 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "fstream_unicode.h" #include #include "Id3Transf.h" #include "Id3V230Stream.h" #include "Id3V240Stream.h" #include "CommonData.h" #include "Mp3Manip.h" #include "Helpers.h" #include "MpegStream.h" #include "OsFile.h" //#include using namespace std; //ttt2 remove id3v1; copy id3v2 to id3v1; copy id3v1 to id3v2; // ttt2 another option: before a group of transforms is executed on a group of files, a function is called on transforms to allow them to ask config info. most won't use this ... bool Id3V2Cleaner::processId3V2Stream(Id3V2StreamBase& strm, ofstream_utf8& out) { Id3V230StreamWriter wrt (m_pCommonData->m_bKeepOneValidImg, m_pCommonData->useFastSave(), 0, strm.getFileName()); //ttt2 unify code with Id3V2Rescuer vector v (strm.getKnownFrames()); for (int i = 0; i < cSize(v); ++i) { const Id3V2Frame* pFrm (v[i]); if ('T' == pFrm->m_szName[0]) { // text frame try { string s (pFrm->getRawUtf8String()); if (!s.empty()) { wrt.addTextFrame(pFrm->m_szName, s); // ttt0 see about TXXX, which isn't text, and how it's used for normalization: Id3V2StreamBase::hasReplayGain() MP3GAIN_MINMAX } } catch (const Id3V2Frame::UnsupportedId3V2Frame&) { // add unchanged wrt.addNonOwnedFrame(pFrm); } } else if (0 == strcmp(KnownFrames::LBL_IMAGE(), pFrm->m_szName)) { CB_ASSERT (Id3V2Frame::NO_APIC != pFrm->m_eApicStatus); if (Id3V2Frame::COVER == pFrm->m_eApicStatus || Id3V2Frame::NON_COVER == pFrm->m_eApicStatus) // not sure about link; OTOH going to the tab editor will get rid of links, so we do it here as well { wrt.addNonOwnedFrame(pFrm); } } else if (pFrm->m_nMemDataSize > 0) { wrt.addNonOwnedFrame(pFrm); } } TagTimestamp time (strm.getTime()); wrt.setRecTime(time); // !!! needed to make sure the 2.3.0 format is used wrt.write(out); return !wrt.contentEqualTo(&strm); } /*override*/ Transformation::Result Id3V2Cleaner::apply(const Mp3Handler& h, const TransfConfig& transfConfig, const std::string& strOrigSrcName, std::string& strTempName) { ValueRestorer rst (strTempName); const vector& vpStreams (h.getStreams()); bool bFoundMatch (false); for (int i = 0, n = cSize(vpStreams); i < n; ++i) { if (0 != dynamic_cast(vpStreams[i])) { bFoundMatch = true; break; } } if (!bFoundMatch) { return NOT_CHANGED; } ifstream_utf8 in (h.getName().c_str(), ios::binary); bool bChanged (false); { // temp transfConfig.getTempName(strOrigSrcName, getActionName(), strTempName); ofstream_utf8 out (strTempName.c_str(), ios::binary); in.seekg(0); for (int i = 0, n = cSize(vpStreams); i < n; ++i) { DataStream* p (vpStreams[i]); Id3V2StreamBase* pId3V2 (dynamic_cast(p)); if (0 != pId3V2) { bool b (processId3V2Stream(*pId3V2, out)); bChanged = bChanged || b; } else { p->copy(in, out); } } } if (bChanged) { rst.setOk(true); return CHANGED_NO_RECALL; } deleteFile(strTempName); return NOT_CHANGED; } //======================================================================================================================== //======================================================================================================================== //======================================================================================================================== // APIC: if it can be loaded or is unsupported - keep; on error - dump // remove empty frames // rewrite text frames to discard null terminators bool Id3V2Rescuer::processId3V2Stream(Id3V2StreamBase& strm, ofstream_utf8* pOut) // nothing gets written if pOut is 0 { Id3V230StreamWriter wrt (m_pCommonData->m_bKeepOneValidImg, m_pCommonData->useFastSave(), 0, strm.getFileName()); const vector& vpFrames (strm.getFrames()); for (int i = 0, n = cSize(vpFrames); i < n; ++i) { const Id3V2Frame* pFrm (vpFrames[i]); if ('T' == pFrm->m_szName[0]) { // text frame try { string s (pFrm->getRawUtf8String()); if (!s.empty()) { wrt.addTextFrame(pFrm->m_szName, s); } } catch (const Id3V2Frame::UnsupportedId3V2Frame&) { // add unchanged wrt.addNonOwnedFrame(pFrm); } } else if (0 == strcmp(KnownFrames::LBL_IMAGE(), pFrm->m_szName)) { CB_ASSERT (Id3V2Frame::NO_APIC != pFrm->m_eApicStatus); if (Id3V2Frame::COVER == pFrm->m_eApicStatus || Id3V2Frame::NON_COVER == pFrm->m_eApicStatus) // not sure about link; OTOH going to the tab editor will get rid of links, so we do it here as well { wrt.addNonOwnedFrame(pFrm); } } else if (pFrm->m_nMemDataSize > 0) { wrt.addNonOwnedFrame(pFrm); } } TagTimestamp time (strm.getTime()); wrt.setRecTime(time); if (0 != pOut) { wrt.write(*pOut); } return !wrt.contentEqualTo(&strm); } //ttt2 consider empty frames; or identifying valid id3v2 frames instead of stopping at first error, also looking in next unknown streams; /*override*/ Transformation::Result Id3V2Rescuer::apply(const Mp3Handler& h, const TransfConfig& transfConfig, const std::string& strOrigSrcName, std::string& strTempName) { ValueRestorer rst (strTempName); const vector& vpStreams (h.getStreams()); bool bFoundMatch (false); for (int i = 0, n = cSize(vpStreams); i < n; ++i) { if (0 != dynamic_cast(vpStreams[i]) || 0 != dynamic_cast(vpStreams[i])) { bFoundMatch = true; break; } } if (!bFoundMatch) { return NOT_CHANGED; } ifstream_utf8 in (h.getName().c_str(), ios::binary); { for (int i = 0, n = cSize(vpStreams); i < n; ++i) { DataStream* p (vpStreams[i]); Id3V2StreamBase* pId3V2 (dynamic_cast(p)); BrokenDataStream* pBrk (dynamic_cast(p)); if (0 != pId3V2) { if (processId3V2Stream(*pId3V2, 0)) { goto e1; } } else if (0 != pBrk && (pBrk->getBaseName() == Id3V230Stream::getClassDisplayName() || pBrk->getBaseName() == Id3V240Stream::getClassDisplayName())) { goto e1; } } return NOT_CHANGED; } e1: bool bChanged (false); bool bRecall (false); { // temp transfConfig.getTempName(strOrigSrcName, getActionName(), strTempName); ofstream_utf8 out (strTempName.c_str(), ios::binary); in.seekg(0); for (int i = 0, n = cSize(vpStreams); i < n; ++i) { DataStream* p (vpStreams[i]); Id3V2StreamBase* pId3V2 (dynamic_cast(p)); BrokenDataStream* pBrk (dynamic_cast(p)); if (0 != pId3V2) { bool b (processId3V2Stream(*pId3V2, &out)); bChanged = bChanged || b; } else if (0 != pBrk && pBrk->getBaseName() == Id3V230Stream::getClassDisplayName()) { in.seekg(pBrk->getPos()); NoteColl notes (20); StringWrp fileName (h.getName()); Id3V230Stream strm (0, notes, in, &fileName, Id3V230Stream::ACCEPT_BROKEN); Id3V230StreamWriter wrt (m_pCommonData->m_bKeepOneValidImg, m_pCommonData->useFastSave(), &strm, h.getName()); wrt.write(out); bChanged = true; bRecall = true; // !!! now we read whatever frames are available from a broken stream, next time we check for empty or otherwise invalid frames } else if (0 != pBrk && pBrk->getBaseName() == Id3V240Stream::getClassDisplayName()) { in.seekg(pBrk->getPos()); NoteColl notes (20); StringWrp fileName (h.getName()); Id3V240Stream strm (0, notes, in, &fileName, Id3V240Stream::ACCEPT_BROKEN); Id3V230StreamWriter wrt (m_pCommonData->m_bKeepOneValidImg, m_pCommonData->useFastSave(), &strm, h.getName()); //ttt2 if useFastSave is true there should probably be an automatic reload; OTOH we may want to delay until more transforms are applied, so probably it's OK as is wrt.write(out); bChanged = true; bRecall = true; } else { p->copy(in, out); } } } if (bChanged) { rst.setOk(true); return bRecall ? CHANGED : CHANGED_NO_RECALL; } deleteFile(strTempName); return NOT_CHANGED; } //======================================================================================================================== //======================================================================================================================== //======================================================================================================================== /*override*/ QString Id3V2UnicodeTransformer::getVisibleActionName() const { return Transformation::tr("Convert non-ASCII ID3V2 text frames to Unicode assuming codepage %1").arg(m_pCommonData->m_pCodec->name().constData()); } void Id3V2UnicodeTransformer::processId3V2Stream(Id3V2StreamBase& strm, ofstream_utf8& out) { Id3V230StreamWriter wrt (m_pCommonData->m_bKeepOneValidImg, m_pCommonData->useFastSave(), 0, strm.getFileName()); const vector& vpFrames (strm.getFrames()); for (int i = 0, n = cSize(vpFrames); i < n; ++i) { const Id3V2Frame* pFrm (vpFrames[i]); //Id3V2FrameDataLoader wrp (*pFrm); if ('T' == pFrm->m_szName[0] && pFrm->m_bHasLatin1NonAscii) { Id3V2FrameDataLoader wrp (*pFrm); const char* pData (wrp.getData()); CB_ASSERT (0 == pData[0]); // "Latin1" encoding QByteArray arr (pData + 1, pFrm->m_nMemDataSize - 1); QString qstrTxt (m_pCommonData->m_pCodec->toUnicode(arr)); wrt.addTextFrame(pFrm->m_szName, convStr(qstrTxt)); } else { wrt.addNonOwnedFrame(pFrm); //ttt2 removes duplicates of POPM/ratings; OTOH it removes duplicates of artist or tite; well, those were supposed to not have duplicates } } TagTimestamp time (strm.getTime()); wrt.setRecTime(time); wrt.write(out); } /*override*/ Transformation::Result Id3V2UnicodeTransformer::apply(const Mp3Handler& h, const TransfConfig& transfConfig, const std::string& strOrigSrcName, std::string& strTempName) { const vector& vpStreams (h.getStreams()); for (int i = 0, n = cSize(vpStreams); i < n; ++i) { Id3V2StreamBase* pId3V2 (dynamic_cast(vpStreams[i])); if (0 != pId3V2) { const vector& vpFrames (pId3V2->getFrames()); for (int i = 0, n = cSize(vpFrames); i < n; ++i) { const Id3V2Frame* pFrm (vpFrames[i]); if (pFrm->m_bHasLatin1NonAscii) { goto e1; } } } } return NOT_CHANGED; e1: ifstream_utf8 in (h.getName().c_str(), ios::binary); { // temp transfConfig.getTempName(strOrigSrcName, getActionName(), strTempName); ofstream_utf8 out (strTempName.c_str(), ios::binary); in.seekg(0); for (int i = 0, n = cSize(vpStreams); i < n; ++i) { DataStream* p (vpStreams[i]); Id3V2StreamBase* pId3V2 (dynamic_cast(p)); if (0 != pId3V2) { processId3V2Stream(*pId3V2, out); } else { p->copy(in, out); } } } return CHANGED_NO_RECALL; } //======================================================================================================================== //======================================================================================================================== //======================================================================================================================== /* struct PLPAS { PLPAS() { for (int i = 32; i < 512; ++i) { QChar c (i); QString q; q += c; cout << i << " " << q.toUtf8().constData() << " " << (int)c.category() << endl; } } }; PLPAS llpwwe; */ /*override*/ QString Id3V2CaseTransformer::getVisibleActionName() const { return Transformation::tr("Change case for ID3V2 text frames: Artists - %1; Others - %2").arg(TagReader::tr(getCaseAsStr(m_pCommonData->m_eCaseForArtists))).arg(TagReader::tr(getCaseAsStr(m_pCommonData->m_eCaseForOthers))); } bool Id3V2CaseTransformer::processId3V2Stream(Id3V2StreamBase& strm, ofstream_utf8& out) { Id3V230StreamWriter wrt (m_pCommonData->m_bKeepOneValidImg, m_pCommonData->useFastSave(), &strm, strm.getFileName()); { const Id3V2Frame* pFrm (strm.getFrame(KnownFrames::LBL_ARTIST())); if (0 != pFrm) { QString s (convStr(pFrm->getUtf8String())); wrt.addTextFrame(KnownFrames::LBL_ARTIST(), convStr(getCaseConv(s, m_pCommonData->m_eCaseForArtists))); } } { const Id3V2Frame* pFrm (strm.getFrame(KnownFrames::LBL_ALBUM())); if (0 != pFrm) { QString s (convStr(pFrm->getUtf8String())); wrt.addTextFrame(KnownFrames::LBL_ALBUM(), convStr(getCaseConv(s, m_pCommonData->m_eCaseForOthers))); } } { const Id3V2Frame* pFrm (strm.getFrame(KnownFrames::LBL_COMPOSER())); if (0 != pFrm) { QString s (convStr(pFrm->getUtf8String())); wrt.addTextFrame(KnownFrames::LBL_COMPOSER(), convStr(getCaseConv(s, m_pCommonData->m_eCaseForArtists))); } } { const Id3V2Frame* pFrm (strm.getFrame(KnownFrames::LBL_TITLE())); if (0 != pFrm) { QString s (convStr(pFrm->getUtf8String())); wrt.addTextFrame(KnownFrames::LBL_TITLE(), convStr(getCaseConv(s, m_pCommonData->m_eCaseForOthers))); } } { const Id3V2Frame* pFrm (strm.getFrame(KnownFrames::LBL_GENRE())); if (0 != pFrm) { QString s (convStr(pFrm->getUtf8String())); wrt.addTextFrame(KnownFrames::LBL_GENRE(), convStr(getCaseConv(s, m_pCommonData->m_eCaseForOthers))); } } wrt.write(out); return !wrt.contentEqualTo(&strm); } /*override*/ Transformation::Result Id3V2CaseTransformer::apply(const Mp3Handler& h, const TransfConfig& transfConfig, const std::string& strOrigSrcName, std::string& strTempName) { //ttt2 identical / similar code to the other Id3V2 Transformers ValueRestorer rst (strTempName); const vector& vpStreams (h.getStreams()); bool bFoundMatch (false); for (int i = 0, n = cSize(vpStreams); i < n; ++i) { if (0 != dynamic_cast(vpStreams[i])) { bFoundMatch = true; break; } } if (!bFoundMatch) { return NOT_CHANGED; } ifstream_utf8 in (h.getName().c_str(), ios::binary); bool bChanged (false); { // temp transfConfig.getTempName(strOrigSrcName, getActionName(), strTempName); ofstream_utf8 out (strTempName.c_str(), ios::binary); in.seekg(0); for (int i = 0, n = cSize(vpStreams); i < n; ++i) { DataStream* p (vpStreams[i]); Id3V2StreamBase* pId3V2 (dynamic_cast(p)); if (0 != pId3V2) { bool b (processId3V2Stream(*pId3V2, out)); bChanged = bChanged || b; } else { p->copy(in, out); } } } if (bChanged) { rst.setOk(true); return CHANGED_NO_RECALL; } deleteFile(strTempName); return NOT_CHANGED; } //======================================================================================================================== //======================================================================================================================== //======================================================================================================================== string Id3V1ToId3V2Copier::convert(const string& s) { QString q (convStr(s)); QByteArray arr (q.toLatin1()); QString qstrTxt (m_pCommonData->m_pCodec->toUnicode(arr)); return convStr(qstrTxt); } bool Id3V1ToId3V2Copier::processId3V2Stream(Id3V2StreamBase& strm, ofstream_utf8& out, Id3V1Stream* pId3V1Stream) { Id3V230StreamWriter wrt (m_pCommonData->m_bKeepOneValidImg, m_pCommonData->useFastSave(), &strm, strm.getFileName()); if (strm.getTitle().empty()) { string s (pId3V1Stream->getTitle()); if (!s.empty()) { wrt.addTextFrame(KnownFrames::LBL_TITLE(), convert(s)); } } if (strm.getArtist().empty()) { string s (pId3V1Stream->getArtist()); if (!s.empty()) { wrt.addTextFrame(KnownFrames::LBL_ARTIST(), convert(s)); } } if (strm.getTrackNumber().empty()) { string s (pId3V1Stream->getTrackNumber()); if (!s.empty()) { wrt.addTextFrame(KnownFrames::LBL_TRACK_NUMBER(), s); } } if (0 == strm.getTime().asString()[0]) { string s (pId3V1Stream->getTime().getYear()); if (!s.empty()) { wrt.addTextFrame(KnownFrames::LBL_TIME_YEAR_230(), s); } } if (strm.getGenre().empty()) { string s (pId3V1Stream->getGenre()); if (!s.empty()) { wrt.addTextFrame(KnownFrames::LBL_GENRE(), s); } } if (strm.getAlbumName().empty()) { string s (pId3V1Stream->getAlbumName()); if (!s.empty()) { wrt.addTextFrame(KnownFrames::LBL_ALBUM(), convert(s)); } } wrt.write(out); return !wrt.contentEqualTo(&strm); } /*override*/ Transformation::Result Id3V1ToId3V2Copier::apply(const Mp3Handler& h, const TransfConfig& transfConfig, const std::string& strOrigSrcName, std::string& strTempName) { ValueRestorer rst (strTempName); const vector& vpStreams (h.getStreams()); bool bId3V2Found (false); Id3V1Stream* pId3V1Stream (0); for (int i = 0, n = cSize(vpStreams); i < n; ++i) { if (0 != dynamic_cast(vpStreams[i])) { bId3V2Found = true; } else if (0 != dynamic_cast(vpStreams[i])) { pId3V1Stream = dynamic_cast(vpStreams[i]); } } if (0 == pId3V1Stream) { return NOT_CHANGED; } if (!bId3V2Found) { Id3V230StreamWriter wrt (m_pCommonData->m_bKeepOneValidImg, m_pCommonData->useFastSave(), 0, h.getName()); { string s (pId3V1Stream->getTitle()); if (!s.empty()) { wrt.addTextFrame(KnownFrames::LBL_TITLE(), convert(s)); } } { string s (pId3V1Stream->getArtist()); if (!s.empty()) { wrt.addTextFrame(KnownFrames::LBL_ARTIST(), convert(s)); } } { string s (pId3V1Stream->getTrackNumber()); if (!s.empty()) { wrt.addTextFrame(KnownFrames::LBL_TRACK_NUMBER(), s); } } { string s (pId3V1Stream->getTime().getYear()); if (!s.empty()) { wrt.addTextFrame(KnownFrames::LBL_TIME_YEAR_230(), s); } } { string s (pId3V1Stream->getGenre()); if (!s.empty()) { wrt.addTextFrame(KnownFrames::LBL_GENRE(), s); } } { string s (pId3V1Stream->getAlbumName()); if (!s.empty()) { wrt.addTextFrame(KnownFrames::LBL_ALBUM(), convert(s)); } } if (wrt.isEmpty()) { return NOT_CHANGED; } ifstream_utf8 in (h.getName().c_str(), ios::binary); transfConfig.getTempName(strOrigSrcName, getActionName(), strTempName); ofstream_utf8 out (strTempName.c_str(), ios::binary); in.seekg(0); wrt.write(out); for (int i = 0, n = cSize(vpStreams); i < n; ++i) { DataStream* p (vpStreams[i]); p->copy(in, out); } rst.setOk(true); return CHANGED_NO_RECALL; } ifstream_utf8 in (h.getName().c_str(), ios::binary); bool bChanged (false); { // temp transfConfig.getTempName(strOrigSrcName, getActionName(), strTempName); ofstream_utf8 out (strTempName.c_str(), ios::binary); in.seekg(0); // ttt2 doesn't seem needed; search for similar cases and perhaps remove them all for (int i = 0, n = cSize(vpStreams); i < n; ++i) { DataStream* p (vpStreams[i]); Id3V2StreamBase* pId3V2 (dynamic_cast(p)); if (0 != pId3V2) { bool b (processId3V2Stream(*pId3V2, out, pId3V1Stream)); bChanged = bChanged || b; } else { p->copy(in, out); } } } if (bChanged) { rst.setOk(true); return CHANGED_NO_RECALL; } deleteFile(strTempName); return NOT_CHANGED; } /*override*/ QString Id3V1ToId3V2Copier::getVisibleActionName() const { return Transformation::tr("Copy missing ID3V2 frames from ID3V1 assuming codepage %1").arg(m_pCommonData->m_pCodec->name().constData()); } //======================================================================================================================== //======================================================================================================================== //======================================================================================================================== /*override*/ Transformation::Result Id3V2ComposerAdder::apply(const Mp3Handler& h, const TransfConfig& transfConfig, const std::string& strOrigSrcName, std::string& strTempName) { const vector& vpStreams (h.getStreams()); Id3V2StreamBase* pId3V2 (0); for (int i = 0, n = cSize(vpStreams); i < n; ++i) { DataStream* p (vpStreams[i]); pId3V2 = dynamic_cast(p); if (0 != pId3V2) { break; } } if (0 == pId3V2) { return NOT_CHANGED; } string strComp (pId3V2->getComposer()); if (strComp.empty()) { return NOT_CHANGED; } string strArtist (pId3V2->getArtist()); if (beginsWith(strArtist, strComp + " [") && endsWith(strArtist, "]")) { return NOT_CHANGED; } { // temp Id3V230StreamWriter wrt (m_pCommonData->m_bKeepOneValidImg, m_pCommonData->useFastSave(), pId3V2, h.getName()); wrt.addTextFrame(KnownFrames::LBL_ARTIST(), strComp + " [" + strArtist + "]"); ifstream_utf8 in (h.getName().c_str(), ios::binary); transfConfig.getTempName(strOrigSrcName, getActionName(), strTempName); ofstream_utf8 out (strTempName.c_str(), ios::binary); wrt.write(out); // may throw, but it will be caught for (int i = 0, n = cSize(vpStreams); i < n; ++i) { DataStream* p (vpStreams[i]); if (p != pId3V2) { p->copy(in, out); } } } return CHANGED_NO_RECALL; } //======================================================================================================================== //======================================================================================================================== //======================================================================================================================== /*override*/ Transformation::Result Id3V2ComposerRemover::apply(const Mp3Handler& h, const TransfConfig& transfConfig, const std::string& strOrigSrcName, std::string& strTempName) { const vector& vpStreams (h.getStreams()); Id3V2StreamBase* pId3V2 (0); for (int i = 0, n = cSize(vpStreams); i < n; ++i) { DataStream* p (vpStreams[i]); pId3V2 = dynamic_cast(p); if (0 != pId3V2) { break; } } if (0 == pId3V2) { return NOT_CHANGED; } string strComp (pId3V2->getComposer()); if (strComp.empty()) { return NOT_CHANGED; } string strArtist (pId3V2->getArtist()); if (!(beginsWith(strArtist, strComp + " [") && endsWith(strArtist, "]"))) { return NOT_CHANGED; } { // temp Id3V230StreamWriter wrt (m_pCommonData->m_bKeepOneValidImg, m_pCommonData->useFastSave(), pId3V2, h.getName()); //wrt.addTextFrame(KnownFrames::LBL_ARTIST(), strComp + " [" + strArtist + "]"); wrt.addTextFrame(KnownFrames::LBL_ARTIST(), strArtist.substr(strComp.size() + 2, strArtist.size() - strComp.size() - 3)); ifstream_utf8 in (h.getName().c_str(), ios::binary); transfConfig.getTempName(strOrigSrcName, getActionName(), strTempName); ofstream_utf8 out (strTempName.c_str(), ios::binary); wrt.write(out); // may throw, but it will be caught for (int i = 0, n = cSize(vpStreams); i < n; ++i) { DataStream* p (vpStreams[i]); if (p != pId3V2) { p->copy(in, out); } } } return CHANGED_NO_RECALL; } //======================================================================================================================== //======================================================================================================================== //======================================================================================================================== /*override*/ Transformation::Result Id3V2ComposerCopier::apply(const Mp3Handler& h, const TransfConfig& transfConfig, const std::string& strOrigSrcName, std::string& strTempName) { const vector& vpStreams (h.getStreams()); Id3V2StreamBase* pId3V2 (0); for (int i = 0, n = cSize(vpStreams); i < n; ++i) { DataStream* p (vpStreams[i]); pId3V2 = dynamic_cast(p); if (0 != pId3V2) { break; } } if (0 == pId3V2) { return NOT_CHANGED; } string strArtist (pId3V2->getArtist()); if (beginsWith(strArtist, "[") || !endsWith(strArtist, "]")) { return NOT_CHANGED; } string::size_type n (strArtist.find(" [")); if (string::npos == n) { return NOT_CHANGED; } string strComp (strArtist.substr(0, n)); string strExistingComp (pId3V2->getComposer()); if (strComp == strExistingComp) { return NOT_CHANGED; } { // temp Id3V230StreamWriter wrt (m_pCommonData->m_bKeepOneValidImg, m_pCommonData->useFastSave(), pId3V2, h.getName()); wrt.addTextFrame(KnownFrames::LBL_COMPOSER(), strComp); ifstream_utf8 in (h.getName().c_str(), ios::binary); transfConfig.getTempName(strOrigSrcName, getActionName(), strTempName); ofstream_utf8 out (strTempName.c_str(), ios::binary); wrt.write(out); // may throw, but it will be caught for (int i = 0, n = cSize(vpStreams); i < n; ++i) { DataStream* p (vpStreams[i]); if (p != pId3V2) { p->copy(in, out); } } } return CHANGED_NO_RECALL; } //======================================================================================================================== //======================================================================================================================== //======================================================================================================================== /*override*/ Transformation::Result SmallerImageRemover::apply(const Mp3Handler& h, const TransfConfig& transfConfig, const std::string& strOrigSrcName, std::string& strTempName) //ttt1 recompress large images { LAST_STEP("SmallerImageRemover::apply() " + h.getName()); const vector& vpStreams (h.getStreams()); Id3V2StreamBase* pId3V2 (0); for (int i = 0, n = cSize(vpStreams); i < n; ++i) { DataStream* p (vpStreams[i]); pId3V2 = dynamic_cast(p); if (0 != pId3V2) { break; } } if (0 == pId3V2) { return NOT_CHANGED; } const vector& vpFrames (pId3V2->getFrames()); const Id3V2Frame* pLargestPic (0); int nPicCnt (0); for (int i = 0; i < cSize(vpFrames); ++i) { const Id3V2Frame* p (vpFrames[i]); //if (0 == strcmp(p->m_szName, KnownFrames::LBL_IMAGE())) if (Id3V2Frame::COVER == p->m_eApicStatus || Id3V2Frame::NON_COVER == p->m_eApicStatus) { ++nPicCnt; if (0 == pLargestPic || pLargestPic->m_nImgSize < p->m_nImgSize) { pLargestPic = p; } } } if (0 == nPicCnt || (1 == nPicCnt && Id3V2Frame::PT_COVER == pLargestPic->m_nPictureType)) { return NOT_CHANGED; } { // temp Id3V230StreamWriter wrt (Id3V230StreamWriter::KEEP_ONE_VALID_IMG, m_pCommonData->useFastSave(), pId3V2, h.getName()); wrt.removeFrames(KnownFrames::LBL_IMAGE()); Id3V2FrameDataLoader ldr (*pLargestPic); vector v; copy (ldr.getData(), ldr.getData() + pLargestPic->m_nMemDataSize, back_inserter(v)); //wrt.addBinaryFrame(KnownFrames::LBL_IMAGE(), v); wrt.addImg(v); ifstream_utf8 in (h.getName().c_str(), ios::binary); transfConfig.getTempName(strOrigSrcName, getActionName(), strTempName); ofstream_utf8 out (strTempName.c_str(), ios::binary); wrt.write(out); // may throw, but it will be caught for (int i = 0, n = cSize(vpStreams); i < n; ++i) { DataStream* p (vpStreams[i]); if (p != pId3V2) { p->copy(in, out); } } } return CHANGED_NO_RECALL; } //======================================================================================================================== //======================================================================================================================== //======================================================================================================================== /*static*/ const int Id3V2Expander::EXTRA_SPACE (4096); // frames other than APIC; title, artist, lyrics, ... /*override*/ Transformation::Result Id3V2Expander::apply(const Mp3Handler& h, const TransfConfig& transfConfig, const std::string& strOrigSrcName, std::string& strTempName) { const vector& vpStreams (h.getStreams()); Id3V2StreamBase* pId3V2 (0); for (int i = 0, n = cSize(vpStreams); i < n; ++i) { DataStream* p (vpStreams[i]); pId3V2 = dynamic_cast(p); if (0 != pId3V2) { break; } } //if (0 == pId3V2) { return NOT_CHANGED; } //(int(pId3V2->getSize()) int nOldPaddingSize (0), nOldSize (0); if (0 != pId3V2) { nOldPaddingSize = pId3V2->getPaddingSize(); nOldSize = int(pId3V2->getSize()); } int nExtraSize (ImageInfo::MAX_IMAGE_SIZE + EXTRA_SPACE); // it is possible for existing pictures with non-cover types to be kept, so we want additional space even if there is already a (big) image // !!! cannot just remove the size of whatever APIC is currently used, because it may be of a non-cover type if (nExtraSize <= nOldPaddingSize) { return NOT_CHANGED; } { // temp Id3V230StreamWriter wrt (m_pCommonData->m_bKeepOneValidImg, m_pCommonData->useFastSave(), pId3V2, h.getName()); ifstream_utf8 in (h.getName().c_str(), ios::binary); transfConfig.getTempName(strOrigSrcName, getActionName(), strTempName); ofstream_utf8 out (strTempName.c_str(), ios::binary); wrt.write(out, nOldSize + nExtraSize - nOldPaddingSize); // may throw, but it will be caught for (int i = 0, n = cSize(vpStreams); i < n; ++i) { DataStream* p (vpStreams[i]); if (p != pId3V2) { p->copy(in, out); } } } return CHANGED_NO_RECALL; } //======================================================================================================================== //======================================================================================================================== //======================================================================================================================== /*override*/ Transformation::Result Id3V2Compactor::apply(const Mp3Handler& h, const TransfConfig& transfConfig, const std::string& strOrigSrcName, std::string& strTempName) { const vector& vpStreams (h.getStreams()); Id3V2StreamBase* pId3V2 (0); for (int i = 0, n = cSize(vpStreams); i < n; ++i) { DataStream* p (vpStreams[i]); pId3V2 = dynamic_cast(p); if (0 != pId3V2) { break; } } if (0 == pId3V2) { return NOT_CHANGED; } if (pId3V2->getPaddingSize() < Id3V230StreamWriter::DEFAULT_EXTRA_SPACE + 512) { return NOT_CHANGED; } { // temp Id3V230StreamWriter wrt (m_pCommonData->m_bKeepOneValidImg, m_pCommonData->useFastSave(), pId3V2, h.getName()); ifstream_utf8 in (h.getName().c_str(), ios::binary); transfConfig.getTempName(strOrigSrcName, getActionName(), strTempName); ofstream_utf8 out (strTempName.c_str(), ios::binary); wrt.write(out, 0); // may throw, but it will be caught for (int i = 0, n = cSize(vpStreams); i < n; ++i) { DataStream* p (vpStreams[i]); if (p != pId3V2) { p->copy(in, out); } } } return CHANGED_NO_RECALL; } //======================================================================================================================== //======================================================================================================================== //======================================================================================================================== //ttt2 perhaps be able to extract composer even when the field is empty, if artist is "composer [artist]", but doesn't look too useful //ttt2 warn in config if user enables fast save and then hides Id3V2Compactor MP3Diags-1.2.02/src/CheckedDir.cpp0000644000175000001440000002730211717301657015443 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #ifndef WIN32 #else #include #endif #include "CheckedDir.h" #include "Helpers.h" using namespace std; /*override*/ QVariant CheckedDirModel::data(const QModelIndex& index, int nRole /* = Qt::DisplayRole*/) const { //qDebug("CheckedDirModel::data(%d, %d, role=%d)", index.row(), index.column(), nRole); LAST_STEP("CheckedDirModel::data()"); if (Qt::CheckStateRole == nRole) { //QString s (getDir(index)); QString s (filePath(index)); bool bIsChecked (false), bIsUnchecked (false), bHasUncheckedDescendant (false); QString qstrClosestCheckedAncestor, qstrClosestUncheckedAncestor; for (int i = 0, n = cSize(m_vCheckedDirs); i < n; ++i) { QString s1 (m_vCheckedDirs[i]); if (s == s1) { bIsChecked = true; } if (isDescendant(s, s1)) { if (isDescendant(s1, qstrClosestCheckedAncestor)) { qstrClosestCheckedAncestor = s1; } } } for (int i = 0, n = cSize(m_vUncheckedDirs); i < n; ++i) { QString s1 (m_vUncheckedDirs[i]); if (s == s1) { bIsUnchecked = true; } if (isDescendant(s, s1)) { if (isDescendant(s1, qstrClosestUncheckedAncestor)) { qstrClosestUncheckedAncestor = s1; } } if (isDescendant(s1, s)) { bHasUncheckedDescendant = true; } } if ( !bIsChecked && (bIsUnchecked || qstrClosestCheckedAncestor.isEmpty() || isDescendant(qstrClosestUncheckedAncestor, qstrClosestCheckedAncestor)) ) { return Qt::Unchecked; } return bHasUncheckedDescendant ? Qt::PartiallyChecked : Qt::Checked; } #ifndef WIN32 #else if (Qt::DisplayRole == nRole) { QVariant x (QDirModel::data(index, nRole)); if (!x.isNull()) { QString qs (x.toString()); if (2 == qs.size() && ':' == qs[1] && 'A' != qs[0] && 'B' != qs[0]) { // get the drive label, but not for floppies char szLabel [MAX_PATH + 1]; char szFsType [MAX_PATH + 1]; static bool s_bSetErrorModeCalled (false); //ttt2 perhaps see how long it takes and disable this if it's too long; OTOH all the drives get stat'ed anyway, so one more time isn't such a big deal if (!s_bSetErrorModeCalled) { s_bSetErrorModeCalled = true; SetErrorMode(SEM_FAILCRITICALERRORS); // so the user isn't told to insert the floppy or CD just to stat it; apparently this happens if set up so } if (GetVolumeInformationA(qs.toUtf8().constData(), szLabel, MAX_PATH + 1, 0, 0, 0, szFsType, MAX_PATH + 1)) { //return qs + " " + szLabel + " " + szFsType; return qs + " [" + szLabel + "]"; } } } } /*if (0 == index.row() && 0 == index.column()) // failed attempt to get rid of scanning A: the thing is that more than just data() needs to be overridden (e.g. roCount()) { return QVariant(); }*/ #endif return QDirModel::data(index, nRole); } bool CheckedDirModel::setData(const QModelIndex& index, const QVariant& value, int nRole /* = Qt::EditRole*/) { Qt::CheckState eCheck ((Qt::CheckState)value.toInt()); if (Qt::PartiallyChecked == eCheck) { eCheck = Qt::Checked == (Qt::CheckState)data(index, Qt::CheckStateRole).toInt() ? Qt::Unchecked : Qt::Checked; } if (Qt::CheckStateRole == nRole) { bool hasClosestAncestorChecked (false); QString sClosestAncestor; QString s (filePath(index)); //if (3 == s.size()) { return false; } vector v; // remove s and its descendants from m_vCheckedDirs, if any are there; compute sClosestAncestor; for (int i = 0, n = cSize(m_vCheckedDirs); i < n; ++i) { QString s1 (m_vCheckedDirs[i]); //qDebug("s1=%s, s=%s", s1.toUtf8().data(), s.toUtf8().constData()); if (!isDescendant(s1, s) && s != s1) { v.push_back(s1); if (isDescendant(s1, sClosestAncestor) && isDescendant(s, s1)) { sClosestAncestor = s1; hasClosestAncestorChecked = true; } } } v.swap(m_vCheckedDirs); v.clear(); // remove s and its descendants from m_vUncheckedDirs, if any are there; compute sClosestAncestor; for (int i = 0, n = cSize(m_vUncheckedDirs); i < n; ++i) { QString s1 (m_vUncheckedDirs[i]); if (!isDescendant(s1, s) && s != s1) { v.push_back(s1); if (isDescendant(s1, sClosestAncestor) && isDescendant(s, s1)) { sClosestAncestor = s1; hasClosestAncestorChecked = false; } } } v.swap(m_vUncheckedDirs); switch (eCheck) { case Qt::Checked: if (!hasClosestAncestorChecked) { m_vCheckedDirs.push_back(s); } break; case Qt::Unchecked: if (hasClosestAncestorChecked) { m_vUncheckedDirs.push_back(s); } break; default: CB_ASSERT (false); } /*for (;;) //ttt2 { if all children of a checked node are unchecked, remove the node from the checked list; repeat until there's nothing to change; must check all in the tree; 2009.04.13 - or maybe not: more directories may be added later; more importantly, the node might have files itself }*/ emit layoutChanged(); return true; } return QDirModel::setData(index, eCheck, nRole); } /*QString CheckedDirModel::getDir(const QModelIndex& index) const { if (!index.isValid()) { return "root"; } // !!! using "root" rather than an empty dir to distinguish between an unassigned string and one that holds the root dir QString s (getDir(index.parent()) + "/" + data(index).toString()); //qDebug("%s", s.toUtf8().constData()); return s; } */ // non-reflexive (isDescendant(s, s) is false) // order: "" is above "/"; "/" is above the rest; bool CheckedDirModel::isDescendant(const QString& s1, const QString& s2) const { if (s1.isEmpty()) { return false; } return s2.isEmpty() || (s2 == "/" && s1.size() > 1) || // ttt3 linux-specific (s1.startsWith(s2) && s1.size() > s2.size() && s1[s2.size()] == '/') #ifndef WIN32 #else || (s1.startsWith(s2) && s1.size() > s2.size() && 3 == s2.size() && ':' == s1[1] && ':' == s2[1]) #endif ; } // besides assigning the vectors, also expands the tree to show them void CheckedDirModel::setDirs(const vector& vstrCheckedDirs, const vector& vstrUncheckedDirs, QTreeView* pTreeView) { m_vCheckedDirs = convStr(vstrCheckedDirs); m_vUncheckedDirs = convStr(vstrUncheckedDirs); expandNodes(pTreeView); } void CheckedDirModel::expandNodes(QTreeView* pTreeView) { for (int i = cSize(m_vUncheckedDirs) - 1; i >= 0; --i) { expandNode(m_vUncheckedDirs[i], pTreeView); } for (int i = cSize(m_vCheckedDirs) - 1; i >= 0; --i) { expandNode(m_vCheckedDirs[i], pTreeView); } emit layoutChanged(); } void CheckedDirModel::expandNode(const QString& s, QTreeView* pTreeView) { pTreeView->expand(index("/")); int n (1); for (;;) { n = s.indexOf('/', n); if (-1 == n) { break; } pTreeView->expand(index(s.left(n))); ++n; } //qDebug("%d %d", pTreeView->width(), pTreeView->height()); pTreeView->scrollTo(index(s), QAbstractItemView::PositionAtCenter); pTreeView->horizontalScrollBar()->setValue(0); // !!! needed because PositionAtCenter scrolls horizontally as well, which is strange for dirs with large names //pTreeView->scrollTo(index(s), QAbstractItemView::EnsureVisible); //pTreeView->scrollTo(index(s), QAbstractItemView::PositionAtTop); //pTreeView->scrollTo(index(s), QAbstractItemView::PositionAtBottom); } std::vector CheckedDirModel::getCheckedDirs() const { return convStr(m_vCheckedDirs); } std::vector CheckedDirModel::getUncheckedDirs() const { return convStr(m_vUncheckedDirs); } #ifdef jiLPojiojsdjoiHJIOHIO //#include "CheckedDir.h" CheckedDirModel* pModel (new CheckedDirModel(this)); pModel->setFilter(QDir::AllDirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::Drives); //pModel->setFilter(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot | QDir::Hidden | QDir::Drives); //QStringList lst; //lst << "/r/temp/1/tmp2/z/pic"; //pModel->setNameFilters(lst); pModel->setSorting(QDir::IgnoreCase); treeView->setModel(pModel); //treeView->setRootIndex(pModel->index("/r/temp/1/tmp2/z/pic")); treeView->expand(pModel->index("/")); treeView->expand(pModel->index("/r")); treeView->expand(pModel->index("/r/temp")); treeView->expand(pModel->index("/r/temp/1")); treeView->expand(pModel->index("/r/temp/1/tmp2")); treeView->expand(pModel->index("/r/temp/1/tmp2/z")); treeView->expand(pModel->index("/r/temp/1/tmp2/z/pic")); treeView->scrollTo(pModel->index("/r/temp/1/tmp2/z/pic", QAbstractItemView::PositionAtCenter)); #endif //ttt2 perhaps replace QDirModel with QFileSystemModel on Qt>4.4. See also https://sourceforge.net/apps/mantisbt/mp3diags/view.php?id=34 ; at least should be more responsive when there are network and floppy drives; see also http://lists.trolltech.com/qt-interest/2007-12/thread00336-0.html and http://www.qtcentre.org/forum/f-qt-programming-2/t-disable-floppy-reading-in-a-qfiledialog-1799.html //ttt2 perhaps use QDirModel::lazyChildCount; or maybe not; it will show that all dirs have children //ttt2 ? use QFileSystemWatcher MP3Diags-1.2.02/src/DataStream.h0000644000175000001440000005554311724446667015171 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef DataStreamH #define DataStreamH #include #include "SerSupport.h" #include "Notes.h" #include "CommonTypes.h" #include "Helpers.h" #define MP3_CHECK(COND, POS, MSG_ID, EXCP) { if (!(COND)) { notes.add(new Note(Notes::MSG_ID(), POS)); throw EXCP; } } // MSG_ID gives a severity #define MP3_CHECK_T(COND, POS, MSG, EXCP) { if (!(COND)) { static Note::SharedData d (MSG, false); notes.add(new Note(d, POS)); throw EXCP; } } // TRACE-only notes #define MP3_THROW(POS, MSG_ID, EXCP) { notes.add(new Note(Notes::MSG_ID(), POS)); throw EXCP; } #define MP3_THROW_T(POS, MSG, EXCP) { static Note::SharedData d (MSG, false); notes.add(new Note(d, POS)); throw EXCP; } // TRACE-only notes #define MP3_NOTE(POS, MSG_ID) { notes.add(new Note(Notes::MSG_ID(), POS)); } #define MP3_NOTE_D(POS, MSG_ID, DETAIL) { notes.add(new Note(Notes::MSG_ID(), POS, convStr(DETAIL))); } #define MP3_TRACE(POS, MSG) { static Note::SharedData d (MSG, false); notes.add(new Note(d, POS)); } // TRACE-only notes /* DataStream constructors may leave the input file in EOF or other invalid state and the read pointer with an arbitrary value, regardless of them succeeding or not. Therefore they the file state should be cleared by the user. */ //============================================================================================================ //============================================================================================================ //============================================================================================================ #define DECL_NAME(s) \ static const char* getClassDisplayName() { return s; } \ /*override*/ const char* getDisplayName() const { return getClassDisplayName(); } \ /*override*/ QString getTranslatedDisplayName() const { return DataStream::tr(s); } // this is to be used with TagReader descendants: //!!! we usually don't want translations for "readers", the exceptions being the pattern and web readers, which have them separately; OTOH we want translations for "streams"; thankfully, there are no conflicts, so streams that are also readers (e.g. Id3V2StreamBase descendants, ApeStream, Id3V1Stream) only need translation as streams, which is important when deciding the context to use ("TagReader" vs. "DataStream"); a stream&reader should be translated as a stream because it may be "broken", "unsupported", ... ; however, as a reader, it is merely an ID3V2, APE, ... #define DECL_RD_NAME(s) \ static const char* getClassDisplayName() { return s; } \ /*override*/ const char* getDisplayName() const { return getClassDisplayName(); } \ /*override*/ const char* getName() const { return getClassDisplayName(); } \ /*override*/ QString getTranslatedDisplayName() const { return DataStream::tr(s); } #define STRM_ASSERT(COND) { if (!(COND)) { assertBreakpoint(); ::trace("assert"); logAssert(__FILE__, __LINE__, #COND, getGlobalMp3HandlerName()); ::exit(1); } } std::string getGlobalMp3HandlerName(); // a hack to get the name of the current file from inside various streams without storing the name there //ttt2 review class DataStream { Q_DECLARE_TR_FUNCTIONS(DataStream) int m_nIndex; DataStream(const DataStream&); DataStream& operator=(const DataStream&); protected: DataStream(int nIndex) : m_nIndex(nIndex) {} DataStream() {} // serialization-only constructor public: virtual ~DataStream() {} // throws WriteError if there are errors writing to the file; derived classes have several options on implementing this, from simply copying content from the input to ignoring the input file completely; e.g. ID3V2 can change the size of the padding, drop some frames, replace others, ... virtual void copy(std::istream& in, std::ostream& out) = 0; virtual const char* getDisplayName() const = 0; virtual QString getTranslatedDisplayName() const = 0; virtual std::string getInfo() const = 0; virtual std::streampos getPos() const = 0; virtual std::streamoff getSize() const = 0; std::streampos getEnd() const { return getPos() + getSize(); } int getIndex() const { return m_nIndex; } private: friend class boost::serialization::access; template void serialize(Archive& ar, const unsigned int nVersion) { if (nVersion > 0) { throw std::runtime_error("invalid version of serialized file"); } ar & m_nIndex; } }; // simply copies content from the input file; stores position and size to be able to do so class SimpleDataStream : public DataStream { protected: std::streampos m_pos; std::streamoff m_nSize; SimpleDataStream(int nIndex, std::streampos pos, std::streamoff nSize) : DataStream(nIndex), m_pos(pos), m_nSize(nSize) {} SimpleDataStream() {} // serialization-only constructor public: // seeks "in" for its beginning and appends m_nSize bytes to "out"; /*override*/ void copy(std::istream& in, std::ostream& out); /*override*/ std::streampos getPos() const { return m_pos; } /*override*/ std::streamoff getSize() const { return m_nSize; } private: friend class boost::serialization::access; template void serialize(Archive& ar, const unsigned int nVersion) { if (nVersion > 0) { throw std::runtime_error("invalid version of serialized file"); } ar & boost::serialization::base_object(*this); ar & m_pos; ar & m_nSize; } }; class UnknownDataStreamBase : public SimpleDataStream // ttt2 perhaps unify with SimpleDataStream, as it's its only descendant; OTOH SimpleDataStream might get other descendants in the future, so maybe not // ttt2 not the best name, but others don't seem better { public: enum { BEGIN_SIZE = 32 }; protected: char m_begin [BEGIN_SIZE]; void append(const UnknownDataStreamBase&); UnknownDataStreamBase() {} // serialization-only constructor public: UnknownDataStreamBase(int nIndex, NoteColl& notes, std::istream& in, std::streamoff nSize); /*override*/ std::string getInfo() const; const char* getBegin() const { return m_begin; } struct BadUnknownStream {}; // e.g. there are not nSize chars left private: friend class boost::serialization::access; template void serialize(Archive& ar, const unsigned int nVersion) { if (nVersion > 0) { throw std::runtime_error("invalid version of serialized file"); } ar & boost::serialization::base_object(*this); ar & m_begin; } }; class UnknownDataStream : public UnknownDataStreamBase { public: UnknownDataStream(int nIndex, NoteColl& notes, std::istream& in, std::streamoff nSize) : UnknownDataStreamBase(nIndex, notes, in, nSize) {} DECL_NAME(QT_TRANSLATE_NOOP("DataStream", "Unknown")) using UnknownDataStreamBase::append; private: friend class boost::serialization::access; UnknownDataStream() {} // serialization-only constructor template void serialize(Archive& ar, const unsigned int nVersion) { if (nVersion > 0) { throw std::runtime_error("invalid version of serialized file"); } ar & boost::serialization::base_object(*this); } }; class BrokenDataStream : public UnknownDataStreamBase { std::string m_strName; std::string m_strBaseName; std::string m_strInfo; public: BrokenDataStream(int nIndex, NoteColl& notes, std::istream& in, std::streamoff nSize, const char* szBaseName, const std::string& strInfo); /*override*/ const char* getDisplayName() const { return m_strName.c_str(); } // DECL_NAME doesn't work in this case /*override*/ std::string getInfo() const { return m_strInfo; } const std::string& getBaseName() const { return m_strBaseName; } /*override*/ QString getTranslatedDisplayName() const; private: friend class boost::serialization::access; BrokenDataStream() {} // serialization-only constructor template void serialize(Archive& ar, const unsigned int nVersion) { if (nVersion > 0) { throw std::runtime_error("invalid version of serialized file"); } ar & boost::serialization::base_object(*this); ar & m_strName; ar & m_strBaseName; ar & m_strInfo; } }; class UnsupportedDataStream : public UnknownDataStreamBase // ttt3 perhaps merge code with BrokenDataStream in some base class { std::string m_strName; std::string m_strBaseName; std::string m_strInfo; public: UnsupportedDataStream(int nIndex, NoteColl& notes, std::istream& in, std::streamoff nSize, const char* szBaseName, const std::string& strInfo); /*override*/ const char* getDisplayName() const { return m_strName.c_str(); } // DECL_NAME doesn't work in this case /*override*/ std::string getInfo() const { return m_strInfo; } const std::string& getBaseName() const { return m_strBaseName; } /*override*/ QString getTranslatedDisplayName() const; using UnknownDataStreamBase::append; private: friend class boost::serialization::access; UnsupportedDataStream() {} // serialization-only constructor template void serialize(Archive& ar, const unsigned int nVersion) { if (nVersion > 0) { throw std::runtime_error("invalid version of serialized file"); } ar & boost::serialization::base_object(*this); ar & m_strName; ar & m_strBaseName; ar & m_strInfo; } }; class MpegStream; struct MpegFrameBase; // follows an MPEG stream and is compatible with it but it is truncated; some other frame begins before where it should end class TruncatedMpegDataStream : public UnknownDataStreamBase { MpegFrameBase* m_pFrame; TruncatedMpegDataStream(const TruncatedMpegDataStream&); TruncatedMpegDataStream& operator=(const TruncatedMpegDataStream&); public: TruncatedMpegDataStream(MpegStream* pPrevMpegStream, int nIndex, NoteColl& notes, std::istream& in, std::streamoff nSize); ~TruncatedMpegDataStream(); DECL_NAME(QT_TRANSLATE_NOOP("DataStream", "Truncated MPEG")) /*override*/ std::string getInfo() const; int getExpectedSize() const; struct NotTruncatedMpegDataStream {}; // thrown if pMpegStream is 0 or it points to an incompatible stream bool hasSpace(std::streamoff nSize) const; using UnknownDataStreamBase::append; private: friend class boost::serialization::access; TruncatedMpegDataStream() {} // serialization-only constructor template void serialize(Archive& ar, const unsigned int nVersion) { if (nVersion > 0) { throw std::runtime_error("invalid version of serialized file"); } ar & boost::serialization::base_object(*this); ar & m_pFrame; } }; // simply copies content from the input file; stores pos and sizes to be able to do so class NullDataStream : public DataStream { std::streampos m_pos; std::streamoff m_nSize; public: NullDataStream(int nIndex, NoteColl& notes, std::istream& in); /*override*/ void copy(std::istream& in, std::ostream& out); DECL_NAME(QT_TRANSLATE_NOOP("DataStream", "Null")) /*override*/ std::string getInfo() const; /*override*/ std::streampos getPos() const { return m_pos; } /*override*/ std::streamoff getSize() const { return m_nSize; } struct NotNullStream {}; private: friend class boost::serialization::access; NullDataStream() {} // serialization-only constructor template void serialize(Archive& ar, const unsigned int nVersion) { if (nVersion > 0) { throw std::runtime_error("invalid version of serialized file"); } ar & boost::serialization::base_object(*this); ar & m_pos; ar & m_nSize; } }; //========================================================================== //========================================================================== struct NotSupportedOp {}; // operation is not currently supported /* While building a stream, by reading from a file, several situations can be encountered: - it is read OK and there are no issues - it is read OK but some parts are incorrect; warnings or errors will be displayed, but it is possible to work with the stream and correct the errors, or perhaps leave it as it is; - it begins as expected, but a serious error is then encountered, so the object cannot be built; - the beginning is incorrect, meaning that it's just a different kind of stream, end of story; nothing should happen in this case; StreamIsBroken is for the 3rd case. It should be thrown when it is pretty likely that a stream of the appropriate type begins at the current position in the file but the stream can't be read. Given that the reason a stream can't be read may actually be that the code is buggy or incomplete, it is possible for a StreamIsBroken to be thrown when an StreamIsUnsupported should have been thrown instead. */ struct StreamIsBroken { // doesn't own the pointer; param is supposed to be string literal StreamIsBroken(const char* szStreamName, const QString& qstrInfo) : m_szStreamName(szStreamName), m_strInfo(convStr(qstrInfo)) {} const char* getStreamName() const { return m_szStreamName; } const std::string& getInfo() const { return m_strInfo; } private: const char* m_szStreamName; const std::string m_strInfo; // may be empty, but it should have some details about what made it broken, if possible; or perhaps what could be decyphred from the stream before being being decided that it was broken }; // to be thrown from the constructor of a stream when it looks like an unsupported version of the stream was found in the input file. struct StreamIsUnsupported { // doesn't own the pointers; params are supposed to be string literals StreamIsUnsupported(const char* szStreamName, const QString& qstrInfo) : m_szStreamName(szStreamName), m_strInfo(convStr(qstrInfo)) {} const char* getStreamName() const { return m_szStreamName; } const std::string& getInfo() const { return m_strInfo; } private: const char* m_szStreamName; const std::string m_strInfo; // may be empty, but it should have some details about what made it unsupported, if possible }; // Normally streams that throw StreamIsUnsupported or StreamIsBroken end up as UnsupportedDataStream or BrokenDataStream //========================================================================== //========================================================================== //#include // ttt2 would be nicer not to have to depend on Qt in this file, but some image library is needed anyway, so it might as well be Qt //========================================================================== //========================================================================== /* Times in ID3V2.4.0: TDRC - when the audio was recorded TDOR - when the original recording of the audio was released TDRL - when the audio was first released TDEN - when the audio was encoded TDTG - when the audio was tagged Format: subset of ISO 8601: yyyy-MM-ddTHH:mm:ss or shorter variants (truncated at the end) TDRC is the one used for TIME; corresponds to TYER and TDAT in ID3V2.3.0 */ //========================================================================== // uses strings in the format "yyyy-MM-ddTHH:mm:ss" or "yyyy-MM-dd HH:mm:ss" or any prefix truncated right after a group of digits; the empty string is valid, as is the null char* passed on constructor; // during initialization 'T' is changed to ' ' and then asString() uses this version, with ' ' class TagTimestamp { char m_szVal [20]; char m_szYear [5]; // either a 4-digit string or empty char m_szDayMonth [5]; // either a 4-digit string or empty; if m_strYear is empty, m_strDayMonth is empty too public: explicit TagTimestamp(const std::string&); // may throw InvalidTime; see init(); explicit TagTimestamp(const char* = 0); // may throw InvalidTime; see init(); const char* getYear() const { return m_szYear; } const char* getDayMonth() const { return m_szDayMonth; } const char* asString() const { return m_szVal; } void init(std::string strVal); // if strVal is invalid it clears m_strVal, m_strYear and m_strDayMonth and throws InvalidTime; the constructors call this too and let the exceptions propagate struct InvalidTime {}; }; struct TagReader { Q_DECLARE_TR_FUNCTIONS(TagReader) public: virtual ~TagReader(); enum Feature { TITLE, ARTIST, TRACK_NUMBER, TIME, GENRE, IMAGE, ALBUM, RATING, COMPOSER, VARIOUS_ARTISTS, LIST_END }; static const char* getLabel(int); // text representation for each Feature enum SuportLevel { NOT_SUPPORTED, READ_ONLY/*, READ_WRITE*/ }; struct FieldNotFound {}; // for variable field lists, to allow for the distinction between an empty field and a missing field virtual std::string getTitle(bool* /*pbFrameExists*/ = 0) const { throw NotSupportedOp(); } // UTF8 virtual std::string getArtist(bool* /*pbFrameExists*/ = 0) const { throw NotSupportedOp(); } // UTF8 virtual std::string getTrackNumber(bool* /*pbFrameExists*/ = 0) const { throw NotSupportedOp(); } // this is string (and not int) because in ID3V2 it's not necessarily a (single) number; it can be 2/12 virtual TagTimestamp getTime(bool* /*pbFrameExists*/ = 0) const { throw NotSupportedOp(); } virtual std::string getGenre(bool* /*pbFrameExists*/ = 0) const { throw NotSupportedOp(); } // UTF8 virtual ImageInfo getImage(bool* /*pbFrameExists*/ = 0) const { throw NotSupportedOp(); } // !!! this must not be called from a non-GUI thread (see comment in Id3V2Stream::preparePictureHlp); if there's a need to do so, perhaps switch from QPixmap to QImage virtual std::string getAlbumName(bool* /*pbFrameExists*/ = 0) const { throw NotSupportedOp(); } // UTF8 virtual double getRating(bool* /*pbFrameExists*/ = 0) const { throw NotSupportedOp(); } // 0 to 5 (5 for best); negative for unknown virtual std::string getComposer(bool* /*pbFrameExists*/ = 0) const { throw NotSupportedOp(); } // UTF8 enum VariousArtists { VA_NONE = 0, VA_ITUNES = 1, VA_WMP = 2 }; virtual int getVariousArtists(bool* /*pbFrameExists*/ = 0) const { throw NotSupportedOp(); } // combination of VariousArtists flags; since several frames might be involved, *pbFrameExists is set to "true" if at least a frame exists virtual std::string getOtherInfo() const { return ""; } // non-editable tags // this is shown in the main window in the "Tag details" tab, in the big text box at the bottom virtual std::vector getImages() const { return std::vector(); } // all images, even those with errors virtual std::string getImageData(bool* pbFrameExists = 0) const { if (0 != pbFrameExists) { *pbFrameExists = false; } return ""; } virtual SuportLevel getSupport(Feature) const { return NOT_SUPPORTED; } virtual const char* getName() const = 0; std::string getValue(Feature) const; // returns the corresponding feature converted to a string; if it's not supported, it returns an empty string; for IMAGE an empty string regardless of a picture being present or not static std::string getVarArtistsValue(); // what getValue(VARIOUS_ARTISTS) returns for VA tags, based on global configuration static int FEATURE_ON_POS[]; // the order in which Features should appear in grids static int POS_OF_FEATURE[]; // the "inverse" of FEATURE_ON_POS: what Feature appears in a given position }; //========================================================================== //========================================================================== /* Position: std::streampos; actually should be using std::istream::pos_type, but streampos is shorter; if the traits template param for streams are not used, std::istream::pos_type is just a typedef for std::streampos; represents a position in a file as a class (not as some integral); has automatic convertion to std::streamoff, so arithmetic operations work, but not necessarily as expected; Relative position: std::streamoff; actually should be using std::istream::off_type, but streamoff is shorter; if the traits template param for streams are not used, std::istream::off_type is just a typedef for std::streamoff; signed integral, not necessarily big enough to represent any position in a file; the difference between 2 std::streampos objects is a streamoff; std::streamsize - count for I/O operations, e.g. bytes read; signed integral; may be smaller than streamoff // ttt2 perhaps replace "long long" with std::streamoff in file routines too, although "long long" seems well suited long-term and easier to deal with instead of the 3 types in std lib (it seems that the only reason the std lib doesn't just use "long long" is to avoid forcing 16bit and 32bit machines perform 64bit calculations); or, well, if we want to prepare for files with sizes exceeding 64bit, the type to use uniformly for positions, offsets and counts could be something implementation-specific that may be an integral type or a class that looks like a number (has +, -, *, /); on most systems it would just be defined to be "long long"; // 2008.09.08: actually streamoff is a typedef for fpos, which also cares about multi-byte characters, so a simple integral type may not be enough; still, it would be nice to be able to use streamoff everywhere for binary files and get rid of all the conversions and overflows; More: - Standard draft 27.4.4 - http://www.velocityreviews.com/forums/t287670-postype-how-to-define-it-.html */ #endif // #ifndef DataStreamH MP3Diags-1.2.02/src/Translation.h0000644000175000001440000000210111724610460015400 0ustar ciobiusers#ifndef TranslationH #define TranslationH #include #include #include /* class LocaleInfo { std::string m_strCountry; std::string m_strLanguage; public: LocaleInfo(std::string strFileName); const std::string& getCountry() const { return m_strCountry; } const std::string& getLanguage() const { return m_strLanguage; } };*/ class TranslatorHandler { std::vector m_vstrLongTranslations; std::vector m_vstrTranslations; QTranslator m_appTranslator; QTranslator m_systemTranslator; std::string m_strCurrentTranslation; QString m_qstrSystemTranslDir; void addTranslations(const std::string& strDir); std::string getLocale(std::string strTranslation); TranslatorHandler(); public: const std::vector& getTranslations() { return m_vstrTranslations; } void setTranslation(const std::string& strTranslation); static TranslatorHandler& getGlobalTranslator(); static std::string getLanguageInfo(std::string strFileName); }; #endif // TranslationH MP3Diags-1.2.02/src/Helpers.cpp0000644000175000001440000014624612135665346015074 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include "fstream_unicode.h" #include #include #include #include #ifndef WIN32 #include #include #include #else #include #include #include #endif #include #include #include #include #include #include #include "Helpers.h" #include "Widgets.h" #include "Version.h" #include "OsFile.h" #include "DataStream.h" // for translation using namespace std; using namespace Version; void assertBreakpoint() { cout << endl; //qDebug("assert"); } void appendFilePart(istream& in, ostream& out, streampos pos, streamoff nSize) { const int BFR_SIZE (1024*128); char* pBfr (new char[BFR_SIZE]); pearl::ArrayPtrRelease rel (pBfr); in.seekg(pos); for (; nSize > 0;) { streamoff nCrtRead (nSize > BFR_SIZE ? BFR_SIZE : nSize); CB_CHECK1 (nCrtRead == read(in, pBfr, nCrtRead), EndOfFile()); out.write(pBfr, nCrtRead); nSize -= nCrtRead; } if (!out) { TRACER("appendFilePart() failed"); } CB_CHECK1 (out, WriteError()); } // prints to stdout the content of a memory location, as ASCII and hex; // GDB has a tendency to not see char arrays and other local variables; actually GCC seems to be the culprit (bug 34767); void inspect(const void* q, int nSize) { ostringstream out; const char* p ((const char*)q); for (int i = 0; i < nSize; ++i) { char c (p[i]); if (c < 32 || c > 126) { c = '.'; } out << c; } out << "\n("; out << hex << setfill('0'); bool b (false); for (int i = 0; i < nSize; ++i) { if (b) { out << " "; } b = true; unsigned char c (p[i]); out << setw(2) << (int)c; } out << dec << ")\n"; qDebug("%s", out.str().c_str()); //logToGlobalFile(out.str()); } int get32BitBigEndian(const char* bfr) { const unsigned char* p (reinterpret_cast(bfr)); int n ((p[0] << 24) + (p[1] << 16) + (p[2] << 8) + (p[3] << 0)); return n; } void put32BitBigEndian(int n, char* bfr) { unsigned u (n); bfr[3] = u & 0xff; u >>= 8; bfr[2] = u & 0xff; u >>= 8; bfr[1] = u & 0xff; u >>= 8; bfr[0] = u & 0xff; } string utf8FromLatin1(const string& strSrc) { int i (0); int n (cSize(strSrc)); for (; i < n; ++i) { unsigned char c (strSrc[i]); if (c >= 128) { goto e1; } } return strSrc; e1: string s (strSrc.substr(0, i)); for (; i < n; ++i) { unsigned char c (strSrc[i]); if (c < 128) { s += char(c); } else { unsigned char c1 (0xc0 | (c >> 6)); unsigned char c2 (0x80 | (c & 0x3f)); s += char(c1); s += char(c2); } } return s; } // removes whitespaces at the end of the string bool CB_LIB_CALL rtrim(string& s) { int n (cSize(s)); int i (n - 1); for (; i >= 0; --i) { unsigned char c (s[i]); if (c >= 128 || !isspace(c)) { break; } //!!! isspace() returns true for some non-ASCII chars, e.g. 0x9f, at least on MinGW (it matters that char is signed) } if (i < n - 1) { s.erase(i + 1); return true; } return false; } // removes whitespaces at the beginning of the string bool CB_LIB_CALL ltrim(string& s) { int n (cSize(s)); int i (0); for (; i < n; ++i) { unsigned char c (s[i]); if (c >= 128 || !isspace(c)) { break; } //!!! isspace() returns true for some non-ASCII chars, e.g. 0x9f, at least on MinGW } if (i > 0) { s.erase(0, i); return true; } return false; } bool CB_LIB_CALL trim(string& s) { bool b1 (ltrim(s)); bool b2 (rtrim(s)); return b1 || b2; } /* // multi-line hex printing void printHex(const string& s, ostream& out, bool bShowAsciiCode = true) //ttt3 see if anybody needs this { int nSize (cSize(s)); int nCrt (0); for (;;) { if (nCrt >= nSize) { return; } int nMax (16); if (nCrt + nMax > nSize) { nMax = nSize - nCrt; } for (int i = 0; i < nMax; ++i) { char c (s[i + nCrt]); if (c < 32 || c >= 127) { c = '?'; } out << " " << c << " "; } out << endl; for (int i = 0; i < nMax; ++i) { unsigned int x ((unsigned char)s[i + nCrt]); if (!bShowAsciiCode && x >= 32 && x < 127) { out << " "; } else { out << setw(2) << hex << x << dec << " "; } } out << endl; nCrt += 16; } } */ std::string asHex(const char* p, int nSize) { ostringstream out; out << "\""; for (int i = 0; i < nSize; ++i) { char c (p[i]); out << (c >= 32 && c < 127 ? c : '.'); } out << "\" (" << hex; for (int i = 0; i < nSize; ++i) { if (i > 0) { out << " "; } unsigned char c (p[i]); out << setw(2) << setfill('0') << (int)c; } out << ")"; return out.str(); } // the total memory currently used by the current process, in kB long getMemUsage() { #ifndef WIN32 //pid_t int n ((int)getpid()); char a [30]; sprintf (a, "/proc/%d/status", n); // ttt2 linux-specific; not sure how version-specific this is; // sprintf (a, "/proc/self/status", n); // ttt2 try this (after checking portability) ifstream_utf8 in (a); string s; while (getline(in, s)) { if (0 == s.find("VmSize:")) { return atol(s.c_str() + 7); } } return 0; #else PROCESS_MEMORY_COUNTERS pmc; if (GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc))) { return pmc.WorkingSetSize; } return 0; #endif } void logToGlobalFile(const string& s) //tttc make sure it is disabled in public releases { #ifndef WIN32 ofstream_utf8 out ( "/tmp/Mp3DiagsLog.txt", ios_base::app); #else char a [500]; int n (GetModuleFileNameA(NULL, a, 500)); //ttt3 using GetModuleFileNameA isn't quite right, but since it's a debug function ... a[n - 4] = 0; ofstream_utf8 out ( //"C:/Mp3DiagsLog.txt", //"Mp3DiagsLog.txt", //"C:/temp/Mp3DiagsLog.txt", (string(a) + "Log.txt").c_str(), ios_base::app); #endif out << s << endl; } #define DECODE_CHECK(COND, MSG) { if (!(COND)) { bRes = false; return MSG; } } namespace { struct Decoder { enum Version { MPEG1, MPEG2 }; enum Layer { LAYER1, LAYER2, LAYER3 }; enum ChannelMode { STEREO, JOINT_STEREO, DUAL_CHANNEL, SINGLE_CHANNEL }; Version m_eVersion; Layer m_eLayer; int m_nBitrate; int m_nFrequency; int m_nPadding; ChannelMode m_eChannelMode; int m_nSize; bool m_bCrc; const char* getSzVersion() const; const char* getSzLayer() const; const char* getSzChannelMode() const; QString initialize(const unsigned char* bfr, bool* pbIsValid); QString decodeMpegFrame(const unsigned char* bfr, const char* szSep, bool* pbIsValid); string decodeMpegFrameAsXml(const unsigned char* bfr, bool* pbIsValid); }; const char* Decoder::getSzVersion() const { static const char* s_versionName[] = { QT_TRANSLATE_NOOP("DataStream", "MPEG-1"), QT_TRANSLATE_NOOP("DataStream", "MPEG-2") }; return s_versionName[m_eVersion]; } const char* Decoder::getSzLayer() const { static const char* s_layerName[] = { QT_TRANSLATE_NOOP("DataStream", "Layer I"), QT_TRANSLATE_NOOP("DataStream", "Layer II"), QT_TRANSLATE_NOOP("DataStream", "Layer III") }; return s_layerName[m_eLayer]; } const char* Decoder::getSzChannelMode() const { static const char* s_channelModeName[] = { QT_TRANSLATE_NOOP("DataStream", "Stereo"), QT_TRANSLATE_NOOP("DataStream", "Joint stereo"), QT_TRANSLATE_NOOP("DataStream", "Dual channel"), QT_TRANSLATE_NOOP("DataStream", "Single channel") }; return s_channelModeName[m_eChannelMode]; } QString Decoder::initialize(const unsigned char* bfr, bool* pbIsValid) //ttt2 perhaps unify with MpegFrameBase::MpegFrameBase(), using the char* constructor; note that they also share translations { bool b; bool& bRes (0 == pbIsValid ? b : *pbIsValid); bRes = true; const unsigned char* pHeader (bfr); //inspect(bfr, BFR_SIZE); DECODE_CHECK (0xff == *pHeader && 0xe0 == (0xe0 & *(pHeader + 1)), DataStream::tr("Not an MPEG frame. Synch missing.")); ++pHeader; { int nVer ((*pHeader & 0x18) >> 3); switch (nVer) {//TRACE case 0x00: bRes = false; return DataStream::tr("Not an MPEG frame. Unsupported version (2.5)."); //ttt2 see about supporting this: search for MPEG1 to find other places // ttt2 in a way it would make more sense to warn that it's not supported, with "MP3_THROW(SUPPORT, ...)", but before warn, make sure it's a valid 2.5 frame, followed by another frame ... case 0x02: m_eVersion = MPEG2; break; case 0x03: m_eVersion = MPEG1; break; default: bRes = false; return DataStream::tr("Not an MPEG frame. Invalid version."); } } { int nLayer ((*pHeader & 0x06) >> 1); switch (nLayer) { case 0x01: m_eLayer = LAYER3; break; case 0x02: m_eLayer = LAYER2; break; case 0x03: m_eLayer = LAYER1; break; default: bRes = false; return DataStream::tr("Not an MPEG frame. Invalid layer."); } } { m_bCrc = !(*pHeader & 0x01); } ++pHeader; { static int s_bitrates [14][5] = { { 32, 32, 32, 32, 8 }, { 64, 48, 40, 48, 16 }, { 96, 56, 48, 56, 24 }, { 128, 64, 56, 64, 32 }, { 160, 80, 64, 80, 40 }, { 192, 96, 80, 96, 48 }, { 224, 112, 96, 112, 56 }, { 256, 128, 112, 128, 64 }, { 288, 160, 128, 144, 80 }, { 320, 192, 160, 160, 96 }, { 352, 224, 192, 176, 112 }, { 384, 256, 224, 192, 128 }, { 416, 320, 256, 224, 144 }, { 448, 384, 320, 256, 160 } }; int nRateIndex ((*pHeader & 0xf0) >> 4); DECODE_CHECK (nRateIndex >= 1 && nRateIndex <= 14, DataStream::tr("Not an MPEG frame. Invalid bitrate.")); int nTypeIndex (m_eVersion*3 + m_eLayer); if (nTypeIndex == 5) { nTypeIndex = 4; } m_nBitrate = s_bitrates[nRateIndex - 1][nTypeIndex]*1000; } { int nSmpl ((*pHeader & 0x0c) >> 2); switch (m_eVersion) { case MPEG1: switch (nSmpl) { case 0x00: m_nFrequency = 44100; break; case 0x01: m_nFrequency = 48000; break; case 0x02: m_nFrequency = 32000; break; default: bRes = false; return DataStream::tr("Not an MPEG frame. Invalid frequency for MPEG1."); } break; case MPEG2: switch (nSmpl) { case 0x00: m_nFrequency = 22050; break; case 0x01: m_nFrequency = 24000; break; case 0x02: m_nFrequency = 16000; break; default: bRes = false; return DataStream::tr("Not an MPEG frame. Invalid frequency for MPEG2."); } break; default: throw 1; // it should have thrown before getting here } } { m_nPadding = (0x02 & *pHeader) >> 1; } ++pHeader; { int nChMode ((*pHeader & 0xc0) >> 6); m_eChannelMode = (ChannelMode)nChMode; } switch (m_eLayer) { case LAYER1: m_nSize = (12*m_nBitrate/m_nFrequency + m_nPadding)*4; break; case LAYER2: m_nSize = 144*m_nBitrate/m_nFrequency + m_nPadding; break; case LAYER3: m_nSize = (MPEG1 == m_eVersion ? 144*m_nBitrate/m_nFrequency + m_nPadding : 72*m_nBitrate/m_nFrequency + m_nPadding); break; default: throw 1; // it should have thrown before getting here } return ""; } QString Decoder::decodeMpegFrame(const unsigned char* bfr, const char* szSep, bool* pbIsValid) //ttt2 perhaps unify with MpegFrameBase::MpegFrameBase(), using the char* constructor { QString s (initialize(bfr, pbIsValid)); if (!s.isEmpty()) { return s; } //ostringstream out; /*out << getSzVersion() << " " << getSzLayer() << ", " << m_nBitrate/1000 << "kbps, " << m_nFrequency << "Hz, " << getSzChannelMode() << ", padding=" << (m_nPadding ? "true" : "false") << ", length " << m_nSize << " (0x" << hex << m_nSize << dec << ")";*/ /*out << boolalpha << getSzVersion() << " " << getSzLayer() << szSep << getSzChannelMode()/ *4* / << szSep << m_nFrequency << "Hz" << szSep << m_nBitrate / *8* / << "bps" << szSep << "CRC=" << boolAsYesNo(m_bCrc) << szSep / *11* /<< "length " << m_nSize << " (0x" << hex << m_nSize << dec << ")" << szSep / *14* /<< "padding=" << (m_nPadding ? "true" : "false");*/ return DataStream::tr("%1 %2%3%4%5%6Hz%7%8bps%9CRC=%10%11length %12 (0x%13)%14padding=%15") .arg(DataStream::tr(getSzVersion())) .arg(DataStream::tr(getSzLayer())) .arg(szSep) .arg(DataStream::tr(getSzChannelMode())) .arg(szSep) .arg(m_nFrequency) .arg(szSep) .arg(m_nBitrate) .arg(szSep) .arg(GlobalTranslHlp::tr(boolAsYesNo(m_bCrc))) .arg(szSep) .arg(m_nSize) .arg(m_nSize, 0, 16) .arg(szSep) .arg(GlobalTranslHlp::tr(boolAsYesNo(m_nPadding))); } string Decoder::decodeMpegFrameAsXml(const unsigned char* bfr, bool* pbIsValid) //ttt2 perhaps unify with MpegFrameBase::MpegFrameBase(), using the char* constructor { QString s (initialize(bfr, pbIsValid)); // !!! XML is not translated if (!s.isEmpty()) { return convStr(s); } ostringstream out; out << " version=\"" << getSzVersion() << "\"" << " layer=\"" << getSzLayer() << "\"" << " channelMode=\"" << getSzChannelMode() << "\"" << " frequency=\"" << m_nFrequency << "\"" << " bps=\"" << m_nBitrate << "\"" << " crc=\"" << boolAsYesNo(m_bCrc) << "\"" << " mpegSize=\"" << m_nSize << "\"" << " padding=\"" << boolAsYesNo(m_nPadding) << "\""; // !!! XML isn't translated return out.str(); } } // namespace string decodeMpegFrame(unsigned int x, const char* szSep, bool* pbIsValid /* = 0*/) { Decoder d; unsigned char bfr [4]; unsigned char* q (reinterpret_cast(&x)); bfr[0] = q[3]; bfr[1] = q[2]; bfr[2] = q[1]; bfr[3] = q[0]; return convStr(d.decodeMpegFrame(bfr, szSep, pbIsValid)); } string decodeMpegFrame(const char* bfr, const char* szSep, bool* pbIsValid /* = 0*/) { Decoder d; const unsigned char* q (reinterpret_cast(bfr)); return convStr(d.decodeMpegFrame(q, szSep, pbIsValid)); } string decodeMpegFrameAsXml(const char* bfr, bool* pbIsValid /* = 0*/) { Decoder d; const unsigned char* q (reinterpret_cast(bfr)); return d.decodeMpegFrameAsXml(q, pbIsValid); } StreamStateRestorer::StreamStateRestorer(istream& in) : m_in(in), m_pos(in.tellg()), m_bOk(false) { } StreamStateRestorer::~StreamStateRestorer() { if (!m_bOk) { m_in.clear(); m_in.seekg(m_pos); } m_in.clear(); } char getPathSep() { return '/'; // ttt2 linux-specific } const string& getPathSepAsStr() { static string s ("/"); return s; // ttt2 linux-specific // ttt look at QDir::fromNativeSeparators } streampos getSize(istream& in) { streampos crt (in.tellg()); in.seekg(0, ios_base::end); streampos size (in.tellg()); in.seekg(crt); return size; } void writeZeros(ostream& out, int nCnt) { CB_ASSERT (nCnt >= 0); char c (0); for (int i = 0; i < nCnt; ++i) //ttt2 perhaps make this faster { out.write(&c, 1); } CB_CHECK1 (out, WriteError()); } void listWidget(QWidget* p, int nIndent /* = 0*/) { //if (nIndent > 1) { return; } if (0 == nIndent) { cout << "\n----------------------------\n"; } cout << string(nIndent*2, ' ') << convStr(p->objectName()) << " " << p->x() << " " << p->y() << " " << p->width() << " " << p->height() << endl; QList lst (p->findChildren()); for (QList::iterator it = lst.begin(); it != lst.end(); ++it) { QWidget* q (*it); if (q->parentWidget() == p) // !!! needed because findChildren() reurns all descendants, not only children { listWidget(q, nIndent + 1); } } } // replaces invalid HTTP characters like ' ' or '"' with their hex code (%20 or %22) string escapeHttp(const string& s) { QUrl url (convStr(s)); return convStr(QString(url.toEncoded())); } vector convStr(const vector& v) { vector u; for (int i = 0, n = cSize(v); i < n; ++i) { u.push_back(convStr(v[i])); } return u; } vector convStr(const vector& v) { vector u; for (int i = 0, n = cSize(v); i < n; ++i) { u.push_back(convStr(v[i])); } return u; } namespace { struct DesktopDetector { enum Desktop { Unknown, Gnome2 = 1, Gnome3 = 2, Kde3 = 4, Kde4 = 8, Gnome = Gnome2 | Gnome3, Kde = Kde3 | Kde4 }; DesktopDetector(); Desktop m_eDesktop; const char* m_szDesktop; bool onDesktop(Desktop desktop) const { return (desktop & m_eDesktop) != 0; } }; #if defined(__linux__) DesktopDetector::DesktopDetector() : m_eDesktop(Unknown) { FileSearcher fs ("/proc"); string strBfr; bool bIsKde (false); bool bIsKde4 (false); while (fs) { if (fs.isDir()) { string strCmdLineName (fs.getName()); if (isdigit(strCmdLineName[6])) { #if 0 char szBfr[5000] = "mP3DiAgS"; szBfr[0] = 0; int k (readlink((strCmdLineName + "/exe").c_str(), szBfr, sizeof(szBfr))); if (k >= 0) { szBfr[k] = 0; } cout << strCmdLineName << " "; szBfr[5000 - 1] = 0; //if (0 != szBfr[0]) cout << szBfr << " |"; //cout << szBfr << endl;//*/ strCmdLineName += "/cmdline"; //cout << strCmdLineName << endl; ifstream in (strCmdLineName.c_str()); if (in) { getline(in, strBfr); //if (!strBfr.empty()) { cout << "<<< " << strBfr.c_str() << " >>>" << endl; } //if (!strBfr.empty()) { cout << strBfr.c_str() << endl; } cout << " " << strBfr.c_str(); if (string::npos != strBfr.find("gnome-settings-daemon")) { m_eDesktop = string::npos != strBfr.find("gnome-settings-daemon-3.") ? Gnome3 : Gnome2; break; } }//*/ cout << endl; #endif char szBfr[5000] = "mP3DiAgS"; szBfr[0] = 0; strCmdLineName += "/cmdline"; //cout << strCmdLineName << endl; ifstream in (strCmdLineName.c_str()); if (in) { getline(in, strBfr); //if (!strBfr.empty()) { cout << "<<< " << strBfr.c_str() << " >>>" << endl; } //if (!strBfr.empty()) { cout << strBfr.c_str() << endl; } //cout << strBfr.c_str() << endl; if (string::npos != strBfr.find("gnome-settings-daemon")) { m_eDesktop = string::npos != strBfr.find("gnome-settings-daemon-3.") ? Gnome3 : Gnome2; break; } if (string::npos != strBfr.find("kdeinit")) { bIsKde = true; } //if (string::npos != strBfr.find("kde4/libexec")) // this gives false positives on openSUSE 11.4 from KDE 3: // for i in `ls /proc | grep '^[0-9]'` ; do a=`cat /proc/$i/cmdline 2>/dev/null` ; echo $a | grep kde4/libexec ; done if (string::npos != strBfr.find("kde4/libexec/start")) // ttt2 probably works only on Suse and only in some cases { bIsKde4 = true; } }//*/ } } //if (string::npos != strBfr.find("kdeinit")) fs.findNext(); } if (m_eDesktop == Unknown) { if (bIsKde4) { m_eDesktop = Kde4; } else if (bIsKde) { m_eDesktop = Kde3; } } if (Gnome2 == m_eDesktop) { // while on openSUSE there's gnome-settings-daemon-3, on Fedora it's always gnome-settings-daemon, regardless of the Gnome version; so if Gnome 3 seems to be installed, we'll override a "Gnome2" value QDir dir ("/usr/share/gnome-shell"); if (dir.exists()) { m_eDesktop = Gnome3; } } switch (m_eDesktop) { case Gnome2: m_szDesktop = "Gnome 2"; break; case Gnome3: m_szDesktop = "Gnome 3"; break; case Kde3: m_szDesktop = "KDE 3"; break; case Kde4: m_szDesktop = "KDE 4"; break; default: m_szDesktop = "Unknown"; } //cout << "desktop: " << m_eDesktop << endl; } #else // #if defined(__linux__) DesktopDetector::DesktopDetector() : m_eDesktop(Unknown) {} #endif const DesktopDetector& getDesktopDetector() { static DesktopDetector desktopDetector; return desktopDetector; } } // namespace bool getDefaultForShowCustomCloseButtons() { return DesktopDetector::Gnome3 == getDesktopDetector().m_eDesktop; } /* Gnome: Qt::Window - minimize, maximize, close; dialog gets its own taskbar entry Qt::WindowTitleHint - close Qt::Dialog - close Qt::WindowTitleHint | Qt::WindowMaximizeButtonHint - nothing Qt::Dialog | Qt::WindowMaximizeButtonHint - nothing Qt::Window | Qt::WindowMaximizeButtonHint - maximize Qt::Window | Qt::WindowMaximizeButtonHint | Qt::WindowMinimizeButtonHint - maximize, minimize Qt::WindowMaximizeButtonHint | Qt::WindowMinimizeButtonHint - nothing Ideally a modal dialog should minimize its parent. If that's not possible, it shouldn't be minimizable. */ //ttt0 look at Qt::CustomizeWindowHint #ifndef WIN32 //Qt::WindowFlags getMainWndFlags() { return isRunningOnGnome() ? Qt::Window : Qt::WindowTitleHint; } // !!! these are incorrect, but seem the best option; the values used for Windows are supposed to be OK; they work as expected with KDE but not with Gnome (asking for maximize button not only fails to show it, but causes the "Close" button to disappear as well); Since in KDE min/max buttons are shown when needed anyway, it's sort of OK // ttt2 see if there is workaround/fix Qt::WindowFlags getMainWndFlags() { const DesktopDetector& dd = getDesktopDetector(); return dd.onDesktop(DesktopDetector::Kde) ? Qt::WindowTitleHint : Qt::Window; } #if QT_VERSION >= 0x040500 //Qt::WindowFlags getDialogWndFlags() { const DesktopDetector& dd = getDesktopDetector(); return dd.onDesktop(DesktopDetector::Kde) ? Qt::WindowTitleHint | Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint : (dd.onDesktop(DesktopDetector::Gnome3) ? Qt::Window : Qt::WindowTitleHint); } Qt::WindowFlags getDialogWndFlags() { const DesktopDetector& dd = getDesktopDetector(); return dd.onDesktop(DesktopDetector::Kde) ? Qt::WindowTitleHint | Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint : (/*dd.onDesktop(DesktopDetector::Gnome3) ? Qt::Window :*/ Qt::WindowTitleHint); } #else //Qt::WindowFlags getDialogWndFlags() { const DesktopDetector& dd = getDesktopDetector(); return dd.onDesktop(DesktopDetector::Gnome3) ? Qt::Window : Qt::WindowTitleHint; } Qt::WindowFlags getDialogWndFlags() { const DesktopDetector& dd = getDesktopDetector(); return /*dd.onDesktop(DesktopDetector::Gnome3) ? Qt::Window :*/ Qt::WindowTitleHint; } // ttt0 perhaps better to make sure all dialogs have their ok/cancel buttons, so there's no need for a dedicated close button and let the app look more "native" #endif Qt::WindowFlags getNoResizeWndFlags() { return Qt::WindowTitleHint; } #else Qt::WindowFlags getMainWndFlags() { return Qt::WindowTitleHint | Qt::WindowMaximizeButtonHint | Qt::WindowMinimizeButtonHint; } // minimize, maximize, no "what's this" Qt::WindowFlags getDialogWndFlags() { return Qt::WindowTitleHint | Qt::WindowMaximizeButtonHint; } // minimize, no "what's this" Qt::WindowFlags getNoResizeWndFlags() { return Qt::WindowTitleHint; } // no "what's this" #endif #if 0 //ttt2 add desktop, distribution, WM, ... #ifndef WIN32 utsname info; uname(&info); cat /proc/version cat /etc/issue dmesg | grep "Linux version" cat /etc/*-release */ cat /etc/*_version */ #else // #endif #endif #ifndef WIN32 static void removeStr(string& main, const string& sub) { for (;;) { string::size_type n (main.find(sub)); if (string::npos == n) { return; } main.erase(n, sub.size()); } } #endif // !!! don't translate QString getSystemInfo() //ttt2 perhaps store this at startup, so fewer things may go wrong fhen the assertion handler needs it { QString s ("OS: "); QString qstrDesktop; #ifndef WIN32 QDir dir ("/etc"); QStringList filters; filters << "*-release" << "*_version"; dir.setNameFilters(filters); QStringList lFiles (dir.entryList(QDir::Files)); utsname utsInfo; uname(&utsInfo); s += utsInfo.sysname; s += " "; s += utsInfo.release; s += " "; s += utsInfo.version; s += " "; for (int i = 0; i < lFiles.size(); ++i) { //qDebug("%s", lFiles[i].toUtf8().constData()); if ("lsb-release" != lFiles[i]) { QFile f ("/etc/" + lFiles[i]); if (f.open(QIODevice::ReadOnly)) { QByteArray b (f.read(1000)); s += b; s += " "; } } } QFile f ("/etc/issue"); if (f.open(QIODevice::ReadOnly)) { QByteArray b (f.read(1000)); string s1 (b.constData()); removeStr(s1, "Welcome to"); removeStr(s1, "Kernel"); trim(s1); string::size_type n (s1.find('\\')); if (string::npos != n) { s1.erase(n); } trim(s1); if (endsWith(s1, "-")) { s1.erase(s1.size() - 1); trim(s1); } s += convStr(s1); //qDebug("a: %s", s.toUtf8().constData()); /*for (;;) { string::size_type n (s.find('\n')); if (string::npos == n) { break; } s1[n] = ' '; }*/ //qDebug("b: %s", s.toUtf8().constData()); } //ttt2 search /proc for kwin, metacity, ... const DesktopDetector& dd = getDesktopDetector(); qstrDesktop = "Desktop: " + QString(dd.m_szDesktop) + "\n"; #else //qstrVer += QString(" Windows version ID: %1").arg(QSysInfo::WinVersion); QSettings settings ("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", QSettings::NativeFormat); //qstrVer += QString(" Windows version: %1").arg(WIN32); s += QString(" Windows version: %1 %2 Build %3 %4").arg(settings.value("ProductName").toString()) .arg(settings.value("CSDVersion").toString()) .arg(settings.value("CurrentBuildNumber").toString()) .arg(settings.value("BuildLab").toString()); #endif s.replace('\n', ' '); s = QString("Version: %1 %2\nWord size: %3 bit\nQt version: %4\nBoost version: %5\n").arg(getAppName()).arg(getAppVer()).arg(QSysInfo::WordSize).arg(qVersion()).arg(BOOST_LIB_VERSION) + qstrDesktop + s; return s; } // sets colors at various points to emulate a non-linear gradient that better suits our needs; // dStart and dEnd should be between 0 and 1, with dStart < dEnd; they may also be both -1, in which case the gradient will have a solid color void configureGradient(QGradient& grad, const QColor& col, double dStart, double dEnd) { if (-1 == dStart && -1 == dEnd) { grad.setColorAt(0, col); grad.setColorAt(1, col); return; } CB_ASSERT (dStart < dEnd && 0 <= dStart && dEnd < 1.0001); static vector vdPoints; static vector vdValues; static bool s_bInit (false); static int SIZE; if (!s_bInit) { s_bInit = true; /* vdPoints.push_back(-0.1); vdValues.push_back(1.3); vdPoints.push_back(0.0); vdValues.push_back(1.3); vdPoints.push_back(0.1); vdValues.push_back(1.2); vdPoints.push_back(0.2); vdValues.push_back(1.15); vdPoints.push_back(0.8); vdValues.push_back(0.85); vdPoints.push_back(0.9); vdValues.push_back(0.8); vdPoints.push_back(1.0); vdValues.push_back(0.7); vdPoints.push_back(1.1); vdValues.push_back(0.7);*/ /*vdPoints.push_back(-0.1); vdValues.push_back(1.1); vdPoints.push_back(0.0); vdValues.push_back(1.1); vdPoints.push_back(0.1); vdValues.push_back(1.07); vdPoints.push_back(0.2); vdValues.push_back(1.03); vdPoints.push_back(0.8); vdValues.push_back(0.97); vdPoints.push_back(0.9); vdValues.push_back(0.93); vdPoints.push_back(1.0); vdValues.push_back(0.9); vdPoints.push_back(1.1); vdValues.push_back(0.9);*/ /*vdPoints.push_back(-0.1); vdValues.push_back(1.3); vdPoints.push_back(0.0); vdValues.push_back(1.3); vdPoints.push_back(0.1); vdValues.push_back(1.15); vdPoints.push_back(0.2); vdValues.push_back(1.03); vdPoints.push_back(0.8); vdValues.push_back(0.95); vdPoints.push_back(0.9); vdValues.push_back(0.9); vdPoints.push_back(1.0); vdValues.push_back(0.8); vdPoints.push_back(1.1); vdValues.push_back(0.8);*/ vdPoints.push_back(-0.1); vdValues.push_back(1.03); vdPoints.push_back(0.0); vdValues.push_back(1.03); vdPoints.push_back(0.1); vdValues.push_back(1.02); vdPoints.push_back(0.2); vdValues.push_back(1.01); vdPoints.push_back(0.8); vdValues.push_back(0.95); vdPoints.push_back(0.9); vdValues.push_back(0.9); vdPoints.push_back(1.0); vdValues.push_back(0.8); vdPoints.push_back(1.1); vdValues.push_back(0.8); SIZE = cSize(vdPoints); //findFont(); } #if 1 for (int i = 0; i < SIZE; ++i) { double x0 (vdPoints[i]), y0 (vdValues[i]), x1 (vdPoints[i + 1]), y1 (vdValues[i + 1]); double x; x = dStart; if (x0 <= x && x < x1) { double y (y0 + (y1 - y0)*(x - x0)/(x1 - x0)); grad.setColorAt((x - dStart)/(dEnd - dStart), col.lighter(int(100*y))); } if (dStart < x0 && x0 < dEnd) { grad.setColorAt((x0 - dStart)/(dEnd - dStart), col.lighter(int(100*y0))); } x = dEnd; if (x < x1) { double y (y0 + (y1 - y0)*(x - x0)/(x1 - x0)); grad.setColorAt((x - dStart)/(dEnd - dStart), col.lighter(int(100*y))); break; } } #else grad.setColorAt(0, col.lighter(dStart < 0.0001 ? 119 : 100)); //ttt2 perhaps use this or at least add an option grad.setColorAt(0.48, col); grad.setColorAt(0.52, col); grad.setColorAt(1, col.lighter(dEnd > 0.9999 ? 80 : 100)); #endif } vector getLocalHelpDirs() { static vector s_v; if (s_v.empty()) { #ifndef WIN32 //s_v.push_back("/home/ciobi/cpp/Mp3Utils/mp3diags/trunk/mp3diags/doc/html/"); s_v.push_back(QString("/usr/share/") + getHelpPackageName() + "-doc/html/"); //ttt0 lower/uppercase variations s_v.push_back(QString("/usr/share/doc/") + getHelpPackageName() + "/html/"); s_v.push_back(QString("/usr/share/doc/") + getHelpPackageName() + "-1.2.02/html/"); #else wchar_t wszModule [200]; int nRes (GetModuleFileName(0, wszModule, 200)); //qDebug("%s", QString::fromWCharArray(wszModule).toUtf8().constData()); if (0 < nRes && nRes < 200) { s_v.push_back(QFileInfo( fromNativeSeparators(QString::fromWCharArray(wszModule))).dir().absolutePath() + "/doc/"); //qDebug("%s", s_v.back().toUtf8().constData()); } #endif } return s_v; } // opens a web page from the documentation in the default browser; // first looks in several places on the local computer; if the file can't be found there, it goes to SourceForge void openHelp(const string& strFileName) { const vector& v (getLocalHelpDirs()); QString strDir; for (int i = 0; i < cSize(v); ++i) { if (QFileInfo(v[i] + convStr(strFileName)).isFile()) { strDir = v[i]; break; } } QString qs (strDir); if (qs.isEmpty()) { qs = "http://mp3diags.sourceforge.net" + QString(getWebBranch()) + "/"; } else { qs = QUrl::fromLocalFile(qs).toString(); } qs = qs + convStr(strFileName); //qDebug("open %s", qs.toUtf8().constData()); //logToGlobalFile(qs.toUtf8().constData()); CursorOverrider ovr; QDesktopServices::openUrl(QUrl(qs, QUrl::TolerantMode)); } // meant for displaying tooltips; converts some spaces to \n, so the tooltips have several short lines instead of a single wide line QString makeMultiline(const QString& qstrDescr) { QString s (qstrDescr); int SIZE (50); for (int i = SIZE; i < qstrDescr.size(); ++i) { if (' ' == s[i]) { s[i] = '\n'; i += SIZE; } } return s; } QString toNativeSeparators(const QString& s) { return QDir::toNativeSeparators(s); } QString fromNativeSeparators(const QString& s) { return QDir::fromNativeSeparators(s); } QString getTempDir() { /* #ifndef WIN32 return "/tmp"; #else wchar_t wszTmp [200]; if (GetTempPath(200, wszTmp) > 1999) { return ""; } return QString::fromWCharArray(wszTmp); #endif*/ static QString s; // ttt3 these static variables are not really thread safe, but in this case it doesn't matter, because they all get called from a single thread (the UI thread) if (s.isEmpty()) { s = QDir::tempPath(); if (s.endsWith(getPathSep())) { s.remove(s.size() - 1, 1); } } return s; } //============================================================================================= //============================================================================================= //============================================================================================= #if defined(__linux__) namespace { //const char* DSK_FOLDER ("~/.local/share/applications/"); const string& getDesktopIntegrationDir() { static string s_s; if (s_s.empty()) { s_s = convStr(QDir::homePath() + "/.local/share/applications"); try { createDir(s_s); } catch (...) { // nothing; this will cause shell integration to be disabled cerr << "failed to create dir " << s_s << endl; } s_s += "/"; } return s_s; } const char* DSK_EXT (".desktop"); class ShellIntegrator { Q_DECLARE_TR_FUNCTIONS(ShellIntegrator) string m_strFileName; string m_strAppName; string m_strArg; bool m_bRebuildAssoc; static string escape(const string& s) { string s1; static string s_strReserved (" '\\><~|&;$*?#()`"); // see http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s06.html for (int i = 0; i < cSize(s); ++i) { char c (s[i]); if (s_strReserved.find(c) != string::npos) { s1 += '\\'; } s1 += c; } return s1; } public: ShellIntegrator(const string& strFileNameBase, const char* szSessType, const string& strArg, bool bRebuildAssoc) : m_strFileName(getDesktopIntegrationDir() + strFileNameBase + DSK_EXT), m_strAppName(convStr(tr("%1 - %2").arg(getAppName()).arg(tr(szSessType)))), m_strArg(strArg), m_bRebuildAssoc(bRebuildAssoc) {} enum { DONT_REBUILD_ASSOC = 0, REBUILD_ASSOC = 1 }; bool isEnabled() { return fileExists(m_strFileName); } void enable(bool b) { if (!(b ^ isEnabled())) { return; } if (b) { char szBfr [5000] = "mP3DiAgS"; szBfr[0] = 0; int k (readlink("/proc/self/exe", szBfr, sizeof(szBfr))); if (k >= 0) { szBfr[k] = 0; } szBfr[5000 - 1] = 0; ofstream_utf8 out (m_strFileName.c_str()); if (!out) { qDebug("couldn't open file %s", m_strFileName.c_str()); } out << "[Desktop Entry]" << endl; out << "Comment=" << m_strAppName << endl; out << "Encoding=UTF-8" << endl; out << "Exec=" << escape(szBfr) << " " << m_strArg << " %f" << endl; out << "GenericName=" << m_strAppName << endl; out << "Icon=" << getIconName() << endl; out << "Name=" << m_strAppName << endl; out << "Path=" << endl; out << "StartupNotify=false" << endl; out << "Terminal=0" << endl; out << "TerminalOptions=" << endl; out << "Type=Application" << endl; out << "X-KDE-SubstituteUID=false" << endl; out << "X-KDE-Username=" << endl; out << "MimeType=inode/directory" << endl; out << "NoDisplay=true" << endl; out.close(); static bool s_bErrorReported (false); bool bError (false); if (m_bRebuildAssoc && getDesktopDetector().onDesktop(DesktopDetector::Kde)) { TRACER1A("ShellIntegrator::enable()", 1); QProcess kbuildsycoca4; kbuildsycoca4.start("kbuildsycoca4"); TRACER1A("ShellIntegrator::enable()", 2); if (!kbuildsycoca4.waitForStarted()) //ttt1 switch to non-blocking calls if these don't work well enough { TRACER1A("ShellIntegrator::enable()", 3); bError = true; } else { TRACER1A("ShellIntegrator::enable()", 4); kbuildsycoca4.closeWriteChannel(); TRACER1A("ShellIntegrator::enable()", 5); if (!kbuildsycoca4.waitForFinished()) { TRACER1A("ShellIntegrator::enable()", 6); bError = true; } } TRACER1A("ShellIntegrator::enable()", 7); if (bError && !s_bErrorReported) { s_bErrorReported = true; HtmlMsg::msg(0, 0, 0, 0, HtmlMsg::CRITICAL, tr("Error setting up shell integration"), tr("It appears that setting up shell integration didn't complete successfully. You might have to configure it manually.") + "

" + tr("This message will not be shown again until the program is restarted, even if more errors occur.") , 400, 300, tr("O&K")); } } } else { try { deleteFile(m_strFileName); } catch (...) { //ttt2 do something } } } }; ShellIntegrator g_tempShellIntegrator ("mp3DiagsTempSess", QT_TRANSLATE_NOOP("ShellIntegrator", "temporary folder"), "-t", ShellIntegrator::REBUILD_ASSOC); ShellIntegrator g_hiddenShellIntegrator ("mp3DiagsHiddenSess", QT_TRANSLATE_NOOP("ShellIntegrator", "hidden folder"), "-f", ShellIntegrator::REBUILD_ASSOC); ShellIntegrator g_visibleShellIntegrator ("mp3DiagsVisibleSess", QT_TRANSLATE_NOOP("ShellIntegrator", "visible folder"), "-v", ShellIntegrator::REBUILD_ASSOC); ShellIntegrator g_testShellIntegrator ("mp3DiagsTestSess_000", "test", "", ShellIntegrator::DONT_REBUILD_ASSOC); } // namespace /*static*/ bool ShellIntegration::isShellIntegrationEditable() { g_testShellIntegrator.enable(true); bool b (g_testShellIntegrator.isEnabled()); g_testShellIntegrator.enable(false); return b; } /*static*/ string ShellIntegration::getShellIntegrationError() { return ""; } /*static*/ void ShellIntegration::enableTempSession(bool b) { g_tempShellIntegrator.enable(b); } /*static*/ bool ShellIntegration::isTempSessionEnabled() { return g_tempShellIntegrator.isEnabled(); } /*static*/ void ShellIntegration::enableVisibleSession(bool b) { g_visibleShellIntegrator.enable(b); } /*static*/ bool ShellIntegration::isVisibleSessionEnabled() { return g_visibleShellIntegrator.isEnabled(); } /*static*/ void ShellIntegration::enableHiddenSession(bool b) { g_hiddenShellIntegrator.enable(b); } /*static*/ bool ShellIntegration::isHiddenSessionEnabled() { return g_hiddenShellIntegrator.isEnabled(); } #elif defined (WIN32) namespace { //ttt2 use a class instead of functions, to handle errors better struct RegKey { HKEY m_hKey; RegKey() : m_hKey(0) {} ~RegKey() { RegCloseKey(m_hKey); } }; /* { HKEY hkey; if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_CLASSES_ROOT, L"Directory\\shell", 0, KEY_READ, &hkey)) { RegCloseKey(hkey); } if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_CLASSES_ROOT, L"Directory\\shell", 0, KEY_WRITE, &hkey)) { HKEY hSubkey; if (ERROR_SUCCESS == RegCreateKeyEx(hkey, L"MySubkey", 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hSubkey, NULL)) { if (ERROR_SUCCESS == RegSetValueExA(hSubkey, NULL, 0, REG_SZ, (const BYTE*)("my string"), 10)) { cout << "OK\n"; } RegCloseKey(hSubkey); } RegCloseKey(hkey); } } */ //bool doesKeyExist(const wchar_t* wszPath) bool doesKeyExist(const char* szPath) { RegKey key; return ERROR_SUCCESS == RegOpenKeyExA(HKEY_CLASSES_ROOT, szPath, 0, KEY_READ, &key.m_hKey); } //bool createEntries(const wchar_t* wszPath, const wchar_t* wszSubkey, const wchar_t* wszDescr, const wchar_t* wszCommand) bool createEntries(const char* szPath, const char* szSubkey, const char* szDescr, const char* szParam) { string s (string("\"") + _pgmptr + "\" " + szParam); const char* szCommand (s.c_str()); RegKey key; if (ERROR_SUCCESS != RegOpenKeyExA(HKEY_CLASSES_ROOT, szPath, 0, KEY_WRITE, &key.m_hKey)) { return false; } RegKey subkey; if (ERROR_SUCCESS != RegCreateKeyExA(key.m_hKey, szSubkey, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &subkey.m_hKey, NULL)) { return false; } if (ERROR_SUCCESS != RegSetValueExA(subkey.m_hKey, NULL, 0, REG_SZ, (const BYTE*)szDescr, strlen(szDescr) + 1)) { return false; } RegKey commandKey; if (ERROR_SUCCESS != RegCreateKeyExA(subkey.m_hKey, "command", 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &commandKey.m_hKey, NULL)) { return false; } if (ERROR_SUCCESS != RegSetValueExA(commandKey.m_hKey, NULL, 0, REG_SZ, (const BYTE*)szCommand, strlen(szCommand) + 1)) { return false; } return true; } bool deleteKey(const char* szPath, const char* szSubkey) { RegKey key; if (ERROR_SUCCESS != RegOpenKeyExA(HKEY_CLASSES_ROOT, szPath, 0, KEY_WRITE, &key.m_hKey)) { return false; } RegKey subkey; if (ERROR_SUCCESS != RegCreateKeyExA(key.m_hKey, szSubkey, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &subkey.m_hKey, NULL)) { return false; } if (ERROR_SUCCESS != RegDeleteKeyA(subkey.m_hKey, "command")) { return false; } if (ERROR_SUCCESS != RegDeleteKeyA(key.m_hKey, szSubkey)) { return false; } return true; } } // namespace //-------------------------------------------------------------- //ttt1 use something like ShellIntegrator in the Linux code /*static*/ bool ShellIntegration::isShellIntegrationEditable() { //RegKey key; // return ERROR_SUCCESS == RegOpenKeyExA(HKEY_CLASSES_ROOT, "Directory\\shell", 0, KEY_WRITE, &key.m_hKey); //!!! if compiled with MinGW on XP there's this issue on W7: it seems to work but it looks like it uses a temporary path, which gets soon deleted createEntries("Directory\\shell", "test_000_mp3diags", "test", "-t \"%1\""); if (!doesKeyExist("Directory\\shell\\test_000_mp3diags")) { return false; } deleteKey("Directory\\shell", "test_000_mp3diags"); return true; } /*static*/ string ShellIntegration::getShellIntegrationError() { if (isShellIntegrationEditable()) { return ""; } return convStr(GlobalTranslHlp::tr("These settings cannot currently be changed. In order to make changes you should probably run the program as an administrator.")); } /*static*/ void ShellIntegration::enableTempSession(bool b) { if (!(isTempSessionEnabled() ^ b)) { return; } // no change needed if (b) { createEntries("Directory\\shell", "mp3diags_temp_dir", "Open as temporary folder in MP3 Diags", "-t \"%1\""); createEntries("Drive\\shell", "mp3diags_temp_dir", "Open as temporary folder in MP3 Diags", "-t %1"); } else { deleteKey("Directory\\shell", "mp3diags_temp_dir"); deleteKey("Drive\\shell", "mp3diags_temp_dir"); } } /*static*/ bool ShellIntegration::isTempSessionEnabled() { return doesKeyExist("Directory\\shell\\mp3diags_temp_dir"); } /*static*/ void ShellIntegration::enableVisibleSession(bool b) { if (!(isVisibleSessionEnabled() ^ b)) { return; } // no change needed if (b) { createEntries("Directory\\shell", "mp3diags_visible_dir", "Open as visible folder in MP3 Diags", "-v \"%1\""); createEntries("Drive\\shell", "mp3diags_visible_dir", "Open as visible folder in MP3 Diags", "-v %1"); } else { deleteKey("Directory\\shell", "mp3diags_visible_dir"); deleteKey("Drive\\shell", "mp3diags_visible_dir"); } } /*static*/ bool ShellIntegration::isVisibleSessionEnabled() { return doesKeyExist("Directory\\shell\\mp3diags_visible_dir"); } /*static*/ void ShellIntegration::enableHiddenSession(bool b) { if (!(isHiddenSessionEnabled() ^ b)) { return; } // no change needed if (b) { createEntries("Directory\\shell", "mp3diags_hidden_dir", "Open as hidden folder in MP3 Diags", "-f \"%1\""); createEntries("Drive\\shell", "mp3diags_hidden_dir", "Open as hidden folder in MP3 Diags", "-f %1"); } else { deleteKey("Directory\\shell", "mp3diags_hidden_dir"); deleteKey("Drive\\shell", "mp3diags_hidden_dir"); } } /*static*/ bool ShellIntegration::isHiddenSessionEnabled() { return doesKeyExist("Directory\\shell\\mp3diags_hidden_dir"); } #else /*static*/ bool ShellIntegration::isShellIntegrationEditable() { return false; } /*static*/ string ShellIntegration::getShellIntegrationError() { return convStr(GlobalTranslHlp::tr("Platform not supported")); } /*static*/ void ShellIntegration::enableTempSession(bool) { } /*static*/ bool ShellIntegration::isTempSessionEnabled() { return false; } /*static*/ void ShellIntegration::enableVisibleSession(bool) { } /*static*/ bool ShellIntegration::isVisibleSessionEnabled() { return false; } /*static*/ void ShellIntegration::enableHiddenSession(bool) { } /*static*/ bool ShellIntegration::isHiddenSessionEnabled() { return false; } #endif //============================================================================================= //============================================================================================= //============================================================================================= Tracer::Tracer(const std::string& s) : m_s(s) { traceToFile("> " + s, 1); } Tracer::~Tracer() { traceToFile(" < " + m_s, -1); } //============================================================================================= LastStepTracer::LastStepTracer(const std::string& s) : m_s(s) { traceLastStep("> " + s, 1); } LastStepTracer::~LastStepTracer() { traceLastStep(" < " + m_s, -1); } //============================================================================================= //============================================================================================= //============================================================================================= //ttt2 F1 help was very slow on XP once, not sure why; later it was OK //ttt1 maybe switch to new spec, lower-case for exe name, package, and icons MP3Diags-1.2.02/src/MainForm.ui0000644000175000001440000010730311614775143015022 0ustar ciobiusers MainFormDlg 0 0 1000 685 630 440 Dialog true 12 0 40 40 40 40 Qt::NoFocus Scan folders for MP3 files [Ctrl+S] prc :/images/scan.svg:/images/scan.svg 36 36 Ctrl+S true 0 0 40 40 40 40 Qt::NoFocus Close this window and open the Session editor ... :/images/session.svg:/images/session.svg 36 36 true 40 40 40 40 Qt::NoFocus Export ... ... :/images/export.svg:/images/export.svg 36 36 true 0 0 40 40 40 40 Qt::NoFocus Filter by notes nflt :/images/filter-note.svg:/images/filter-note.svg 36 36 true false true 40 40 40 40 Qt::NoFocus Filter by folders dflt :/images/filter-folder.svg:/images/filter-folder.svg 36 36 true false true 0 0 40 40 40 40 Qt::NoFocus Show the full list of files (after applying the filters) ... :/images/mode_all.svg:/images/mode_all.svg 36 36 true true true true 40 40 40 40 Qt::NoFocus Show one album (i.e. folder) at a time d :/images/mode_album.svg:/images/mode_album.svg 36 36 true true true 40 40 40 40 Qt::NoFocus Show one song at a time f :/images/mode_file.svg:/images/mode_file.svg 36 36 true false true true 3 0 40 40 40 40 Qt::NoFocus Previous [Ctrl+P] < :/images/go-previous.svg:/images/go-previous.svg 36 36 Ctrl+P true Qt::NoArrow Folder true 40 40 40 40 Qt::NoFocus Next [Ctrl+N] > :/images/go-next.svg:/images/go-next.svg 36 36 Ctrl+N true Qt::NoArrow 0 0 40 40 40 40 Qt::NoFocus Apply a single transformation rep :/images/transform.svg:/images/transform.svg 36 36 true 40 40 40 40 Qt::NoFocus Apply custom set of transforms #1 :/images/transform1.svg:/images/transform1.svg 36 36 true 40 40 40 40 Qt::NoFocus Apply custom set of transforms #2 :/images/transform2.svg:/images/transform2.svg 36 36 true 40 40 40 40 Qt::NoFocus Apply custom set of transforms #3 :/images/transform3.svg:/images/transform3.svg 36 36 true 40 40 40 40 Qt::NoFocus ... :/images/transform4.svg:/images/transform4.svg 36 36 true 40 40 40 40 Qt::NoFocus Tag editor ... :/images/tag_editor.svg:/images/tag_editor.svg 36 36 true 40 40 40 40 Qt::NoFocus Normalize ... :/images/normalize.svg:/images/normalize.svg 36 36 true 40 40 40 40 Qt::NoFocus Reload ... :/images/edit-undo.svg:/images/edit-undo.svg 36 36 true 40 40 40 40 Qt::NoFocus Rename files ... :/images/file-rename.svg:/images/file-rename.svg 36 36 true 0 0 40 40 40 40 Qt::NoFocus Configuration cfg :/images/configure.svg:/images/configure.svg 36 36 true 40 40 40 40 Qt::NoFocus Debug ... :/images/debug.svg:/images/debug.svg 36 36 true 40 40 40 40 Qt::NoFocus About ... :/images/about.svg:/images/about.svg 36 36 true Qt::Vertical false QAbstractItemView::ExtendedSelection 0 0 1 0 0 File info true true All notes true Tag details true Qt::Horizontal 40 20 0 10 0 Removable TextLabel 0 File info 0 Qt::Vertical false QAbstractItemView::ExtendedSelection QAbstractItemView::SelectRows false QAbstractItemView::SelectRows All notes 0 QAbstractItemView::NoSelection Tag details MP3Diags-1.2.02/src/Version.h0000644000175000001440000000413212126133313014527 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ namespace Version { const char* getAppVer(); // used, e.g. for location at SourceForge const char* getWebBranch(); // to be shown to the user in various forms (app title, About box, shell integration, ...) const char* getAppName(); // icon name, needed for shell integration in Linux const char* getIconName(); // used for location of the documentation const char* getHelpPackageName(); // used for location of the translation files const char* getTranslationPackageName(); // for the global settings const char* getSettingsAppName(); // for config only const char* getOrganization(); } MP3Diags-1.2.02/src/fstream_unicode.cpp0000644000175000001440000001402312265757271016627 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #if defined(__GNUC__) && !defined(__llvm__) #include "fstream_unicode.h" #include // might want to look at basic_file_stdio.cc or ext/stdio_filebuf.h // http://www2.roguewave.com/support/docs/leif/sourcepro/html/stdlibref/basic-ifstream.html // STLPort // MSVC // Dinkumware - not // http://www.aoc.nrao.edu/~tjuerges/ALMA/STL/html/class____gnu__cxx_1_1stdio__filebuf.html //ttt2 perhaps add flush() /* ttt2 see about file flushing: http://support.microsoft.com/default.aspx/kb/148505 - fdatasync() - like fsync() but doesn't change metadata (e.g. mtime) so it's faster - since the C++ Library has nothing to do flushing, perhaps this would work: standalone "commit(const ofstream_utf8&)" and/or "commit(const string&)" ; also, we don't want to commit all the files; or perhaps "commit(ofstream_utf8&)" is needed, which would first close the file - there's also out.rdbud()->pubsync(), but what it does is OS-dependent - use external disk / flash, to see the LED, for testing */ // converts __mode to flags that can be used by the POSIX open() function int getOpenFlags(std::ios_base::openmode __mode) { int nAcc; if (std::ios_base::in & __mode) { if (std::ios_base::out & __mode) { nAcc = O_RDWR | O_CREAT; } else { nAcc = O_RDONLY; } } else if (std::ios_base::out & __mode) { nAcc = O_RDWR | O_CREAT; } else { throw 1; // ttt2 } #ifdef O_LARGEFILE nAcc |= O_LARGEFILE; #endif if (std::ios_base::trunc & __mode) { nAcc |= O_TRUNC; } if (std::ios_base::app & __mode) { nAcc |= O_APPEND; } #ifdef O_BINARY if (std::ios_base::binary & __mode) { nAcc |= O_BINARY; } #endif return nAcc; } /* template<> int unicodeOpenHlp(const QString& s, std::ios_base::openmode __mode) { int nFd (open(s.toUtf8().constData(), getOpenFlags(__mode), S_IREAD | S_IWRITE)); return nFd; } */ #if defined(WIN32) #include #include #include using namespace std; static wstring wstrFromUtf8(const string& s) { vector w (s.size() + 1); MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, &w[0], w.size()); //inspect(&w[0], w.size()*2); return &w[0]; } template<> int unicodeOpenHlp(const wchar_t* const& wszUtf16Name, std::ios_base::openmode __mode) { int nAcc (getOpenFlags(__mode)); int nFd (_wopen(wszUtf16Name, nAcc, S_IREAD | S_IWRITE)); //int nFd (open(szUtf8Name, nAcc, S_IREAD | S_IWRITE)); //qDebug("fd %d %s acc=%d", nFd, szUtf8Name, nAcc); return nFd; } template<> int unicodeOpenHlp(const char* const& szUtf8Name, std::ios_base::openmode __mode) { return unicodeOpenHlp(wstrFromUtf8(szUtf8Name).c_str(), __mode); } #elif defined(__OS2__) // "#if defined(WIN32)" #include template<> int unicodeOpenHlp(const char* const& szUtf8Name, std::ios_base::openmode __mode) { //return unicodeOpenHlp(wstrFromUtf8(szUtf8Name).c_str(), __mode); QString s (QString::fromUtf8(szUtf8Name)); QByteArray ba (s.toLocal8Bit()); int nAcc (getOpenFlags(__mode)); int nFd (open(ba.data(), nAcc, S_IREAD | S_IWRITE)); return nFd; }//*/ template<> int unicodeOpenHlp(char* const& szUtf8Name, std::ios_base::openmode __mode) //ttt0 see why is this needed { return unicodeOpenHlp((const char*)szUtf8Name, __mode); }//*/ #else // "#if defined(WIN32)" / "#elif defined(__OS2__)" template<> int unicodeOpenHlp(const char* const& szUtf8Name, std::ios_base::openmode __mode) { int nAcc (getOpenFlags(__mode)); int nFd (open(szUtf8Name, nAcc, S_IREAD | S_IWRITE)); //qDebug("fd %d %s acc=%d", nFd, szUtf8Name, nAcc); return nFd; } #if 0 template<> int unicodeOpenHlp(const wchar_t* const& /*wszUtf16Name*/, std::ios_base::openmode /*__mode*/) { throw 1; //ttt2 add if needed } #endif #endif // "#if defined(WIN32)" / "#elif defined(__OS2__)" / "#else" template<> int unicodeOpenHlp(const int& fd, std::ios_base::openmode /*__mode*/) { return fd; } //ttt2 review O_SHORT_LIVED #else // #if defined(__GNUC__) && !defined(__llvm__) // nothing to do for now; the MSVC version is fully inline and no ports to other compilers exist #endif // #if defined(__GNUC__) && !defined(__llvm__) MP3Diags-1.2.02/src/ScanDlgImpl.cpp0000644000175000001440000000647311265064011015605 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include "ScanDlgImpl.h" #include "CommonData.h" #include "CheckedDir.h" using namespace std; using namespace pearl; ScanDlgImpl::ScanDlgImpl(QWidget* pParent, CommonData* pCommonData) : QDialog(pParent, getDialogWndFlags()), Ui::ScanDlg(), m_pCommonData(pCommonData) { setupUi(this); m_pDirModel = new CheckedDirModel(this, CheckedDirModel::USER_CHECKABLE); m_pDirModel->setFilter(QDir::AllDirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::Drives); m_pDirModel->setSorting(QDir::IgnoreCase); m_pDirsT->setModel(m_pDirModel); m_pDirsT->expand(m_pDirModel->index("/")); m_pDirsT->header()->hide(); m_pDirsT->header()->setStretchLastSection(false); m_pDirsT->header()->setResizeMode(0, QHeaderView::ResizeToContents); //m_pDirModel->setDirs(pCommonData->getIncludeDirs(), pCommonData->getExcludeDirs(), m_pDirsT); //m_pDirModel->expandNodes(m_pDirsT); QTimer::singleShot(1, this, SLOT(onShow())); { QAction* p (new QAction(this)); p->setShortcut(QKeySequence("F1")); connect(p, SIGNAL(triggered()), this, SLOT(onHelp())); addAction(p); } } ScanDlgImpl::~ScanDlgImpl() { } void ScanDlgImpl::onShow() { m_pDirModel->setDirs(m_pCommonData->getIncludeDirs(), m_pCommonData->getExcludeDirs(), m_pDirsT); // !!! needed here because on the constructor the tree view doesn't have the right size; //ttt2 perhaps do the same in SessionEditorDlgImpl / see which approach is better } // if returning true, it also calls CommonData::setDirs() bool ScanDlgImpl::run(bool& bForce) { if (QDialog::Accepted != exec()) { return false; } bForce = m_pForceScanCkB->isChecked(); m_pCommonData->setDirectories(m_pDirModel->getCheckedDirs(), m_pDirModel->getUncheckedDirs()); return true; } void ScanDlgImpl::onHelp() { // openHelp("index.html"); } MP3Diags-1.2.02/src/SimpleSaxHandler.h0000644000175000001440000001274611734164463016335 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef SimpleSaxHandlerH #define SimpleSaxHandlerH #include #include template struct SimpleSaxHandler : public QXmlDefaultHandler { typedef void (T::* Start)(const QXmlAttributes&); typedef void (T::* End)(); typedef void (T::* Char)(const std::string&); class Node { NoDefaults k; friend struct SimpleSaxHandler; int m_nLevel; Node(const std::string& strName) : k(0), m_nLevel(0), m_pParent(0), m_strName(strName), onStart(0), onEnd(0), onChar(0) { } Node(Node& parent, const std::string& strName) : k(0), m_nLevel(parent.m_nLevel + 1), m_pParent(&parent), m_strName(strName), onStart(0), onEnd(0), onChar(0) { parent.add(this); } std::vector m_vpChildren; Node* m_pParent; std::string m_strName; void add(Node* p) { if (0 != getChild(p->m_strName)) { throw 1; } // ttt2 throw something else m_vpChildren.push_back(p); } Node* getChild(const std::string& strName) { for (int i = 0, n = cSize(m_vpChildren); i < n; ++i) { if (strName == m_vpChildren[i]->m_strName) { return m_vpChildren[i]; } } return 0; } public: Start onStart; End onEnd; Char onChar; }; Node& getRoot() { //return *m_lpNodes.front(); typename std::list::iterator it (m_lpNodes.begin()); ++it; return **it; } Node& makeNode(Node& node, const std::string& strName) { m_lpNodes.push_back(new Node(node, strName)); return *m_lpNodes.back(); } SimpleSaxHandler(const std::string& strName) { m_lpNodes.push_back(new Node("qqq")); // to avoid comparisons to 0 m_lpNodes.push_back(new Node(*m_lpNodes.back(), strName)); // the "root" node has a level of 1, because the artificial first elem has level 0 m_pCrtNode = m_lpNodes.front(); m_nCrtLevel = 0; // ttt2 reset these if an error occurs and then the object is used for another parsing; } /*override*/ ~SimpleSaxHandler() { pearl::clearPtrContainer(m_lpNodes); } private: std::list m_lpNodes; Node* m_pCrtNode; int m_nCrtLevel; /*override*/ bool startElement(const QString& /*qstrNamespaceUri*/, const QString& qstrLocalName, const QString& /*qstrName*/, const QXmlAttributes& attrs) { ++m_nCrtLevel; if (m_nCrtLevel == m_pCrtNode->m_nLevel + 1) { std::string s (qstrLocalName.toUtf8()); Node* pChild (m_pCrtNode->getChild(s)); if (0 != pChild) { m_pCrtNode = pChild; if (0 != m_pCrtNode->onStart) { T* p (dynamic_cast(this)); (p->*(m_pCrtNode->onStart))(attrs); } } } return true; } /*override*/ bool endElement(const QString& /*qstrNamespaceUri*/, const QString& qstrLocalName, const QString& /*qstrName*/) { --m_nCrtLevel; if (m_nCrtLevel == m_pCrtNode->m_nLevel - 1) { std::string s (qstrLocalName.toUtf8()); CB_ASSERT (s == m_pCrtNode->m_strName); if (0 != m_pCrtNode->onEnd) { T* p (dynamic_cast(this)); (p->*(m_pCrtNode->onEnd))(); } m_pCrtNode = m_pCrtNode->m_pParent; } return true; } /*override*/ bool characters(const QString& qstrCh) { std::string s (qstrCh.toUtf8()); if (m_nCrtLevel == m_pCrtNode->m_nLevel) { if (0 != m_pCrtNode->onChar) { T* p (dynamic_cast(this)); (p->*(m_pCrtNode->onChar))(s); } } return true; } }; #endif // #ifndef SimpleSaxHandlerH MP3Diags-1.2.02/src/SessionEditorDlgImpl.cpp0000644000175000001440000003604011731106121017501 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include "SessionEditorDlgImpl.h" #include "CheckedDir.h" #include "Helpers.h" #include "Transformation.h" #include "StoredSettings.h" #include "OsFile.h" #include "Translation.h" #include "CommonData.h" #include "Widgets.h" using namespace std; //using namespace pearl; void SessionEditorDlgImpl::commonConstr() // common code for both constructors { setupUi(this); m_pDirModel = new CheckedDirModel(this, CheckedDirModel::USER_CHECKABLE); m_pDirModel->setFilter(QDir::AllDirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::Drives); m_pDirModel->setSorting(QDir::IgnoreCase); m_pDirectoriesT->setModel(m_pDirModel); m_pDirectoriesT->expand(m_pDirModel->index("/")); m_pDirectoriesT->header()->hide(); m_pDirectoriesT->header()->setStretchLastSection(false); m_pDirectoriesT->header()->setResizeMode(0, QHeaderView::ResizeToContents); m_bOpenLastSession = true; { QAction* p (new QAction(this)); p->setShortcut(QKeySequence("F1")); connect(p, SIGNAL(triggered()), this, SLOT(onHelp())); addAction(p); } QPalette grayPalette (m_pBackupE->palette()); grayPalette.setColor(QPalette::Base, grayPalette.color(QPalette::Disabled, QPalette::Window)); m_pBackupE->setPalette(grayPalette); m_pFileNameE->setPalette(grayPalette); { // language int nCrt (0); const vector& vstrTranslations (TranslatorHandler::getGlobalTranslator().getTranslations()); string strTmpTranslation (m_strTranslation); // !!! needed because on_m_pTranslationCbB_currentIndexChanged() will get triggered and change m_strTranslation for (int i = 0; i < cSize(vstrTranslations); ++i) { m_pTranslationCbB->addItem(convStr(TranslatorHandler::getLanguageInfo(vstrTranslations[i]))); if (strTmpTranslation == vstrTranslations[i]) { nCrt = i; } } m_pTranslationCbB->setCurrentIndex(nCrt); m_strTranslation = vstrTranslations[m_pTranslationCbB->currentIndex()]; } } // used for creating a new session; SessionEditorDlgImpl::SessionEditorDlgImpl(QWidget* pParent, const string& strDir, bool bFirstTime, const string& strTranslation) : QDialog(pParent, getDialogWndFlags()), Ui::SessionEditorDlg(), m_strDir(strDir), m_bNew(true), m_strTranslation(strTranslation) { commonConstr(); bool bAutoFileName (false); { #ifndef WIN32 QString qs (QDir::homePath() + "/Documents"); // OK on openSUSE, not sure how standardized it is //ttt0 this is localized, so not OK; look at Qt #else QSettings settings (QSettings::UserScope, "Microsoft", "Windows"); settings.beginGroup("CurrentVersion/Explorer/Shell Folders"); QString qs (fromNativeSeparators(settings.value("Personal").toString())); #endif if (QFileInfo(qs).isDir()) { qs += "/MP3Diags"; qs += SESS_EXT; if (!QDir().exists(qs)) { m_pFileNameE->setText(toNativeSeparators(qs)); bAutoFileName = true; } } } //setWindowTitle("MP3 Diags - Create a new session or load an existing one"); setWindowTitle(); m_pDontCreateBackupRB->setChecked(true); m_pScanAtStartupCkB->setChecked(true); m_pFileNameE->setToolTip(bAutoFileName ? tr("This is the name of the \"settings file\"\n\n" "It is supposed to be a file that doesn't already exist. You don't need to set it up. MP3 Diags\n" "will store its settings in this file.\n\n" "The name was generated automatically. If you want to choose a different name, simply click on\n" "the button at the right to change it.", "this is a multiline tooltip") : tr("Here you need to specify the name of a \"settings file\"\n\n" "This is supposed to be a file that doesn't already exist. You don't need to set it up. MP3 Diags\n" "will store its settings in this file.\n\n" "To change it, simply click on the button at the right to choose the name of the settings file.", "this is a multiline tooltip")); if (!bFirstTime) { m_pOpenLastCkB->hide(); m_pOpenSessionsB->hide(); } } // used for editing an existing session; SessionEditorDlgImpl::SessionEditorDlgImpl(QWidget* pParent, const string& strSessFile) : QDialog(pParent, getDialogWndFlags()), Ui::SessionEditorDlg(), m_bNew(false) { SessionSettings st (strSessFile); { CommonData commonData(st, 0, 0, 0, 0, 0, 0, 0, 0, 0, false); st.loadMiscConfigSettings(&commonData, SessionSettings::DONT_INIT_GUI); m_strTranslation = commonData.m_strTranslation; } commonConstr(); setWindowTitle(); m_pFileNameE->setReadOnly(true); m_pFileNameE->setText(toNativeSeparators(convStr(strSessFile))); m_pFileNameB->hide(); m_pOpenLastCkB->hide(); //m_pLoadB->hide(); m_pOpenSessionsB->hide(); m_strSessFile = strSessFile; CB_ASSERT (!m_strSessFile.empty()); //SessionSettings st (m_strSessFile); vector vstrCheckedDirs, vstrUncheckedDirs; st.loadDirs(vstrCheckedDirs, vstrUncheckedDirs); m_pScanAtStartupCkB->setChecked(st.loadScanAtStartup()); TransfConfig tc; st.loadTransfConfig(tc); if (TransfConfig::Options::PO_MOVE_OR_ERASE == tc.m_options.m_eProcOrigChange) { m_pCreateBackupRB->setChecked(true); } else { m_pDontCreateBackupRB->setChecked(true); } m_pBackupE->setText(toNativeSeparators(convStr(tc.getProcOrigDir()))); m_pDirModel->setDirs(vstrCheckedDirs, vstrUncheckedDirs, m_pDirectoriesT); QTimer::singleShot(1, this, SLOT(onShow())); } SessionEditorDlgImpl::~SessionEditorDlgImpl() { delete m_pDirModel; } void SessionEditorDlgImpl::onShow() { m_pDirModel->expandNodes(m_pDirectoriesT); } void SessionEditorDlgImpl::setWindowTitle() { QDialog::setWindowTitle(m_bNew ? tr("MP3 Diags - Create new session") : tr("MP3 Diags - Edit session")); } // returns the name of an INI file for OK and an empty string for Cancel; returns "*" to just go to the sessions dialog; string SessionEditorDlgImpl::run() { GlobalSettings gs; int nWidth, nHeight; gs.loadSessionEdtSize(nWidth, nHeight); if (nWidth > 400 && nHeight > 400) { resize(nWidth, nHeight); } if (QDialog::Accepted != exec()) { return ""; } gs.saveSessionEdtSize(width(), height()); m_strTranslation = TranslatorHandler::getGlobalTranslator().getTranslations()[m_pTranslationCbB->currentIndex()]; return m_strSessFile; } void SessionEditorDlgImpl::on_m_pOkB_clicked() { m_bOpenLastSession = m_pOpenLastCkB->isChecked(); vector vstrCheckedDirs; vector vstrUncheckedDirs; if (m_bNew) { QString qstrFile (fromNativeSeparators(m_pFileNameE->text())); if (!qstrFile.isEmpty() && !qstrFile.endsWith(SESS_EXT)) { qstrFile += SESS_EXT; m_pFileNameE->setText(toNativeSeparators(qstrFile)); } //m_strSessFile = convStr(QFileInfo(qstrFile).canonicalFilePath()); m_strSessFile = convStr(qstrFile); if (m_strSessFile.empty()) { showCritical(this, tr("Error"), tr("You need to specify the name of the settings file.\n\nThis is supposed to be a file that doesn't already exist. You don't need to set it up, but just to pick a name for it. MP3 Diags will store its settings in this file.")); on_m_pFileNameB_clicked(); return; } } vstrCheckedDirs = m_pDirModel->getCheckedDirs(); if (vstrCheckedDirs.empty()) { showCritical(this, tr("Error"), tr("You need to select at least a directory to be included in the session.")); return; } if (m_pCreateBackupRB->isChecked()) { QString s (fromNativeSeparators(m_pBackupE->text())); if (s.isEmpty() || !QFileInfo(s).isDir()) { showCritical(this, tr("Error"), tr("If you want to create backups, you must select an existing directory to store them.")); return; } } vstrUncheckedDirs = m_pDirModel->getUncheckedDirs(); { if (m_bNew) { removeSession(m_strSessFile); } SessionSettings st (m_strSessFile); TransfConfig tc; if (!m_bNew) { st.loadTransfConfig(tc); } if (m_pDontCreateBackupRB->isChecked()) { tc.m_options.m_eProcOrigChange = TransfConfig::Options::PO_ERASE; //ttt2 inconsistency with how config handles this; perhaps just hide the backup settings for existing sessions } else { tc.m_options.m_eProcOrigChange = TransfConfig::Options::PO_MOVE_OR_ERASE; tc.setProcOrigDir(fromNativeSeparators(convStr(m_pBackupE->text()))); } st.saveTransfConfig(tc); st.saveDirs(vstrCheckedDirs, vstrUncheckedDirs); st.saveScanAtStartup(m_pScanAtStartupCkB->isChecked()); { CommonData commonData(st, 0, 0, 0, 0, 0, 0, 0, 0, 0, false); st.loadMiscConfigSettings(&commonData, SessionSettings::DONT_INIT_GUI); commonData.m_strTranslation = m_strTranslation; st.saveMiscConfigSettings(&commonData); } if (!st.sync()) { showCritical(this, tr("Error"), tr("Failed to write to file %1").arg(m_pFileNameE->text())); if (m_bNew) { removeSession(m_strSessFile); } return; } } accept(); } void SessionEditorDlgImpl::on_m_pCancelB_clicked() { reject(); } void SessionEditorDlgImpl::on_m_pBackupB_clicked() { QString s (fromNativeSeparators(m_pBackupE->text())); if (s.isEmpty()) { s = QDir::homePath(); } QFileDialog dlg (this, tr("Select folder"), s, tr("All files (*)")); //dlg.setAcceptMode(QFileDialog::AcceptSave); dlg.setFileMode(QFileDialog::Directory); if (QDialog::Accepted != dlg.exec()) { return; } QStringList fileNames (dlg.selectedFiles()); if (1 != fileNames.size()) { return; } s = fileNames.first(); QFileInfo f (s); if (!f.isDir()) { return; } m_pBackupE->setText(toNativeSeparators(s)); m_pCreateBackupRB->setChecked(true); } void SessionEditorDlgImpl::on_m_pFileNameB_clicked() { QString s (fromNativeSeparators(m_pFileNameE->text())); if (s.isEmpty()) { if (m_strDir.empty()) { s = QDir::homePath(); } else { s = convStr(m_strDir); } } QFileDialog dlg (this, tr("Enter configuration file"), s, tr("MP3 Diags session files (*%1)").arg(SESS_EXT)); dlg.setAcceptMode(QFileDialog::AcceptSave); //dlg.setFileMode(QFileDialog::Directory); if (QDialog::Accepted != dlg.exec()) { return; } QStringList fileNames (dlg.selectedFiles()); if (1 != fileNames.size()) { return; } s = fileNames.first(); if (!s.endsWith(SESS_EXT)) { s += SESS_EXT; } m_pFileNameE->setText(toNativeSeparators(s)); } /*static*/ string SessionEditorDlgImpl::getDataFileName(const string& strSessFile) { CB_ASSERT (endsWith(strSessFile, SESS_EXT)); return strSessFile.substr(0, strSessFile.size() - SESS_EXT_LEN) + ".dat"; } /*static*/ string SessionEditorDlgImpl::getLogFileName(const string& strSessFile) { CB_ASSERT (endsWith(strSessFile, SESS_EXT)); return strSessFile.substr(0, strSessFile.size() - SESS_EXT_LEN) + ".transf_log.txt"; } /*static*/ string SessionEditorDlgImpl::getBaseName(const string& strSessFile) { CB_ASSERT (endsWith(strSessFile, SESS_EXT)); return strSessFile.substr(0, strSessFile.size() - SESS_EXT_LEN); } /*static*/ string SessionEditorDlgImpl::getTitleName(const string& strSessFile) { CB_ASSERT (endsWith(strSessFile, SESS_EXT)); string::size_type n (strSessFile.rfind(getPathSep())); return strSessFile.substr(n + 1, strSessFile.size() - n - SESS_EXT_LEN - 1); } // removes all files associated with a session: .ini, .mp3ds, .log, .dat, _trace.txt, _step1.txt, _step2.txt /*static*/ void SessionEditorDlgImpl::removeSession(const string& strSessFile) { eraseFiles(strSessFile.substr(0, strSessFile.size() - SESS_EXT_LEN)); } /*static*/ const char* const SessionEditorDlgImpl::SESS_EXT (".ini"); //ttt0 perhaps switch to .mp3ds (keep in mind that there may be many folders with .ini in them now); the point is to be able to double-click on a .mp3ds file /*static*/ int SessionEditorDlgImpl::SESS_EXT_LEN (strlen(SessionEditorDlgImpl::SESS_EXT)); void SessionEditorDlgImpl::on_m_pOpenSessionsB_clicked() { m_strSessFile = "*"; accept(); } void SessionEditorDlgImpl::on_m_pTranslationCbB_currentIndexChanged(int) { const vector& vstrTranslations (TranslatorHandler::getGlobalTranslator().getTranslations()); m_strTranslation = vstrTranslations[m_pTranslationCbB->currentIndex()]; TranslatorHandler::getGlobalTranslator().setTranslation(m_strTranslation); retranslateUi(this); setWindowTitle(); } void SessionEditorDlgImpl::onHelp() { openHelp("110_first_run.html"); //ttt2 not quite right, since there are 2 "modes" } //================================================================================================================================================ //================================================================================================================================================ //================================================================================================================================================ //ttt2 see about dereferenced symlinks MP3Diags-1.2.02/src/DoubleList.cpp0000644000175000001440000006022511724340425015517 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include "DoubleList.h" #include "Helpers.h" #include "CommonData.h" using namespace std; using namespace DoubleListImpl; //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== AvailableModel::AvailableModel(ListPainter& listPainter) : m_listPainter(listPainter) { } int AvailableModel::rowCount() const { int n (cSize(m_listPainter.getAvailable())); return n; } int AvailableModel::columnCount() const { return m_listPainter.getColCount(); } /*override*/ QVariant AvailableModel::data(const QModelIndex& index, int nRole) const { LAST_STEP("AvailableModel::data()"); if (!index.isValid()) { return QVariant(); } if (nRole != Qt::DisplayRole) { return QVariant(); } int nRow (index.row()); int nAllIndex (m_listPainter.getAvailable()[nRow]); return convStr(m_listPainter.getAll()[nAllIndex]->getText(index.column())); } /*override*/ QVariant AvailableModel::headerData(int nSection, Qt::Orientation eOrientation, int nRole /* = Qt::DisplayRole*/) const { LAST_STEP("AvailableModel::headerData"); if (nRole == Qt::SizeHintRole) { return getNumVertHdrSize(cSize(m_listPainter.getAvailable()), eOrientation); } if (nRole != Qt::DisplayRole) { return QVariant(); } if (Qt::Horizontal == eOrientation) { return convStr(m_listPainter.getColTitle(nSection)); } return nSection + 1; } void AvailableModel::emitLayoutChanged() { emit layoutChanged(); } //--------------------------------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------------------------------- /*override*/ void AvailableModelDelegate::paint(QPainter* pPainter, const QStyleOptionViewItem& option, const QModelIndex& index) const { pPainter->save(); //pPainter->fillRect(option.rect, QBrush(m_listPainter.getColor(m_listPainter.getAvailable()[index.row()], index.column(), pPainter->background().color()))); //ttt2 make sure background() is the option to use QColor bckgCol (pPainter->background().color()); QColor penCol (pPainter->pen().color()); double dGradStart (-1), dGradEnd (-1); m_listPainter.getColor(m_listPainter.getAvailable()[index.row()], index.column(), ListPainter::ALL_LIST, bckgCol, penCol, dGradStart, dGradEnd); QLinearGradient grad (0, option.rect.y(), 0, option.rect.y() + option.rect.height()); configureGradient(grad, bckgCol, dGradStart, dGradEnd); pPainter->fillRect(option.rect, grad); QStyleOptionViewItemV2 myOption (option); myOption.displayAlignment = m_listPainter.getAlignment(index.column()); myOption.palette.setColor(QPalette::Text, penCol); QItemDelegate::paint(pPainter, myOption, index); pPainter->restore(); } //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== SelectedModel::SelectedModel(ListPainter& listPainter) : m_listPainter(listPainter) { } int SelectedModel::rowCount() const { int n (cSize(m_listPainter.getSel())); if (0 == n && !m_listPainter.getNothingSelStr().empty()) { return 1; } return n; } int SelectedModel::columnCount() const { if (m_listPainter.getSel().empty() && !m_listPainter.getNothingSelStr().empty()) { return 1; } return m_listPainter.getColCount(); } /*override*/ QVariant SelectedModel::data(const QModelIndex& index, int nRole) const { LAST_STEP("SelectedModel::data()"); if (!index.isValid()) { return QVariant(); } if (nRole != Qt::DisplayRole && nRole != Qt::ToolTipRole) { return QVariant(); } int nRow (index.row()); QString qstrRes; if (m_listPainter.getSel().empty()) { CB_ASSERT(0 == nRow); //qstrRes = 0 == index.column() ? "" : ""; qstrRes = convStr(m_listPainter.getNothingSelStr()); // !!! in this case there's only 1 column } else { int nAllIndex (m_listPainter.getSel()[nRow]); qstrRes = convStr(m_listPainter.getAll()[nAllIndex]->getText(index.column())); } if (nRole == Qt::ToolTipRole) { #if 0 // works but tooltip is no longer needed here //QFontMetrics fm (QApplication::fontMetrics()); QFontMetrics fm (m_pTableView->fontMetrics()); // !!! no need to use "QApplication::fontMetrics()", because m_pTableView is needed anyway to get the column widths int nWidth (fm.width(qstrRes)); if (nWidth + 10 < m_pTableView->horizontalHeader()->sectionSize(index.column())) // ttt2 "10" is hard-coded { return QVariant(); }//*/ #else return QVariant(); #endif } return qstrRes; } /*override*/ QVariant SelectedModel::headerData(int nSection, Qt::Orientation eOrientation, int nRole /* = Qt::DisplayRole*/) const { LAST_STEP("SelectedModel::headerData"); if (nRole == Qt::SizeHintRole) { return getNumVertHdrSize(cSize(m_listPainter.getSel()), eOrientation); } if (nRole != Qt::DisplayRole) { return QVariant(); } if (Qt::Horizontal == eOrientation) { if (m_listPainter.getSel().empty() && !m_listPainter.getNothingSelStr().empty()) { return ""; } return convStr(m_listPainter.getColTitle(nSection)); } if (m_listPainter.getSel().empty()) { return ""; } return nSection + 1; } void SelectedModel::emitLayoutChanged() { emit layoutChanged(); } //--------------------------------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------------------------------- /*override*/ void SelectedModelDelegate::paint(QPainter* pPainter, const QStyleOptionViewItem& option, const QModelIndex& index) const { if (m_listPainter.getSel().empty()) { QItemDelegate::paint(pPainter, option, index); return; } pPainter->save(); //int nRow (index.row()); //int nCol (index.column()); //const Note* pNote (m_pSelectedModel->m_vpNotes[nRow]); /* // first approach; works, but it's more complicated QStyleOptionViewItemV2 myOption (option); if (0 == index.column()) { myOption.displayAlignment |= Qt::AlignHCenter; } pPainter->fillRect(myOption.rect, QBrush(noteColor(*pNote))); QString qstrText (index.model()->data(index, Qt::DisplayRole).toString()); drawDisplay(pPainter, myOption, myOption.rect, qstrText); drawFocus(pPainter, myOption, myOption.rect);*/ // second approach //pPainter->fillRect(option.rect, QBrush(m_listPainter.getColor(m_listPainter.getSel()[nRow], nCol, option.palette.color(QPalette::Active, QPalette::Base)))); //ttt3 " Active" not right if the window is inactive QColor bckgCol (option.palette.color(QPalette::Active, QPalette::Base)); //ttt3 compare to avl, where it's "QColor bckgCol (pPainter->background().color());" see why // 2009.07.15 - probably "option..." is better; the other one relies on the painter to be initialized to what's in option, but not sure that is required; so penCol is not quite right as well; however, the painter probably comes initialized correctly QColor penCol (pPainter->pen().color()); double dGradStart (-1), dGradEnd (-1); m_listPainter.getColor(m_listPainter.getSel()[index.row()], index.column(), ListPainter::SUB_LIST, bckgCol, penCol, dGradStart, dGradEnd); QLinearGradient grad (0, option.rect.y(), 0, option.rect.y() + option.rect.height()); configureGradient(grad, bckgCol, dGradStart, dGradEnd); pPainter->fillRect(option.rect, grad); QStyleOptionViewItemV2 myOption (option); myOption.displayAlignment = m_listPainter.getAlignment(index.column()); myOption.palette.setColor(QPalette::Text, penCol); QItemDelegate::paint(pPainter, myOption, index); pPainter->restore(); } //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== //#include DoubleList::DoubleList( ListPainter& listPainter, int nButtons, //bool bAllowMultipleSel, //bool bUserSortSel, SelectionMode eSelectionMode, const std::string& strAvailableLabel, const std::string& strSelLabel, QWidget* pParent, Qt::WFlags fl /* =0*/) : QWidget(pParent, fl), Ui::DoubleListWdg(), m_listPainter(listPainter), //m_bAllowMultipleSel(bAllowMultipleSel), //m_bUserSortSel(bUserSortSel), m_eSelectionMode(eSelectionMode), m_availableModel(listPainter), m_selectedModel(listPainter), m_bSectionMovedLock(false) { setupUi(this); //printContainer(listPainter.m_vOrigSel, cout); if (SINGLE_UNSORTABLE == eSelectionMode) { for (int i = 0, n = cSize(listPainter.m_vOrigSel); i < n - 1; ++i) { CB_ASSERT(listPainter.m_vOrigSel[i] < listPainter.m_vOrigSel[i + 1]); } } m_pAvailableL->setText(convStr(strAvailableLabel)); m_pSelL->setText(convStr(strSelLabel)); if (0 == (nButtons & ADD_ALL)) { delete m_pAddAllB; } if (0 == (nButtons & DEL_ALL)) { delete m_pDeleteAllB; } if (0 == (nButtons & RESTORE_OPEN)) { delete m_pRestoreOpenB; } if (0 == (nButtons & RESTORE_DEFAULT)) { delete m_pRestoreDefaultB; } //m_listPainter.m_vSel = m_listPainter.m_vOrigSel; initAvailable(); m_pAvailableG->setModel(&m_availableModel); m_pSelectedG->setModel(&m_selectedModel); AvailableModelDelegate* pAvDel = new AvailableModelDelegate(m_listPainter, m_pAvailableG); m_pAvailableG->setItemDelegate(pAvDel); SelectedModelDelegate* pSelDel = new SelectedModelDelegate(m_listPainter, m_pSelectedG); m_pSelectedG->setItemDelegate(pSelDel); setUpGrid(m_pAvailableG); setUpGrid(m_pSelectedG); { string s; s = m_listPainter.getTooltip(ListPainter::SELECTED_G); if (!s.empty()) { m_pSelectedG->setToolTip(convStr(s)); } s = m_listPainter.getTooltip(ListPainter::AVAILABLE_G); if (!s.empty()) { m_pAvailableG->setToolTip(convStr(s)); } s = m_listPainter.getTooltip(ListPainter::ADD_B); if (!s.empty()) { m_pAddB->setToolTip(convStr(s)); } s = m_listPainter.getTooltip(ListPainter::DELETE_B); if (!s.empty()) { m_pDeleteB->setToolTip(convStr(s)); } s = m_listPainter.getTooltip(ListPainter::ADD_ALL_B); if (!s.empty()) { m_pAddAllB->setToolTip(convStr(s)); } s = m_listPainter.getTooltip(ListPainter::DELETE_ALL_B); if (!s.empty()) { m_pDeleteAllB->setToolTip(convStr(s)); } s = m_listPainter.getTooltip(ListPainter::RESTORE_DEFAULT_B); if (!s.empty()) { m_pRestoreDefaultB->setToolTip(convStr(s)); } s = m_listPainter.getTooltip(ListPainter::RESTORE_OPEN_B); if (!s.empty()) { m_pRestoreOpenB->setToolTip(convStr(s)); } } if (SINGLE_UNSORTABLE != m_eSelectionMode) { m_pSelectedG->verticalHeader()->setMovable(true); } connect(m_pSelectedG->verticalHeader(), SIGNAL(sectionMoved(int, int, int)), this, SLOT(onSelSectionMoved(int, int, int))); connect(m_pAvailableG, SIGNAL(doubleClicked(const QModelIndex&)), this, SLOT(onAvlDoubleClicked(const QModelIndex&))); //ttt2 perhaps use QAction::shortcutContext(Qt::WidgetShortcut) to set up shortcuts, if it works well enough } DoubleList::~DoubleList() { } void DoubleList::onSelSectionMoved(int /*nLogicalIndex*/, int nOldVisualIndex, int nNewVisualIndex) { CB_ASSERT(SINGLE_SORTABLE == m_eSelectionMode || MULTIPLE == m_eSelectionMode); NonblockingGuard sectionMovedGuard (m_bSectionMovedLock); if (!sectionMovedGuard) { return; } //m_pSelectedG->verticalHeader()->headerDataChanged(Qt::Vertical, 0, 4); m_pSelectedG->verticalHeader()->moveSection(nNewVisualIndex, nOldVisualIndex); SubList& vSel (m_listPainter.m_vSel); int x (vSel[nOldVisualIndex]); vSel.erase(vSel.begin() + nOldVisualIndex); vSel.insert(vSel.begin() + nNewVisualIndex, x); adjustOnDataChanged(); } void DoubleList::initAvailable() { m_listPainter.m_vAvailable.clear(); switch (m_eSelectionMode) { case MULTIPLE: for (int i = 0, n = cSize(m_listPainter.getAll()); i < n; ++i) { m_listPainter.m_vAvailable.push_back(i); // in a sense, m_listPainter.m_vAvailable shouldn't be used at all in this case, and it isn't in the DoubleList code; however, it makes AvailableModel easier, because while DoubleList always has to check the value of m_eSelectionMode anyway, AvailableModel doesn't have to do this, but an empty m_listPainter.m_vAvailable in the MULTIPLE case would force it to do so; } break; case SINGLE_UNSORTABLE: { int n (cSize(m_listPainter.getAll())); m_listPainter.m_vSel.push_back(n); for (int i = 0, j = 0; i < n; ++i) { if (i < m_listPainter.m_vSel[j]) { m_listPainter.m_vAvailable.push_back(i); } else { ++j; } } m_listPainter.m_vSel.pop_back(); } break; case SINGLE_SORTABLE: { SubList v (m_listPainter.m_vSel.begin(), m_listPainter.m_vSel.end()); sort(v.begin(), v.end()); int n (cSize(m_listPainter.getAll())); v.push_back(n); for (int i = 0, j = 0; i < n; ++i) { if (i < v[j]) { m_listPainter.m_vAvailable.push_back(i); } else { ++j; } } } break; } } void DoubleList::setUpGrid(QTableView* pGrid) { pGrid->verticalHeader()->setMinimumSectionSize(m_listPainter.getHdrHeight()); pGrid->verticalHeader()->setDefaultSectionSize(m_listPainter.getHdrHeight()); pGrid->verticalHeader()->setResizeMode(QHeaderView::Interactive); pGrid->verticalHeader()->setDefaultAlignment(Qt::AlignRight | Qt::AlignVCenter); resizeColumns(pGrid); } void DoubleList::resizeColumns(QTableView* pGrid) { if (1 == pGrid->horizontalHeader()->count()) { pGrid->horizontalHeader()->setResizeMode(0, QHeaderView::Stretch); } else { for (int i = 0, n = m_listPainter.getColCount(); i < n; ++i) { int w (m_listPainter.getColWidth(i)); if (w >= 0) { pGrid->horizontalHeader()->setResizeMode(i, QHeaderView::Interactive); pGrid->horizontalHeader()->resizeSection(i, w); } else { pGrid->horizontalHeader()->setResizeMode(i, QHeaderView::Stretch); } } } } void DoubleList::clearSel() { m_pSelectedG->selectionModel()->clear(); m_pAvailableG->selectionModel()->clear(); } void DoubleList::resizeRows() { m_pAvailableG->resizeRowsToContents(); m_pSelectedG->resizeRowsToContents(); } void DoubleList::emitLayoutChanged() { m_availableModel.emitLayoutChanged(); m_selectedModel.emitLayoutChanged(); } void DoubleList::adjustOnDataChanged() { emitLayoutChanged(); resizeColumns(m_pSelectedG); // ttt2 too much: this should only be done when switching between 1 and multiple columns; and even then, going from 1 to many, it should use the "previous" column widths, rather than the "default" ones resizeRows(); clearSel(); emit dataChanged(); } void DoubleList::on_m_pAddB_clicked() { QItemSelectionModel* pSelMdl (m_pAvailableG->selectionModel()); QModelIndexList lSel (pSelMdl->selection().indexes()); set sSelPos; // the rows must be sorted for (QModelIndexList::iterator it = lSel.begin(), end = lSel.end(); it != end; ++it) { int nRow (it->row()); CB_ASSERT (nRow >= 0); sSelPos.insert(nRow); } add(sSelPos); } void DoubleList::add(const std::set& sSelPos) // adds elements from the specified indexes { switch (m_eSelectionMode) { case SINGLE_UNSORTABLE: for (set::const_reverse_iterator it = sSelPos.rbegin(), end = sSelPos.rend(); it != end; ++it) // the last must be processed first, so removal of elements doesn't change the row number for the remaining ones { int nRow (*it); int nIndex (m_listPainter.m_vAvailable[nRow]); vector::iterator it1 (lower_bound(m_listPainter.m_vSel.begin(), m_listPainter.m_vSel.end(), nIndex)); m_listPainter.m_vSel.insert(it1, nIndex); m_listPainter.m_vAvailable.erase(m_listPainter.m_vAvailable.begin() + nRow); } break; case SINGLE_SORTABLE: for (set::const_iterator it = sSelPos.begin(), end = sSelPos.end(); it != end; ++it) { int nRow (*it); int nIndex (m_listPainter.m_vAvailable[nRow]); m_listPainter.m_vSel.push_back(nIndex); } for (set::const_reverse_iterator it = sSelPos.rbegin(), end = sSelPos.rend(); it != end; ++it) { int nRow (*it); m_listPainter.m_vAvailable.erase(m_listPainter.m_vAvailable.begin() + nRow); } break; case MULTIPLE: for (set::const_iterator it = sSelPos.begin(), end = sSelPos.end(); it != end; ++it) { int nRow (*it); //int nIndex (m_listPainter.m_vAvailable[nRow]); //m_listPainter.getSel_().push_back(nIndex); m_listPainter.m_vSel.push_back(nRow); } break; default: CB_ASSERT(false); } adjustOnDataChanged(); } void DoubleList::on_m_pDeleteB_clicked() { if (m_listPainter.m_vSel.empty()) { return; } QItemSelectionModel* pSelMdl (m_pSelectedG->selectionModel()); QModelIndexList lSel (pSelMdl->selection().indexes()); set sSelPos; // the rows must be sorted and the last must be processed first, so removal of elements doesn't change the row number for the remaining ones for (QModelIndexList::iterator it = lSel.begin(), end = lSel.end(); it != end; ++it) { int nRow (it->row()); CB_ASSERT (nRow >= 0); sSelPos.insert(nRow); } remove(sSelPos); } void DoubleList::remove(const std::set& sSelPos) // removes elements from the specified indexes { for (set::const_reverse_iterator it = sSelPos.rbegin(), end = sSelPos.rend(); it != end; ++it) { int nRow (*it); int nIndex (m_listPainter.m_vSel[nRow]); if (MULTIPLE != m_eSelectionMode) { vector::iterator it1 (lower_bound(m_listPainter.m_vAvailable.begin(), m_listPainter.m_vAvailable.end(), nIndex)); m_listPainter.m_vAvailable.insert(it1, nIndex); } m_listPainter.m_vSel.erase(m_listPainter.m_vSel.begin() + nRow); } adjustOnDataChanged(); } void DoubleList::on_m_pAddAllB_clicked() { switch (m_eSelectionMode) { case SINGLE_UNSORTABLE: m_listPainter.m_vAvailable.clear(); m_listPainter.m_vSel.clear(); for (int i = 0, n = cSize(m_listPainter.getAll()); i < n; ++i) { m_listPainter.m_vSel.push_back(i); } break; case SINGLE_SORTABLE: m_listPainter.m_vSel.insert(m_listPainter.m_vSel.end(), m_listPainter.m_vAvailable.begin(), m_listPainter.m_vAvailable.end()); m_listPainter.m_vAvailable.clear(); break; case MULTIPLE: m_listPainter.m_vSel.insert(m_listPainter.m_vSel.end(), m_listPainter.m_vAvailable.begin(), m_listPainter.m_vAvailable.end()); break; default: CB_ASSERT(false); } adjustOnDataChanged(); } void DoubleList::on_m_pDeleteAllB_clicked() { m_listPainter.m_vSel.clear(); if (MULTIPLE != m_eSelectionMode) { m_listPainter.m_vAvailable.clear(); for (int i = 0, n = cSize(m_listPainter.getAll()); i < n; ++i) { m_listPainter.m_vAvailable.push_back(i); } } adjustOnDataChanged(); } void DoubleList::on_m_pRestoreDefaultB_clicked() { m_listPainter.reset(); m_listPainter.m_bResultInReset = true; initAvailable(); adjustOnDataChanged(); } void DoubleList::on_m_pRestoreOpenB_clicked() { m_listPainter.m_vSel = m_listPainter.m_vOrigSel; m_listPainter.m_bResultInReset = false; initAvailable(); adjustOnDataChanged(); } /*override*/ void DoubleList::resizeEvent(QResizeEvent*) { //return; //QDialog::resizeEvent(pEvent); /*cout << this << ": size: " << width() << "x" << height() << " m_pAvailableG: " << m_pAvailableG->width() << "x" << m_pAvailableG->height() << " m_pSelectedG: " << m_pSelectedG->width() << "x" << m_pSelectedG->height() << " sel hdr: " << m_pSelectedG->horizontalHeader()->sectionSize(0) << ", " << m_pSelectedG->horizontalHeader()->sectionSize(1) << endl; m_pAvailableG->resizeRowsToContents(); m_pSelectedG->resizeRowsToContents();*/ QTimer::singleShot(1, this, SLOT(onResizeTimer())); // !!! 2008.10.27 - this is a workaround for what appears to be a bug in Qt: resizeEvent() doesn't get called after applying a layout; as a result, resizeRowsToContents() uses incorrect sizes to figure line heights; perhaps there's no bug and there's some other way to achieve a QTableView that adjusts the row heights such that all rows fit on the screen (perhaps after Qt 4.3.1), but for now this approach seems good enough; (calling layout()->activate() might do something similar, but it's cumbersome to use) } void DoubleList::onResizeTimer() { //cout << this << ": size: " << width() << "x" << height() << " m_pAvailableG: " << m_pAvailableG->width() << "x" << m_pAvailableG->height() << " m_pSelectedG: " << m_pSelectedG->width() << "x" << m_pSelectedG->height() << " sel hdr: " << m_pSelectedG->horizontalHeader()->sectionSize(0) << ", " << m_pSelectedG->horizontalHeader()->sectionSize(1) << endl; resizeRows(); } void DoubleList::onAvlDoubleClicked(const QModelIndex& index) { emit avlDoubleClicked(index.row()); } MP3Diags-1.2.02/src/CommonData.h0000644000175000001440000007236311714056071015146 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef CommonDataH #define CommonDataH #include #include //#include #include #include #include "SerSupport.h" #include "Notes.h" #include "Mp3Manip.h" #include "Helpers.h" #include "FileEnum.h" #include "CommonTypes.h" //class QFont; //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== class QToolButton; class FilesModel; struct NotesModel; struct StreamsModel; struct UniqueNotesModel; extern int CELL_WIDTH; // ttt2 perhaps replace with functions extern int CELL_HEIGHT; extern const int CUSTOM_TRANSF_CNT; // search for this to find all places that need changes to add another custom transform list // Holds together a collection of Notes (All) and a subset of it (Flt). Flt is used when filtering, when All holds all the notes that exist in all the handlers (CommonData::m_vpAllHandlers), while Flt holds the notes from the handlers that are selected by the current filter (CommonData::m_vpFltHandlers). // Uses both a vector and a set to provide fast access, althought this doesn't seem like the best idea. //ttt2 perhaps switch from to a treap class UniqueNotes { // all pointers are from Notes::, so none are owned; std::set m_spAll; // owns the pointers std::set m_spFlt; // subset of m_spAll, so it doesn't own these pointers mutable std::vector m_vpAll; // only has elements of m_spAll, so it doesn't own these pointers mutable std::vector m_vpFlt; // only has elements of m_spAll, so it doesn't own these pointers mutable bool m_bAllDirty; mutable bool m_bFltDirty; void updateVAll() const; void updateVFlt() const; public: UniqueNotes(); //UniqueNotes(const UniqueNotes&); ~UniqueNotes(); void clear(); void clearFlt() { m_spFlt.clear(); m_bFltDirty = true; } bool addNote(const Note* pNote); // if the note doesn't exist in m_spAll, it adds the corresponding note from Notes; returns true if the param really was added; //void add(const std::vector& vpNotes); //void add(const std::vector& vpNotes); // returns true if anything got added template bool addColl(const T& coll) { bool bRes (false); for (typename T::const_iterator it = coll.begin(), end = coll.end(); it != end; ++it) { if (0 == m_spAll.count(*it)) { const Note* p (Notes::getMaster(*it)); CB_ASSERT (0 != p); m_spAll.insert(p); m_bAllDirty = true; bRes = true; } } return bRes; } void _selectAll() { m_spFlt = m_spAll; m_bFltDirty = true; } // throws NoteNotFound if a note is not in m_spAll; template void setFlt(const T& coll) { m_spFlt.clear(); m_bFltDirty = true; for (typename T::const_iterator it = coll.begin(), end = coll.end(); it != end; ++it) { int nPos (getPos(*it)); CB_CHECK1 (-1 != nPos, NoteNotFound()); m_spFlt.insert(get(nPos)); } } struct NoteNotFound {}; int getFltCount() const { return cSize(m_spFlt); } const Note* getFlt(int n) const; int getCount() const { return cSize(m_spAll); } const Note* get(int n) const; int getPos(const Note*) const; // position in the "all" notes; -1 if the note wasn't found; int getFltPos(const Note*) const; // position in the "flt" notes; -1 if the note wasn't found; const std::vector& getAllVec() const { updateVAll(); return m_vpAll; } const std::vector& getFltVec() const { updateVFlt(); return m_vpFlt; } }; // Owners for notes: // DescrOwnerNote::m_spAll owns DescrOwnerNote* // Mp3Handler owns Note*, through NoteColl // // Filter::m_vpSelNotes references CommonData::m_uniqueNotes.m_spAll // DescrOwnerNote::m_spSel, DescrOwnerNote::m_spSel and DescrOwnerNote::m_spSel all referece the notes in DescrOwnerNote::m_spAll class Filter : public QObject { Q_OBJECT bool m_bNoteFilter; bool m_bDirFilter; bool m_bSavedNoteFilter; bool m_bSavedDirFilter; std::vector m_vstrDirs; std::vector m_vpNotes; // doesn't own the pointers, because it is just a subset of CommonData::m_uniqueNotes.getAllVec(), which in turn uses pointers from Notes:: // !!! it's OK for this to be a vector (rather than a set), because the way it is used most of the time, namely to determine if the intersection of 2 sets (represented as sorted vectors) is empty or not public: Filter() : m_bNoteFilter(false), m_bDirFilter(false), m_bSavedNoteFilter(false), m_bSavedDirFilter(false) {} const std::vector& getDirs() const { return m_vstrDirs; } const std::vector& getNotes() const { return m_vpNotes; } void setDirs(const std::vector&); void setNotes(const std::vector&); void disableDir(); // !!! needed because we need m_vstrDirs next time we press the filter button, so we don't want to delete it with a "setDirs(vector())", but just ignore it void disableNote(); void disableAll(); // saves m_bNoteFilter to m_bSavedNoteFilter and m_bDirFilter to m_bSavedDirFilter, then disables the filters void restoreAll(); // loads m_bNoteFilter from m_bSavedNoteFilter and m_bDirFilter from m_bSavedDirFilter, then enables the filters, if they are true bool isNoteEnabled() const { return m_bNoteFilter; } bool isDirEnabled() const { return m_bDirFilter; } signals: void filterChanged(); private: friend class boost::serialization::access; /*template void serialize(Archive& ar, const unsigned int / *nVersion* /) { ar & m_bNoteFilter; ar & m_bDirFilter; ar & m_bSavedNoteFilter; ar & m_bSavedDirFilter; ar & m_vstrDirs; ar & m_vpNotes; }*/ template void save(Archive& ar, const unsigned int nVersion) const { if (nVersion > 0) { throw std::runtime_error("invalid version of serialized file"); } ar << m_bNoteFilter; ar << m_bDirFilter; ar << m_bSavedNoteFilter; ar << m_bSavedDirFilter; ar << m_vstrDirs; //qDebug("saved dirs sz %d", cSize(m_vstrDirs)); //ar << m_vpNotes; //ttt2 weird behaviour: this compiles and doesn't trigger runtime errros, and neither does the loading, but when loading a vector it always ends up empty; it's probably some incorrect use of the ser library (share / global / const pointers), but the library is broken too, because it should have failed to compile or at least crashed when running instead of just failing to load anything (the files are different, so something is saved) //ar << (const std::vector&)m_vpNotes; qDebug("saved notes sz %d", cSize(m_vpNotes)); std::vector v; for (int i = 0; i < cSize(m_vpNotes); ++i) { v.push_back(m_vpNotes[i]->getDescription()); } ar << (const std::vector&)v;//*/ } template void load(Archive& ar, const unsigned int nVersion) { if (nVersion > 0) { throw std::runtime_error("invalid version of serialized file"); } ar >> m_bNoteFilter; ar >> m_bDirFilter; ar >> m_bSavedNoteFilter; ar >> m_bSavedDirFilter; ar >> m_vstrDirs; /*qDebug("loaded flt dirs size %d", cSize(m_vstrDirs)); for (int i = 0; i < cSize(m_vstrDirs); ++i) { qDebug("flt dir %d: %s", i, m_vstrDirs[i].c_str()); }*/ /*std::vector v; ar >> v; qDebug("loaded note sz %d", cSize(v)); for (int i = 0; i < cSize(v); ++i) { const Note* p (Notes::getMaster(v[i])); CB_ASSERT (0 != p); m_vpNotes.push_back(p); } pearl::clearPtrContainer(v);// */ std::vector v; ar >> v; //qDebug("loaded note sz %d", cSize(v)); for (int i = 0; i < cSize(v); ++i) { const Note* p (Notes::getNote(v[i])); if (0 != p) // !!! when a new version loads an old one's data, some notes might be gone { m_vpNotes.push_back(p); } }// */ } BOOST_SERIALIZATION_SPLIT_MEMBER() }; class ImageInfoPanelWdgImpl; class SessionSettings; class Transformation; class QTableView; // doesn't own the QTableView pointers; // doesn't own the model pointers class CommonData : public QObject { Q_OBJECT public: CommonData( SessionSettings& settings, QTableView* pFilesG, QTableView* pNotesG, QTableView* pStreamsG, QTableView* pUniqueNotesG, QToolButton* pNoteFilterB, QToolButton* pDirFilterB, QToolButton* pModeAllB, QToolButton* pModeAlbumB, QToolButton* pModeSongB, bool bDefaultForVisibleSessBtn); ~CommonData(); std::string save(const std::string& strFile) const; // returns an error message (or empty string if there's no error) std::string load(const std::string& strFile); // returns an error message (or empty string if there's no error) FilesModel* m_pFilesModel; NotesModel* m_pNotesModel; StreamsModel* m_pStreamsModel; UniqueNotesModel* m_pUniqueNotesModel; QTableView* m_pFilesG; QTableView* m_pNotesG; QTableView* m_pStreamsG; QTableView* m_pUniqueNotesG; std::vector m_vExternalToolInfos; const std::deque& getViewHandlers() const { return m_vpViewHandlers; } const std::deque& getSelHandlers(); // results are sorted const UniqueNotes& getUniqueNotes() const { return m_uniqueNotes; } // "albums" for the tag editor; independent of the main window; void setCrtAlbum(const std::string& strName) const; std::deque getCrtAlbum() const; bool nextAlbum() const; bool prevAlbum() const; // finds the position of a note in the global vector with notes sorted by severity and description; // the position can then be used to find the corresponding label; // returns -1 if the note is not found; (needed because trace and info notes aren't shown in the grid, so they are not found) int findPos(const Note* pNote) const; bool m_bChangeGuard; // used with a NonblockingGuard to avoid recursive calls when updating the UI int getFilesGCrtRow() const; // returns -1 if no current element exists (e.g. because the table is empty) int getFilesGCrtCol() const; // returns -1 if no current element exists (e.g. because the table is empty) const std::vector& getCrtNotes() const { return m_vpCrtNotes; } // notes for the "current" file const std::vector& getCrtStreams() const; const std::vector& getAllTransf() const { return m_vpAllTransf; } const QualThresholds& getQualThresholds() const { return m_qualThresholds; } void setQualThresholds(const QualThresholds&); const std::vector >& getCustomTransf() const { return m_vvnCustomTransf; } void setCustomTransf(const std::vector >&); void setCustomTransf(int nTransf, const std::vector&); const std::vector& getVisibleTransf() const { return m_vnVisibleTransf; } void setVisibleTransf(const std::vector&); const std::vector& getIgnoredNotes() const { return m_vnIgnoredNotes; } void setIgnoredNotes(const std::vector&); int getTransfPos(const char* szTransfName) const; // the index in m_vpAllTransf for a transformation with a given name; throws if the name doesn't exist; std::set getAllDirs() const; // needed by the dir filter enum KeepWhenUpdate { NOTHING = 0x00, CURRENT = 0x01, SEL = 0x02 }; void mergeHandlerChanges(const std::vector& vpAdd, const std::vector& vpDel, int nKeepWhenUpdate); // elements from vpAdd that are not "included" are discarded; when a new version of a handler is in vpAdd, the old version may be in vpDel, but this is optional; takes ownership of elements from vpAdd; deletes the pointers from vpDel; enum ViewMode { ALL, FOLDER, FILE }; // although the current elem can be identified (so it shouldn't be passed) and most of the time pMp3Handler will be just that, sometimes it will deliberately be 0, so a param is actually needed; void setViewMode(ViewMode eViewMode, const Mp3Handler* pMp3Handler = 0); ViewMode getViewMode() const { return m_eViewMode; } const Mp3Handler* getCrtMp3Handler() const; // returns 0 if the list is empty // called after config change or filter change or mergeHandlerChanges(), mainly to update unique notes (which is reflected in columns in the file grid and in lines in the unique notes list); also makes sure that something is displayed if there are any files in m_vpFltHandlers (e.g. if a transform was applied that removed all the files in an album, the next album gets loaded); // this is needed after transforms/normalization/tag editing, but there's no need for an explicit call, because all these call mergeHandlerChanges() (directly or through MainFormDlgImpl::scan()) void updateWidgets(const std::string& strCrtName = "", const std::vector& vstrSel = std::vector()); void next(); void previous(); void resizeFilesGCols(); // resizes the first column to use all the available space; doesn't shrink it to less than 400px, though void setSongInCrtAlbum(); const std::vector& getIncludeDirs() const { return m_vstrIncludeDirs; } const std::vector& getExcludeDirs() const { return m_vstrExcludeDirs; } void setDirectories(const std::vector& vstrIncludeDirs, const std::vector& vstrExcludeDirs); // keeps existing handlers as long as they are still "included" DirTreeEnumerator m_dirTreeEnum; void resetFileEnum() { m_dirTreeEnum.reset(); } const Mp3Handler* getHandler(const std::string& strName) const; // looks in m_vpAllHandlers; returns 0 if there's no such handler const std::string& getCrtName() const; // returns the file name of the current handler; returns "" if the list is empty void setFontInfo(const std::string& strGenName, int nGenSize, int nLabelFontSizeDecr, const std::string& strFixedName, int nFixedSize); const QFont& getGeneralFont() const { CB_ASSERT (!m_strGenFontName.empty()); return m_generalFont; } const QFont& getLabelFont() const { CB_ASSERT (!m_strFixedFontName.empty()); return m_labelFont; } const QFont& getFixedFont() const { CB_ASSERT (!m_strGenFontName.empty()); return m_fixedFont; } int getLabelFontSizeDecr() const { return m_nLabelFontSizeDecr; } QFont getNewGeneralFont() const; QFont getNewFixedFont() const; void setCrtAtStartup() { updateWidgets(m_strLoadCrtName); } // to be called from the main thread at startup //QString getNoteLabel(int nPosInFlt); // gets the label of a note based on its position in m_uniqueNotes.m_vpFlt // color is normally the category color, but for support notes it's a "support" color; if the note isn't found in vpNoteSet, dGradStart and dGradEnd are set to -1, but normally they get a segment obtained by dividing [0, 1] in equal parts; void getNoteColor(const Note& note, const std::vector& vpNoteSet, QColor& color, double& dGradStart, double& dGradEnd) const; bool getDefaultForVisibleSessBtn() const { return m_bDefaultForVisibleSessBtn; } enum { DONT_UPDATE_TRANSFORMS, UPDATE_TRANSFORMS }; void setFastSave(bool bFastSave, bool bUpdateTransforms); bool useFastSave() const { return m_bFastSave; } public: TextCaseOptions m_eCaseForArtists; TextCaseOptions m_eCaseForOthers; bool m_bWarnOnNonSeqTracks, m_bWarnPastingToNonSeqTracks; bool m_bShowExport, m_bShowDebug, m_bShowSessions; bool m_bShowCustomCloseButtons; bool m_bScanAtStartup; std::string m_strNormalizeCmd; bool m_bKeepNormWndOpen; QByteArray m_locale; QTextCodec* m_pCodec; Filter m_filter; enum Save { SAVE, DISCARD, ASK }; Save m_eAssignSave; Save m_eNonId3v2Save; //AssgnBtnWrp m_assgnBtnWrp; bool m_bUseAllNotes; bool m_bTraceEnabled; void trace(const std::string& s); void clearLog(); const std::deque& getLog() const { return m_vLogs; } bool m_bAutoSizeIcons; int m_nMainWndIconSize; SessionSettings& m_settings; bool m_bKeepOneValidImg; bool m_bWmpVarArtists; bool m_bItunesVarArtists; std::string m_strTransfLog; // log file with transformations; bool m_bLogTransf; bool m_bSaveDownloadedData; enum { COLOR_ALB_NORM, COLOR_ALB_NONID3V2, COLOR_ALB_ASSIGNED, COLOR_FILE_NORM, COLOR_FILE_TAG_MISSING, COLOR_FILE_NA, COLOR_FILE_NO_DATA, COLOR_COL_CNT }; std::vector m_vTagEdtColors; std::vector m_vNoteCategColors; bool m_bWarnedAboutSel; bool m_bWarnedAboutBackup; bool m_bToldAboutPatterns; // see also s_bToldAboutPatternsInCrtRun bool m_bToldAboutSupport; // see also s_bToldAboutSupportInCrtRun std::string m_strCheckForNewVersions; // really an enum with 3 values QDateTime m_timeLastNewVerCheck; std::string m_strDontTellAboutVer; bool isTraceToFileEnabled() const { return m_bTraceToFile; } void setTraceToFile(bool bTraceToFile); // also removes the file std::string m_strRenamerInvalidChars; std::string m_strRenamerReplacementString; std::string m_strTranslation; private: std::deque m_vpAllHandlers; // owns the pointers; sorted by CmpMp3HandlerPtrByName; std::deque m_vpFltHandlers; // filtered m_vpHandlers (so it doesn't own the pointers); std::deque m_vpViewHandlers; // subset of m_vpFltHandlers (so it doesn't own the pointers), containing the handlers that are currently visible; depending on the view mode, it may be equal to m_vpFltHandlers, contain a single directory or a single file std::deque m_vpSelHandlers; // subset of m_vpViewHandlers (so it doesn't own the pointers), containing the handlers that are currently selected; it is not regularly kept up-to-date, instead being updated only when needed, with updateSelList() // !!! m_vpAllHandlers, m_vpFltHandlers and m_vpViewHandlers must be kept sorted at all times, so binary search can be run on them std::vector getSelNames(); // calls updateSelList() and returns the names ot the selected files; void updateSelList(); UniqueNotes m_uniqueNotes; // a copy of every "relevant" note; basically notes with a severity other than TRACE; also, notes that should be ignored don't get added here; "Sel" contains the notes that are shown after filtering (by note or by dir) is applied (so by default "Sel" is equal to "All"); has pointers from Notes:: not dynamically created ones; std::vector m_vnIgnoredNotes; // indexes into Notes::getAllNotes(); not sorted; std::vector m_vpCrtNotes; // notes for the "current" file std::vector m_vpAllTransf; // owns the pointers QualThresholds m_qualThresholds; // copies to spUniqueNotes all the unique notes from commonData.m_vpFltHandlers, with comparison done by CmpNotePtrById; // ignored notes (given by m_vnIgnoredNotes) are not included; // not actual pointers from vpHandlers are stored, but the corresponding ones from given by Notes::getMaster(); so they must not be freed; void getUniqueNotes(const std::deque& vpHandlers, std::set& spUniqueNotes); void printFilesCrt() const; // for debugging; prints what's current in m_pFilesG, using several methods std::vector > m_vvnCustomTransf; std::vector m_vnVisibleTransf; enum { APPROX_MATCH, EXACT_MATCH }; // if bExactMatch is false, it finds the nearest position in m_vpViewHandlers (so even if a file was deleted, it still finds something close); // returns -1 only if not found (either m_vpViewHandlers is empty or bExactMatch is true and the file is missing); int getPosInView(const std::string& strName/*, bool bUsePrevIfNotFound = true*/, bool bExactMatch = false) const; void updateCurrentStreams(); // resizes the rows to fit the data and notifies that the model has changed and there are new streams void updateCurrentNotes(); // updates m_pCommonData->m_vpCrtNotes, to hold the notes corresponding to the current file and resizes the rows to fit the data // returns the position of the "current" elem in m_vpViewHandlers (so it can be selected in the grid); if m_vpViewHandlers is empty, it returns -1; if pMp3Handler is 0, it returns 0 (unless m_vpViewHandlers is empty); int setViewModeHlp(ViewMode eViewMode, const Mp3Handler* pMp3Handler); int nextHlp(); // returns the position of the "current" elem in m_vpViewHandlers (see setViewMode() for details) int previousHlp(); void updateUniqueNotes(); // updates m_uniqueNotes to reflect the current m_vpAllHandlers and m_vpFltHandlers; mutable int m_nSongInCrtAlbum; // index into m_vpAllHandlers; something in the "current album" used by the tag editor; might be first, last or in the middle; std::string m_strGenFontName; int m_nGenFontSize; int m_nLabelFontSizeDecr; std::string m_strFixedFontName; int m_nFixedFontSize; QFont m_generalFont; QFont m_fixedFont; QFont m_labelFont; std::string m_strLoadCrtName; // needed by setCrtAtStartup(), because calling updateWidgets() from a secondary thread has issues; so instead, the name of the current file is saved here for later, to be set from the main thread, through setCrtAtStartup() //bool m_bDirty; // seemed like a good idea, but since we also save filters and what's current, pretty much all the time the data will need to be saved bool m_bDefaultForVisibleSessBtn; // the Sessions button is shown by default if there are several sessions bool m_bFastSave; bool m_bTraceToFile; private: ViewMode m_eViewMode; int getPosInFlt(const Mp3Handler* pMp3Handler) const; // finds the position in m_vpFltHandlers; returns -1 if not found; 0 is a valid argument, in which case -1 is returned; needed by next() and previous(), to help with navigation when going into "folder" mode QToolButton* m_pNoteFilterB; QToolButton* m_pDirFilterB; QToolButton* m_pModeAllB; QToolButton* m_pModeAlbumB; QToolButton* m_pModeSongB; std::deque m_vLogs; std::vector m_vstrIncludeDirs, m_vstrExcludeDirs; public slots: void onCrtFileChanged(); void onFilterChanged(); // updates m_vpFltHandlers and m_vpViewHandlers; also updates the state of the filter buttons (deselecting them if the user chose empty filters) private: friend class boost::serialization::access; template void save(Archive& ar, const unsigned int nVersion) const; template void load(Archive& ar, const unsigned int nVersion); BOOST_SERIALIZATION_SPLIT_MEMBER(); }; CommonData* getCommonData(); // the only CommonData that exists at a given moment QWidget* getMainForm(); //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== // For the vertical header of a QTableView whose labels are the current row number, it determines the width necessary to accomodate any of those labels. // Currently it uses a hard-coded value to add to the width. //ttt2 fix // It makes 2 assumptions: // - the TableView uses the same FontMetrics as the ones returned by QApplication::fontMetrics() (this is intended to be called from a TableModel's headerData(), for which finding the table is not easy; and anyway, a model can be connected to several tables) // - digits in the font have the same size, or at least there is no digit with a size larger than that of '9'; (in many fonts all the digits do have the same size, so it should be OK most of the time) // Only the width is calculated; the height is returned as "1". This allows the content of a cell to determine the width of a row. Returning the height actually needed to draw the label would cause the rows to be to large, because significant spacing is added to the result. This is the opposite of what happens to the width, where a big number of pixels must be added by getNumVertHdrSize() just to have everything displayed. // ttt3 is this a Qt bug? (adds spacing where it's not needed and doesn't add it where it should) // // If nRowCount<=0 it behaves as if nRowCount==1 // // Returns QVariant() for horizontal headers. // // The real reason this is needed: Qt can easily resize the header to accomodate all the header labels, and that's enough for fixed-height rows, whose height is set with verticalHeader()->setDefaultSectionSize(). However, if resizeRowsToContents() gets called (and it seems that it must be called to get variable-height working, and some flag to enable this doesn't seem to exist) the height of each row's header becomes too big. Using getNumVertHdrSize() we force the height to be 1, thus allowing the data cells to tell the height (the final height is the maximum between data cells and the header cell for each row). //ttt2 At some point it seemed that rows would get larger even without calling getNumVertHdrSize(). This should be looked at again. QVariant getNumVertHdrSize(int nRowCount, Qt::Orientation eOrientation); // ttt2 add optional param QTableView to take the metrics from //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== // returns a label for a note; first 26 notes get labels "a" .. " z", next 26 get "A" .. "Z", then "aa" .. "az", "aA" .. "aZ", "ba", ... QString getNoteLabel(const Note* pNote); class QColor; const QColor& ERROR_PEN_COLOR(); const QColor& SUPPORT_PEN_COLOR(); //QColor getNoteColor(const Note& note); // color based on severity void defaultResize(QDialog& dlg); // resizes a dialog with inexisting/invalid size settings, so it covers an area slightly smaller than MainWnd; however, if the dialog is alrady bigger than that, it doesn't get shrinked QColor getDefaultBkgCol(); //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== void printFontInfo(const char* szLabel, const QFont& font); // adjusts the global font so it displays legible characters (on Windows it is possible under some unclear circumstances for all characters to be shown as small rectangles) void fixAppFont(QFont& font, std::string& strNewFont, int& nNewSize); #endif // #ifndef CommonDataH MP3Diags-1.2.02/src/TagReadPanel.cpp0000644000175000001440000002706711724673405015756 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include #include #include #include "TagReadPanel.h" #include "DataStream.h" #include "Helpers.h" #include "Widgets.h" extern int CELL_HEIGHT; using namespace std; TagReadPanel::TagReadPanel(QWidget* pParent, TagReader* pTagReader) : QFrame(pParent) { QVBoxLayout* pLayout (new QVBoxLayout(this)); pLayout->setSpacing(6); pLayout->setContentsMargins(0, 10, 0, 0); setMaximumWidth(400); { QLabel* pLabel (new QLabel(pTagReader->getName(), this)); QFont font (pLabel->font()); int nSize (font.pixelSize()); if (-1 == nSize) { nSize = font.pointSize(); if (-1 != nSize) { nSize = nSize*4/3; font.setPointSize(nSize); } } else { nSize = nSize*4/3; font.setPixelSize(nSize); } font.setBold(true); pLabel->setFont(font); pLayout->addWidget(pLabel); } QColor gray (palette().color(QPalette::Window)); { QTableWidget* pTable (new QTableWidget(this)); pTable->setVerticalHeader(new NoCropHeaderView(pTable)); pTable->setEditTriggers(QAbstractItemView::NoEditTriggers); pTable->verticalHeader()->setMinimumSectionSize(CELL_HEIGHT); pTable->verticalHeader()->setDefaultSectionSize(CELL_HEIGHT); pTable->horizontalHeader()->setStretchLastSection(true); const int ROW_CNT (9); pTable->setRowCount(ROW_CNT); pTable->setColumnCount(1); QStringList lLabels; //lLabels << "Title" << "Artist" << "Track#" << "Time" << "Genre" << "Composer" << "Album" << "VA" << "Rating"; lLabels << tr("Track#") << tr("Artist") << tr("Title") << tr("Album") << tr("VA") << tr("Time") << tr("Genre") << tr("Rating") << tr("Composer"); pTable->setVerticalHeaderLabels(lLabels); pTable->horizontalHeader()->hide(); //pTable->setMinimumHeight(300); int nHeight (CELL_HEIGHT*ROW_CNT + 2*QApplication::style()->pixelMetric(QStyle::PM_DefaultFrameWidth, 0, pTable)); pTable->setMaximumHeight(nHeight); pTable->setMinimumHeight(nHeight); //pTable->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); pTable->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); //pTable->setFrameShape(QFrame::NoFrame); QTableWidgetItem* pItem; pItem = new QTableWidgetItem(); // !!! from doc: The table takes ownership of the item. switch (pTagReader->getSupport(TagReader::TRACK_NUMBER)) { case TagReader::NOT_SUPPORTED: pItem->setBackground(gray); break; default: pItem->setText(convStr(pTagReader->getTrackNumber())); } pTable->setItem(0, 0, pItem); pItem = new QTableWidgetItem(); switch (pTagReader->getSupport(TagReader::ARTIST)) { case TagReader::NOT_SUPPORTED: pItem->setBackground(gray); break; default: pItem->setText(convStr(pTagReader->getArtist())); } pTable->setItem(1, 0, pItem); pItem = new QTableWidgetItem(); switch (pTagReader->getSupport(TagReader::TITLE)) { case TagReader::NOT_SUPPORTED: pItem->setBackground(gray); break; default: pItem->setText(convStr(pTagReader->getTitle())); } pTable->setItem(2, 0, pItem); pItem = new QTableWidgetItem(); switch (pTagReader->getSupport(TagReader::ALBUM)) { case TagReader::NOT_SUPPORTED: pItem->setBackground(gray); break; default: pItem->setText(convStr(pTagReader->getAlbumName())); } pTable->setItem(3, 0, pItem); pItem = new QTableWidgetItem(); switch (pTagReader->getSupport(TagReader::VARIOUS_ARTISTS)) { case TagReader::NOT_SUPPORTED: pItem->setBackground(gray); break; default: pItem->setText(convStr(pTagReader->getValue(TagReader::VARIOUS_ARTISTS))); } pTable->setItem(4, 0, pItem); pItem = new QTableWidgetItem(); switch (pTagReader->getSupport(TagReader::TIME)) { case TagReader::NOT_SUPPORTED: pItem->setBackground(gray); break; default: pItem->setText(pTagReader->getTime().asString()); } pTable->setItem(5, 0, pItem); pItem = new QTableWidgetItem(); switch (pTagReader->getSupport(TagReader::GENRE)) { case TagReader::NOT_SUPPORTED: pItem->setBackground(gray); break; default: pItem->setText(convStr(pTagReader->getGenre())); } pTable->setItem(6, 0, pItem); pItem = new QTableWidgetItem(); switch (pTagReader->getSupport(TagReader::RATING)) { case TagReader::NOT_SUPPORTED: pItem->setBackground(gray); break; default: { double d (pTagReader->getRating()); if (d >= 0) { pItem->setText(QString().sprintf("%1.1f", d)); } } } pTable->setItem(7, 0, pItem); pItem = new QTableWidgetItem(); switch (pTagReader->getSupport(TagReader::COMPOSER)) { case TagReader::NOT_SUPPORTED: pItem->setBackground(gray); break; default: pItem->setText(convStr(pTagReader->getComposer())); } pTable->setItem(8, 0, pItem); pLayout->addWidget(pTable); //ttt2 Oxygen shows regular cells under mouse with diferent background color } { // ttt2 perhaps use QScrollArea vector vImg (pTagReader->getImages()); m_pImgWidget = 0; QHBoxLayout* pImgWidgetLayout (0); for (int i = 0; i < cSize(vImg); ++i) { const ImageInfo& img (vImg[i]); if (0 == m_pImgWidget) { m_pImgWidget = new QWidget (this); pImgWidgetLayout = new QHBoxLayout(m_pImgWidget); pImgWidgetLayout->setContentsMargins(0, 0, 0, 0); //pImgWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); m_pImgWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); //pImgWidget->setMaximumHeight(40); } ImageInfoPanel* p (new ImageInfoPanel (this, img)); m_vpImgPanels.push_back(p); pImgWidgetLayout->addWidget(p); } if (0 != m_pImgWidget) { pImgWidgetLayout->addStretch(); pLayout->addWidget(m_pImgWidget); } } { QTextEdit* pOtherInfoM (new QTextEdit(this)); pOtherInfoM->setPlainText(convStr(pTagReader->getOtherInfo())); //pOtherInfoM->setTabStopWidth(fontMetrics().width("aBcDeF")); pOtherInfoM->setTabStopWidth(fontMetrics().width("F")); //pOtherInfoM->setEnabled(false); QPalette grayPalette (pOtherInfoM->palette()); grayPalette.setColor(QPalette::Base, gray); pOtherInfoM->setPalette(grayPalette); pOtherInfoM->setReadOnly(true); pOtherInfoM->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); //pOtherInfoM->setFrameShape(QFrame::NoFrame); pLayout->addWidget(pOtherInfoM); } if (0 != m_pImgWidget) { QTimer::singleShot(1, this, SLOT(onCheckSize())); } } void TagReadPanel::onCheckSize() { QSize hint (m_pImgWidget->sizeHint()); QSize act (m_pImgWidget->size()); //qDebug("pImgWidget %dx%d %dx%d", hint.width(), hint.height(), act.width(), act.height()); if (hint.width() > act.width()) { int n (cSize(m_vpImgPanels)); int nNewSize ((act.width() + m_pImgWidget->layout()->spacing()) / n - m_pImgWidget->layout()->spacing() - 8); //ttt2 hard-coded "8"; it's for the frame drawn around auto-raise buttons; minimum value that works is 6, but not sure where to get it from; using 8 just in case) if (nNewSize > 64) { nNewSize = 64; } //qDebug("new sz %d", nNewSize); for (int i = 0; i < n; ++i) { m_vpImgPanels[i]->resize(nNewSize); } } } ImageInfoPanel::ImageInfoPanel(QWidget* pParent, const ImageInfo& imageInfo) : QFrame(pParent), m_imageInfo(imageInfo) //ttt1 name conflict with ImageInfoPanelWdgImpl { QVBoxLayout* pLayout (new QVBoxLayout(this)); pLayout->setContentsMargins(0, 0, 0, 0); pLayout->setSpacing(4); pLayout->addStretch(); createButton(64); m_pInfoLabel = new QLabel(tr(""), this); m_pInfoLabel->setText(m_imageInfo.getTextDescr()); m_pInfoLabel->setAlignment(Qt::AlignHCenter); pLayout->addWidget(m_pInfoLabel, 0, Qt::AlignHCenter); //setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); } void ImageInfoPanel::onShowFull() { m_imageInfo.showFull(this); } void ImageInfoPanel::createButton(int nSize) { QVBoxLayout* pLayout (dynamic_cast(layout())); const int BTN_SIZE (nSize); const int ICON_SIZE (BTN_SIZE - 2*QApplication::style()->pixelMetric(QStyle::PM_DefaultFrameWidth) - 2); //ttt2 not sure PM_DefaultFrameWidth is right m_pBtn = new QToolButton(this); m_pBtn->setIcon(QPixmap::fromImage(m_imageInfo.getImage(ICON_SIZE))); m_pBtn->setMaximumSize(BTN_SIZE, BTN_SIZE); m_pBtn->setMinimumSize(BTN_SIZE, BTN_SIZE); m_pBtn->setAutoRaise(true); m_pBtn->setToolTip(tr("Click to see larger image")); m_pBtn->setIconSize(QSize(ICON_SIZE, ICON_SIZE)); pLayout->addWidget(m_pBtn, 0, Qt::AlignHCenter); connect(m_pBtn, SIGNAL(clicked(bool)), this, SLOT(onShowFull())); } void ImageInfoPanel::resize(int nSize) { delete m_pBtn; delete m_pInfoLabel; createButton(nSize); QVBoxLayout* pLayout (dynamic_cast(layout())); // ttt2 see why is this needed (otherwise the button isn't shown) m_pInfoLabel = new QLabel("", this); m_pInfoLabel->setMaximumSize(100, 1); pLayout->addWidget(m_pInfoLabel, 0, Qt::AlignHCenter); pLayout->setSpacing(0); } MP3Diags-1.2.02/src/Sessions.ui0000644000175000001440000001744211715723417015123 0ustar ciobiusers SessionsDlg 0 0 788 537 MP3 Diags - Sessions true 0 75 45 :/images/preferences-desktop-locale.svg true Language 0 0 0 false 0 0 &Open true &New ... &Edit ... Save &as ... E&rase &Load ... &Hide Close Qt::Vertical 20 40 0 81 0 0 Temporary session template 0 0 QComboBox::AdjustToContents 0 0 Persistent session template 0 0 QComboBox::AdjustToContents Qt::Horizontal 40 20 At s&tartup open the last session automatically m_pSessionsG m_pDirectoriesT m_pOpenB m_pNewB m_pEditB m_pSaveAsB m_pEraseB m_pLoadB m_pHideB m_pCloseB m_pOpenLastCkB MP3Diags-1.2.02/src/ExportDlgImpl.h0000644000175000001440000000502611261057356015652 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef EXPORTDLGIMPL_H #define EXPORTDLGIMPL_H #include #include #include "ui_Export.h" class Mp3Handler; class ExportDlgImpl : public QDialog, private Ui::ExportDlg { Q_OBJECT bool exportAsText(const std::string& strFileName); bool exportAsM3u(const std::string& strFileName); bool exportAsXml(const std::string& strFileName); void getHandlers(std::vector& v); void setFormatBtn(); QString getFileName(); void setExt(const char* szExt); public: ExportDlgImpl(QWidget* pParent); ~ExportDlgImpl(); /*$PUBLIC_FUNCTIONS$*/ void run(); public slots: /*$PUBLIC_SLOTS$*/ protected: /*$PROTECTED_FUNCTIONS$*/ protected slots: /*$PROTECTED_SLOTS$*/ void on_m_pCloseB_clicked() { accept(); } void on_m_pExportB_clicked(); void on_m_pChooseFileB_clicked(); void on_m_pXmlRB_clicked() { setExt("xml"); } void on_m_pM3uRB_clicked() { setExt("m3u"); } void on_m_pTextRB_clicked() { setExt("txt"); } }; #endif MP3Diags-1.2.02/src/src.pro0000644000175000001440000000620412477144004014254 0ustar ciobiusersSOURCES += \ Helpers.cpp \ main.cpp \ DiscogsDownloader.cpp \ DoubleList.cpp \ FileEnum.cpp \ FileRenamerDlgImpl.cpp \ FilesModel.cpp \ Id3Transf.cpp \ Id3V230Stream.cpp \ Id3V240Stream.cpp \ Id3V2Stream.cpp \ ImageInfoPanelWdgImpl.cpp \ LogModel.cpp \ LyricsStream.cpp \ MainFormDlgImpl.cpp \ Mp3Manip.cpp \ Mp3TransformThread.cpp \ MpegFrame.cpp \ MpegStream.cpp \ MultiLineTvDelegate.cpp \ MusicBrainzDownloader.cpp \ ExternalToolDlgImpl.cpp \ NoteFilterDlgImpl.cpp \ Notes.cpp \ NotesModel.cpp \ OsFile.cpp \ PaletteDlgImpl.cpp \ RenamerPatternsDlgImpl.cpp \ ScanDlgImpl.cpp \ SessionEditorDlgImpl.cpp \ SessionsDlgImpl.cpp \ SongInfoParser.cpp \ StoredSettings.cpp \ StreamsModel.cpp \ StructuralTransformation.cpp \ TagEditorDlgImpl.cpp \ TagEdtPatternsDlgImpl.cpp \ TagReadPanel.cpp \ TagWriter.cpp \ ThreadRunnerDlgImpl.cpp \ Transformation.cpp \ UniqueNotesModel.cpp \ Widgets.cpp \ AboutDlgImpl.cpp \ AlbumInfoDownloaderDlgImpl.cpp \ ApeStream.cpp \ CheckedDir.cpp \ ColumnResizer.cpp \ CommonData.cpp \ CommonTypes.cpp \ ConfigDlgImpl.cpp \ DataStream.cpp \ DebugDlgImpl.cpp \ DirFilterDlgImpl.cpp \ Version.cpp \ fstream_unicode.cpp \ ExportDlgImpl.cpp \ SerSupport.cpp \ FullSizeImgDlg.cpp \ Translation.cpp TEMPLATE = app CONFIG += warn_on \ thread \ qt \ debug_and_release TARGET = MP3Diags DESTDIR = ../bin QT += xml \ network RESOURCES += Mp3Diags.qrc HEADERS += AboutDlgImpl.h \ AlbumInfoDownloaderDlgImpl.h \ ApeStream.h \ CheckedDir.h \ ColumnResizer.h \ CommonData.h \ CommonTypes.h \ ConfigDlgImpl.h \ DataStream.h \ DebugDlgImpl.h \ DirFilterDlgImpl.h \ DiscogsDownloader.h \ DoubleList.h \ FileEnum.h \ FileRenamerDlgImpl.h \ FilesModel.h \ Helpers.h \ Id3Transf.h \ Id3V230Stream.h \ Id3V240Stream.h \ Id3V2Stream.h \ ImageInfoPanelWdgImpl.h \ LogModel.h \ LyricsStream.h \ MainFormDlgImpl.h \ Mp3Manip.h \ Mp3TransformThread.h \ MpegFrame.h \ MpegStream.h \ MultiLineTvDelegate.h \ MusicBrainzDownloader.h \ ExternalToolDlgImpl.h \ NoteFilterDlgImpl.h \ Notes.h \ NotesModel.h \ OsFile.h \ PaletteDlgImpl.h \ RenamerPatternsDlgImpl.h \ ScanDlgImpl.h \ SerSupport.h \ SessionEditorDlgImpl.h \ SessionsDlgImpl.h \ SimpleSaxHandler.h \ SongInfoParser.h \ StoredSettings.h \ StreamsModel.h \ StructuralTransformation.h \ TagEditorDlgImpl.h \ TagEdtPatternsDlgImpl.h \ TagReadPanel.h \ TagWriter.h \ ThreadRunnerDlgImpl.h \ Transformation.h \ UniqueNotesModel.h \ Widgets.h \ fstream_unicode.h \ ExportDlgImpl.h \ FullSizeImgDlg.h \ Version.h \ Translation.h FORMS += About.ui \ AlbumInfoDownloader.ui \ Config.ui \ Debug.ui \ DirFilter.ui \ DoubleListWdg.ui \ FileRenamer.ui \ ImageInfoPanel.ui \ MainForm.ui \ ExternalTool.ui \ NoteFilter.ui \ Palette.ui \ Patterns.ui \ Scan.ui \ SessionEditor.ui \ Sessions.ui \ TagEditor.ui \ ThreadRunner.ui \ Export.ui UI_DIR = ui-forms #CONFIG += console #DEFINES += DISABLE_CHECK_FOR_UPDATES #DEFINES += OS2 QMAKE_CXXFLAGS_DEBUG += -DGENERATE_TOC_zz LIBS += -lz \ -lboost_serialization-mt \ -lboost_program_options-mt TRANSLATIONS = translations/mp3diags_cs.ts \ translations/mp3diags_de_DE.ts \ translations/mp3diags_fr_FR.ts MP3Diags-1.2.02/src/TagEdtPatternsDlgImpl.h0000644000175000001440000000473311233524007017255 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef TagEdtPatternsDlgImplH #define TagEdtPatternsDlgImplH #include #include "ui_Patterns.h" #include #include class SessionSettings; class TagEdtPatternsDlgImpl : public QDialog, private Ui::PatternsDlg { Q_OBJECT std::vector m_vPatterns; SessionSettings& m_settings; const std::vector& m_vstrPredef; int m_nCrtLine, m_nCrtCol; public: TagEdtPatternsDlgImpl(QWidget* pParent, SessionSettings& settings, const std::vector& vstrPredef); ~TagEdtPatternsDlgImpl(); bool run(std::vector >&); // the int is ignored for input; for output, it tells which position a given pattern occupied in the input, or -1 if it's a new string protected slots: void on_m_pOkB_clicked(); void on_m_pCancelB_clicked(); void on_m_pAddPredefB_clicked(); void onHelp(); void onCrtPosChanged(); }; #endif // #ifndef TagEdtPatternsDlgImplH MP3Diags-1.2.02/src/DebugDlgImpl.h0000644000175000001440000000452011257657123015420 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef DebugDlgImplH #define DebugDlgImplH #include #include #include "ui_Debug.h" class CommonData; class QSettings; class LogModel; class DebugDlgImpl : public QDialog, private Ui::DebugDlg { Q_OBJECT CommonData* m_pCommonData; void exportAsText(const std::string& strFileName); void exportLog(const std::string& strFileName); LogModel* m_pLogModel; public: DebugDlgImpl(QWidget* pParent, CommonData* pCommonData); ~DebugDlgImpl(); /*$PUBLIC_FUNCTIONS$*/ void run(); public slots: /*$PUBLIC_SLOTS$*/ void on_m_pSaveLogB_clicked(); void on_m_pTst01B_clicked(); void on_m_pDecodeMpegFrameB_clicked(); protected: /*$PROTECTED_FUNCTIONS$*/ protected slots: /*$PROTECTED_SLOTS$*/ void on_m_pCloseB_clicked(); void onHelp(); }; #endif MP3Diags-1.2.02/src/MpegStream.cpp0000644000175000001440000010152411724734563015525 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include "MpegStream.h" #include "Mp3Manip.h" #include "Widgets.h" // for GlobalTranslHlp using namespace std; /*namespace { // moves the read pointer to the end of the frame; throws if it cannot do so MpegFrame getMpegFrame(istream& in) { MpegFrame frame (in); } }*/ MpegStreamBase::MpegStreamBase(int nIndex, NoteColl& notes, istream& in) : DataStream(nIndex) { m_pRst = new StreamStateRestorer(in); auto_ptr pRst (m_pRst); m_pos = in.tellg(); m_firstFrame = MpegFrame(notes, in); /*m_eVersion; m_eLayer; m_eChannelMode; m_nFrequency; m_nSize;*/ MP3_TRACE (m_pos, "MpegStreamBase built."); pRst.release(); } MpegStreamBase::~MpegStreamBase() { delete m_pRst; } /*override*/ void MpegStreamBase::copy(std::istream& in, std::ostream& out) { appendFilePart(in, out, m_pos, m_firstFrame.getSize()); } MpegStream::MpegStream(int nIndex, NoteColl& notes, istream& in) : MpegStreamBase(nIndex, notes, in), m_bVbr(false), m_nFrameCount(1), m_posLastFrame(-1), m_bRemoveLastFrameCalled(false) { MpegFrame frm; //int nFrameCount (1); streampos pos (m_pos); pos += m_firstFrame.getSize(); m_nTotalBps = m_firstFrame.getBitrate(); int nSecondFrameBitrate (-1); // to determine if the first frame has different bitrate from the others bool bVbr2 (false); for (;;) { try { frm = MpegFrame(notes, in); } catch (const EndOfFile&) { break; } catch (const MpegFrame::NotMpegFrame&) { break; } catch (const MpegFrame::PrematurelyEndedMpegFrame&) { if (m_nFrameCount >= MIN_FRAME_COUNT) { MP3_NOTE (pos, incompleteFrameInAudio); } //ttt2 perhaps include ver/layer/... in PrematurelyEndedMpegFrame and have different messages when the incomplete frame matches the stream and when it doesn't break; } if (m_firstFrame.getVersion() != frm.getVersion()) { // end; not part of the sequence if (m_nFrameCount >= MIN_FRAME_COUNT) { MP3_NOTE (pos, validFrameDiffVer); } break; } if (m_firstFrame.getLayer() != frm.getLayer()) { // end; not part of the sequence if (m_nFrameCount >= MIN_FRAME_COUNT) { MP3_NOTE (pos, validFrameDiffLayer); } break; } if (m_firstFrame.getChannelMode() != frm.getChannelMode()) { // end; not part of the sequence if (m_nFrameCount >= MIN_FRAME_COUNT) { MP3_NOTE (pos, validFrameDiffMode); } break; } if (m_firstFrame.getFrequency() != frm.getFrequency()) { // end; not part of the sequence if (m_nFrameCount >= MIN_FRAME_COUNT) { MP3_NOTE (pos, validFrameDiffFreq); } break; } if (m_firstFrame.getCrcUsage() != frm.getCrcUsage()) { // end; not part of the sequence if (m_nFrameCount >= MIN_FRAME_COUNT) { MP3_NOTE (pos, validFrameDiffCrc); } break; } m_posLastFrame = pos; m_lastFrame = frm; pos += frm.getSize(); m_nTotalBps += frm.getBitrate(); ++m_nFrameCount; if (frm.getBitrate() != m_firstFrame.getBitrate()) { m_bVbr = true; } if (-1 == nSecondFrameBitrate) { nSecondFrameBitrate = frm.getBitrate(); } else { if (nSecondFrameBitrate != frm.getBitrate()) { bVbr2 = true; } } } //MP3_CHECK (m_nFrameCount >= MIN_FRAME_COUNT, m_pos, "Invalid MPEG stream. Stream has fewer than 10 frames.", StreamTooShort(getInfo())); if (m_nFrameCount < MIN_FRAME_COUNT) { in.clear(); in.seekg(m_pos); char bfr [4]; string strInfo; if (4 == read(in, bfr, 4)) // normally this should work, because to get here at least a full frame needs to be read { strInfo = decodeMpegFrame(bfr, ", "); } MP3_THROW (m_pos, audioTooShort, StreamTooShort(strInfo, m_nFrameCount)); } MP3_CHECK (!m_bVbr || bVbr2, m_pos, diffBitrateInFirstFrame, UnknownHeader()); //ttt2 perhaps add test for "null": whatever is in the first bytes that allows Xing & Co to not generate audio in decoders that don't know about them m_nSize = pos - m_pos; in.seekg(pos); m_nBitrate = int(m_nTotalBps / m_nFrameCount); /*if (m_firstFrame.getCrcUsage()) { MP3_NOTE (m_pos, "Stream uses CRC."); }*/ if ( !(MpegFrame::MPEG1 == m_firstFrame.getVersion() && MpegFrame::LAYER3 == m_firstFrame.getLayer()) && !(MpegFrame::MPEG2 == m_firstFrame.getVersion() && MpegFrame::LAYER3 == m_firstFrame.getLayer())) { MP3_NOTE (m_pos, untestedEncoding); } MP3_TRACE (m_pos, "MpegStream built."); setRstOk(); } // this can only be called once; the second call will throw (it's harder and quite pointless to allow more than one call) void MpegStream::removeLastFrame() { STRM_ASSERT (!m_bRemoveLastFrameCalled); m_bRemoveLastFrameCalled = true; m_nSize -= m_lastFrame.getSize(); m_nTotalBps -= m_lastFrame.getBitrate(); STRM_ASSERT (m_nFrameCount >= 10); --m_nFrameCount; m_nBitrate = int(m_nTotalBps / m_nFrameCount); } /*override*/ void MpegStream::copy(std::istream& in, std::ostream& out) { appendFilePart(in, out, m_pos, m_nSize); } /*override*/ std::string MpegStream::getInfo() const { ostringstream out; out << getDuration() << ", " << m_firstFrame.getSzVersion() << " " << m_firstFrame.getSzLayer() << ", " << m_firstFrame.getSzChannelMode() << ", " << m_firstFrame.getFrequency() << "Hz, " << m_nBitrate << "bps " << (m_bVbr ? "VBR" : "CBR") << ", CRC=" << convStr(GlobalTranslHlp::tr(boolAsYesNo(m_firstFrame.getCrcUsage()))) << ", " << convStr(DataStream::tr("frame count=")) << m_nFrameCount; out << "; " << convStr((m_bRemoveLastFrameCalled ? DataStream::tr("last frame removed; it was located at 0x%1") : DataStream::tr("last frame located at 0x%1")).arg(m_posLastFrame, 0, 16)); return out.str(); } std::string MpegStream::getDuration() const { int nDur (int(m_nSize*8.0/m_nBitrate)); int nMin (nDur/60); int nSec (nDur - nMin*60); char a [15]; sprintf(a, "%d:%02d", nMin, nSec); return a; } bool MpegStream::isCompatible(const MpegFrameBase& frame) { if (getFirstFrame().getVersion() != frame.getVersion()) { return false; } if (getFirstFrame().getLayer() != frame.getLayer()) { return false; } if (getFirstFrame().getChannelMode() != frame.getChannelMode()) { return false; } if (getFirstFrame().getFrequency() != frame.getFrequency()) { return false; } if (getFirstFrame().getCrcUsage() != frame.getCrcUsage()) { return false; } return true; } // moves the read pointer to the first frame compatible with the stream; returns "false" if no such frame is found bool MpegStream::findNextCompatFrame(std::istream& in, std::streampos posMax) { streampos pos (in.tellg()); NoteColl notes (10); for (;;) { try { pos = getNextStream(in, pos); if (pos > posMax) { return false; } in.seekg(pos); try { MpegFrameBase frm (notes, in); if (isCompatible(frm)) { in.seekg(pos); return true; } } catch (const MpegFrameBase::NotMpegFrame&) { } } catch (const EndOfFile&) { return false; } } } #ifdef GENERATE_TOC //ttt2 maybe improve and use // throws if it can't write to the disk void createXing(ostream& out, const MpegFrame& frame1, int nFrameCount, streamoff nStreamSize) { const MpegFrameBase& frame (frame1.getBigBps()); int nSize (frame.getSize()); out.write(frame.getHeader(), MpegFrame::MPEG_FRAME_HDR_SIZE); int nSideInfoSize (frame.getSideInfoSize()); writeZeros(out, nSideInfoSize); out.write("Xing\0\0\0\7", 8); char bfr [4]; put32BitBigEndian(nFrameCount, bfr); out.write(bfr, 4); put32BitBigEndian(nStreamSize, bfr); out.write(bfr, 4); for (int i = 0; i < 100; ++ i) { bfr[0] = i*255/(100 - 1); out.write(bfr, 1); } writeZeros(out, nSize - MpegFrame::MPEG_FRAME_HDR_SIZE - nSideInfoSize - 8 - 4 - 4 - 100); CB_CHECK1 (out, WriteError()); } #else void createXing(ostream& out, const MpegFrame& frame, int nFrameCount, streamoff nStreamSize) { int nSize (frame.getSize()); out.write(frame.getHeader(), MpegFrame::MPEG_FRAME_HDR_SIZE); int nSideInfoSize (frame.getSideInfoSize()); writeZeros(out, nSideInfoSize); out.write("Xing\0\0\0\3", 8); char bfr [4]; put32BitBigEndian(nFrameCount, bfr); out.write(bfr, 4); put32BitBigEndian(nStreamSize, bfr); out.write(bfr, 4); writeZeros(out, nSize - MpegFrame::MPEG_FRAME_HDR_SIZE - nSideInfoSize - 8 - 4 - 4); CB_CHECK1 (out, WriteError()); } #endif void MpegStream::createXing(ostream& out) { static const int MIN_FRAME_SIZE (200); // ttt2 this 200 is arbitrary, but there's probably enough room for TOC if (m_firstFrame.getSize() >= MIN_FRAME_SIZE) { ::createXing(out, m_firstFrame, m_nFrameCount, getSize()); return; } static const int BFR_SIZE (2000); static char aNewHeader [BFR_SIZE]; ::fill(aNewHeader, aNewHeader + BFR_SIZE, 0); ::copy(m_firstFrame.getHeader(), m_firstFrame.getHeader() + MpegFrame::MPEG_FRAME_HDR_SIZE, aNewHeader); NoteColl notes; for (int i = 1; i <= 14; ++i) { aNewHeader[2] = (aNewHeader[2] & 0x0f) + (i << 4); istringstream in (string(aNewHeader, BFR_SIZE)); MpegFrame frame (notes, in); if (frame.getSize() >= MIN_FRAME_SIZE) { ::createXing(out, frame, m_nFrameCount, getSize()); return; } } CB_ASSERT(false); } //ttt2 perhaps make clear in messages that Xing is OK with CBR, which by convention should use "Info" instead of "Xing" but some tools don't follow this; don't delete Xing just because it's followed a CBR XingStreamBase::XingStreamBase(int nIndex, NoteColl& notes, istream& in) : MpegStreamBase(nIndex, notes, in), m_nFrameCount(-1), m_nByteCount(-1), m_nQuality(-1) { fill(&m_toc[0], &m_toc[100], 0); in.seekg(m_pos); const int XING_LABEL_SIZE (4); const int BFR_SIZE (MpegFrame::MPEG_FRAME_HDR_SIZE + 32 + XING_LABEL_SIZE); // MPEG header + side info + "Xing" size //ttt2 not sure if space for CRC16 should be added; then not sure if frame size should be increased by 2 when CRC is found char bfr [BFR_SIZE]; int nSideInfoSize (m_firstFrame.getSideInfoSize()); int nBfrSize (MpegFrame::MPEG_FRAME_HDR_SIZE + nSideInfoSize + XING_LABEL_SIZE); MP3_CHECK_T (nBfrSize <= m_firstFrame.getSize(), m_pos, "Not a Xing stream. This kind of MPEG audio doesn't support Xing.", NotXingStream()); // !!! some kinds of MPEG audio (e.g. "MPEG-1 Layer I, 44100Hz 32000bps" or "MPEG-2 Layer III, 22050Hz 8000bps") have very short frames, which can't accomodate a Xing header streamsize nRead (read(in, bfr, nBfrSize)); STRM_ASSERT (nBfrSize == nRead); // this was supposed to be a valid frame to begin with (otherwise the base class would have thrown) and nBfrSize is no bigger than the frame char* pLabel (bfr + MpegFrame::MPEG_FRAME_HDR_SIZE + nSideInfoSize); MP3_CHECK_T (0 == strncmp("Xing", pLabel, XING_LABEL_SIZE) || 0 == strncmp("Info", pLabel, XING_LABEL_SIZE), m_pos, "Not a Xing stream. Header not found.", NotXingStream()); // ttt0 perhaps if it gets this far it should generate some "broken xing": with the incorrect "vbr fix" which created a xing header longer than the mpeg frame that was supposed to contain it, followed by the removal of those extra bytes by the "unknown stream removal" causes the truncated xing header to be considered audio; that wouldn't happen if a "broken xing" stream would be tried before the "audio" stream in Mp3Handler::parse() MP3_CHECK_T (4 == read(in, bfr, 4) && 0 == bfr[0] && 0 == bfr[1] && 0 == bfr[2], m_pos, "Not a Xing stream. Header not found.", NotXingStream()); m_cFlags = bfr[3]; MP3_CHECK_T ((m_cFlags & 0x0f) == m_cFlags, m_pos, "Not a Xing stream. Invalid flags.", NotXingStream()); if (0x01 == (m_cFlags & 0x01)) { // has frames MP3_CHECK_T (4 == read(in, bfr, 4), m_pos, "Not a Xing stream. File too short.", NotXingStream()); m_nFrameCount = get32BitBigEndian(bfr); } if (0x02 == (m_cFlags & 0x02)) { // has bytes MP3_CHECK_T (4 == read(in, bfr, 4), m_pos, "Not a Xing stream. File too short.", NotXingStream()); m_nByteCount = get32BitBigEndian(bfr); } if (0x04 == (m_cFlags & 0x04)) { // has TOC MP3_CHECK_T (100 == read(in, m_toc, 100), m_pos, "Not a Xing stream. File too short.", NotXingStream()); } if (0x08 == (m_cFlags & 0x08)) { // has quality MP3_CHECK_T (4 == read(in, bfr, 4), m_pos, "Not a Xing stream. File too short.", NotXingStream()); m_nQuality = get32BitBigEndian(bfr); } streampos posEnd (m_pos); posEnd += m_firstFrame.getSize(); in.seekg(posEnd); //ttt2 2010.12.07 - A header claiming to have TOC but lacking one isn't detected. 1) Should check that values in TOC are ascending. 2) Should check that it actually fits: a 104 bytes-long 32kbps frame cannot hold a 100 bytes TOC along with the header and other things. } void XingStreamBase::getXingInfo(std::ostream& out) const { out << "[" << convStr(DataStream::tr("Xing header info:")); bool b (false); if (0x01 == (m_cFlags & 0x01)) { out << convStr(DataStream::tr(" frame count=")) << m_nFrameCount; b = true; } if (0x02 == (m_cFlags & 0x02)) { out << (b ? "," : "") << convStr(DataStream::tr(" byte count=")) << m_nByteCount; b = true; } //ttt2 see what to do with this: it's the size of the whole file, all headers&tags included (at least with c03 Valentin Moldovan - Marea Irlandei.mp3); ??? and anyway, what's the point of including the size of the whole file as a field? if (0x04 == (m_cFlags & 0x04)) { out << (b ? "," : "") << convStr(DataStream::tr(" TOC present")); b = true; } if (0x08 == (m_cFlags & 0x08)) { out << (b ? "," : "") << convStr(DataStream::tr(" quality=")) << m_nQuality; b = true; } out << "]"; } std::string XingStreamBase::getInfoForXml() const { ostringstream out; if (0x01 == (m_cFlags & 0x01)) { out << " frameCount=\"" << m_nFrameCount << "\""; } if (0x02 == (m_cFlags & 0x02)) { out << " byteCount=\"" << m_nByteCount << "\""; } //ttt2 see what to do with this: it's the size of the whole file, all headers&tags included (at least with c03 Valentin Moldovan - Marea Irlandei.mp3); ??? and anyway, what's the point of including the size of the whole file as a field? if (0x04 == (m_cFlags & 0x04)) { out << " toc=\"yes\""; } if (0x08 == (m_cFlags & 0x08)) { out << " quality=\"" << m_nQuality << "\""; } return out.str(); } /*override*/ std::string XingStreamBase::getInfo() const { ostringstream out; out << m_firstFrame.getSzVersion() << " " << m_firstFrame.getSzLayer() << ", " << m_firstFrame.getSzChannelMode() << ", " << m_firstFrame.getFrequency() << "Hz" << ", " << m_firstFrame.getBitrate() << "bps, CRC=" << convStr(GlobalTranslHlp::tr(boolAsYesNo(m_firstFrame.getCrcUsage()))) << "; "; getXingInfo(out); return out.str(); } bool XingStreamBase::matchesStructure(const MpegStream& mpeg) const { const MpegFrame& mpegFrm (mpeg.getFirstFrame()); return (m_firstFrame.getVersion() == mpegFrm.getVersion() && m_firstFrame.getLayer() == mpegFrm.getLayer() && //m_firstFrame.getChannelMode() == mpegFrm.getChannelMode() && m_firstFrame.getFrequency() == mpegFrm.getFrequency()); } // checks that there is a metch for version, layer, frequency and frame count bool XingStreamBase::matches(const MpegStream& mpeg) const { return matchesStructure(mpeg) && getFrameCount() == mpeg.getFrameCount(); } // checks that pNext is MpegStream* in addition to matches(const MpegStream&) bool XingStreamBase::matches(const DataStream* pNext) const { const MpegStream* q (dynamic_cast(pNext)); return 0 != q && matches(*q); } bool XingStreamBase::isBrokenByMp3Fixer(const DataStream* pNext, const DataStream* pAfterNext) const { if (16 != pNext->getSize()) { return false; } const MpegStream* q (dynamic_cast(pAfterNext)); return 0 != q && matchesStructure(*q) && getFrameCount() == q->getFrameCount() + 1; } XingStream::XingStream(int nIndex, NoteColl& notes, std::istream& in) : XingStreamBase(nIndex, notes, in) { MP3_TRACE (m_pos, "XingStream built."); setRstOk(); } LameStream::LameStream(int nIndex, NoteColl& notes, istream& in) : XingStreamBase(nIndex, notes, in) { in.seekg(m_pos); const int LAME_LABEL_SIZE (4); const int LAME_OFFS (156); const int BFR_SIZE (LAME_OFFS + LAME_LABEL_SIZE); // MPEG header + side info + "Xing" size //ttt2 not sure if space for CRC16 should be added; then not sure if frame size should be increased by 2 when CRC is found char bfr [BFR_SIZE]; MP3_CHECK_T (BFR_SIZE <= m_firstFrame.getSize(), m_pos, "Not a LAME stream. This kind of MPEG audio doesn't support LAME.", NotLameStream()); // !!! some kinds of MPEG audio have very short frames, which can't accomodate a VBRI header streamsize nRead (read(in, bfr, BFR_SIZE)); STRM_ASSERT (BFR_SIZE == nRead); // this was supposed to be a valid frame to begin with (otherwise the base class would have thrown) and BFR_SIZE is no bigger than the frame MP3_CHECK_T (0 == strncmp("LAME", bfr + LAME_OFFS, LAME_LABEL_SIZE), m_pos, "Not a LAME stream. Header not found.", NotLameStream()); streampos posEnd (m_pos); posEnd += m_firstFrame.getSize(); in.seekg(posEnd); MP3_TRACE (m_pos, "LameStream built."); setRstOk(); } /*override*/ std::string LameStream::getInfo() const { ostringstream out; out << m_firstFrame.getSzVersion() << " " << m_firstFrame.getSzLayer() << ", " << m_firstFrame.getSzChannelMode() << ", " << m_firstFrame.getFrequency() << "Hz" << ", " << m_firstFrame.getBitrate() << "bps, CRC=" << convStr(GlobalTranslHlp::tr(boolAsYesNo(m_firstFrame.getCrcUsage()))) << "; "; getXingInfo(out); return out.str(); } //ttt2 see why after most VBRI headers comes an "unknown" stream; perhaps there's an error in how VbriStream works VbriStream::VbriStream(int nIndex, NoteColl& notes, istream& in) : MpegStreamBase(nIndex, notes, in) { in.seekg(m_pos); const int VBRI_LABEL_SIZE (4); const int BFR_SIZE (MpegFrame::MPEG_FRAME_HDR_SIZE + 32 + VBRI_LABEL_SIZE); // MPEG header + side info + "Xing" size //ttt2 not sure if space for CRC16 should be added; then not sure if frame size should be increased by 2 when CRC is found char bfr [BFR_SIZE]; MP3_CHECK_T (BFR_SIZE <= m_firstFrame.getSize(), m_pos, "Not a VBRI stream. This kind of MPEG audio doesn't support VBRI.", NotVbriStream()); // !!! some kinds of MPEG audio have very short frames, which can't accomodate a VBRI header streamsize nRead (read(in, bfr, BFR_SIZE)); STRM_ASSERT (BFR_SIZE == nRead); // this was supposed to be a valid frame to begin with (otherwise the base class would have thrown) and BFR_SIZE is no bigger than the frame char* pLabel (bfr + MpegFrame::MPEG_FRAME_HDR_SIZE + 32); MP3_CHECK_T (0 == strncmp("VBRI", pLabel, VBRI_LABEL_SIZE), m_pos, "Not a VBRI stream. Header not found.", NotVbriStream()); streampos posEnd (m_pos); posEnd += m_firstFrame.getSize(); in.seekg(posEnd); MP3_TRACE (m_pos, "VbriStream built."); setRstOk(); } /*override*/ std::string VbriStream::getInfo() const { ostringstream out; out << m_firstFrame.getSzVersion() << " " << m_firstFrame.getSzLayer() << ", " << m_firstFrame.getSzChannelMode() << ", " << m_firstFrame.getFrequency() << "Hz" << ", " << m_firstFrame.getBitrate() << "bps, CRC=" << convStr(GlobalTranslHlp::tr(boolAsYesNo(m_firstFrame.getCrcUsage()))); return out.str(); } //=========================================================================================================================== //=========================================================================================================================== //=========================================================================================================================== Id3V1Stream::Id3V1Stream(int nIndex, NoteColl& notes, istream& in) : DataStream(nIndex), m_pos(in.tellg()) { StreamStateRestorer rst (in); const int BFR_SIZE (128); streamsize nRead (read(in, m_data, BFR_SIZE)); MP3_CHECK_T (BFR_SIZE == nRead, m_pos, "Invalid ID3V1 tag. File too short.", NotId3V1Stream()); MP3_CHECK_T (0 == strncmp("TAG", m_data, 3), m_pos, "Invalid ID3V1 tag. Invalid header.", NotId3V1Stream()); MP3_CHECK (BFR_SIZE == nRead, m_pos, id3v1TooShort, NotId3V1Stream()); // not 100% correct, but should generally work if (0 == m_data[125] && 0 != m_data[126]) { m_eVersion = V11b; } else { unsigned char c ((unsigned char)m_data[127]); m_eVersion = (' ' == m_data[125] && ' ' == m_data[126]) || (0 == m_data[125] && 0 == m_data[126]) || (c > 0 && c < ' ') ? V11 : V10; } // http://uweb.txstate.edu/~me02/tutorials/sound_file_formats/mpeg/tags.htm TestResult eTrack (checkId3V1String(m_data + 3, 30)); MP3_CHECK (BAD != eTrack, m_pos, id3v1InvalidName, NotId3V1Stream()); TestResult eArtist (checkId3V1String(m_data + 33, 30)); MP3_CHECK (BAD != eArtist, m_pos, id3v1InvalidArtist, NotId3V1Stream()); TestResult eAlbum (checkId3V1String(m_data + 63, 30)); MP3_CHECK (BAD != eAlbum, m_pos, id3v1InvalidAlbum, NotId3V1Stream()); TestResult eYear (checkId3V1String(m_data + 93, 4)); MP3_CHECK (BAD != eYear, m_pos, id3v1InvalidYear, NotId3V1Stream()); TestResult eComment (checkId3V1String(m_data + 97, 28)); MP3_CHECK (BAD != eComment, m_pos, id3v1InvalidComment, NotId3V1Stream()); // "28" is for ID3V1.1b (there's no reliable way to distinguish among versions 1.0 and 1.1 by design, and in practice among any of them because some tools use 0 instead of space and 0 seems to be a valid value for 1.1b's track and genre, for "undefined") //ttt2 use m_eVersion if (ZERO_PADDED == eTrack || ZERO_PADDED == eArtist || ZERO_PADDED == eAlbum || ZERO_PADDED == eYear || ZERO_PADDED == eComment) { // MP3_NOTE (m_pos, zeroInId3V1 /*"ID3V1 tag contains characters with the code 0, although this is not allowed by the standard (yet used by some tools)."*/); if (SPACE_PADDED == eTrack || SPACE_PADDED == eArtist || SPACE_PADDED == eAlbum || SPACE_PADDED == eYear || SPACE_PADDED == eComment) { MP3_NOTE (m_pos, mixedPaddingInId3V1); } } if (MIXED_PADDED == eTrack || MIXED_PADDED == eArtist || MIXED_PADDED == eAlbum || MIXED_PADDED == eYear || MIXED_PADDED == eComment) { MP3_NOTE (m_pos, mixedFieldPaddingInId3V1); } MP3_TRACE (m_pos, "Id3V1Stream built."); rst.setOk(); } /*static*/ bool Id3V1Stream::isLegal(char c) { unsigned char x (c); return x >= 32; } // makes sure that a valid string is stored at the address given, meaning no chars smaller than 32; well, except for 0: 0 isn't really valid, as the fields are supposed to be padded with spaces at the right, but some tools use 0 anyway /*static*/ Id3V1Stream::TestResult Id3V1Stream::checkId3V1String(const char* p, int nSize) { bool bZeroFound (false); int i (0); for (; i < nSize; ++i) { if (0 == p[i]) { bZeroFound = true; break; } if (!isLegal(p[i])) { return BAD; } } if (bZeroFound) { bool bMixed (false); for (; i < nSize; ++i) { if (' ' == p[i]) { bMixed = true; } if (0 != p[i] && ' ' != p[i]) { return BAD; } } return bMixed ? MIXED_PADDED : ZERO_PADDED; } if (' ' == p[nSize - 1] && ' ' == p[nSize - 2]) { return SPACE_PADDED; } return NOT_PADDED; } /*override*/ void Id3V1Stream::copy(std::istream&, std::ostream& out) { out.write(m_data, 128); CB_CHECK1 (out, WriteError()); } static string getSpacedStr(const string& s) { if (s.empty()) { return string(); } return ", " + s; } const char* Id3V1Stream::getVersion() const { switch (m_eVersion) { case V10: return "ID3V1.0"; case V11: return "ID3V1.1"; case V11b: return "ID3V1.1b"; default: STRM_ASSERT (false); } } /*override*/ std::string Id3V1Stream::getInfo() const { string strRes (getVersion()); strRes += getSpacedStr(getTitle(0)) + getSpacedStr(getArtist(0)) + getSpacedStr(getAlbumName(0)) + getSpacedStr(getGenre(0)); return strRes; } /*override*/ TagReader::SuportLevel Id3V1Stream::getSupport(Feature eFeature) const { switch (eFeature) { case TITLE: case ARTIST: case TRACK_NUMBER: case TIME: case GENRE: case ALBUM: return READ_ONLY; default: return NOT_SUPPORTED; } } // returns the string located at nAddr, removing trailing spaces; the result is in UTF8 format string Id3V1Stream::getStr(int nAddr, int nMaxSize) const { int nSize (0); for (; nSize < nMaxSize; ++nSize) { unsigned char c ((unsigned char)(m_data[nAddr + nSize])); if (c < ' ') { break; } // ASCII-specific, but probably OK for dealing with MP3 tags } for (; nSize > 0 && ' ' == m_data[nAddr + nSize - 1]; --nSize) {} return utf8FromLatin1(string(m_data + nAddr, m_data + nAddr + nSize)); } /*override*/ std::string Id3V1Stream::getTitle(bool* pbFrameExists /* = 0*/) const { if (0 != pbFrameExists) { *pbFrameExists = true; } return getStr(3, 30); } /*override*/ std::string Id3V1Stream::getArtist(bool* pbFrameExists /* = 0*/) const { if (0 != pbFrameExists) { *pbFrameExists = true; } return getStr(33, 30); } /*override*/ std::string Id3V1Stream::getTrackNumber(bool* pbFrameExists /* = 0*/) const { if (V11b != m_eVersion) { if (0 != pbFrameExists) { *pbFrameExists = false; } return ""; } if (0 != pbFrameExists) { *pbFrameExists = true; } char a [10]; sprintf(a, "%02d", (int)(unsigned char)(m_data[126])); return a; } /*override*/ std::string Id3V1Stream::getGenre(bool* pbFrameExists /* = 0*/) const { switch (m_eVersion) { case V11: case V11b: { int n (m_data[127]); if (0 != pbFrameExists) { *pbFrameExists = true; } return getId3V1Genre(n); } default: if (0 != pbFrameExists) { *pbFrameExists = false; } return ""; } } /*override*/ std::string Id3V1Stream::getAlbumName(bool* pbFrameExists /* = 0*/) const { if (0 != pbFrameExists) { *pbFrameExists = true; } return getStr(63, 30); } /*override*/ TagTimestamp Id3V1Stream::getTime(bool* pbFrameExists /* = 0*/) const { if (0 != pbFrameExists) { *pbFrameExists = true; } string s (getStr(93, 4)); if (" " == s || string("\0\0\0\0") == s) { return TagTimestamp(""); } try { return TagTimestamp(s); } catch (const TagTimestamp::InvalidTime&) { return TagTimestamp(""); } } int Id3V1Stream::getCommSize() const { switch (m_eVersion) { case V10: return 31; case V11: return 30; case V11b: return 28; } return 0; } /*override*/ std::string Id3V1Stream::getOtherInfo() const { int nCommSize (getCommSize()); string strComm (getStr(97, nCommSize)); if (strComm.empty()) { return ""; } return convStr(TagReader::tr("Comment: %1").arg(convStr(strComm))); } const char* getId3V1Genre(int n) { if (n <= 0 || n > 147) { return ""; } static const char* aGenres [148] = { "Blues", //ttt0 review transl - probably not, as ID3V2 is more important but hard to translate "Classic Rock", "Country", "Dance", "Disco", "Funk", "Grunge", "Hip-Hop", "Jazz", "Metal", "New Age", "Oldies", "Other", "Pop", "R&B", "Rap", "Reggae", "Rock", "Techno", "Industrial", "Alternative", "Ska", "Death Metal", "Prank", "Soundtrack", "Euro-Techno", "Ambient", "Trip-Hop", "Vocal", "Jazz+Funk", "Fusion", "Trance", //20 - 3F "Classical", "Instrumental", "Acid", "House", "Game", "Sound Clip", "Gospel", "Noise", "Alternative Rock", "Bass", "Soul", "Punk", "Space", "Meditative", "Instrumental Pop", "Instrumental Rock", "Ethnic", "Gothic", "Darkwave", "Techno-Industrial", "Electronic", "Pop-Folk", "Eurodance", "Dream", "Southern Rock", "Comedy", "Cult", "Gangsta", "Top 40", "Christian Rap", "Pop/Funk", "Jungle", //40 - 5F "Native US", "Cabaret", "New Wave", "Psychedelic", // ??? "Psychadelic" in the specs "Rave", "Showtunes", "Trailer", "Lo-Fi", "Tribal", "Acid Punk", "Acid Jazz", "Polka", "Retro", "Musical", "Rock & Roll", "Hard Rock", "Folk", "Folk-Rock", "National Folk", "Swing", "Fast Fusion", "Bebop", "Latin", "Revival", "Celtic", "Bluegrass", "Avantgarde", "Gothic Rock", "Progressive Rock", "Psychedelic Rock", "Symphonic Rock", "Slow Rock", //60 - 7F "Big Band", "Chorus", "Easy Listening", "Acoustic", "Humour", "Speech", "Chanson", "Opera", "Chamber Music", "Sonata", "Symphony", "Booty Bass", "Primus", "Porn Groove", "Satire", "Slow Jam", "Club", "Tango", "Samba", "Folklore", "Ballad", "Power Ballad", "Rhytmic Soul", "Freestyle", "Duet", "Punk Rock", "Drum Solo", "A capella", "Euro-House", "Dance Hall", "Goa", "Drum & Bass", //80 - 93 "Club-House", "Hardcore", "Terror", "Indie", "BritPop", "Negerpunk", "Polsk Punk", "Beat", "Christian Gangsta Rap", "Heavy Metal", "Black Metal", "Crossover", "Contemporary Christian", "Christian Rock", "Merengue", "Salsa", "Trash Meta", "Anime", "Jpop", "Synthpop" }; return aGenres[n]; } //=========================================================================================================================== //=========================================================================================================================== //=========================================================================================================================== //ttt1 GC - Stillness & Crafted Prayer 1.mp3 - audio present but not detected, because it's MPEG 2.5 MP3Diags-1.2.02/src/StructuralTransformation.h0000644000175000001440000003277311720127233020220 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef StructuralTransformationH #define StructuralTransformationH #include #include "Transformation.h" class DataStream; //=================================================================================================================== // Searches for unknown streams following audio streams. If found, it toggles each bit of the 4 bytes of such a stream, to see if this leads to a compatible frame. If it does, it transforms the file by actually toggling that bit class SingleBitRepairer : public Transformation { static bool isUnknown(const DataStream*); public: /*override*/ Transformation::Result apply(const Mp3Handler&, const TransfConfig&, const std::string& strOrigSrcName, std::string& strTempName); /*override*/ const char* getActionName() const { return getClassName(); } /*override*/ const char* getDescription() const { return QT_TRANSLATE_NOOP("Transformation", "Sometimes a bit gets flipped in a file. This tries to identify places having this issue in audio frame headers. If found, it fixes the problem. It is most likely to apply to files that have 2 audio streams."); } static const char* getClassName() { return QT_TRANSLATE_NOOP("Transformation", "Restore flipped bit in audio"); } // ttt1 "getClassName" is misleading and should be renamed }; //=================================================================================================================== // base class for stream removal class GenericRemover : public Transformation { virtual bool matches(DataStream*) const = 0; // if this returns true the stream must be removed public: virtual ~GenericRemover() {} /*override*/ Transformation::Result apply(const Mp3Handler&, const TransfConfig&, const std::string& strOrigSrcName, std::string& strTempName); }; // removes all streams between audio streams class InnerNonAudioRemover : public GenericRemover { /*override*/ bool matches(DataStream* p) const; std::set m_spStreamsToDiscard; void setupDiscarded(const Mp3Handler& h); public: /*override*/ Transformation::Result apply(const Mp3Handler&, const TransfConfig&, const std::string& strOrigSrcName, std::string& strTempName); /*override*/ const char* getActionName() const { return getClassName(); } /*override*/ const char* getDescription() const { return QT_TRANSLATE_NOOP("Transformation", "Removes all non-audio data that is found between audio streams. In this context, VBRI streams are considered audio streams (while Xing streams are not.)"); } static const char* getClassName() { return QT_TRANSLATE_NOOP("Transformation", "Remove inner non-audio"); } }; class UnknownDataStreamRemover : public GenericRemover { /*override*/ bool matches(DataStream* p) const; public: /*override*/ const char* getActionName() const { return getClassName(); } /*override*/ const char* getDescription() const { return QT_TRANSLATE_NOOP("Transformation", "Removes all unknown streams."); } static const char* getClassName() { return QT_TRANSLATE_NOOP("Transformation", "Remove unknown streams"); } }; class BrokenDataStreamRemover : public GenericRemover { /*override*/ bool matches(DataStream* p) const; public: /*override*/ const char* getActionName() const { return getClassName(); } /*override*/ const char* getDescription() const { return QT_TRANSLATE_NOOP("Transformation", "Removes all broken streams."); } static const char* getClassName() { return QT_TRANSLATE_NOOP("Transformation", "Remove broken streams"); } }; class UnsupportedDataStreamRemover : public GenericRemover { /*override*/ bool matches(DataStream* p) const; public: /*override*/ const char* getActionName() const { return getClassName(); } /*override*/ const char* getDescription() const { return QT_TRANSLATE_NOOP("Transformation", "Removes all unsupported streams."); } static const char* getClassName() { return QT_TRANSLATE_NOOP("Transformation", "Remove unsupported streams"); } }; class TruncatedMpegDataStreamRemover : public GenericRemover { /*override*/ bool matches(DataStream* p) const; public: /*override*/ const char* getActionName() const { return getClassName(); } /*override*/ const char* getDescription() const { return QT_TRANSLATE_NOOP("Transformation", "Removes all truncated audio streams."); } static const char* getClassName() { return QT_TRANSLATE_NOOP("Transformation", "Remove truncated audio streams"); } }; class NullStreamRemover : public GenericRemover { /*override*/ bool matches(DataStream* p) const; public: /*override*/ const char* getActionName() const { return getClassName(); } /*override*/ const char* getDescription() const { return QT_TRANSLATE_NOOP("Transformation", "Removes all null streams."); } static const char* getClassName() { return QT_TRANSLATE_NOOP("Transformation", "Remove null streams"); } }; #if 0 class StreamRemover : public GenericRemover { /*override*/ bool matches(DataStream* p) const; public: /*override*/ const char* getActionName() const { return getClassName(); } /*override*/ const char* getDescription() const { return QT_TRANSLATE_NOOP("Transformation", "Removes all streams that are broken, truncated, unsupported or have some errors making them unusable."); } static const char* getClassName() { return "General cleanup"; } }; #endif class BrokenId3V2Remover : public GenericRemover { /*override*/ bool matches(DataStream* p) const; public: /*override*/ const char* getActionName() const { return getClassName(); } /*override*/ const char* getDescription() const { return QT_TRANSLATE_NOOP("Transformation", "Removes broken ID3V2 streams."); } static const char* getClassName() { return QT_TRANSLATE_NOOP("Transformation", "Remove broken ID3V2 streams"); } }; class UnsupportedId3V2Remover : public GenericRemover { /*override*/ bool matches(DataStream* p) const; public: /*override*/ const char* getActionName() const { return getClassName(); } /*override*/ const char* getDescription() const { return QT_TRANSLATE_NOOP("Transformation", "Removes unsupported ID3V2 streams."); } static const char* getClassName() { return QT_TRANSLATE_NOOP("Transformation", "Remove unsupported ID3V2 streams"); } }; class MultipleId3StreamRemover : public GenericRemover { /*override*/ bool matches(DataStream* p) const; std::set m_spStreamsToDiscard; void setupDiscarded(const Mp3Handler& h); public: /*override*/ const char* getActionName() const { return getClassName(); } /*override*/ const char* getDescription() const { return QT_TRANSLATE_NOOP("Transformation", "If a file has multiple ID3 streams it keeps only the last ID3V1 and the first ID3V2 stream."); } static const char* getClassName() { return QT_TRANSLATE_NOOP("Transformation", "Remove multiple ID3 streams"); } /*override*/ Transformation::Result apply(const Mp3Handler& h, const TransfConfig& cfg, const std::string& strOrigSrcName, std::string& strTempName) { setupDiscarded(h); return GenericRemover::apply(h, cfg, strOrigSrcName, strTempName); } }; class MismatchedXingRemover : public GenericRemover { /*override*/ bool matches(DataStream* p) const; std::set m_spStreamsToDiscard; void setupDiscarded(const Mp3Handler& h); public: /*override*/ const char* getActionName() const { return getClassName(); } /*override*/ const char* getDescription() const { return QT_TRANSLATE_NOOP("Transformation", "Sometimes the number of frames in an audio stream is different from the number of frames in a preceding Xing (or Lame) header, usually because the audio stream was damaged. It's probably best for the Xing header to be removed in this case. If the audio is VBR, you may want to try \"Repair VBR data\" first."); } static const char* getClassName() { return QT_TRANSLATE_NOOP("Transformation", "Remove mismatched Xing headers"); } /*override*/ Transformation::Result apply(const Mp3Handler& h, const TransfConfig& cfg, const std::string& strOrigSrcName, std::string& strTempName) { setupDiscarded(h); return GenericRemover::apply(h, cfg, strOrigSrcName, strTempName); } }; class Id3V1Remover : public GenericRemover { /*override*/ bool matches(DataStream* p) const; public: /*override*/ const char* getActionName() const { return getClassName(); } /*override*/ const char* getDescription() const { return QT_TRANSLATE_NOOP("Transformation", "Removes all ID3V1 streams."); } static const char* getClassName() { return QT_TRANSLATE_NOOP("Transformation", "Remove all ID3V1 streams"); } }; class ApeRemover : public GenericRemover { /*override*/ bool matches(DataStream* p) const; public: /*override*/ const char* getActionName() const { return getClassName(); } /*override*/ const char* getDescription() const { return QT_TRANSLATE_NOOP("Transformation", "Removes all APE streams."); } static const char* getClassName() { return QT_TRANSLATE_NOOP("Transformation", "Remove all APE streams"); } }; class NonAudioRemover : public GenericRemover { /*override*/ bool matches(DataStream* p) const; public: /*override*/ const char* getActionName() const { return getClassName(); } /*override*/ const char* getDescription() const { return QT_TRANSLATE_NOOP("Transformation", "Removes all non-audio streams."); } static const char* getClassName() { return QT_TRANSLATE_NOOP("Transformation", "Remove all non-audio streams"); } }; //=================================================================================================================== class TruncatedAudioPadder : public Transformation { public: /*override*/ Transformation::Result apply(const Mp3Handler&, const TransfConfig&, const std::string& strOrigSrcName, std::string& strTempName); /*override*/ const char* getActionName() const { return getClassName(); } /*override*/ const char* getDescription() const { return QT_TRANSLATE_NOOP("Transformation", "Pads truncated audio frames with 0 to the right. Its usefulness hasn't been determined yet (it might be quite low.)"); } static const char* getClassName() { return QT_TRANSLATE_NOOP("Transformation", "Pad truncated audio"); } }; //=================================================================================================================== class VbrRepairerBase : public Transformation { protected: enum { DONT_FORCE_REBUILD, FORCE_REBUILD }; Transformation::Result repair(const Mp3Handler&, const TransfConfig&, const std::string& strOrigSrcName, std::string& strTempName, bool bForceRebuild); }; class VbrRepairer : public VbrRepairerBase { public: /*override*/ Transformation::Result apply(const Mp3Handler&, const TransfConfig&, const std::string& strOrigSrcName, std::string& strTempName); /*override*/ const char* getActionName() const { return getClassName(); } /*override*/ const char* getDescription() const { return QT_TRANSLATE_NOOP("Transformation", "If a file contains VBR audio but doesn't have a Xing header, one such header is added. If a VBRI header exists, it is removed. If a Xing header exists but is determined to be incorrect, it is corrected or replaced. Only the first audio stream is considered; if a file contains more than one audio stream, this should be fixed first."); } static const char* getClassName() { return QT_TRANSLATE_NOOP("Transformation", "Repair VBR data"); } }; class VbrRebuilder : public VbrRepairerBase { public: /*override*/ Transformation::Result apply(const Mp3Handler&, const TransfConfig&, const std::string& strOrigSrcName, std::string& strTempName); /*override*/ const char* getActionName() const { return getClassName(); } /*override*/ const char* getDescription() const { return QT_TRANSLATE_NOOP("Transformation", "If a file contains VBR audio, any existing VBRI or Xing headers are removed and a new Xing header is created. Only the first audio stream is considered; if a file contains more than one audio stream, this should be fixed first."); } static const char* getClassName() { return QT_TRANSLATE_NOOP("Transformation", "Rebuild VBR data"); } }; #endif // #ifndef StructuralTransformationH MP3Diags-1.2.02/src/Mp3Diags.qrc0000644000175000001440000000501311713556174015065 0ustar ciobiusers images/logo.svg images/configure.svg images/decode.svg images/filter-folder.svg images/filter-note.svg images/go-next.svg images/go-previous.svg images/mode_album.svg images/mode_all.svg images/mode_file.svg images/scan.svg images/session.svg images/test.svg images/transform1.svg images/transform2.svg images/transform3.svg images/transform4.svg images/transform.svg images/normalize.svg images/file-rename.svg images/dialog-close.svg images/arrow-left-double.svg images/arrow-left.svg images/arrow-right-double.svg images/arrow-right.svg images/reset_settings.svg images/undo_settings.svg images/preferences-desktop-locale.svg images/save_log.svg images/save_notes.svg images/about.svg images/zoom-in.png images/assign.png images/remove_file.png images/assgn-all.svg images/assgn-none.svg images/assgn-some.svg images/change-case.svg images/document-save.svg images/discogs.png images/musicbrainz_logo1.svg images/duplicate_first.svg images/edit-paste.svg images/edit-undo.svg images/patterns.svg images/sort_asc.svg images/palette.svg images/debug.svg images/tag_editor.svg images/rename.svg images/export.svg images/va_sa.svg images/va_va.svg licences/gplv2.txt licences/gplv3.txt licences/lgplv3.txt licences/lgpl-2.1.txt licences/boost.txt licences/zlib.txt MP3Diags-1.2.02/src/ThreadRunnerDlgImpl.h0000644000175000001440000002140511701146154016763 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef ThreadRunnerDlgImplH #define ThreadRunnerDlgImplH #include #include #include #include #include #include #include "ui_ThreadRunner.h" typedef QList StrList; //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== /* This class is supposed to be derived from to create the actual thread class, then instantiated and passed to a ThreadRunnerDlgImpl, which will handle the thread. Usage: A) The user thread calls "pause()", "resume()" or "abort()" as needed. B) The class derived from PausableThread must declare a CompleteNotif at the beginning of its "run()" function. C) The class derived from PausableThread should do these things periodically in run(): 1) have something like this: "if (isAborted()) return;" 2) call "checkPause()"; this call blocks if the user called "pause()" 3) emit "stepChanged()", to allow progress to be displayed in the associated dialog D) The class derived from PausableThread must call "setSuccess(true)" on the CompleteNotif variable just before exiting, if the execution was successful. Notes: - the user thread shouldn't make any assumptions about how long it would take for a step in PausableThread, but this should not be long (less than 0.1 s) - in order to help performance it is possible that one more step in PausableThread will be executed after sending pause(); (functionally this shouldn't matter, because it's the same as if pause() was called just after PausableThread checked if it should pause) Another functionality this class provides is "sleep". The sleep functions in QThread are protected but occasionally useful, so they are exposed here. //ttt2 perhaps create separate class / free functions */ class PausableThread : public QThread { Q_OBJECT QMutex m_mutex; QWaitCondition m_waitCondition; bool m_bPaused; // set by pause() / resume() ; derived classes should check it periodically by calling isPaused() bool m_bAborted; // set by abort() ; derived classes should check it periodically by calling isAborted() protected: //bool isPaused() const { return m_bPaused; } // !!! no synch needed class CompleteNotif { bool m_bSuccess; PausableThread* m_pThread; public: CompleteNotif(PausableThread* pThread) : m_bSuccess(false), m_pThread(pThread) {} ~CompleteNotif() { m_pThread->notifComplete(m_bSuccess); } void setSuccess(bool bSuccess) { m_bSuccess = bSuccess; } }; public: PausableThread(/*QObject* pParent = 0*/); virtual ~PausableThread(); bool isAborted() const { return m_bAborted; } // !!! no synch needed void checkPause(); // if m_bPaused is set, it waits until resume() or abort() get called; otherwise it returns immediately //void emitStepChanged(const QString& qstrLabel1, const QString& qstrLabel2 = "", const QString& qstrLabel3 = "", const QString& qstrLabel4 = "") { emit stepChanged(qstrLabel1, qstrLabel2, qstrLabel3, qstrLabel4); } void emitStepChanged(const StrList& v, int nStep = -1) { emit stepChanged(v, nStep); } //public slots: //private slots: void pause(); void resume(); void abort(); using QThread::msleep; using QThread::sleep; using QThread::usleep; private: friend class ThreadRunnerDlgImpl; friend class PausableThread::CompleteNotif; void notifComplete(bool bSuccess) { emit completed(bSuccess); } signals: //void stepChanged(const QString& qstrLabel1, const QString& qstrLabel2, const QString& qstrLabel3, const QString& qstrLabel4); void stepChanged(const StrList& v, int nStep); // normally nStep should be -1, which causes the steps to be handled automatically; void completed(bool bSuccess); }; //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== /* Takes a PausableThread* on the constructor and sets itself as the owner (so the thread should be created with "new()") The thread must not be already started. It starts the thread and has buttons for pause/resume and abort. Closes itself when the thread completes. ---------- There are 2 events that can't be allow to work as usual: 1) clicking the top-right "Close" button 2) pressing ESC (which normally triggers the closing) Allowing the window ro close would kill the thread too and leave everything in an inconsistent state. So on_m_pAbortB_clicked() is called instead. To deal with Close, closeEvent() is overridden. To prevent ESC from closing the dialog, Qt::Key_Escape is set as a shortcut for on_m_pAbortB_clicked(). (Alternatively, keyPressEvent and keyReleaseEvent could be used, but it's more complicated.) */ class ThreadRunnerDlgImpl : public QDialog, private Ui::ThreadRunnerDlg { Q_OBJECT PausableThread* m_pThread; bool m_bSuccess; QTimer m_closeTimer; QTimer m_updateTimer; int m_nCounter; bool m_bShowCounter; time_t m_tRealBegin; time_t m_tRunningBegin; time_t m_tPauseBegin; StrList m_vStepInfo; bool m_bShowPauseAbort; bool m_bFirstTime; public: enum TruncatePos { TRUNCATE_BEGIN, TRUNCATE_MIDDLE, TRUNCATE_END }; private: /*override*/ void closeEvent(QCloseEvent* pEvent); // needed to sort of disable the close button; the event gets ignored and on_m_pAbortB_clicked() gets called instead #if 0 int m_nLastKey; /*override*/ void keyPressEvent(QKeyEvent* pEvent); /*override*/ void keyReleaseEvent(QKeyEvent* pEvent); #endif TruncatePos m_eTruncatePos; QString truncateLarge(const QString& s, int nKeepFirst = 0); // truncates strings that are too wide to display without resizing; uses m_eTruncatePos to determine where to truncate public: enum { HIDE_COUNTER, SHOW_COUNTER }; enum { HIDE_PAUSE_ABORT, SHOW_PAUSE_ABORT }; //ThreadRunnerDlgImpl(PausableThread* pThread, bool bShowCounter, QWidget* pParent = 0, Qt::WFlags fl = 0); ThreadRunnerDlgImpl(QWidget* pParent, Qt::WFlags flags, PausableThread* pThread, bool bShowCounter, TruncatePos eTruncatePos, bool bShowPauseAbort = true); ~ThreadRunnerDlgImpl(); /*$PUBLIC_FUNCTIONS$*/ //void startThread(); // !!! start automatically /*void pauseThread(); void resumeThread(); void abortThread();*/ public slots: /*$PUBLIC_SLOTS$*/ protected: /*$PROTECTED_FUNCTIONS$*/ protected slots: /*$PROTECTED_SLOTS$*/ private slots: void onThreadCompleted(bool bSuccess); void on_m_pPauseResumeB_clicked(); void on_m_pAbortB_clicked(); //void onStepChanged(const QString& qstrLabel1, const QString& qstrLabel2 = "", const QString& qstrLabel3 = "", const QString& qstrLabel4 = ""); void onStepChanged(const StrList& v, int nStep); void onCloseTimer(); void onUpdateTimer(); void onHelp(); }; #endif // #ifndef ThreadRunnerDlgImplH MP3Diags-1.2.02/src/StoredSettings.cpp0000644000175000001440000003720311710077573016440 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include "StoredSettings.h" #include "Helpers.h" using namespace std; //================================================================================================================================================ //================================================================================================================================================ //================================================================================================================================================ SessionSettings::SessionSettings(const string& strSessFile) { m_pSettings = new QSettings(convStr(strSessFile), QSettings::IniFormat); } SessionSettings::~SessionSettings() { delete m_pSettings; } void SessionSettings::saveDirs(const vector& vstrIncludedDirs, const vector& vstrExcludedDirs) { saveVector("folders/included", vstrIncludedDirs); saveVector("folders/excluded", vstrExcludedDirs); } // returns false if there were inconsistencies in the settings bool SessionSettings::loadDirs(vector& vstrIncludedDirs, vector& vstrExcludedDirs) const { bool bRes1 (true), bRes2 (true); { vector v; vstrIncludedDirs = loadVector("folders/included", bRes1); for (int i = 0, n = cSize(vstrIncludedDirs); i < n; ++i) { string s (vstrIncludedDirs[i]); if (QFileInfo(convStr(s)).isDir()) { v.push_back(s); } else { bRes1 = false; } } v.swap(vstrIncludedDirs); } { vector v; vstrExcludedDirs = loadVector("folders/excluded", bRes2); for (int i = 0, n = cSize(vstrExcludedDirs); i < n; ++i) { string s (vstrExcludedDirs[i]); if (QFileInfo(convStr(s)).isDir()) { v.push_back(s); } else { bRes2 = false; } } v.swap(vstrExcludedDirs); } return bRes1 && bRes2; } void SessionSettings::saveScanAtStartup(bool b) { m_pSettings->setValue("main/scanAtStartup", b); } bool SessionSettings::loadScanAtStartup() const { return m_pSettings->value("main/scanAtStartup", true).toBool(); } bool SessionSettings::sync() { m_pSettings->sync(); return QSettings::NoError == m_pSettings->status(); } //================================================================================================================================================ //================================================================================================================================================ //================================================================================================================================================ void SessionSettings::saveMainSettings(int nWidth, int nHeight, int nNotesGW0, int nNotesGW2, int nStrmsGW0, int nStrmsGW1, int nStrmsGW2, int nStrmsGW3, int nUnotesGW0, const QByteArray& stateMainSpl, const QByteArray& stateLwrSpl, int nIconSize, int nScanWidth) { m_pSettings->setValue("main/width", nWidth); m_pSettings->setValue("main/height", nHeight); m_pSettings->setValue("main/notesGWidth0", nNotesGW0); m_pSettings->setValue("main/notesGWidth2", nNotesGW2); m_pSettings->setValue("main/streamsGWidth0", nStrmsGW0); m_pSettings->setValue("main/streamsGWidth1", nStrmsGW1); m_pSettings->setValue("main/streamsGWidth2", nStrmsGW2); m_pSettings->setValue("main/streamsGWidth3", nStrmsGW3); m_pSettings->setValue("main/uniqueNotesGWidth0", nUnotesGW0); /*QList l (m_pMainSplitter->sizes()); for (QList::iterator it = l.begin(), end = l.end(); it != end; ++it) { qDebug("l %d", *it); }*/ m_pSettings->setValue("main/splitterState", stateMainSpl); m_pSettings->setValue("main/lowerSplitterState", stateLwrSpl); m_pSettings->setValue("main/iconSize", nIconSize); m_pSettings->setValue("main/scanWidth", nScanWidth); } void SessionSettings::loadMainSettings(int& nWidth, int& nHeight, int& nNotesGW0, int& nNotesGW2, int& nStrmsGW0, int& nStrmsGW1, int& nStrmsGW2, int& nStrmsGW3, int& nUnotesGW0, QByteArray& stateMainSpl, QByteArray& stateLwrSpl, int& nIconSize, int& nScanWidth) const { nWidth = m_pSettings->value("main/width").toInt(); nHeight = m_pSettings->value("main/height").toInt(); nNotesGW0 = m_pSettings->value("main/notesGWidth0").toInt(); nNotesGW2= m_pSettings->value("main/notesGWidth2").toInt(); nStrmsGW0 = m_pSettings->value("main/streamsGWidth0").toInt(); nStrmsGW1 = m_pSettings->value("main/streamsGWidth1").toInt(); nStrmsGW2 = m_pSettings->value("main/streamsGWidth2").toInt(); nStrmsGW3 = m_pSettings->value("main/streamsGWidth3").toInt(); nUnotesGW0 = m_pSettings->value("main/uniqueNotesGWidth0").toInt(); stateMainSpl = m_pSettings->value("main/splitterState").toByteArray(); stateLwrSpl = m_pSettings->value("main/lowerSplitterState").toByteArray(); nIconSize = m_pSettings->value("main/iconSize", 0).toInt(); nScanWidth = m_pSettings->value("main/scanWidth").toInt(); } void SessionSettings::saveMusicBrainzSettings(int nWidth, int nHeight) { m_pSettings->setValue("musicbrainz/width", nWidth); m_pSettings->setValue("musicbrainz/height", nHeight); } void SessionSettings::loadMusicBrainzSettings(int& nWidth, int& nHeight) const { nWidth = m_pSettings->value("musicbrainz/width").toInt(); nHeight = m_pSettings->value("musicbrainz/height").toInt(); } void SessionSettings::saveDebugSettings(int nWidth, int nHeight) { m_pSettings->setValue("debug/width", nWidth); m_pSettings->setValue("debug/height", nHeight); } void SessionSettings::loadDebugSettings(int& nWidth, int& nHeight) const { nWidth = m_pSettings->value("debug/width").toInt(); nHeight = m_pSettings->value("debug/height").toInt(); } void SessionSettings::saveExportSettings(int nWidth, int nHeight, bool bSortByShortNames, const std::string& strFile, bool bUseVisible, const std::string& strM3uRoot, const std::string& strM3uLocale) { m_pSettings->setValue("export/width", nWidth); m_pSettings->setValue("export/height", nHeight); m_pSettings->setValue("export/sortByShortNames", bSortByShortNames); m_pSettings->setValue("export/fileName", convStr(strFile)); m_pSettings->setValue("export/useVisible", bUseVisible); m_pSettings->setValue("export/m3uRoot", convStr(strM3uRoot)); m_pSettings->setValue("export/m3uLocale", convStr(strM3uLocale)); } void SessionSettings::loadExportSettings(int& nWidth, int& nHeight, bool& bSortByShortNames, std::string& strFile, bool& bUseVisible, std::string& strM3uRoot, std::string& strM3uLocale) const { nWidth = m_pSettings->value("export/width").toInt(); nHeight = m_pSettings->value("export/height").toInt(); bSortByShortNames = m_pSettings->value("export/sortByShortNames", false).toBool(); QString s; s = m_pSettings->value("export/fileName", "").toString(); strFile = convStr(s); bUseVisible = m_pSettings->value("export/useVisible", true).toBool(); s = m_pSettings->value("export/m3uRoot", "").toString(); strM3uRoot = convStr(s); s = m_pSettings->value("export/m3uLocale", "System").toString(); strM3uLocale = convStr(s); } void SessionSettings::saveDirFilterSettings(int nWidth, int nHeight) { m_pSettings->setValue("dirFilter/width", nWidth); m_pSettings->setValue("dirFilter/height", nHeight); } void SessionSettings::loadDirFilterSettings(int& nWidth, int& nHeight) const { nWidth = m_pSettings->value("dirFilter/width").toInt(); nHeight = m_pSettings->value("dirFilter/height").toInt(); } void SessionSettings::saveDiscogsSettings(int nWidth, int nHeight, int nStyleOption) { m_pSettings->setValue("discogs/width", nWidth); m_pSettings->setValue("discogs/height", nHeight); m_pSettings->setValue("discogs/styleOption", nStyleOption); } void SessionSettings::loadDiscogsSettings(int& nWidth, int& nHeight, int& nStyleOption) const { nWidth = m_pSettings->value("discogs/width").toInt(); nHeight = m_pSettings->value("discogs/height").toInt(); nStyleOption = m_pSettings->value("discogs/styleOption").toInt(); } void SessionSettings::saveRenamerSettings(int nWidth, int nHeight, int nSaButton, int nVaButton, bool bKeepOrig, bool bUnratedAsDuplicate) { m_pSettings->setValue("fileRenamer/width", nWidth); m_pSettings->setValue("fileRenamer/height", nHeight); m_pSettings->setValue("fileRenamer/saButton", nSaButton); m_pSettings->setValue("fileRenamer/vaButton", nVaButton); m_pSettings->setValue("fileRenamer/keepOriginal", bKeepOrig); m_pSettings->setValue("fileRenamer/unratedAsDuplicate", bUnratedAsDuplicate); } void SessionSettings::loadRenamerSettings(int& nWidth, int& nHeight, int& nSaButton, int& nVaButton, bool& bKeepOrig, bool& bUnratedAsDuplicate) const { nWidth = m_pSettings->value("fileRenamer/width").toInt(); nHeight = m_pSettings->value("fileRenamer/height").toInt(); nSaButton = m_pSettings->value("fileRenamer/saButton").toInt(); nVaButton = m_pSettings->value("fileRenamer/vaButton").toInt(); bKeepOrig = m_pSettings->value("fileRenamer/keepOriginal", false).toBool(); bUnratedAsDuplicate = m_pSettings->value("fileRenamer/unratedAsDuplicate", false).toBool(); } void SessionSettings::saveExternalToolSettings(int nWidth, int nHeight) { m_pSettings->setValue("externalTool/width", nWidth); m_pSettings->setValue("externalTool/height", nHeight); /*m_pSettings->remove("normalizer/width"); //ttt1 maybe enable; keeping both settings should be avoided, but it is useful if an older and a newer version use thesame INI. ttt0 see about normalization keeping its own settings rather than sharing m_pSettings->remove("normalizer/height");*/ } void SessionSettings::loadExternalToolSettings(int& nWidth, int& nHeight) const { nWidth = m_pSettings->value("externalTool/width").toInt(); nHeight = m_pSettings->value("externalTool/height").toInt(); if (0 == nWidth) { nWidth = m_pSettings->value("normalizer/width").toInt(); } if (0 == nHeight) { nHeight = m_pSettings->value("normalizer/height").toInt(); } } void SessionSettings::saveNoteFilterSettings(int nWidth, int nHeight) { m_pSettings->setValue("noteFilter/width", nWidth); m_pSettings->setValue("noteFilter/height", nHeight); } void SessionSettings::loadNoteFilterSettings(int& nWidth, int& nHeight) const { nWidth = m_pSettings->value("noteFilter/width").toInt(); nHeight = m_pSettings->value("noteFilter/height").toInt(); } void SessionSettings::saveRenamerPatternsSettings(int nWidth, int nHeight) { m_pSettings->setValue("fileRenamer/patterns/width", nWidth); m_pSettings->setValue("fileRenamer/patterns/height", nHeight); } void SessionSettings::loadRenamerPatternsSettings(int& nWidth, int& nHeight) const { nWidth = m_pSettings->value("fileRenamer/patterns/width").toInt(); nHeight = m_pSettings->value("fileRenamer/patterns/height").toInt(); } void SessionSettings::saveTagEdtSettings(int nWidth, int nHeight, int nArtistsCase, int nOthersCase) { m_pSettings->setValue("tagEditor/width", nWidth); m_pSettings->setValue("tagEditor/height", nHeight); m_pSettings->setValue("tagEditor/artistsCase", nArtistsCase); m_pSettings->setValue("tagEditor/othersCase", nOthersCase); } void SessionSettings::loadTagEdtSettings(int& nWidth, int& nHeight, int& nArtistCase, int& nOthersCase) const { nWidth = m_pSettings->value("tagEditor/width").toInt(); nHeight = m_pSettings->value("tagEditor/height").toInt(); nArtistCase = m_pSettings->value("tagEditor/artistsCase", -1).toInt(); nOthersCase = m_pSettings->value("tagEditor/othersCase", -1).toInt(); //splitterState = m_pSettings->value("tagEditor/splitterState").toByteArray(); } void SessionSettings::saveTagEdtPatternsSettings(int nWidth, int nHeight) { m_pSettings->setValue("tagEditor/patterns/width", nWidth); m_pSettings->setValue("tagEditor/patterns/height", nHeight); } void SessionSettings::loadTagEdtPatternsSettings(int& nWidth, int& nHeight) const { nWidth = m_pSettings->value("tagEditor/patterns/width").toInt(); nHeight = m_pSettings->value("tagEditor/patterns/height").toInt(); } void SessionSettings::saveVector(const string& strPath, const vector& v) { // not sure how beginReadArray() & Co handle error cases, so it seems safer this way (considering that the users may want to alter the config file manually) char bfr [50]; QString s (convStr(strPath)); m_pSettings->remove(s); int n (cSize(v)); m_pSettings->setValue(s + "/count", n); for (int i = 0; i < n; ++i) { sprintf(bfr, "/val%04d", i); m_pSettings->setValue(s + bfr, convStr(v[i])); } } // allows empty entries, but stops at the first missing entry, in which case sets bErr vector SessionSettings::loadVector(const string& strPath, bool& bErr) const { vector v; QString s (convStr(strPath)); bErr = false; int n (m_pSettings->value(s + "/count", 0).toInt()); char bfr [50]; for (int i = 0; i < n; ++i) { sprintf(bfr, "/val%04d", i); string strName (convStr(m_pSettings->value(s + bfr, "\2").toString())); if ("\2" == strName) { bErr = true; break; } v.push_back(strName); } return v; } //================================================================================================================================================ //================================================================================================================================================ //================================================================================================================================================ void SessionSettings::saveConfigSize(int nWidth, int nHeight) { m_pSettings->setValue("config/width", nWidth); m_pSettings->setValue("config/height", nHeight); } void SessionSettings::loadConfigSize(int& nWidth, int& nHeight) const { nWidth = m_pSettings->value("config/width").toInt(); nHeight = m_pSettings->value("config/height").toInt(); } MP3Diags-1.2.02/src/DataStream.cpp0000644000175000001440000002752711724614730015511 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include "DataStream.h" #include "Helpers.h" #include "MpegFrame.h" #include "MpegStream.h" #include "CommonData.h" using namespace std; using namespace pearl; //====================================================================================================== //====================================================================================================== /*override*/ void SimpleDataStream::copy(std::istream& in, std::ostream& out) { appendFilePart(in, out, m_pos, m_nSize); } UnknownDataStreamBase::UnknownDataStreamBase(int nIndex, NoteColl& notes, istream& in, streamoff nSize) : SimpleDataStream(nIndex, in.tellg(), nSize) { StreamStateRestorer rst (in); STRM_ASSERT (in); STRM_ASSERT (nSize > 0); streamoff nBfrSize (min(streamoff(BEGIN_SIZE), nSize)); read(in, m_begin, nBfrSize); streampos pos (m_pos); pos += nSize - 1; in.seekg(pos); char c; MP3_CHECK (1 == read(in, &c, 1), m_pos, unknTooShort, BadUnknownStream()); rst.setOk(); } /*override*/ std::string UnknownDataStreamBase::getInfo() const { return convStr(DataStream::tr("begins with: ")) + asHex(m_begin, min(int(BEGIN_SIZE), int(m_nSize))); } void UnknownDataStreamBase::append(const UnknownDataStreamBase& other) { streampos pos (m_pos); pos += m_nSize; STRM_ASSERT (pos == other.m_pos); if (m_nSize < BEGIN_SIZE) { int n (min(BEGIN_SIZE - m_nSize, other.m_nSize)); std::copy(other.m_begin, other.m_begin + n, m_begin + m_nSize); } m_nSize += other.m_nSize; } bool TruncatedMpegDataStream::hasSpace(std::streamoff nSize) const { return nSize < getExpectedSize() - getSize(); // "<" rather than "<="; if it gets full why would it be called "truncated" } TruncatedMpegDataStream::TruncatedMpegDataStream(MpegStream* pPrevMpegStream, int nIndex, NoteColl& notes, std::istream& in, std::streamoff nSize) : UnknownDataStreamBase(nIndex, notes, in, nSize), m_pFrame(0) { if (0 == pPrevMpegStream) { throw NotTruncatedMpegDataStream(); } in.seekg(m_pos); StreamStateRestorer rst (in); NoteColl tmpNotes (20); try { m_pFrame = new MpegFrameBase(tmpNotes, in); } catch (const MpegFrameBase::NotMpegFrame&) { throw NotTruncatedMpegDataStream(); } if (!pPrevMpegStream->isCompatible(*m_pFrame)) { delete m_pFrame; throw NotTruncatedMpegDataStream(); } streampos pos (m_pos); pos += nSize; in.seekg(pos); rst.setOk(); } TruncatedMpegDataStream::~TruncatedMpegDataStream() { delete m_pFrame; } /*override*/ std::string TruncatedMpegDataStream::getInfo() const { string s (decodeMpegFrame(m_begin, ", ")); return s; } int TruncatedMpegDataStream::getExpectedSize() const { return m_pFrame->getSize(); } NullDataStream::NullDataStream(int nIndex, NoteColl& notes, std::istream& in) : DataStream(nIndex), m_nSize(0) { StreamStateRestorer rst (in); streampos pos (in.tellg()); m_pos = pos; const int BFR_SIZE (256); char bfr [BFR_SIZE]; for (;;) { streamsize nRead (read(in, bfr, BFR_SIZE)); for (int i = 0; i < nRead; ++i) { if (0 != bfr[i]) { m_nSize += i; goto e1; } } m_nSize += nRead; if (nRead < BFR_SIZE) { break; } } e1: MP3_CHECK_T (m_nSize >= 16, m_pos, "Not a NULL stream. File too short.", NotNullStream()); pos += m_nSize; in.clear(); in.seekg(pos); rst.setOk(); } /*override*/ void NullDataStream::copy(std::istream&, std::ostream& out) { const int BFR_SIZE (256); char bfr [BFR_SIZE]; for (int i = 0; i < BFR_SIZE; ++i) { bfr[i] = 0; } streamoff n (m_nSize); while (n > BFR_SIZE) { out.write(bfr, BFR_SIZE); n -= BFR_SIZE; } out.write(bfr, n); CB_CHECK1 (out, WriteError()); } /*override*/ std::string NullDataStream::getInfo() const { return ""; } BrokenDataStream::BrokenDataStream(int nIndex, NoteColl& notes, std::istream& in, std::streamoff nSize, const char* szBaseName, const std::string& strInfo) : UnknownDataStreamBase(nIndex, notes, in, nSize), m_strName(std::string("Broken ") + szBaseName), // !!! doesn't need translation as getTranslatedDisplayName() does that m_strBaseName(szBaseName), m_strInfo(strInfo.empty() ? UnknownDataStreamBase::getInfo() : strInfo + "; " + UnknownDataStreamBase::getInfo()) { } /*override*/ QString BrokenDataStream::getTranslatedDisplayName() const { return DataStream::tr("Broken %1").arg(m_strBaseName.c_str()); } UnsupportedDataStream::UnsupportedDataStream(int nIndex, NoteColl& notes, istream& in, streamoff nSize, const char* szBaseName, const string& strInfo) : UnknownDataStreamBase(nIndex, notes, in, nSize), m_strName(string("Unsupported ") + szBaseName), // !!! doesn't need translation as getTranslatedDisplayName() does that m_strBaseName(szBaseName), m_strInfo(UnknownDataStreamBase::getInfo()) { if (!strInfo.empty()) { string s1 (strInfo); if (endsWith(s1, ".") || endsWith(s1, ";")) { s1.erase(s1.size() - 1); } m_strInfo = s1 + " - " + m_strInfo; } } /*override*/ QString UnsupportedDataStream::getTranslatedDisplayName() const { return DataStream::tr("Unsupported %1").arg(m_strBaseName.c_str()); } //====================================================================================================== //====================================================================================================== // /*static*/ /*bool TagReader::isTimeValid(const std::string& s) { } yyy-MM-ddTHH:mm:ss */ TagTimestamp::TagTimestamp(const std::string& strVal) { init(strVal); } TagTimestamp::TagTimestamp(const char* szVal /* = 0*/) { init(0 != szVal ? szVal : ""); } // if strVal is invalid it clears m_strVal, m_strYear and m_strDayMonth and throws InvalidTime void TagTimestamp::init(std::string s) { m_szVal[0] = 0; m_szYear[0] = 0; m_szDayMonth[0] = 0; int n (cSize(s)); if (n >= 11 && 'T' == s[10]) { s[10] = ' '; } static const char* szPatt ("****-**-** **:**:**X"); // 'X' is needed to avoid checking indexes CB_CHECK1 (n <= 19, InvalidTime()); CB_CHECK1 (0 == n || '*' != szPatt[n], InvalidTime()); for (int i = 0; i < n; ++i) { char c (szPatt[i]); if ('*' == c) { CB_CHECK1 (isdigit(s[i]), InvalidTime()); } else { CB_CHECK1 (szPatt[i] == s[i], InvalidTime()); } } //ttt2 check validity for years and dates strcpy(m_szVal, s.c_str()); if (n > 0) { strcpy(m_szYear, s.substr(0, 4).c_str()); if (n > 4) { strcpy(m_szDayMonth, ((n > 7 ? s.substr(8, 2) : "01") + s.substr(5, 2)).c_str()); } } } // text representation for each Feature /*static*/ const char* TagReader::getLabel(int n) { static const char* s_szTitle[] = { QT_TR_NOOP("Title"), QT_TR_NOOP("Artist"), QT_TR_NOOP("Track #"), QT_TR_NOOP("Time"), QT_TR_NOOP("Genre"), QT_TR_NOOP("Picture"), QT_TR_NOOP("Album"), QT_TR_NOOP("Rating"), QT_TR_NOOP("Composer"), QT_TR_NOOP("VA") }; //ttt2 can these be merged with values in TagReadPanel? CB_ASSERT (n >= 0 && n < LIST_END); return s_szTitle[n]; } // orig: { TITLE, ARTIST, TRACK_NUMBER, TIME, GENRE, IMAGE, ALBUM, RATING, COMPOSER, VARIOUS_ARTISTS, LIST_END }; /*static*/ int TagReader::FEATURE_ON_POS[] = { TRACK_NUMBER, ARTIST, TITLE, ALBUM, VARIOUS_ARTISTS, TIME, GENRE, IMAGE, RATING, COMPOSER }; //ttt2 perhaps move to CommonData and make configurable, as long as discarding some columns (e.g. composer) // static int INV_FEATURE_ON_POS[]; // the "inverse" of FEATURE_ON_POS: what Feature appears in a given position // /*static*/ int TagReader::INV_FEATURE_ON_POS[] = { 1, 0, 2, 3, 4, 5, 6, 7, 8 }; // INV_FEATURE_ON_POS ~ POS_OF_FEATURE /*static*/ int TagReader::POS_OF_FEATURE[LIST_END]; namespace { struct Init { Init() { for (int i = 0; i < TagReader::LIST_END; ++i) { TagReader::POS_OF_FEATURE[TagReader::FEATURE_ON_POS[i]] = i; } } }; Init init; } // returns the corresponding feature converted to a string; if it's not supported, it returns an empty string; for IMAGE an empty string regardless of a picture being present or not std::string TagReader::getValue(Feature f) const { switch (f) { case TITLE: if (getSupport(TITLE)) { return getTitle(); } else { return ""; } case ARTIST: if (getSupport(ARTIST)) { return getArtist(); } else { return ""; } case TRACK_NUMBER: if (getSupport(TRACK_NUMBER)) { return getTrackNumber(); } else { return ""; } case TIME: if (getSupport(TIME)) { return getTime().asString(); } else { return ""; } //!!! used only by XML export and in Mp3HandlerTagData::setUp(), so no need for translation case GENRE: if (getSupport(GENRE)) { return getGenre(); } else { return ""; } case IMAGE: return ""; case ALBUM: if (getSupport(ALBUM)) { return getAlbumName(); } else { return ""; } case RATING: if (getSupport(RATING)) { double r (getRating()); if (r >= 0) { char a [10]; sprintf(a, "%0.1f", r); return a; } } return ""; case COMPOSER: if (getSupport(COMPOSER)) { return getComposer(); } else { return ""; } case VARIOUS_ARTISTS: if (getSupport(VARIOUS_ARTISTS)) { int nVa (getVariousArtists()); string s; if (nVa & VA_ITUNES) { s += "i"; } if (nVa & VA_WMP) { s += "w"; } return s; } else { return ""; } default: return ""; } } /*static*/ string TagReader::getVarArtistsValue() // what getValue(VARIOUS_ARTISTS) returns for VA tags, based on global configuration { string s; const CommonData* pCommonData (getCommonData()); if (pCommonData->m_bItunesVarArtists) { s += "i"; } if (pCommonData->m_bWmpVarArtists) { s += "w"; } return s; } /*virtual*/ TagReader::~TagReader() { // qDebug("destroy TagReader at %p", this); } MP3Diags-1.2.02/src/ReadMe0000644000175000001440000000052611200232050014002 0ustar ciobiusersREAD THIS IN ORDER TO COMPILE THE Qt4 TEMPLATE ---------------------------------------------- Before compiling, check the Qt Options, go to Project->Project Options->C++ Support and open the Qt Options tab. Check that the Qt installation directory is correct for the Qt version you've chosen. ------------------ Andreas Pakulat July 2006 MP3Diags-1.2.02/src/FileEnum.h0000644000175000001440000001007211265050316014612 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef FileEnumH #define FileEnumH #include #include #include "Helpers.h" struct FileEnumerator { virtual ~FileEnumerator() {} virtual std::string next() = 0; // returns an empty string if there's nothing next virtual void reset() = 0; }; /* Returns a list with all the files in the "include directories", discarding the ones in the "exclude directories". If the directories have redundant information, an InvalidDirs is thrown. Here's how they should be: - a directory may be "included" iff it has no ancestors OR its closest ancestor is "excluded"; - a directory may be "excluded" iff it has ancestors AND its closest ancestor is "included"; - duplicates are not allowed; Assumes there are no "." or ".." in the paths, but doesn't check. // ttt2 perhaps check What is really on the disk is not important, in the sense that names of directories that don't exist or of files are ignored. Ending of directory names with the path separator is optional and it doesn't have to be consistent. */ class DirTreeEnumerator : public FileEnumerator { struct DirTreeEnumeratorImpl; DirTreeEnumeratorImpl* m_pImpl; NoDefaults m_noDefaults; public: DirTreeEnumerator(const std::vector& vstrIncludeDirs, const std::vector& vstrExcludeDirs); DirTreeEnumerator(); /*override*/ ~DirTreeEnumerator(); /*override*/ void reset(); void reset(const std::vector& vstrIncludeDirs, const std::vector& vstrExcludeDirs); /*override*/ std::string next(); // returns an empty string if there's nothing next bool isIncluded(const std::string& strFile); struct InvalidDirs {}; }; #if 0 class DirEnumerator : public FileEnumerator { struct DirEnumeratorImpl; DirEnumeratorImpl* m_pImpl; NoDefaults m_noDefaults; int m_nCrt; public: DirEnumerator(const std::string& strDir); ~DirEnumerator(); /*override*/ void reset() { m_nCrt = 0; } /*override*/ std::string next(); // returns an empty string if there's nothing next }; #endif class ListEnumerator : public FileEnumerator { //struct ListEnumeratorImpl; //ListEnumeratorImpl* m_pImpl; NoDefaults m_noDefaults; std::vector m_vstrFiles; int m_nCrt; public: ListEnumerator(const std::vector& vstrFiles); ListEnumerator(const std::string& strDir); /*override*/ ~ListEnumerator() {} /*override*/ void reset() { m_nCrt = 0; } /*override*/ std::string next(); // returns an empty string if there's nothing next }; #endif // #ifndef FileEnumH MP3Diags-1.2.02/src/TagEditorDlgImpl.h0000644000175000001440000002362611724340543016256 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef TagEditorDlgImplH #define TagEditorDlgImplH #include #include #include #include #include "ui_TagEditor.h" #include "CommonTypes.h" class CommonData; class QSettings; class QScrollArea; class TagWriter; class TagEditorDlgImpl; class TransfConfig; class QToolButton; namespace TagEditor { class CurrentAlbumModel : public QAbstractTableModel { Q_OBJECT TagEditorDlgImpl* m_pTagEditorDlgImpl; TagWriter* m_pTagWriter; const CommonData* m_pCommonData; public: //CurrentAlbumModel(CommonData* pCommonData); CurrentAlbumModel(TagEditorDlgImpl* pTagEditorDlgImpl); /*override*/ int rowCount(const QModelIndex&) const; /*override*/ int columnCount(const QModelIndex&) const; /*override*/ QVariant data(const QModelIndex&, int) const; /*override*/ QVariant headerData(int nSection, Qt::Orientation eOrientation, int nRole = Qt::DisplayRole) const; /*override*/ Qt::ItemFlags flags(const QModelIndex& index) const; /*override*/ bool setData(const QModelIndex& index, const QVariant& value, int nRole /* = Qt::EditRole*/); void emitLayoutChanged() { emit layoutChanged(); } }; class CurrentFileModel : public QAbstractTableModel { Q_OBJECT const TagEditorDlgImpl* m_pTagEditorDlgImpl; const TagWriter* m_pTagWriter; const CommonData* m_pCommonData; public: CurrentFileModel(const TagEditorDlgImpl* pTagEditorDlgImpl); /*override*/ int rowCount(const QModelIndex&) const; /*override*/ int columnCount(const QModelIndex&) const; /*override*/ QVariant data(const QModelIndex&, int) const; /*override*/ QVariant headerData(int nSection, Qt::Orientation eOrientation, int nRole = Qt::DisplayRole) const; void emitLayoutChanged() { emit layoutChanged(); } }; #if 0 class CurrentAlbumGDelegate : public QAbstractItemDelegate //class CurrentAlbumGDelegate : public QItemDelegate //class CurrentAlbumGDelegate : public MultiLineTvDelegate { Q_OBJECT public: //NotesGDelegate(CommonData* pCommonData, QObject* pParent) : QItemDelegate(pParent), m_pCommonData(pCommonData) {} NotesGDelegate(CommonData* pCommonData); /*override*/ void paint(QPainter* pPainter, const QStyleOptionViewItem& option, const QModelIndex& index) const; CommonData* m_pCommonData; }; #endif class CurrentFileDelegate : public QItemDelegate { Q_OBJECT protected: QTableView* m_pTableView; const CommonData* m_pCommonData; public: CurrentFileDelegate(QTableView* pTableView, const CommonData* pCommonData); /*override*/ void paint(QPainter* pPainter, const QStyleOptionViewItem& option, const QModelIndex& index) const; /*override*/ QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const; }; class CurrentAlbumDelegate : public QItemDelegate { Q_OBJECT protected: QTableView* m_pTableView; const TagEditorDlgImpl* m_pTagEditorDlgImpl; const TagWriter* m_pTagWriter; //const CommonData* m_pCommonData; mutable std::set m_spEditors; public: CurrentAlbumDelegate(QTableView* pTableView, TagEditorDlgImpl* pTagEditorDlgImpl); /*override*/ void paint(QPainter* pPainter, const QStyleOptionViewItem& option, const QModelIndex& index) const; // /*override*/ QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const; /*override*/ QWidget* createEditor(QWidget* pParent, const QStyleOptionViewItem&, const QModelIndex& index) const; bool closeEditor(); // closes the editor opened with F2, saving the data; returns false if there was some error and it couldn't close private slots: void onEditorDestroyed(QObject*); }; } // namespace TagEditor struct AssgnBtnWrp; class TagEditorDlgImpl : public QDialog, private Ui::TagEditorDlg { Q_OBJECT CommonData* m_pCommonData; void resizeTagEditor(); // resizes the widgets in the tag editor area: album table, image list; for file table and album table resizes the columns void resizeFile(); // resizes the "current file" grid; called by resizeTagEditor() and by onFileChanged(); QScrollArea* m_pImgScrollArea; TagWriter* m_pTagWriter; /*override*/ void resizeEvent(QResizeEvent* pEvent); /*override*/ void closeEvent(QCloseEvent* pEvent); void loadTagWriterInf(); void saveTagWriterInf(); AssgnBtnWrp* m_pAssgnBtnWrp; TagEditor::CurrentAlbumModel* m_pCurrentAlbumModel; TagEditor::CurrentFileModel* m_pCurrentFileModel; TagEditor::CurrentAlbumDelegate* m_pAlbumDel; bool m_bSectionMovedLock; void clearSelection(); void resizeIcons(); void selectMainCrt(); // selects the song that is current in the main window (to be called on the constructor); TransfConfig& m_transfConfig; enum SaveOpt { SAVED, DISCARDED, CANCELLED, PARTIALLY_SAVED }; enum { EXPLICIT, IMPLICIT }; SaveOpt save(bool bImplicitCall); // based on configuration, either just saves the tags or asks the user for confirmation; returns true iff all tags have been saved or if none needed saving; it should be followed by a reload(), either for the current or for the next/prev album; if bImplicitCall is false, the "ASK" option is turned into "SAVE"; bool closeEditor(/*const std::string& strAction*/) { return m_pAlbumDel->closeEditor(); } // closes the editor opened with F2, saving the data; returns false if there was some error and it couldn't close //ttt2 perhaps use strAction for a more personalize message /*override*/ bool eventFilter(QObject* pObj, QEvent* pEvent); std::vector > getSelFields() const; // returns the selceted fields, with the first elem as the song number and the second as the field index in TagReader::Feature (it converts columns to fields using TagReader::FEATURE_ON_POS); first column (file name) is ignored void eraseSelFields(); // erases the values in the selected fields bool m_bIsFastSaving; bool m_bIsSaving; bool m_bIsNavigating; bool& m_bDataSaved; bool m_bWaitingAlbumResize, m_bWaitingFileResize; // to avoid ColumnResizer being called lots of times void setupVarArtistsBtn(); void createPatternButtons(); std::vector m_vpPattButtons; TextCaseOptions m_eArtistsCase, m_eOthersCase; public: TagEditorDlgImpl(QWidget* pParent, CommonData* pCommonData, TransfConfig& transfConfig, bool& bDataSaved); // transfConfig is needed both to be able to instantiate the config dialog and for saving ID3V2 ~TagEditorDlgImpl(); /*$PUBLIC_FUNCTIONS$*/ std::string run(); TagWriter* getTagWriter() const { return m_pTagWriter; } const CommonData* getCommonData() const { return m_pCommonData; } using Ui::TagEditorDlg::m_pCurrentAlbumG; using Ui::TagEditorDlg::m_pCurrentFileG; void updateAssigned(); // updates the state of "assigned" fields and the assign button bool isSaving() const { return m_bIsSaving; } bool isNavigating() const { return m_bIsNavigating; } public slots: /*$PUBLIC_SLOTS$*/ protected: /*$PROTECTED_FUNCTIONS$*/ protected slots: /*$PROTECTED_SLOTS$*/ void on_m_pNextB_clicked(); void on_m_pPrevB_clicked(); void on_m_pQueryDiscogsB_clicked(); void on_m_pQueryMusicBrainzB_clicked(); void on_m_pEditPatternsB_clicked(); void on_m_pPaletteB_clicked(); void on_m_pToggleAssignedB_clicked(); void on_m_pReloadB_clicked(); void on_m_pVarArtistsB_clicked(); void on_m_pCaseB_clicked(); void on_m_pCopyFirstB_clicked(); void on_m_pSaveB_clicked(); void on_m_pPasteB_clicked(); void on_m_pSortB_clicked(); void on_m_pConfigB_clicked(); void on_m_pCloseB_clicked(); void onAlbSelChanged(); void onAlbCrtChanged(); void onFileSelSectionMoved(int nLogicalIndex, int nOldVisualIndex, int nNewVisualIndex); void onShow() { resizeTagEditor(); onShowPatternNote(); } void onAlbumChanged(/*bool bContentOnly*/); // the param was meant to determine if the selection should be kept (bContentOnly is true) or cleared (false); no longer needed, because the clearing the selection is done separately; //ttt2 perhaps put back, after restructuring the tag editor signals void onFileChanged(); void onImagesChanged(); // adds new ImageInfoPanelWdgImpl instances, connects assign button and calls resizeTagEditor() void onShowPatternNote(); void onResizeTagEditorDelayed(); void onResizeFileDelayed(); void onHelp(); void onVarArtistsUpdated(bool bVarArtists); void onPatternClicked(); }; #endif MP3Diags-1.2.02/src/MultiLineTvDelegate.cpp0000644000175000001440000002061011274256145017317 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include "MultiLineTvDelegate.h" #include "Helpers.h" //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== MultiLineTvDelegate::MultiLineTvDelegate(QTableView* pTableView/*, QObject* pParent = 0*/) : QItemDelegate(pTableView), m_pTableView(pTableView), m_nLineHeight(0), m_nAddPerLine(0) { CB_CHECK1 (0 != pTableView, std::runtime_error("NULL QTableView not allowed")); connect(pTableView->horizontalHeader(), SIGNAL(sectionResized(int, int, int)), pTableView, SLOT(resizeRowsToContents())); } QSize MultiLineTvDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const { if (!index.isValid()) { return QSize(); } if (0 == m_nLineHeight) { calibrate(option.fontMetrics, option.font); } //cout << option.rect.width() << "x" << option.rect.height() << " "; int nMargin (QApplication::style()->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1); //cout << "margin=" << nMargin << endl; int j (index.column()); int nColWidth (m_pTableView->horizontalHeader()->sectionSize(j)); /*if (4 == j) { qDebug("%s %d %d", m_pTableView->objectName().toUtf8().constData(), m_pTableView->verticalScrollBar()->maximum(), nColWidth); } QRect r (0, 0, nColWidth - 2*nMargin - 1, 10000); // !!! this "-1" is what's different from Qt's implementation (4.3); it is for the vertical line that delimitates the cells //ttt2 do a screen capture to be sure //ttt2 see if this is fixed in 4.4 2008.30.06 - apparently it's not fixed and the workaround no longer works */ // !!! 2009.04.17 - while working in most cases, the "1" above has this issue: Qt may toggle between showing a scrollbar and hiding it, doing this as many times per second as the CPU can handle; while the app is not frozen, what happens is quite annoying; so we'll just assume there's a scrollbar, until a proper solution is found; (it looks like Qt bug, though, because it can't make up its mind about showing a scrollbar; what Qt should do is try first to remove the scrollbar, see if it can fit everything and if not put back the scrollbar and don't try anything more); the downside is that in some cases more lines are requested than actually needed, but that happened before too (but to a lesser extent); //ttt2 perhaps at least don't do the same for all columns, normally only one is stretcheable int nSpace (1); //if (m_pTableView->verticalScrollBar()->isVisible()) if (1 == m_pTableView->verticalScrollBar()->maximum()) // the scrollbar gets 1 up for each line; the issues are around switching between no scrollbar and a scrollbar for 1 line, so hopefully this should take care of the issue; { nSpace += QApplication::style()->pixelMetric(QStyle::PM_ScrollBarExtent); if (0 != QApplication::style()->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents)) { nSpace += 2*QApplication::style()->pixelMetric(QStyle::PM_DefaultFrameWidth, 0, m_pTableView); //ttt2 Qt 4.4 (and below) - specific; in 4.5 there's a QStyle::PM_ScrollView_ScrollBarSpacing // see also ColumnResizer } } QRect r (0, 0, nColWidth - 2*nMargin - nSpace, 10000);//*/ //QWidget* p (m_pTableView->viewport()); //qDebug("%d %d / %d %d", p->width(), p->height(), m_pTableView->width(), m_pTableView->height()); //QSize s (m_pTableView->maximumViewportSize()); //qDebug("%s %d %d / %d %d", m_pTableView->objectName().toUtf8().constData(), m_pTableView->verticalScrollBar()->maximum(), m_pTableView->verticalScrollBar()->minimum(), m_pTableView->width(), m_pTableView->height()); //QString s (index.data(Qt::DisplayRole).toString()); //const char* sz (index.data(Qt::DisplayRole).toString().toUtf8().constData()); //if (s.startsWith("No normal")) //qDebug("#%s", ""); QSize res (option.fontMetrics.boundingRect(r, Qt::AlignTop | Qt::TextWordWrap, index.data(Qt::DisplayRole).toString()).size()); //cout << "at (" << index.row() << "," << index.column() << "): " << res.width() << "x" << res.height(); //if (index.column() == 4) //qDebug("sz %d %d / spc %d", res.width(), res.height(), option.fontMetrics.lineSpacing()); //if (s.startsWith("No normal")) //qDebug("sz %s %d %d", "", res.width(), res.height()); res.setWidth(nColWidth); //res.setHeight(res.height() + 6); /*if (1 == m_nAddPerLine) { res.setHeight(res.height() + res.height()/m_nLineHeight - 1); //res.setHeight(res.height() + res.height()/m_nLineHeight); // ??m_nAddPerLine } else if (2 == m_nAddPerLine) { res.setHeight(res.height() + 2*res.height()/m_nLineHeight - 2); } else */if (m_nAddPerLine > 0) { res.setHeight(res.height() + m_nAddPerLine*(res.height()/m_nLineHeight - 1)); } //if (s.startsWith("No normal")) //qDebug("adj sz %s %d %d", "", res.width(), res.height()); //cout << " => " << res.width() << "x" << res.height() << endl; //QSize res (fontMetrics().size(0, text())); return res; }//*/ //ttt2 visible transf - empty lines sometimes, after resizing; seems related to scrollbar detection //s.toUtf8().constData() /*void MultiLineTvDelegate::paint(QPainter* pPainter, const QStyleOptionViewItem& option, const QModelIndex& index) const { //cout << "draw: " << option.rect.width() << "x" << option.rect.height() << endl; //QString s (index.data(Qt::DisplayRole).toString()); //if (s.startsWith("No normal")) //qDebug("draw: %d x %d - %s ", option.rect.width(), option.rect.height(), ""); //int nCol (index.column()); //if (0 == nCol) { return QItemDelegate::paint(pPainter, option, index); } }*/ void MultiLineTvDelegate::calibrate(const QFontMetrics& fm, const QFont& /*f*/) const// sets up m_nLine and m_nTotalAdd { //set snHeights; //QString s; QRect r (0, 0, 300, 10000);//*/ QSize res (fm.boundingRect(r, Qt::AlignTop | Qt::TextWordWrap, "a").size()); m_nAddPerLine = res.height() - fm.lineSpacing(); //qDebug("%d ww", m_nAddPerLine); //CB_ASSERT (0 <= m_nAddPerLine && m_nAddPerLine <= 1); //ttt2 triggered by "Microsoft Sans Serif 7pt"; see if it can be fixed /*if (m_nAddPerLine < 0 || m_nAddPerLine > 1) { QString s (QString("%1, %2pt").arg(f.family()).arg(f.pointSize())); static QString s_qstrLastErrFont; if (s != s_qstrLastErrFont) { s_qstrLastErrFont = s; QMessageBox::warning(m_pTableView, "Warning", "The font \"" + s + "\" cannot be displayed correctly. You should go to the Configuration Dialog and choose another general font"); } }*/ m_nLineHeight = fm.lineSpacing(); //qDebug("%d", m_nLine); } MP3Diags-1.2.02/src/MainFormDlgImpl.h0000644000175000001440000001671211723502571016102 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef MainFormDlgImplH #define MainFormDlgImplH #include #include #include "ui_MainForm.h" #include "StructuralTransformation.h" #include "CommonData.h" #include "StoredSettings.h" class ModifInfoToolButton; class TagWriter; class QScrollArea; class QStackedLayout; class QHttp; class QHttpResponseHeader; class QPoint; struct FileEnumerator; /** @author ciobi */ class MainFormDlgImpl : public QDialog, private Ui::MainFormDlg { Q_OBJECT public: MainFormDlgImpl(const std::string& strSession, bool bDefaultForVisibleSessBtn); ~MainFormDlgImpl(); /*override*/ void resizeEvent(QResizeEvent* pEvent); /*override*/ void keyPressEvent(QKeyEvent* pEvent); /*override*/ void keyReleaseEvent(QKeyEvent* pEvent); /*override*/ void closeEvent(QCloseEvent* pEvent); enum CloseOption { EXIT, OPEN_SESS_DLG }; CloseOption run(); public slots: /*$PUBLIC_SLOTS$*/ void on_m_pScanB_clicked(); void on_m_pSessionsB_clicked(); void on_m_pNoteFilterB_clicked(); void on_m_pDirFilterB_clicked(); void on_m_pConfigB_clicked(); void on_m_pModeAllB_clicked(); void on_m_pModeAlbumB_clicked(); void on_m_pModeSongB_clicked(); void on_m_pPrevB_clicked(); void on_m_pNextB_clicked(); void on_m_pTransformB_clicked(); void on_m_pCustomTransform1B_clicked() { applyCustomTransf(0); } void on_m_pCustomTransform2B_clicked() { applyCustomTransf(1); } void on_m_pCustomTransform3B_clicked() { applyCustomTransf(2); } void on_m_pCustomTransform4B_clicked() { applyCustomTransf(3); } void on_m_pNormalizeB_clicked(); void on_m_pReloadB_clicked(); void on_m_pViewFileInfoB_clicked(); void on_m_pViewAllNotesB_clicked(); void on_m_pViewTagDetailsB_clicked(); void on_m_pAboutB_clicked(); void on_m_pTagEdtB_clicked(); void on_m_pRenameFilesB_clicked(); void on_m_pDebugB_clicked(); void on_m_pExportB_clicked(); void emptySlot() {} // needed to disable exiting on ESCAPE void onCrtFileChanged(); void testSlot(); void onShow(); void onShowAssert(); void onHelp(); void onMenuHovered(QAction*); void onNewVersionQueryFinished(int, bool); void onNewVersionQueryFinished2(); // needed because some bug in Qt4.3.1, 4.4.3 and some others, resulting in a segfault if onNewVersionQueryFinished() lasts 14 seconds or more void readResponseHeader(const QHttpResponseHeader&); void onMainGridRightClick(); private: void scan(FileEnumerator& fileEnum, bool bForce, std::deque vpExisting, int nKeepWhenUpdate); // a subset of vpExisting gets copied to vpDel in the m_pCommonData->mergeHandlerChanges() call; so if vpExisting is empty, vpDel will be empty too; if bForce is true, thw whole vpExisting is copied to vpDel; void scan(bool bForce); enum { IGNORE_SEL = 0, USE_SEL = 1 }; //enum { KEEP_FLT = 0, DISABLE_FLT = 1 }; //enum ReloadSrc { SEL, ALL_KEEP_FLT, ALL_REMOVE_FLT }; enum { DONT_FORCE = 0, FORCE = 1 }; //void reload(ReloadSrc eReloadSrc, bool bForce); void reload(bool bSelOnly, bool bForce); void fullReload(bool bForceReload); SessionSettings m_settings; int m_nLastKey; friend void trace(const std::string& s); CommonData* m_pCommonData; QWidget* m_pTagDetailsW; // this gets erased and recreated each time the current file changes QHBoxLayout* m_pTagDetailsLayout; QStackedLayout* m_pLowerHalfLayout; TransfConfig m_transfConfig; void saveIgnored(); void loadIgnored(); void saveCustomTransf(int k); void loadCustomTransf(int k); void saveVisibleTransf(); void loadVisibleTransf(); void saveExternalTools(); void loadExternalTools(); enum Subset { SELECTED, ALL, CURRENT }; void transform(std::vector& vpTransf, Subset eSubset); std::vector m_vpTransfButtons; void setTransfTooltip(int k); void setTransfTooltips() { for (int i = 0; i < CUSTOM_TRANSF_CNT; ++i) { setTransfTooltip(i); } } void applyCustomTransf(int k); ModifInfoToolButton* m_pModifNormalizeB; ModifInfoToolButton* m_pModifReloadB; ModifInfoToolButton* m_pModifRenameFilesB; std::string m_strSession; void resizeIcons(); void updateUi(const std::string& strCrt); // strCrt may be empty /*override*/ bool eventFilter(QObject* pObj, QEvent* pEvent); void initializeUi(); bool m_bShowMaximized; int m_nScanWidth; void showBackupWarn(); void showSelWarn(); void showRestartAfterCrashMsg(const QString& qstrText, const QString& qstrCloseBtn); void checkForNewVersion(); // returns immediately; when the request completes it will send a signal void fixCurrentNote(const QPoint& coords); void fixCurrentNoteOneFile(); void fixCurrentNoteAllFiles(int nCol); std::vector getFixes(const Note* pNote, const Mp3Handler* pHndl) const; // what might fix a note void showFixes(std::vector& vpTransf, Subset eSubset); void showExternalTools(); bool askConfirm(const std::deque& vpHandlers, const QString& qstrAction); QHttp* m_pQHttp; QString m_qstrNewVer; // needed by onNewVersionQueryFinished2() int m_nGlobalX, m_nGlobalY; // needed so fixCurrentNote() can be called on a timer, rather than directly (which seems to guarantee that tooltips are shown for menus in Linux; with a direct call it's sort of random; well, right-clicking on a non-current cell rather than left click followed by right click incresases the odds of the tooltips not being shown) signals: void tagEditorClosed(); }; class AssertSender : public QObject { Q_OBJECT //MainFormDlgImpl* m_pDlg; public: AssertSender(MainFormDlgImpl* pDlg) { connect(this, SIGNAL(showAssertMsg()), pDlg, SLOT(onShowAssert())); emit showAssertMsg(); } //void emitAssrt() { emit assrt(); } signals: void showAssertMsg(); }; #endif // #ifndef MainFormDlgImplH MP3Diags-1.2.02/src/NoteFilter.ui0000644000175000001440000000265311700345541015356 0ustar ciobiusers NoteFilterDlg 0 0 616 424 Note filter true 6 0 Qt::Horizontal 131 10 OK Cancel MP3Diags-1.2.02/src/LogModel.h0000644000175000001440000000511111200236210014572 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef LogModelH #define LogModelH #include //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== class CommonData; class QTableView; // all files class LogModel : public QAbstractTableModel { Q_OBJECT CommonData* m_pCommonData; QTableView* m_pLogG; public: LogModel(CommonData* pCommonData, QTableView* pLogG); /*override*/ int rowCount(const QModelIndex&) const; /*override*/ int columnCount(const QModelIndex&) const; /*override*/ QVariant data(const QModelIndex&, int nRole) const; /*override*/ QVariant headerData(int nSection, Qt::Orientation eOrientation, int nRole = Qt::DisplayRole) const; //void selectTopLeft(); private: //public slots: //void onLoGSelChanged(); }; #endif // #ifndef LogModelH MP3Diags-1.2.02/src/StreamsModel.h0000644000175000001440000000606611200236431015506 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef StreamsModelH #define StreamsModelH #include #include "MultiLineTvDelegate.h" //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== class CommonData; // current streams struct StreamsModel : public QAbstractTableModel { Q_OBJECT public: StreamsModel(CommonData* pCommonData); ~StreamsModel() {} /*override*/ int rowCount(const QModelIndex&) const; /*override*/ int columnCount(const QModelIndex&) const; /*override*/ QVariant data(const QModelIndex&, int) const; /*override*/ QVariant headerData(int nSection, Qt::Orientation eOrientation, int nRole = Qt::DisplayRole) const; CommonData* m_pCommonData; //void matchSelToMain(); void matchSelToNotes(); void emitLayoutChanged() { emit layoutChanged(); } public slots: void onStreamsGSelChanged(); }; class StreamsGDelegate : public MultiLineTvDelegate { Q_OBJECT public: //StreamsGDelegate(CommonData* pCommonData, QObject* pParent) : QItemDelegate(pParent), m_pCommonData(pCommonData) {} StreamsGDelegate(CommonData* pCommonData); /*override*/ void paint(QPainter* pPainter, const QStyleOptionViewItem& option, const QModelIndex& index) const; CommonData* m_pCommonData; }; #endif // #ifndef StreamsModelH MP3Diags-1.2.02/src/ColumnResizer.h0000644000175000001440000002353211200236023015702 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef ColumnResizerH #define ColumnResizerH #include class QTreeWidget; class QTableView; class QAbstractItemModel; /* ColumnResizer resizes the columns in a QTableView or a QTreeWidget, trying to maximize the number of cells that can be displayed without being truncated, while using no horizontal scrollbar. It can handle huge tables, because it doesn't read all the data in such a case. There is a threshold for the values in a column that are going to be considered (they are selected randomly). Of course, in some cases this may lead to less than optimal results, but that may happen anyway, because deciding which column is best to shrink and by how much is something quite subjective. A horizontal scrollbar may still be visible, if even after shrinking the columns need more space than the table has. To avoid such a thing, it is possible to manually assign fixed or minimum widths to various columns. This is done in a TableWidthInterface-derived object, which is what a ColumnResizer works with (it doesn't interact directly with QTableView or QTreeWidget). ColumnResizer doesn't really have an interface that the user calls. It does everything on its constructor. Note that for this to work correctly, all layouts must be applied before calling ColumnResizer, which may require a timer. See SimpleQTableViewWidthInterface for more details. The optional parameter bConsistentResults that is passed to ColumnResizer can be important if other parts of the current program use rand(). If there are more rows than nMaxRows, this triggers the pseudorandom selection of elements. If we want the same data to produce the same result, srand() must be called so the pseudorandom process always gives the same numbers. Calling srand() is something that other functions might not appreciate, which is why bConsistentResults is false by default. (A future version might use its own pseudorandom generator, which won't interfere with srand().) Usage sample: SimpleQTableViewWidthInterface intf1 (*tblView); intf1.setMinWidth(0, 50); intf1.setFixedWidth(2, 60); ColumnResizer rsz1 (intf1); */ //====================================================================================================================== //====================================================================================================================== //====================================================================================================================== class TableWidthInterface { protected: //std::vector m_vdFractionLimit; // fraction of the total table width (? min/max) std::vector m_vbFixedWidth; // if this is set, the width should be exactly getMinWidth() std::vector m_vnMinWidth; std::vector m_vnHdrWidth; public: TableWidthInterface(int nCols) { m_vbFixedWidth.resize(nCols); m_vnMinWidth.resize(nCols); m_vnHdrWidth.resize(nCols); } virtual ~TableWidthInterface() {} virtual int getTableWidth() const = 0; // space available for data columns virtual int getRowCount() const = 0; virtual int getColumnCount() const = 0; virtual int getRequestedWidth(int nRow, int nCol) const = 0; virtual bool isHidden(int nCol) const = 0; void setMinWidth(int nCol, int nLim) { m_vnMinWidth.at(nCol) = nLim; } void setFixedWidth(int nCol, int nLim) { m_vbFixedWidth.at(nCol) = true; m_vnMinWidth.at(nCol) = nLim; } bool hasFixedWidth(int nCol) const { return m_vbFixedWidth.at(nCol); } int getMinWidthDataHdr(int nCol) const // the minimum width required by the header or set explicitely { return std::max(m_vnMinWidth.at(nCol), m_vnHdrWidth.at(nCol)); } int getMinWidth(int nCol) const { return m_vnMinWidth.at(nCol); } virtual void setWidth(int nCol, int nWidth) = 0; // sets the column width for the underlying widget }; /* !!! Note that this class should be used after the table got displayed. There seems to be an issue in Qt 4.3 (and perhaps others), causing "resizeEvent()" to not get called after all the layouts are applied. As a result, incorrect sizes and scrollbars get used. The workaround is to use QTimer to do the calculations after laying out was done: "QTimer::singleShot(1, this, SLOT(onResizeTimer()));", where onResizeTimer does a table resizing. */ class SimpleQTableViewWidthInterface : public TableWidthInterface { std::vector m_vbBold; // bold is defined on a "per-column" basis; another class is needed to handle the "per-cell" case QTableView& m_tbl; QAbstractItemModel* m_pModel; /*override*/ int getTableWidth() const; // space available for data columns /*override*/ int getRowCount() const; /*override*/ int getColumnCount() const; /*override*/ int getRequestedWidth(int nRow, int nCol) const; /*override*/ bool isHidden(int nCol) const; /*override*/ void setWidth(int nCol, int nWidth); public: SimpleQTableViewWidthInterface(QTableView& tbl); void setBold(int nCol, bool bVal = true) { m_vbBold.at(nCol) = bVal; } }; /* !!! Note that this class should be used after the table got displayed. There seems to be an issue in Qt 4.3 (and perhaps others), causing "resizeEvent()" to not get called after all the layouts are applied. As a result, incorrect sizes and scrollbars get used. The workaround is to use QTimer to do the calculations after laying out was done: "QTimer::singleShot(1, this, SLOT(onResizeTimer()));", where onResizeTimer does a table resizing. */ class SimpleQTreeWidgetWidthInterface : public TableWidthInterface { std::vector m_vbBold; // bold is defined on a "per-column" basis; another class is needed to handle the "per-cell" case //ttt2 see if "bold" can actually be done QTreeWidget& m_tbl; /*override*/ int getTableWidth() const; // space available for data columns /*override*/ int getRowCount() const; /*override*/ int getColumnCount() const; /*override*/ int getRequestedWidth(int nRow, int nCol) const; /*override*/ bool isHidden(int nCol) const; /*override*/ void setWidth(int nCol, int nWidth); public: SimpleQTreeWidgetWidthInterface(QTreeWidget& tbl); void setBold(int nCol, bool bVal = true) { m_vbBold.at(nCol) = bVal; } }; /* nMaxRows sets a limit to how many values are considered for a column; useful because going through all of them may be too costly with big tables; if bFill is set, the columns get widened more than needed, so the whole area of the table gets covered (fixed-width columns don't get touched, though) */ class ColumnResizer { TableWidthInterface& m_intf; std::vector > m_vvAllWidths; int getFitCount(int nCol, int nWidth) const; // how many rows from a column can be shown completely for a given size struct ColInfo { int m_nLargeAvg; double m_dLargeDev; int m_nMinWidth; int m_nWidth; int m_nAskSmall; double m_dPrioSmall; // usually between 0 and 1, but may be higher for narrow columns int m_nAskLarge; double m_dPrioLarge; // between 0 and 1 }; void autoSize(bool bFill); int m_nMaxRows; bool m_bConsistentResults; int m_nTotalRows; int m_nUsedRows; int m_nCols; int m_nFixedWidthCount; std::vector m_vColInfo; int m_nTotalWidth; int m_nTotalMax; int m_nTableWidth; void readAllWidths(); void computeColInfo(); void setUpMaxWidths(); void setUpAvgWidths(); void setUpMinWidths(); void distributeAskSmall(); void distributeAskLarge(); void distributeRemaining(); // several pixels might remain undistributed after distributeAskSmall() and distributeAskLarge() because of rounding errors; they are dealt with here; void setWidths(); void printWidths(const char* szLabel/*, std::ostream& out*/); void printCellInfo(const char* szLabel/*, std::ostream& out*/); class SequenceGen { std::vector m_vn; int m_nCrt; double rnd() const; public: SequenceGen(int nTotalRows, int nTargetRows); int getNext() { return m_vn.at(m_nCrt++); } }; public: enum { DONT_FILL, FILL }; enum { INCONSISTENT_RESULTS, CONSISTENT_RESULTS }; ColumnResizer(TableWidthInterface& intf, int nMaxRows = 80, bool bFill = false, bool bConsistentResults = false); // resizing is achieved by just instantiating ColumnResizer }; #endif // #ifndef ColumnResizerH MP3Diags-1.2.02/src/MusicBrainzDownloader.cpp0000644000175000001440000005027411724661233017724 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #ifndef WIN32 #include #else #include #endif #include #include "MusicBrainzDownloader.h" #include "Helpers.h" #include "SimpleSaxHandler.h" #include "StoredSettings.h" using namespace std; using namespace pearl; #if 1 using namespace MusicBrainz; namespace MusicBrainz { /* metadata release-list release track-list */ struct SearchXmlHandler : public SimpleSaxHandler { SearchXmlHandler(MusicBrainzDownloader& dlg) : SimpleSaxHandler("metadata"), m_dlg(dlg) { Node& meta (getRoot()); meta.onStart = &SearchXmlHandler::onMetaStart; Node& relList (makeNode(meta, "release-list")); Node& rel (makeNode(relList, "release")); rel.onStart = &SearchXmlHandler::onRelStart; Node& trackList (makeNode(rel, "track-list")); trackList.onStart = &SearchXmlHandler::onTrackListStart; } private: MusicBrainzDownloader& m_dlg; void onMetaStart(const QXmlAttributes& /*attrs*/) { m_dlg.m_nLastLoadedPage = 0; } void onRelStart(const QXmlAttributes& attrs) { m_dlg.m_vAlbums.push_back(MusicBrainzAlbumInfo()); m_dlg.m_vAlbums.back().m_strId = convStr(attrs.value("id")); } void onTrackListStart(const QXmlAttributes& attrs) { m_dlg.m_vAlbums.back().m_nTrackCount = attrs.value("count").toInt(); } }; /* metadata release title asin artist name release-event-list event track-list track title artist name relation-list relation */ struct AlbumXmlHandler : public SimpleSaxHandler { AlbumXmlHandler(MusicBrainzAlbumInfo& albumInfo) : SimpleSaxHandler("metadata"), m_albumInfo(albumInfo), m_bTargetIsUrl(false) { Node& meta (getRoot()); meta.onEnd = &AlbumXmlHandler::onMetaEnd; Node& rel (makeNode(meta, "release")); rel.onStart = &AlbumXmlHandler::onRelStart; Node& albTitle (makeNode(rel, "title")); albTitle.onChar = &AlbumXmlHandler::onAlbTitleChar; Node& asin (makeNode(rel, "asin")); asin.onChar = &AlbumXmlHandler::onAsinChar; Node& albArtist (makeNode(rel, "artist")); Node& albArtistName (makeNode(albArtist, "name")); albArtistName.onChar = &AlbumXmlHandler::onAlbArtistNameChar; Node& albRelEvents (makeNode(rel, "release-event-list")); Node& albEvent (makeNode(albRelEvents, "event")); albEvent.onStart = &AlbumXmlHandler::onAlbEventStart; Node& albTrackList (makeNode(rel, "track-list")); Node& track (makeNode(albTrackList, "track")); track.onStart = &AlbumXmlHandler::onTrackStart; Node& trackTitle (makeNode(track, "title")); trackTitle.onChar = &AlbumXmlHandler::onTrackTitleChar; Node& trackArtist (makeNode(track, "artist")); Node& trackArtistName (makeNode(trackArtist, "name")); trackArtistName.onChar = &AlbumXmlHandler::onTrackArtistName; Node& relationList (makeNode(rel, "relation-list")); relationList.onStart = &AlbumXmlHandler::onRelationListStart; Node& relation (makeNode(relationList, "relation")); relation.onStart = &AlbumXmlHandler::onRelationStart; m_albumInfo.m_eVarArtists = AlbumInfo::VA_SINGLE; } private: MusicBrainzAlbumInfo& m_albumInfo; bool m_bTargetIsUrl; void onRelStart(const QXmlAttributes& attrs) { CB_ASSERT (m_albumInfo.m_strId == convStr(attrs.value("id"))); } void onAlbEventStart(const QXmlAttributes& attrs) { string strDate (convStr(attrs.value("date"))); if (!strDate.empty()) { m_albumInfo.m_strReleased = m_albumInfo.m_strReleased.empty() ? strDate : min(m_albumInfo.m_strReleased, strDate); } string strFormat (convStr(attrs.value("format"))); if (!strFormat.empty() && string::npos == m_albumInfo.m_strFormat.find(strFormat)) { addIfMissing(m_albumInfo.m_strFormat, strFormat); } } void onTrackStart(const QXmlAttributes&) { m_albumInfo.m_vTracks.push_back(TrackInfo()); char a [10]; sprintf(a, "%d", cSize(m_albumInfo.m_vTracks)); m_albumInfo.m_vTracks.back().m_strPos = a; } void onRelationListStart(const QXmlAttributes& attrs) { m_bTargetIsUrl = "Url" == attrs.value("target-type"); } void onRelationStart(const QXmlAttributes& attrs) { if (m_bTargetIsUrl) { QString qstrType (attrs.value("type")); if ("AmazonAsin" == qstrType) { m_albumInfo.m_strAmazonLink = convStr(attrs.value("target")); } else if ("CoverArtLink" == qstrType) { string strUrl (convStr(attrs.value("target"))); if (beginsWith(strUrl, "http://")) { m_albumInfo.m_vstrImageNames.push_back(strUrl); } else { //ttt2 perhaps tell the user qDebug("Unsupported image link"); } } } } void onMetaEnd() { m_albumInfo.m_vpImages.resize(m_albumInfo.m_vstrImageNames.size()); m_albumInfo.m_vstrImageInfo.resize(m_albumInfo.m_vstrImageNames.size()); if (m_albumInfo.m_strAmazonLink.empty() && !m_albumInfo.m_strAsin.empty()) { m_albumInfo.m_strAmazonLink = "http://www.amazon.com/gp/product/" + m_albumInfo.m_strAsin; } for (int i = 0, n = cSize(m_albumInfo.m_vTracks); i < n; ++i) { TrackInfo& t (m_albumInfo.m_vTracks[i]); addList(t.m_strArtist, m_albumInfo.m_strArtist); } } void onAlbTitleChar(const string& s) { m_albumInfo.m_strTitle = s; } void onAsinChar(const string& s) { m_albumInfo.m_strAsin = s; m_albumInfo.m_vstrImageNames.push_back("http://images.amazon.com/images/P/" + s + ".01.LZZZZZZZ.jpg"); // ttt2 "01" is country code for US, perhaps try others //ttt2 perhaps check for duplicates } void onAlbArtistNameChar(const string& s) { if (0 == convStr(s).compare("VaRiOuS Artists", Qt::CaseInsensitive)) { m_albumInfo.m_eVarArtists = AlbumInfo::VA_VARIOUS; } else { m_albumInfo.m_strArtist = s; } } void onTrackTitleChar(const string& s) { m_albumInfo.m_vTracks.back().m_strTitle = s; } void onTrackArtistName(const string& s) { m_albumInfo.m_vTracks.back().m_strArtist = s; } }; /*override*/ void MusicBrainzAlbumInfo::copyTo(AlbumInfo& dest) { dest.m_strTitle = m_strTitle; //dest.m_strArtist = m_strArtist; //dest.m_strComposer; // !!! missing //dest.m_strFormat = m_strFormat; // CD, tape, ... //dest.m_strGenre = m_strGenre; // !!! missing dest.m_strReleased = m_strReleased; //dest.m_strNotes; // !!! missing dest.m_vTracks = m_vTracks; dest.m_eVarArtists = m_eVarArtists; dest.m_strSourceName = MusicBrainzDownloader::SOURCE_NAME; // Discogs, MusicBrainz, ... ; needed by MainFormDlgImpl; //dest.m_imageInfo; // !!! not set } } // namespace MusicBrainz //============================================================================================================================= //============================================================================================================================= //============================================================================================================================= /*override*/ void MusicBrainzDownloader::saveSize() { m_settings.saveMusicBrainzSettings(width(), height()); } /*static*/ const char* MusicBrainzDownloader::SOURCE_NAME ("MusicBrainz"); MusicBrainzDownloader::MusicBrainzDownloader(QWidget* pParent, SessionSettings& settings, bool bSaveResults) : AlbumInfoDownloaderDlgImpl(pParent, settings, bSaveResults), m_nLastReqTime(0) { setWindowTitle(tr("Download album data from MusicBrainz.org")); int nWidth, nHeight; m_settings.loadMusicBrainzSettings(nWidth, nHeight); if (nWidth > 400 && nHeight > 400) { resize(nWidth, nHeight); } m_pViewAtAmazonL->setText(getAmazonText()); m_pGenreE->hide(); m_pGenreL->hide(); m_pAlbumNotesM->hide(); m_pVolumeL->hide(); m_pVolumeCbB->hide(); m_pStyleL->hide(); m_pStyleCbB->hide(); m_pImgSizeL->setMinimumHeight(m_pImgSizeL->height()*2); m_pQHttp->setHost("musicbrainz.org"); m_pImageQHttp = new QHttp (this); m_pModel = new WebDwnldModel(*this, *m_pTrackListG); // !!! in a way these would make sense to be in the base constructor, but that would cause calls to pure virtual methods m_pTrackListG->setModel(m_pModel); connect(m_pImageQHttp, SIGNAL(requestFinished(int, bool)), this, SLOT(onRequestFinished(int, bool))); connect(m_pSearchB, SIGNAL(clicked()), this, SLOT(on_m_pSearchB_clicked())); connect(m_pViewAtAmazonL, SIGNAL(linkActivated(const QString&)), this, SLOT(onAmazonLinkActivated(const QString&))); } MusicBrainzDownloader::~MusicBrainzDownloader() { resetNavigation(); // !!! not in base class, because it calls virtual method resetNavigation() clear(); } void MusicBrainzDownloader::clear() { LAST_STEP("MusicBrainzDownloader::clear"); clearPtrContainer(m_vpImages); m_vAlbums.clear(); } /*override*/ bool MusicBrainzDownloader::initSearch(const std::string& strArtist, const std::string& strAlbum) { LAST_STEP("MusicBrainzDownloader::initSearch"); m_pSrchArtistE->setText(convStr((removeParentheses(strArtist)))); m_pSrchAlbumE->setText(convStr((removeParentheses(strAlbum)))); return !strArtist.empty() || !strAlbum.empty(); } /*override*/ std::string MusicBrainzDownloader::createQuery() { LAST_STEP("MusicBrainzDownloader::createQuery"); string s ("/ws/1/release/?type=xml&artist=" + replaceSymbols(convStr(m_pSrchArtistE->text())) + "&title=" + replaceSymbols(convStr(m_pSrchAlbumE->text()))); //qDebug("qry: %s", s.c_str()); if (m_pMatchCountCkB->isChecked()) { s += convStr(QString("&count=%1").arg(m_nExpectedTracks)); } /*for (string::size_type i = 0; i < s.size(); ++i) { if (' ' == s[i]) { s[i] = '+'; } }*/ //s = "/ws/1/release/?type=xml&artist=Beatles&title=Help"; return s; } void MusicBrainzDownloader::delay() { long long t (getTime()); long long nDiff (t - m_nLastReqTime); //qDebug("crt: %lld, prev: %lld, diff: %lld", t, m_nLastReqTime, t - m_nLastReqTime); if (nDiff < 1000) { if (nDiff < 0) { nDiff = 0; } int nWait (999 - (int)nDiff); //qDebug(" wait: %d", nWait); addNote(tr("waiting %1ms").arg(nWait + 100)); //ttt1 perhaps use PausableThread::usleep() #ifndef WIN32 timespec ts; ts.tv_sec = 0; ts.tv_nsec = 100000000; // 0.1s, to be sure nanosleep(&ts, 0); ts.tv_nsec = nWait*1000000; nanosleep(&ts, 0); #else Sleep(nWait + 100); #endif //qDebug("waiting %d", nWait); } m_nLastReqTime = t; } long long MusicBrainzDownloader::getTime() // time in milliseconds { QDateTime t (QDateTime::currentDateTime()); long long nRes (t.toTime_t()); //ttt3 32bit nRes *= 1000; nRes += t.time().msec(); return nRes; #if 0 qDebug("t1 %lld", nRes); #ifndef WIN32 timeval tv; gettimeofday(&tv, 0); qDebug("t2 %lld", tv.tv_sec*1000LL + tv.tv_usec/1000); return tv.tv_sec*1000LL + tv.tv_usec/1000; #else QDateTime t (QDateTime::currentDateTime()); return t.toTime_t(); //ttt3 32bit #endif #endif } //========================================================================================================================== //========================================================================================================================== //========================================================================================================================== void MusicBrainzDownloader::on_m_pSearchB_clicked() { LAST_STEP("MusicBrainzDownloader::on_m_pSearchB_clicked"); clear(); search(); } //========================================================================================================================== //========================================================================================================================== //========================================================================================================================== void MusicBrainzDownloader::loadNextPage() { LAST_STEP("MusicBrainzDownloader::loadNextPage"); CB_ASSERT (!m_pQHttp->hasPendingRequests()); CB_ASSERT (!m_pImageQHttp->hasPendingRequests()); ++m_nLastLoadedPage; CB_ASSERT (m_nLastLoadedPage <= m_nTotalPages - 1); //m_eState = NEXT; setWaiting(SEARCH); //char a [20]; //sprintf(a, "&page=%d", m_nLastLoadedPage + 1); //string s (m_strQuery + a); QHttpRequestHeader header ("GET", convStr(m_strQuery)); //header.setValue("Host", "www.musicbrainz.org"); header.setValue("Host", "musicbrainz.org"); //header.setValue("Accept-Encoding", "gzip"); delay(); //qDebug("--------------\npath %s", header.path().toUtf8().constData()); //qDebug("qry %s", m_strQuery.c_str()); m_pQHttp->request(header); //cout << "sent search " << m_pQHttp->request(header) << " for page " << (m_nLastLoadedPage + 1) << endl; } //ttt2 see if it is possible for a track to have its own genre QString MusicBrainzDownloader::getAmazonText() const { LAST_STEP("MusicBrainzDownloader::getAmazonText"); if (m_nCrtAlbum < 0 || m_nCrtAlbum >= cSize(m_vAlbums)) { return AlbumInfoDownloaderDlgImpl::tr(NOT_FOUND_AT_AMAZON); } const MusicBrainzAlbumInfo& album (m_vAlbums[m_nCrtAlbum]); if (album.m_strAmazonLink.empty()) { return AlbumInfoDownloaderDlgImpl::tr(NOT_FOUND_AT_AMAZON); } else { return tr("view at amazon.com").arg(album.m_strAmazonLink.c_str()); } } void MusicBrainzDownloader::onAmazonLinkActivated(const QString& qstrLink) // !!! it's possible to set openExternalLinks on a QLabel to open links automatically, but this leaves an ugly frame around the text, with it right side missing; also, manual handling is needed to open a built-in browser; { LAST_STEP("MusicBrainzDownloader::onAmazonLinkActivated"); m_pViewAtAmazonL->setText("qq"); // !!! the text needs to CHANGE to make the frame disappear m_pViewAtAmazonL->setText(getAmazonText()); QDesktopServices::openUrl(qstrLink); } void MusicBrainzDownloader::reloadGui() { LAST_STEP("MusicBrainzDownloader::reloadGui"); AlbumInfoDownloaderDlgImpl::reloadGui(); m_pViewAtAmazonL->setText(getAmazonText()); } void MusicBrainzDownloader::requestAlbum(int nAlbum) { LAST_STEP("MusicBrainzDownloader::requestAlbum"); //CB_ASSERT (!m_pQHttp->hasPendingRequests() && !m_pImageQHttp->hasPendingRequests()); // ttt1 triggered: https://sourceforge.net/apps/mantisbt/mp3diags/view.php?id=36 ?? perhaps might happen when MB returns errors ; see also DiscogsDownloader::requestAlbum CB_ASSERT (!m_pQHttp->hasPendingRequests()); CB_ASSERT (!m_pImageQHttp->hasPendingRequests()); m_nLoadingAlbum = nAlbum; setWaiting(ALBUM); //string s ("/release/" + m_vAlbums[nAlbum].m_strId + "?f=xml&api_key=f51e9c8f6c"); string s ("/ws/1/release/" + m_vAlbums[nAlbum].m_strId + "?type=xml&inc=tracks+artist+release-events+url-rels"); QHttpRequestHeader header ("GET", convStr(s)); header.setValue("Host", "musicbrainz.org"); //header.setValue("Accept-Encoding", "gzip"); delay(); m_pQHttp->request(header); //cout << "sent album " << m_vAlbums[nAlbum].m_strId << " - " << m_pQHttp->request(header) << endl; addNote(AlbumInfoDownloaderDlgImpl::tr("getting album info ...")); } void MusicBrainzDownloader::requestImage(int nAlbum, int nImage) { LAST_STEP("MusicBrainzDownloader::requestImage"); CB_ASSERT (!m_pQHttp->hasPendingRequests()); CB_ASSERT (!m_pImageQHttp->hasPendingRequests()); m_nLoadingAlbum = nAlbum; m_nLoadingImage = nImage; setWaiting(IMAGE); const string& strUrl (m_vAlbums[nAlbum].m_vstrImageNames[nImage]); setImageType(strUrl); QUrl url (convStr(strUrl)); m_pImageQHttp->setHost(url.host()); delay(); // probably not needed, because doesn't seem that MusicBrainz would want to store images //connect(m_pImageQHttp, SIGNAL(requestFinished(int, bool)), this, SLOT(onRequestFinished(int, bool))); //qDebug("host: %s, path: %s", url.host().toLatin1().constData(), url.path().toLatin1().constData()); //qDebug("%s", strUrl.c_str()); m_pImageQHttp->get(url.path()); addNote(AlbumInfoDownloaderDlgImpl::tr("getting image ...")); } //========================================================================================================================== //========================================================================================================================== //========================================================================================================================== /*override*/ QHttp* MusicBrainzDownloader::getWaitingHttp() { LAST_STEP("MusicBrainzDownloader::getWaitingHttp"); return IMAGE == m_eWaiting ? m_pImageQHttp : m_pQHttp; } /*override*/ void MusicBrainzDownloader::resetNavigation() { LAST_STEP("MusicBrainzDownloader::resetNavigation"); m_pImageQHttp->clearPendingRequests(); AlbumInfoDownloaderDlgImpl::resetNavigation(); } /*override*/ WebAlbumInfoBase& MusicBrainzDownloader::album(int i) { return m_vAlbums.at(i); } /*override*/ int MusicBrainzDownloader::getAlbumCount() const { return cSize(m_vAlbums); } /*override*/ QXmlDefaultHandler* MusicBrainzDownloader::getSearchXmlHandler() { return new SearchXmlHandler(*this); } /*override*/ QXmlDefaultHandler* MusicBrainzDownloader::getAlbumXmlHandler(int nAlbum) { //return new AlbumXmlHandler(m_vAlbums.at(nAlbum)); return new AlbumXmlHandler(m_vAlbums.at(nAlbum)); } /*override*/ const WebAlbumInfoBase* MusicBrainzDownloader::getCrtAlbum() const // returns 0 if there's no album { if (m_nCrtAlbum < 0 || m_nCrtAlbum >= cSize(m_vAlbums)) { return 0; } return &m_vAlbums[m_nCrtAlbum]; } /* */ //ttt2 perhaps look at Last.fm for more pictures (see Cover Fetcher for AmaroK 1.4; a brief look at the API seems to indicate that a generic "search" is not possible) //ttt2 detect Qt 4.4 and use QWebView #endif MP3Diags-1.2.02/src/DirFilter.ui0000644000175000001440000000265311700345124015164 0ustar ciobiusers DirFilterDlg 0 0 725 415 Folder filter true 6 0 Qt::Horizontal 131 10 OK Cancel MP3Diags-1.2.02/src/Palette.ui0000644000175000001440000001723211700345571014703 0ustar ciobiusers PaletteDlg 0 0 606 472 Background colors 0 0 Album 60 40 60 40 Qt::NoFocus Field equal to the ID3V2 field or missing 60 40 60 40 Qt::NoFocus Field is different from the corresponding ID3V2 value (or no ID3V2 field exists) 60 40 60 40 Qt::NoFocus Value marked as "assigned" 0 0 Song 60 40 60 40 Qt::NoFocus Tag and field present 60 40 60 40 Qt::NoFocus Tag not present 60 40 60 40 Qt::NoFocus Tag present but field is not supported 60 40 60 40 Qt::NoFocus Tag present, field is supported but missing Qt::Vertical 20 40 0 Qt::Horizontal 391 20 OK MP3Diags-1.2.02/src/DoubleList.h0000644000175000001440000003474411252461427015175 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef DoubleListH #define DoubleListH #include #include #include #include "ui_DoubleListWdg.h" #include "MultiLineTvDelegate.h" class ListPainter; //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== namespace DoubleListImpl { // TableModels and Delegates don't have to be public, but they must be in a header, for the Qt preprocessing to work. // ttt3 perhaps move to separate header class AvailableModel : public QAbstractTableModel { Q_OBJECT public: //std::vector m_vpElems; // doesn't own the pointers const ListPainter& m_listPainter; public: AvailableModel(ListPainter& listPainter); /*override*/ int rowCount(const QModelIndex&) const { return rowCount(); } /*override*/ int columnCount(const QModelIndex&) const { return columnCount(); } /*override*/ QVariant data(const QModelIndex&, int nRole) const; int columnCount() const; int rowCount() const; /*override*/ QVariant headerData(int nSection, Qt::Orientation eOrientation, int nRole = Qt::DisplayRole) const; void emitLayoutChanged(); }; class SelectedModel : public QAbstractTableModel { Q_OBJECT public: //std::vector m_vpElems; // doesn't own the pointers const ListPainter& m_listPainter; public: SelectedModel(ListPainter& listPainter); /*override*/ int rowCount(const QModelIndex&) const { return rowCount(); } /*override*/ int columnCount(const QModelIndex&) const { return columnCount(); } /*override*/ QVariant data(const QModelIndex&, int nRole) const; int columnCount() const; int rowCount() const; /*override*/ QVariant headerData(int nSection, Qt::Orientation eOrientation, int nRole = Qt::DisplayRole) const; QTableView* m_pTableView; // needed to retrieve column widths, which are needed for tooltips, which are no longer used but this is an example of how to do it void emitLayoutChanged(); }; //class AvailableModelDelegate : public QItemDelegate class AvailableModelDelegate : public MultiLineTvDelegate { Q_OBJECT const ListPainter& m_listPainter; public: //AvailableModelDelegate(AvailableModel* pAvailableModel, QObject* pParent) : QItemDelegate(pParent), m_pAvailableModel(pAvailableModel) {} //AvailableModelDelegate(AvailableModel* pAvailableModel, QTableView* pTableView) : MultiLineTvDelegate(pTableView), m_pAvailableModel(pAvailableModel) {} AvailableModelDelegate(ListPainter& listPainter, QTableView* pTableView) : MultiLineTvDelegate(pTableView), m_listPainter(listPainter) {} /*override*/ void paint(QPainter* pPainter, const QStyleOptionViewItem& option, const QModelIndex& index) const; //AvailableModel* m_pAvailableModel; }; class SelectedModelDelegate : public MultiLineTvDelegate { Q_OBJECT const ListPainter& m_listPainter; public: SelectedModelDelegate(ListPainter& listPainter, QTableView* pTableView) : MultiLineTvDelegate(pTableView), m_listPainter(listPainter) {} /*override*/ void paint(QPainter* pPainter, const QStyleOptionViewItem& option, const QModelIndex& index) const; }; } // namespace DoubleListImpl //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== class ListElem { public: virtual ~ListElem() {} // virtual std::string getText(int nCol) const = 0; }; class DoubleList; //ttt2 perhaps have a "mirror" option, where available elems are on the left and selected on the right /* ListPainter has 2 related purposes: 1) it holds vectors and some other data used to describe the contents of the lists; 2) provides data needed by QAbstractTableModel and QItemDelegate to display the tables; to do this it uses the vectors described above, as well as abstract functions, which have to be implemented by derived classes; The basic idea is that there are 3 vectors: one with "elements", containing everything that is included in the lists, and two with integers, which are indexes in the vector with "elements". One of these two is used for "selected" items, while the other is for "available" ones. Depending on the settings, elements that are included in "selected" may be removed or not from "available" (this is DoubleList's business, though; ListPainter only provides the storage). Actually it's a bit more complex: 2 more vectors are used to support "restore open" and "restore default" functionality. "Restore default" is optional. To work, it needs reset() to be implemented and a param must be passed to DoubleList, telling it to show the corresponding button. If reset() is implemented, it should populate m_vpResetAll with pointers to elements and m_vSel with indices into m_vpResetAll. In most cases, reset() would only change m_vpResetAll if it is empty, because "there's only one default"; however, m_vSel should most likely be changed. Assuming DoubleList is used in a dialog window that gets closed, if reset() is implemented, the caller should check if m_vpResetAll contains elements regardless of the dialog being closed with OK or Cancel. Then it should probably transfer the information and delete the pointers in both vpOrigAll and vpResetAll (or perhaps only in one of them, based on the dialog result and m_bResultInReset). It's the caller's responsibility to provide reset() and to deal with m_vpResetAll in a compatible way. ListPainter and DoubleList don't delete any pointers from m_vpOrigAll or m_vpResetAll. It is the responsability of the class derived from ListPainter or of its users to release pointers. The reason for this is that no reasonable assumptions can be made about the lifetime of the pointers. They may have to be deleted after DoubleList is destroyed or they may have to live until the program is closed. Also, more things may have to be done besides deleting the pointers. Sometimes it makes sense to consider the fact that nothing is "selected" as meaning that everything is "selected". To make this convention obvious from a visual point of view, m_strNothingSel should be used. If its value is non-empty, when m_vSel is empty the "selected" list switches to using a single column (with no title) and a single row, containing a single data cell, whose text is m_strNothingSel (which should be something like "<>". Of course, this is just visual. The user of the class must also make sure that the meaning of an empty m_vSel is the same as that of a m_vSel that contains all the available values. */ class ListPainter { public: typedef std::vector AllList; typedef std::vector SubList; ListPainter(const std::string& strNothingSel) : m_pDoubleList(0), m_bResultInReset(false), m_strNothingSel(strNothingSel) {} virtual ~ListPainter() {} virtual int getColCount() const = 0; virtual std::string getColTitle(int nCol) const = 0; enum { ALL_LIST, SUB_LIST }; virtual void getColor(int nIndex, int nColumn, bool bSubList, QColor& bckgColor, QColor& penColor, double& dGradStart, double& dGradEnd) const = 0; // nIndex is an index in the "all" table; color is both input and output param; dGradStart and dGradEnd must be either -1 (in which case no gradient is used) or between 0 and 1, with dGradStart < dGradEnd; they are -1 when the call is made, so can be left alone virtual int getColWidth(int nCol) const = 0; // positive values are used for fixed widths, while negative ones are for "stretched" virtual int getHdrHeight() const = 0; enum TooltipKey { SELECTED_G, AVAILABLE_G, ADD_B, DELETE_B, ADD_ALL_B, DELETE_ALL_B, RESTORE_DEFAULT_B, RESTORE_OPEN_B }; virtual std::string getTooltip(TooltipKey eTooltipKey) const = 0; // !!! this must return an empty string for buttons that are removed, otherwise deallocated memory gets accessed virtual Qt::Alignment getAlignment(int nCol) const = 0; virtual void reset() = 0; // "restore default" functionality const std::string& getNothingSelStr() const { return m_strNothingSel; } const SubList& getSel() const { return m_vSel; } const SubList& getAvailable() const { return m_vAvailable; } const AllList& getAll() const { return m_bResultInReset ? m_vpResetAll : m_vpOrigAll; } protected: DoubleList* m_pDoubleList; // widget initialized with "this" as parent; deleted automatically by Qt; put here more as a convenience place to store the pointer, but it doesn't HAVE to be used (unlike the other member variables); AllList m_vpOrigAll; // the initial list with "all" elements; the derived class must initialize this; AllList m_vpResetAll; // the list with "all" elements after reset() got called; the derived class must initialize this when reset() is called; SubList m_vOrigSel; // used by on_m_pRestoreOpenB_clicked(); the derived class must initialize this; SubList m_vSel; // the "selected" elements; the derived class must initialize this; usually it's identical to m_vOrigSel at creation; // !!! note that both m_vOrigSel and m_vSel must be initialized (in a previous version m_vSel used to be copied from m_vOrigSel, but the current way is more flexible with cases when a DoubleList is created and deleted dynamically, multiple times, while its parent is alive and visible;) bool isResultInReset() const { return m_bResultInReset; } private: SubList m_vAvailable; // shouldn't be initialized in the derived class (well, it can't anyway, since it's private); DoubleList takes care of this, making it a sorted vector containing either all the numbers between 0 and "getAll().size() - 1", or only those numbers that aren't in m_vSel bool m_bResultInReset; // if m_vSel indexes m_vpResetAll or m_vpOrigAll std::string m_strNothingSel; friend class DoubleList; }; template void getCastElem(T*& p, V& v, int i) { p = dynamic_cast(v[i]); CB_ASSERT(0 != p); } /* Widget handling a sublist of elements that are "selected" from a full list. It is quite configurable, but more options can be envisioned. Most of the data are stored in a ListPainter, which must be passed in the constructor (actually some class derived from ListPainter, which is abstract). This is mainly for convenience and clarity (some vectors could be moved to DoubleList, but some must stay in ListPainter and having them all in one place seems better). */ class DoubleList : public QWidget, private Ui::DoubleListWdg { Q_OBJECT typedef ListPainter::AllList AllList; typedef ListPainter::SubList SubList; public: enum SelectionMode { SINGLE_UNSORTABLE, SINGLE_SORTABLE, MULTIPLE }; // "SORTABLE" means "sortable by the user"; if the user can't sort the "sel" list, the elements are sorted to match the order they appear in the "all" list"; MULTIPLE is always "sortable" //ttt2 the word "Selection" is confusing in this context private: ListPainter& m_listPainter; SelectionMode m_eSelectionMode; // ttt2 perhaps add "Up" and "Down" buttons void setUpGrid(QTableView* pGrid); void resizeColumns(QTableView* pGrid); // this should be called after LayoutChanged was sent void clearSel(); void resizeRows(); void emitLayoutChanged(); void adjustOnDataChanged(); DoubleListImpl::AvailableModel m_availableModel; DoubleListImpl::SelectedModel m_selectedModel; friend class DoubleListImpl::AvailableModel; friend class DoubleListImpl::SelectedModel; friend class DoubleListImpl::AvailableModelDelegate; friend class DoubleListImpl::SelectedModelDelegate; /*override*/ void resizeEvent(QResizeEvent* pEvent); void initAvailable(); bool m_bSectionMovedLock; public: enum UsedButtons { NONE = 0x00, ADD_ALL = 0x01, DEL_ALL = 0x02, RESTORE_OPEN = 0x04, RESTORE_DEFAULT = 0x08 }; DoubleList( ListPainter& listPainter, int nButtons, // or/xor of UsedButtons SelectionMode eSelectionMode, const std::string& strAvailableLabel, const std::string& strSelLabel, QWidget* pParent, Qt::WFlags fl = 0); ~DoubleList(); void add(const std::set&); // adds elements from the specified indexes void remove(const std::set&); // removes elements from the specified indexes signals: void dataChanged(); void avlDoubleClicked(int nRow); protected slots: void on_m_pAddB_clicked(); void on_m_pDeleteB_clicked(); void on_m_pAddAllB_clicked(); void on_m_pDeleteAllB_clicked(); void on_m_pRestoreOpenB_clicked(); void on_m_pRestoreDefaultB_clicked(); void onSelSectionMoved(int nLogicalIndex, int nOldVisualIndex, int nNewVisualIndex); void onResizeTimer(); void onAvlDoubleClicked(const QModelIndex& index); }; #endif MP3Diags-1.2.02/src/Export.ui0000644000175000001440000001153211700345212014553 0ustar ciobiusers ExportDlg 0 0 988 370 Export true File name: ... Format 0 XML Text M3U Remove root: When creating an M3U file, this text gets removed from the beginning of the file names. Meant to be used for creating M3U files containing relative paths. Locale: Files to save 0 Visible files Selected files Qt::Horizontal 40 20 Sort by short names Qt::Vertical 20 31 Qt::Horizontal 40 20 Close Export true MP3Diags-1.2.02/src/DiscogsDownloader.h0000644000175000001440000000752612040471524016532 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef DiscogsDownloaderH #define DiscogsDownloaderH #include "AlbumInfoDownloaderDlgImpl.h" namespace Discogs { struct SearchXmlHandler; struct DiscogsAlbumInfo : public WebAlbumInfoBase { enum StyleOption { GENRE_ONLY, GENRE_COMMA_STYLE, GENRE_PAR_STYLE, STYLE_ONLY }; const StyleOption* m_peStyleOption; // should be a reference, but that would make assignment fail DiscogsAlbumInfo(const StyleOption* peStyleOption) : m_peStyleOption(peStyleOption) {} std::string m_strComposer; std::string m_strGenre; std::string m_strStyle; std::string m_strNotes; std::string m_strId; /*override*/ void copyTo(AlbumInfo& dest); std::string getGenre() const; // combination of m_strGenre and m_strStyle }; }; class DiscogsDownloader : public AlbumInfoDownloaderDlgImpl { Q_OBJECT std::vector m_vAlbums; Discogs::DiscogsAlbumInfo::StyleOption m_eStyleOption; friend struct Discogs::SearchXmlHandler; void clear(); /*override*/ bool initSearch(const std::string& strArtist, const std::string& strAlbum); /*override*/ std::string createQuery(); /*override*/ void loadNextPage(); /*override*/ void requestAlbum(int nAlbum); /*override*/ void requestImage(int nAlbum, int nImage); /*override*/ void reloadGui(); /*override*/ QHttp* getWaitingHttp(); /*override*/ WebAlbumInfoBase& album(int i); /*override*/ int getAlbumCount() const; /*override*/ QXmlDefaultHandler* getSearchXmlHandler(); /*override*/ QXmlDefaultHandler* getAlbumXmlHandler(int nAlbum); /*override*/ const WebAlbumInfoBase* getCrtAlbum() const; // returns 0 if there's no album /*override*/ int getColumnCount() const { return 4; } /*override*/ void saveSize(); /*override*/ char getReplacementChar() const { return '+'; } protected: int m_nPageSize; public: DiscogsDownloader(QWidget* pParent, SessionSettings& settings, bool bSaveResults); ~DiscogsDownloader(); /*$PUBLIC_FUNCTIONS$*/ static const char* SOURCE_NAME; public slots: /*$PUBLIC_SLOTS$*/ protected: /*$PROTECTED_FUNCTIONS$*/ protected slots: /*$PROTECTED_SLOTS$*/ void on_m_pSearchB_clicked(); void on_m_pStyleCbB_currentIndexChanged(int); private: }; #endif // #ifndef DiscogsDownloaderH MP3Diags-1.2.02/src/Widgets.h0000644000175000001440000001340711724234352014525 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef WidgetsH #define WidgetsH #include #include #include #include #include // for translation //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== // A menu that makes available the "modifier" keys that were pressed when an item was selected. Currently only used to determine if SHIFT was pressed. // // To achieve the same result without using the keyboard, selecting with the right button will also tag SHIFT as pressed. // // (an alternative approch that would usually work is to query the keyboard status immediately after something is selected; however, this approach may lead to incorrect results if significant time passes between selection and query; besides, Qt doesn't seem to offer any means to query the keyboard and posts on the net suggest to use X or Windows-specific means (XQueryKeymap or GetAsyncKeyState), which is neither portable nor particularly obvious class ModifInfoMenu : public QMenu { Q_OBJECT /*override*/ void mousePressEvent(QMouseEvent* pEvent); Qt::KeyboardModifiers m_modif; public: ModifInfoMenu(QWidget* pParent = 0) : QMenu(pParent) {} Qt::KeyboardModifiers getModifiers() const { return m_modif; } }; // A ToolButton that makes available the "modifier" keys that were pressed when it was clicked. Currently only used to determine if SHIFT or CTRL were pressed. // // To achieve the same result without using the keyboard, the following sequence will also tag SHIFT as pressed: left button down > right button down > left button up (it doesn't matter when the right button is released). // // On the constructor it replaces an existing QToolButton with itself, copying some of the properties, such as icon or size. // // ttt2 perhaps turn this class into something generic, by duplicating all the properties and handling other layouts class ModifInfoToolButton : public QToolButton { Q_OBJECT /*override*/ void mousePressEvent(QMouseEvent* pEvent); /*override*/ void keyPressEvent(QKeyEvent* pEvent); Qt::KeyboardModifiers m_modif; void contextMenuEvent(QContextMenuEvent* pEvent); public: ModifInfoToolButton(QToolButton* pOldBtn); Qt::KeyboardModifiers getModifiers() const { return m_modif; } }; int showMessage(QWidget* pParent, QMessageBox::Icon icon, int nDefault, int nEscape, const QString& qstrTitle, const QString& qstrMessage, const QString& qstrButton0, const QString& qstrButton1 = "", const QString& qstrButton2 = "", const QString& qstrButton3 = ""); void showWarning(QWidget* pParent, const QString& qstrTitle, const QString& qstrMessage); void showCritical(QWidget* pParent, const QString& qstrTitle, const QString& qstrMessage); void showInfo(QWidget* pParent, const QString& qstrTitle, const QString& qstrMessage); class GlobalTranslHlp { Q_DECLARE_TR_FUNCTIONS(GlobalTranslHlp) }; class QDialog; class HtmlMsg : public QObject { Q_OBJECT int m_nBtn; QDialog* m_pDlg; HtmlMsg(QDialog* pDlg, int nBtn) : m_nBtn(nBtn), m_pDlg(pDlg) {} public: static int msg(QWidget* pParent, int nDefault, int nEscape, bool* pbGotTheMessage, int nFlags, const QString& qstrTitle, const QString& qstrMessage, int nWidth, int nHeight, const QString& qstrButton0, const QString& qstrButton1 = "", const QString& qstrButton2 = "", const QString& qstrButton3 = ""); enum Flags { DEFAULT = 0, STAY_ON_TOP = 1, SHOW_SYS_INFO = 2, CRITICAL = 4, VERT_BUTTONS = 8 }; public slots: void onClick0(); void onClick1(); void onClick2(); void onClick3(); }; // switches the cursor to hourglass on the constructor and back to normal on the destructor struct CursorOverrider { CursorOverrider(Qt::CursorShape crs = Qt::BusyCursor); ~CursorOverrider(); }; class NoCropHeaderView : public QHeaderView { Q_OBJECT /*override*/ void paintSection(QPainter* pPainter, const QRect& rect, int nLogicalIndex) const; public: NoCropHeaderView(QWidget* pParent) : QHeaderView(Qt::Vertical, pParent) {} }; #endif // #ifndef WidgetsH MP3Diags-1.2.02/src/FileRenamerDlgImpl.cpp0000644000175000001440000011727611750277656017142 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include "FileRenamerDlgImpl.h" #include "Helpers.h" #include "ColumnResizer.h" #include "CommonData.h" #include "DataStream.h" #include "RenamerPatternsDlgImpl.h" #include "OsFile.h" #include "ThreadRunnerDlgImpl.h" #include "StoredSettings.h" #include "Id3V230Stream.h" #include "Widgets.h" using namespace std; using namespace pearl; using namespace FileRenamer; HndlrListModel::HndlrListModel(CommonData* pCommonData, FileRenamerDlgImpl* pFileRenamerDlgImpl, bool bUseCurrentView) : m_pFileRenamerDlgImpl(pFileRenamerDlgImpl), m_pCommonData(pCommonData), m_pRenamer(0), m_bUseCurrentView(bUseCurrentView) { } HndlrListModel::~HndlrListModel() { delete m_pRenamer; } void HndlrListModel::setRenamer(const Renamer* p) { delete m_pRenamer; m_pRenamer = p; emitLayoutChanged(); } void HndlrListModel::setUnratedAsDuplicates(bool bUnratedAsDuplicate) { if (0 == m_pRenamer) { return; } m_pRenamer->m_bUnratedAsDuplicate = bUnratedAsDuplicate; emitLayoutChanged(); } // returns either m_pCommonData->getCrtAlbum() or m_pCommonData->getViewHandlers(), based on m_bUseCurrentView const deque HndlrListModel::getHandlerList() const { return m_bUseCurrentView ? m_pCommonData->getViewHandlers() : m_pCommonData->getCrtAlbum(); } /*override*/ int HndlrListModel::rowCount(const QModelIndex&) const { return cSize(getHandlerList()); } /*override*/ int HndlrListModel::columnCount(const QModelIndex&) const { return TagReader::LIST_END + 2 - 1; } /*override*/ Qt::ItemFlags HndlrListModel::flags(const QModelIndex& index) const { Qt::ItemFlags flg (QAbstractTableModel::flags(index)); if (1 == index.column() && 0 != m_pRenamer) { flg = flg | Qt::ItemIsEditable; } return flg; } /*override*/ QVariant HndlrListModel::data(const QModelIndex& index, int nRole) const { LAST_STEP("HndlrListModel::data()"); if (!index.isValid()) { return QVariant(); } int i (index.row()); int j (index.column()); if (Qt::DisplayRole != nRole && Qt::ToolTipRole != nRole && Qt::EditRole != nRole) { return QVariant(); } QString s; const Mp3Handler* p (getHandlerList().at(i)); const Id3V2StreamBase* pId3V2 (p->getId3V2Stream()); if (0 == j) { s = convStr(p->getShortName()); } else if (0 == pId3V2) { //s = "N/A"; s = tr("<< missing ID3V2 >>"); } else if (1 == j) { //s = convStr(p->getName()); if (0 == m_pRenamer) { //s = "N/A"; s = tr("<< no pattern defined >>"); } else { s = toNativeSeparators(convStr(m_pRenamer->getNewName(p))); if (s.isEmpty()) { //s = "N/A"; s = tr("<< missing fields >>"); } } } else { j -= 2; if (j >= TagReader::POS_OF_FEATURE[TagReader::IMAGE]) { j += 1; } s = convStr(pId3V2->getValue((TagReader::Feature)TagReader::FEATURE_ON_POS[j])); } if (Qt::DisplayRole == nRole || Qt::EditRole == nRole) { return s; } // so it's Qt::ToolTipRole QFontMetrics fm (m_pFileRenamerDlgImpl->m_pCurrentAlbumG->fontMetrics()); // !!! no need to use "QApplication::fontMetrics()" int nWidth (fm.width(s)); //qDebug("tooltip for %s, width %d int nMargin (QApplication::style()->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1); // this "1" should probably be another "metric" constant if (nWidth + 2*nMargin + 1 <= m_pFileRenamerDlgImpl->m_pCurrentAlbumG->horizontalHeader()->sectionSize(index.column())) // ttt2 not sure this "nMargin" is correct { //return QVariant(); return ""; // !!! with "return QVariant()" the previous tooltip remains until the cursor moves over another cell that has a tooltip } return s; } /*override*/ bool HndlrListModel::setData(const QModelIndex& index, const QVariant& value, int nRole /* = Qt::EditRole*/) { if (Qt::EditRole != nRole) { return false; } CB_ASSERT (0 != m_pRenamer); m_pRenamer->m_mValues[getHandlerList().at(index.row())] = convStr(fromNativeSeparators(value.toString())); return true; } /*override*/ QVariant HndlrListModel::headerData(int nSection, Qt::Orientation eOrientation, int nRole /* = Qt::DisplayRole*/) const { LAST_STEP("HndlrListModel::headerData"); if (nRole != Qt::DisplayRole) { return QVariant(); } if (Qt::Horizontal == eOrientation) { if (0 == nSection) { return tr("File name"); } else if (1 == nSection) { return tr("New file name"); } else { nSection -= 2; if (nSection >= TagReader::POS_OF_FEATURE[TagReader::IMAGE]) { nSection += 1; } return TagReader::tr(TagReader::getLabel(TagReader::FEATURE_ON_POS[nSection])); } } return nSection + 1; } //====================================================================================================================== //====================================================================================================================== CurrentAlbumDelegate::CurrentAlbumDelegate(QWidget* pParent, HndlrListModel* pHndlrListModel) : QItemDelegate(pParent), m_pHndlrListModel(pHndlrListModel) { } /*override*/ void CurrentAlbumDelegate::paint(QPainter* pPainter, const QStyleOptionViewItem& option, const QModelIndex& index) const { pPainter->save(); const Mp3Handler* p (m_pHndlrListModel->getHandlerList().at(index.row())); const Id3V2StreamBase* pId3V2 (p->getId3V2Stream()); if (0 == pId3V2) { //pPainter->fillRect(option.rect, QColor(255, 226, 236)); //ttt2 perhaps put back, but should work for "missing fields" as well } QStyleOptionViewItemV2 myOption (option); if (0 != m_pHndlrListModel->getRenamer() && index.column() == 1) { string strNewName (m_pHndlrListModel->getRenamer()->getNewName(p)); if (fileExists(strNewName)) { pPainter->fillRect(option.rect, strNewName == p->getName() ? QColor(226, 236, 255) : QColor(255, 226, 236)); } if (m_pHndlrListModel->getRenamer()->m_mValues.count(p) > 0) { myOption.font.setItalic(true); } } QItemDelegate::paint(pPainter, myOption, index); pPainter->restore(); } //====================================================================================================================== //====================================================================================================================== //====================================================================================================================== FileRenamerDlgImpl::FileRenamerDlgImpl(QWidget* pParent, CommonData* pCommonData, bool bUseCurrentView) : QDialog(pParent, getDialogWndFlags()), Ui::FileRenamerDlg(), m_pCommonData(pCommonData), m_bUseCurrentView(bUseCurrentView), m_pEditor(0) { setupUi(this); resizeIcons(); m_pHndlrListModel = new HndlrListModel(m_pCommonData, this, bUseCurrentView); { m_pCurrentAlbumG->verticalHeader()->setResizeMode(QHeaderView::Interactive); m_pCurrentAlbumG->verticalHeader()->setMinimumSectionSize(CELL_HEIGHT + 1); m_pCurrentAlbumG->verticalHeader()->setDefaultSectionSize(CELL_HEIGHT + 1);//*/ m_pCurrentAlbumG->setModel(m_pHndlrListModel); CurrentAlbumDelegate* pDel (new CurrentAlbumDelegate(this, m_pHndlrListModel)); m_pCurrentAlbumG->setItemDelegate(pDel); m_pCurrentAlbumG->viewport()->installEventFilter(this); } m_pButtonGroup = new QButtonGroup(this); loadPatterns(); updateButtons(); { int nWidth, nHeight; bool bKeepOriginal, bUnratedAsDuplicate; m_pCommonData->m_settings.loadRenamerSettings(nWidth, nHeight, m_nSaButton, m_nVaButton, bKeepOriginal, bUnratedAsDuplicate); if (nWidth > 400 && nHeight > 400) { resize(nWidth, nHeight); } else { defaultResize(*this); } if (m_nVaButton >= cSize(m_vstrPatterns) || m_nVaButton <= 0) { m_nVaButton = 0; } if (m_nSaButton >= cSize(m_vstrPatterns) || m_nSaButton <= 0) { m_nSaButton = 0; } m_pKeepOriginalCkB->setChecked(bKeepOriginal); m_pMarkUnratedAsDuplicatesCkB->setChecked(bUnratedAsDuplicate); } { m_pModifRenameB = new ModifInfoToolButton(m_pRenameB); connect(m_pModifRenameB, SIGNAL(clicked()), this, SLOT(on_m_pRenameB_clicked())); m_pRenameB = m_pModifRenameB; } { QAction* p (new QAction(this)); p->setShortcut(QKeySequence("F1")); connect(p, SIGNAL(triggered()), this, SLOT(onHelp())); addAction(p); } if (m_bUseCurrentView) { m_pCrtDirTagEdtE->setEnabled(false); m_pPrevB->setEnabled(false); m_pNextB->setEnabled(false); } if (!m_pCommonData->m_bShowCustomCloseButtons) { m_pCloseB->hide(); } QTimer::singleShot(1, this, SLOT(onShow())); // just calls reloadTable(); !!! needed to properly resize the table columns; album and file tables have very small widths until they are actually shown, so calling resizeTagEditor() earlier is pointless; calling update() on various layouts seems pointless as well; (see also DoubleList::resizeEvent() ) } FileRenamerDlgImpl::~FileRenamerDlgImpl() { m_pCommonData->m_settings.saveRenamerSettings(width(), height(), m_nSaButton, m_nVaButton, m_pKeepOriginalCkB->isChecked(), m_pMarkUnratedAsDuplicatesCkB->isChecked()); } string FileRenamerDlgImpl::run() { exec(); int k (m_pCurrentAlbumG->currentIndex().row()); const deque& vpCrtAlbum (m_pHndlrListModel->getHandlerList()); if (k < 0 || k >= cSize(vpCrtAlbum)) { return ""; } return vpCrtAlbum[k]->getName(); } void FileRenamerDlgImpl::on_m_pNextB_clicked() { closeEditor(); if (m_pCommonData->nextAlbum()) { reloadTable(); } } void FileRenamerDlgImpl::on_m_pPrevB_clicked() { closeEditor(); if (m_pCommonData->prevAlbum()) { reloadTable(); } } void FileRenamerDlgImpl::on_m_pEditPatternsB_clicked() { RenamerPatternsDlgImpl dlg (this, m_pCommonData->m_settings); if (dlg.run(m_vstrPatterns)) { m_nVaButton = m_nSaButton = 0; savePatterns(); updateButtons(); selectPattern(); } } void FileRenamerDlgImpl::updateButtons() { m_nBtnId = 0; createButtons(); } void FileRenamerDlgImpl::createButtons() { QBoxLayout* pLayout (dynamic_cast(m_pButtonsW->layout())); CB_ASSERT (0 != pLayout); /*int nPos (pLayout->indexOf(pOldBtn)); pLayout->insertWidget(nPos, this);*/ QObjectList l (m_pButtonsW->children()); //qDebug("cnt: %d", l.size()); for (int i = 1, n = l.size(); i < n; ++i) // l[0] is m_pButtonsW's layout (note that m_pAlbumTypeL is in m_pBtnPanelW) { delete l[i]; } for (int i = 0, n = cSize(m_vstrPatterns); i < n; ++i) { QToolButton* p (new QToolButton(m_pButtonsW)); p->setText(toNativeSeparators(convStr(m_vstrPatterns[i]))); p->setCheckable(true); m_pButtonGroup->addButton(p, m_nBtnId++); //p->setAutoExclusive(true); connect(p, SIGNAL(clicked()), this, SLOT(onPatternClicked())); pLayout->insertWidget(i, p); } } void FileRenamerDlgImpl::onPatternClicked() { int nId (m_pButtonGroup->checkedId()); int n (cSize(m_vstrPatterns)); //qDebug("id=%d", nId); //CB_ASSERT (nId >= 0 && nId < 2*n); CB_ASSERT (nId >= 0 && nId < n); if (nId >= n) { nId -= n; } if (isSingleArtist()) { m_nSaButton = nId; } else { m_nVaButton = nId; } m_pHndlrListModel->setRenamer(new Renamer(m_vstrPatterns[nId], m_pCommonData, m_pMarkUnratedAsDuplicatesCkB->isChecked())); resizeUi(); } void FileRenamerDlgImpl::closeEditor() { if (0 != m_pEditor) { delete m_pEditor; } } /*override*/ bool FileRenamerDlgImpl::eventFilter(QObject* pObj, QEvent* pEvent) { //qDebug("ev %d", int(pEvent->type())); if (QEvent::ChildAdded == pEvent->type()) { //qDebug("add"); QObject* pChild (((QChildEvent*)pEvent)->child()); if (pChild->isWidgetType()) { CB_ASSERT (0 == m_pEditor); m_pEditor = pChild; } } else if (QEvent::ChildRemoved == pEvent->type()) { //qDebug("rm"); QObject* pChild (((QChildEvent*)pEvent)->child()); if (pChild->isWidgetType()) { CB_ASSERT (pChild == m_pEditor); m_pEditor = 0; } } return QDialog::eventFilter(pObj, pEvent); } //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== namespace { struct RenameThread : public PausableThread { const deque& m_vpHndl; bool m_bKeepOrig; const Renamer* m_pRenamer; CommonData* m_pCommonData; vector& m_vpDel; vector& m_vpAdd; QString m_qstrErr; RenameThread(const deque& vpHndl, bool bKeepOrig, const Renamer* pRenamer, CommonData* pCommonData, vector& vpDel, vector& vpAdd) : m_vpHndl(vpHndl), m_bKeepOrig(bKeepOrig), m_pRenamer(pRenamer), m_pCommonData(pCommonData), m_vpDel(vpDel), m_vpAdd(vpAdd) { } /*override*/ void run() { CompleteNotif notif(this); notif.setSuccess(proc()); } bool proc(); }; bool RenameThread::proc() { for (int i = 0, n = cSize(m_vpHndl); i < n; ++i) { if (isAborted()) { return false; } checkPause(); QString qstrName (m_vpHndl[i]->getUiName()); StrList l; l.push_back(qstrName); emit stepChanged(l, -1); string strDest (m_pRenamer->getNewName(m_vpHndl[i])); if (!strDest.empty()) { CB_ASSERT (string::npos == strDest.find("//")); try { bool bSkipped (false); //qDebug("ren %s", strDest.c_str()); if (m_bKeepOrig) { copyFile2(m_vpHndl[i]->getName(), strDest); } else { if (m_vpHndl[i]->getName() == strDest) // ttt2 doesn't work well on case-insensitive file systems { bSkipped = true; } else { renameFile(m_vpHndl[i]->getName(), strDest); m_vpDel.push_back(m_vpHndl[i]); } } if (!bSkipped && m_pCommonData->m_dirTreeEnum.isIncluded(strDest)) { try { m_vpAdd.push_back(new Mp3Handler(strDest, m_pCommonData->m_bUseAllNotes, m_pCommonData->getQualThresholds())); } catch (const Mp3Handler::FileNotFound&) { } } } catch (const FoundDir&) { m_qstrErr = FileRenamerDlgImpl::tr("Source or destination is a directory"); } catch (const CannotCopyFile&) { m_qstrErr = FileRenamerDlgImpl::tr("Error during copying"); } catch (const CannotRenameFile&) { m_qstrErr = FileRenamerDlgImpl::tr("Error during renaming"); } catch (const AlreadyExists&) { m_qstrErr = FileRenamerDlgImpl::tr("Destination already exists"); } catch (const NameNotFound&) { m_qstrErr = FileRenamerDlgImpl::tr("Source not found"); } catch (const CannotCreateDir& ex) { m_qstrErr = FileRenamerDlgImpl::tr("Cannot create folder %1").arg(convStr(ex.m_strDir)); } catch (const std::bad_alloc&) { throw; } catch (const IncorrectDirName&) { CB_ASSERT (false); } catch (...) { m_qstrErr = FileRenamerDlgImpl::tr("Unknown error"); } if (!m_qstrErr.isEmpty()) { return false; } } } return true; } } // namespace //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== void FileRenamerDlgImpl::on_m_pRenameB_clicked() { if (m_vstrPatterns.empty()) { showCritical(this, tr("No patterns exist"), tr("You must create at least a pattern before you can start renaming files.")); return; } const Renamer* pRenamer (m_pHndlrListModel->getRenamer()); CB_ASSERT (0 != pRenamer); bool bAll (0 == (Qt::ShiftModifier & m_pModifRenameB->getModifiers())); bool bKeepOrig (m_pKeepOriginalCkB->isChecked()); const deque& vpAllHndl (m_pHndlrListModel->getHandlerList()); deque vpHndl; if (bAll) { vpHndl = vpAllHndl; } else { QItemSelectionModel* pSelModel (m_pCurrentAlbumG->selectionModel()); QModelIndexList listSel (pSelModel->selectedIndexes()); set snSongs; for (QModelIndexList::iterator it = listSel.begin(), end = listSel.end(); it != end; ++it) { QModelIndex ndx (*it); int nSong (ndx.row()); snSongs.insert(nSong); } for (set::iterator it = snSongs.begin(), end = snSongs.end(); it != end; ++it) { vpHndl.push_back(vpAllHndl[*it]); } } if (showMessage(this, QMessageBox::Question, 1, 1, tr("Confirm"), bKeepOrig ? (bAll ? tr("Copy all the files?") : tr("Copy the selected files?")) : (bAll ? tr("Rename all the files?") : tr("Rename the selected files?")), tr("&Yes"), tr("Cancel")) != 0) { return; } { set s; for (int i = 0, n = cSize(vpHndl); i < n; ++i) { if (0 == vpHndl[i]->getId3V2Stream()) { showCritical(this, tr("Error"), tr("Operation aborted because file \"%1\" doesn't have an ID3V2 tag.").arg(vpHndl[i]->getUiName())); return; } string strDest (pRenamer->getNewName(vpHndl[i])); if (strDest.empty()) { showCritical(this, tr("Error"), tr("Operation aborted because file \"%1\" is missing some required fields in its ID3V2 tag.").arg(vpHndl[i]->getUiName())); return; } if (s.count(strDest) > 0) { showCritical(this, tr("Error"), tr("Operation aborted because it would create 2 copies of a file called \"%1\"").arg(toNativeSeparators(convStr(strDest)))); return; } if (fileExists(strDest) && (strDest != vpHndl[i]->getName() || bKeepOrig)) { showCritical(this, tr("Error"), tr("Operation aborted because a file called \"%1\" already exists.").arg(toNativeSeparators(convStr(strDest)))); return; } s.insert(strDest); } } vector vpDel, vpAdd; { RenameThread* pThread (new RenameThread(vpHndl, bKeepOrig, pRenamer, m_pCommonData, vpDel, vpAdd)); ThreadRunnerDlgImpl dlg (this, getNoResizeWndFlags(), pThread, ThreadRunnerDlgImpl::SHOW_COUNTER, ThreadRunnerDlgImpl::TRUNCATE_BEGIN); dlg.setWindowTitle(bKeepOrig ? (bAll ? tr("Copying all the files in the current album") : tr("Copying the selected files in the current album")) : (bAll ? tr("Renaming all the files in the current album") : tr("Renaming the selected files in the current album"))); dlg.exec(); if (!pThread->m_qstrErr.isEmpty()) { showCritical(this, tr("Error"), pThread->m_qstrErr); } } m_pCommonData->mergeHandlerChanges(vpAdd, vpDel, CommonData::SEL | CommonData::CURRENT); if (!bKeepOrig || pRenamer->isSameDir()) { reloadTable(); } } void FileRenamerDlgImpl::reloadTable() { { bool bVa (false); bool bErr (false); // it's error if any file lacks ID3V2 const deque& vpHndl (m_pHndlrListModel->getHandlerList()); if (vpHndl.empty()) { accept(); return; } int n (cSize(vpHndl)); CB_ASSERT (n > 0); string strArtist ("\1"); for (int i = 0; i < n; ++i) { const Mp3Handler* pHndl (vpHndl[i]); const Id3V2StreamBase* pId3V2 (pHndl->getId3V2Stream()); if (0 == pId3V2) { bErr = true; } else { if ("\1" == strArtist) { strArtist = pId3V2->getArtist(); } else { if (strArtist != pId3V2->getArtist()) { bVa = true; } } } } if (bErr) { m_eState = "\1" == strArtist ? ERR : bVa ? VARIOUS_ERR : SINGLE_ERR; } else { m_eState = bVa ? VARIOUS : SINGLE; } } selectPattern(); m_pHndlrListModel->emitLayoutChanged(); const Mp3Handler* p (m_pHndlrListModel->getHandlerList().at(0)); // !!! it was supposed to close the window if nothing remained if (!m_bUseCurrentView) { m_pCrtDirTagEdtE->setText(toNativeSeparators(convStr(p->getDir()))); } //m_pCrtDirTagEdtE->hide(); //setWindowTitle("MP3 Diags - " + convStr(p->getDir()) + " - File renamer"); QItemSelectionModel* pSelModel (m_pCurrentAlbumG->selectionModel()); pSelModel->clear(); pSelModel->setCurrentIndex(m_pHndlrListModel->index(0, 0), QItemSelectionModel::SelectCurrent); resizeUi(); } void FileRenamerDlgImpl::resizeUi() { SimpleQTableViewWidthInterface intf (*m_pCurrentAlbumG); ColumnResizer rsz (intf, 100, ColumnResizer::FILL, ColumnResizer::CONSISTENT_RESULTS); } /*override*/ void FileRenamerDlgImpl::resizeEvent(QResizeEvent* pEvent) { resizeUi(); QDialog::resizeEvent(pEvent); } // selects the appropriate pattern for a new album, based on whether it's VA or SA; sets m_nCrtPattern and checks the right button; assumes m_eState is properly set up; void FileRenamerDlgImpl::selectPattern() { if (!m_vstrPatterns.empty()) { if (isSingleArtist()) { m_pButtonGroup->button(m_nSaButton)->setChecked(true); m_pHndlrListModel->setRenamer(new Renamer(m_vstrPatterns[m_nSaButton], m_pCommonData, m_pMarkUnratedAsDuplicatesCkB->isChecked())); } else { m_pButtonGroup->button(m_nVaButton /*+ cSize(m_vstrPatterns)*/)->setChecked(true); m_pHndlrListModel->setRenamer(new Renamer(m_vstrPatterns[m_nVaButton], m_pCommonData, m_pMarkUnratedAsDuplicatesCkB->isChecked())); } } else { m_pHndlrListModel->setRenamer(0); } resizeUi(); m_pAlbumTypeL->setText(isSingleArtist() ? tr("Single artist") : tr("Various artists")); //ttt2 see if "single" is the best word } void FileRenamerDlgImpl::loadPatterns() { // pattern readers bool bErr (false); //vector v (m_pCommonData->m_settings.loadRenamerPatterns(bErr)); vector v (m_pCommonData->m_settings.loadVector("fileRenamer/patterns", bErr)); m_vstrPatterns.clear(); for (int i = 0, n = cSize(v); i < n; ++i) { string strPatt (v[i]); try { Renamer r (strPatt, m_pCommonData, m_pMarkUnratedAsDuplicatesCkB->isChecked()); m_vstrPatterns.push_back(strPatt); } catch (const Renamer::InvalidPattern&) { bErr = true; } } /*if (m_vstrPatterns.empty()) // ttt2 because there is no default, the user is forced to add a pattern first; see if a meaningful default can be used { // use default (only if the user didn't remove all patterns on purpose) string s ("/tmp/%a/%b[ %y]/%r%n %t"); //ttt2 change from tmp to root/out; //ttt2 perhaps ask the user //ttt2 OS specific Renamer r (s); // may throw after porting, but that's OK, because it signals a fix is needed m_vstrPatterns.push_back(s); }*/ if (bErr) { showWarning(this, tr("Error setting up patterns"), tr("An invalid value was found in the configuration file. You'll have to set up the patterns manually.")); } } void FileRenamerDlgImpl::savePatterns() { //m_pCommonData->m_settings.saveRenamerPatterns(m_vstrPatterns); m_pCommonData->m_settings.saveVector("fileRenamer/patterns", m_vstrPatterns); } //ttt2 perhaps have a "reload" button void FileRenamerDlgImpl::resizeIcons() { vector v; v.push_back(m_pPrevB); v.push_back(m_pNextB); v.push_back(m_pEditPatternsB); v.push_back(m_pRenameB); v.push_back(m_pCloseB); int k (m_pCommonData->m_nMainWndIconSize); for (int i = 0, n = cSize(v); i < n; ++i) { QToolButton* p (v[i]); p->setMaximumSize(k, k); p->setMinimumSize(k, k); p->setIconSize(QSize(k - 4, k - 4)); } } void FileRenamerDlgImpl::onHelp() { openHelp("240_file_renamer.html"); } //void FileRenamerDlgImpl::on_m_pMarkUnratedAsDuplicatesCkB_stateChanged() void FileRenamerDlgImpl::on_m_pMarkUnratedAsDuplicatesCkB_clicked() { m_pHndlrListModel->setUnratedAsDuplicates(m_pMarkUnratedAsDuplicatesCkB->isChecked()); } void FileRenamerDlgImpl::on_m_pCloseB_clicked() { reject(); } //====================================================================================================================== //====================================================================================================================== //====================================================================================================================== namespace FileRenamer { class InvalidCharsReplacer { string m_strRenamerInvalidChars; string m_strRenamerReplacementString; public: std::string fixName(std::string s) const; InvalidCharsReplacer(const string& strRenamerInvalidChars, const string& strRenamerReplacementString) : m_strRenamerInvalidChars(strRenamerInvalidChars), m_strRenamerReplacementString(strRenamerReplacementString) {} }; string InvalidCharsReplacer::fixName(string s) const { CB_ASSERT (string::npos == m_strRenamerReplacementString.find_first_of(m_strRenamerInvalidChars)); if (m_strRenamerInvalidChars.empty()) { return s; } for (;;) { string::size_type n (s.find_first_of(m_strRenamerInvalidChars)); if (string::npos == n) { break; } s.replace(n, 1, m_strRenamerReplacementString); } return s; } struct PatternBase { const InvalidCharsReplacer* m_pInvalidCharsReplacer; PatternBase(const InvalidCharsReplacer* pInvalidCharsReplacer) : m_pInvalidCharsReplacer(pInvalidCharsReplacer) {} virtual ~PatternBase() {} virtual string getVal(const Mp3Handler*) const = 0; }; struct StaticPattern : public PatternBase { StaticPattern(string strVal, const InvalidCharsReplacer* pInvalidCharsReplacer) : PatternBase(pInvalidCharsReplacer), m_strVal(strVal) {} /*override*/ string getVal(const Mp3Handler*) const { return m_strVal; } private: string m_strVal; }; struct FieldPattern : public PatternBase { FieldPattern(TagReader::Feature eFeature, const InvalidCharsReplacer* pInvalidCharsReplacer) : PatternBase(pInvalidCharsReplacer), m_eFeature(eFeature) {} /*override*/ string getVal(const Mp3Handler* pHndl) const { const Id3V2StreamBase* p (pHndl->getId3V2Stream()); if (0 == p) { return ""; } return m_pInvalidCharsReplacer->fixName(p->getValue(m_eFeature)); } private: TagReader::Feature m_eFeature; }; struct YearPattern : public PatternBase { YearPattern(const InvalidCharsReplacer* pInvalidCharsReplacer) : PatternBase(pInvalidCharsReplacer) {} /*override*/ string getVal(const Mp3Handler* pHndl) const { const Id3V2StreamBase* p (pHndl->getId3V2Stream()); if (0 == p) { return ""; } return p->getTime().getYear(); } }; struct TrackNoPattern : public PatternBase { TrackNoPattern(const InvalidCharsReplacer* pInvalidCharsReplacer) : PatternBase(pInvalidCharsReplacer) {} /*override*/ string getVal(const Mp3Handler* pHndl) const { const Id3V2StreamBase* p (pHndl->getId3V2Stream()); if (0 == p) { return "00"; } string s (p->getTrackNumber()); int n (atoi(s.c_str())); if (n <= 0) { return "00"; } char a [20]; sprintf(a, "%02d", n); return a; } }; struct RatingPattern : public PatternBase { RatingPattern(const InvalidCharsReplacer* pInvalidCharsReplacer, const bool& bUnratedAsDuplicate) : PatternBase(pInvalidCharsReplacer), m_bUnratedAsDuplicate(bUnratedAsDuplicate) {} const bool& m_bUnratedAsDuplicate; /*override*/ string getVal(const Mp3Handler* pHndl) const { const Id3V2StreamBase* p (pHndl->getId3V2Stream()); if (0 == p) { return ""; } double r (p->getRating()); if (r < 0) { return m_bUnratedAsDuplicate ? "q" : ""; } // from TrackTextReader::TrackTextReader // a b c d e f g h i j k l m n o p q r s t u v w x y z //static double s_ratingMap [] = { 5.0, 4.5, 4.0, 3.5, 3.0, 2.5, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 1.5, 1.0, 1.0, 1.0, -1, -1, 1.0, 1.0, 1.0, 1.0, 1.0, 0.5, 0.5, 0.0 }; // note that if this changes it should be synchronized with TrackTextReader if (r >= 4.9) { return "a"; } if (r >= 4.4) { return "b"; } if (r >= 3.9) { return "c"; } if (r >= 3.4) { return "d"; } if (r >= 2.9) { return "e"; } if (r >= 2.4) { return "f"; } if (r >= 1.9) { return "g"; } if (r >= 1.4) { return "m"; } if (r >= 0.9) { return "n"; } if (r >= 0.4) { return "x"; } return "z"; } }; struct SequencePattern : public PatternBase { SequencePattern(const InvalidCharsReplacer* pInvalidCharsReplacer) : PatternBase(pInvalidCharsReplacer) {} /*override*/ ~SequencePattern() { clearPtrContainer(m_vpPatterns); } /*override*/ string getVal(const Mp3Handler* pHndl) const { return getVal(pHndl, ACCEPT_EMPTY); } string getNonNullVal(const Mp3Handler* pHndl) const { return getVal(pHndl, DONT_ACCEPT_EMPTY); } // returns an empty string if any of its components are empty void addPattern(const PatternBase* p) { m_vpPatterns.push_back(p); } private: enum { DONT_ACCEPT_EMPTY, ACCEPT_EMPTY }; vector m_vpPatterns; string getVal(const Mp3Handler* pHndl, bool bAcceptEmpty) const { string strRes; for (int i = 0, n = cSize(m_vpPatterns); i < n; ++i) { string s (m_vpPatterns[i]->getVal(pHndl)); if (s.empty() && !bAcceptEmpty) { return s; } strRes += s; } return strRes; } }; struct OptionalPattern : public PatternBase { /*override*/ ~OptionalPattern() { delete m_pSequencePattern; } OptionalPattern(SequencePattern* pSequencePattern, const InvalidCharsReplacer* pInvalidCharsReplacer) : PatternBase(pInvalidCharsReplacer), m_pSequencePattern(pSequencePattern) {} /*override*/ string getVal(const Mp3Handler* pHndl) const { return m_pSequencePattern->getNonNullVal(pHndl); } private: SequencePattern* m_pSequencePattern; }; } // namespace FileRenamer //using namespace RenamerPatterns; Renamer::Renamer(const std::string& strPattern, const CommonData* pCommonData, bool bUnratedAsDuplicate1) : m_strPattern(strPattern), m_bSameDir(string::npos == strPattern.find(getPathSep())), m_pCommonData(pCommonData), m_bUnratedAsDuplicate(bUnratedAsDuplicate1) { if (0 != pCommonData) { m_pInvalidCharsReplacer.reset(new InvalidCharsReplacer(pCommonData->m_strRenamerInvalidChars, pCommonData->m_strRenamerReplacementString)); } else { m_pInvalidCharsReplacer.reset(new InvalidCharsReplacer("", "")); } m_pRoot = new SequencePattern(m_pInvalidCharsReplacer.get()); auto_ptr ap (m_pRoot); const char* p (strPattern.c_str()); SequencePattern* pSeq (m_pRoot); // pSeq is either m_pRoot or a new sequence, for optional elements if (0 == *p) { throw InvalidPattern(strPattern, tr("A pattern cannot be empty")); } // add pattern str on constr, to always have access to the pattern if (!m_bSameDir) { #ifndef WIN32 if (getPathSep() != *p) { throw InvalidPattern(strPattern, tr("A pattern must either begin with '%1' or contain no '%1' at all").arg(getPathSep())); } #else if (cSize(strPattern) < 3 || ((p[0] < 'a' || p[0] > 'z') && (p[0] < 'A' || p[0] > 'Z')) || p[1] != ':') // ttt2 allow network drives as well { throw InvalidPattern(strPattern, tr("A pattern must either begin with \":\\\" or contain no '\\' at all")); } p += 2; m_pRoot->addPattern(new StaticPattern(strPattern.substr(0, 2), m_pInvalidCharsReplacer.get())); if (getPathSep() != *p) { throw InvalidPattern(strPattern, tr("A pattern must either begin with \":\\\" or contain no '\\' at all")); } #endif } #ifndef WIN32 #else #endif auto_ptr optAp; string strStatic; bool bTitleFound (false); //const char* q (p); for (; *p != 0; ++p) { char c (*p); switch (c) { case '%': { ++p; char c1 (*p); switch (c1) { case 'n': case 'a': case 't': case 'b': case 'y': case 'g': case 'c': case 'r': if (!strStatic.empty()) { pSeq->addPattern(new StaticPattern(strStatic, m_pInvalidCharsReplacer.get())); strStatic.clear(); } } switch (c1) { case 'n': pSeq->addPattern(new TrackNoPattern(m_pInvalidCharsReplacer.get())); break; case 'a': pSeq->addPattern(new FieldPattern(TagReader::ARTIST, m_pInvalidCharsReplacer.get())); break; case 't': pSeq->addPattern(new FieldPattern(TagReader::TITLE, m_pInvalidCharsReplacer.get())); bTitleFound = true; break; case 'b': pSeq->addPattern(new FieldPattern(TagReader::ALBUM, m_pInvalidCharsReplacer.get())); break; case 'y': pSeq->addPattern(new YearPattern(m_pInvalidCharsReplacer.get())); break; case 'g': pSeq->addPattern(new FieldPattern(TagReader::GENRE, m_pInvalidCharsReplacer.get())); break; case 'c': pSeq->addPattern(new FieldPattern(TagReader::COMPOSER, m_pInvalidCharsReplacer.get())); break; //ttt2 perhaps add something for "various artists" case 'r': pSeq->addPattern(new RatingPattern(m_pInvalidCharsReplacer.get(), m_bUnratedAsDuplicate)); break; case '%': case '[': case ']': //case '/': // ttt linux-specific // actually there's no need for '/' to be a special character here strStatic += c; break; default: { throw InvalidPattern(strPattern, tr("Error in column %1.").arg(p - strPattern.c_str())); // ttt2 more details, perhaps make tag edt errors more similar to this } } } break; case '[': { if (!strStatic.empty()) { pSeq->addPattern(new StaticPattern(strStatic, m_pInvalidCharsReplacer.get())); strStatic.clear(); } if (pSeq != m_pRoot) { throw InvalidPattern(strPattern, tr("Nested optional elements are not allowed")); } //ttt2 column pSeq = new SequencePattern(m_pInvalidCharsReplacer.get()); optAp.reset(pSeq); break; } case ']': { if (!strStatic.empty()) { pSeq->addPattern(new StaticPattern(strStatic, m_pInvalidCharsReplacer.get())); strStatic.clear(); } if (pSeq == m_pRoot) { throw InvalidPattern(strPattern, tr("Trying to close and optional element although none is open")); } //ttt2 column m_pRoot->addPattern(new OptionalPattern(pSeq, m_pInvalidCharsReplacer.get())); pSeq = m_pRoot; optAp.release(); break; } default: strStatic += c; } } if (!strStatic.empty()) { pSeq->addPattern(new StaticPattern(strStatic, m_pInvalidCharsReplacer.get())); strStatic.clear(); } if (pSeq != m_pRoot) { throw InvalidPattern(strPattern, tr("Optional element must be closed")); } //ttt2 column if (!bTitleFound) { throw InvalidPattern(strPattern, tr("Title entry (%t) must be present")); } ap.release(); } Renamer::~Renamer() { delete m_pRoot; } string Renamer::getNewName(const Mp3Handler* pHndl) const { if (0 == pHndl->getId3V2Stream()) { return ""; } if (m_mValues.count(pHndl) > 0) { return m_mValues[pHndl]; } string s (m_pRoot->getVal(pHndl)); CB_ASSERT (!m_bSameDir ^ (string::npos == s.find(getPathSep()))); if (m_bSameDir) { s = getParent(pHndl->getName()) + getPathSepAsStr() + s; } if (string::npos != s.find("//")) { return ""; } if (!s.empty()) { s += ".mp3"; } return s; } //ttt2 should be possible to filter by var/single artists and do the renaming for all; or have a checkbox in the renamer, but that requires the renamer to have the concept of an album //ttt2 add "reload()" //ttt2 add palette MP3Diags-1.2.02/src/ThreadRunner.ui0000644000175000001440000000257611700346115015706 0ustar ciobiusers ThreadRunnerDlg 0 0 741 99 Thread Runner true TextLabel Qt::Horizontal 40 20 &Pause &Abort MP3Diags-1.2.02/src/MpegFrame.h0000644000175000001440000001140111476664262014765 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef MpegFrameH #define MpegFrameH #include #include #include #include "SerSupport.h" struct Note; class NoteColl; // based on http://mpgedit.org/mpgedit/mpeg_format/mpeghdr.htm struct MpegFrameBase { enum Version { MPEG1, MPEG2 }; enum Layer { LAYER1, LAYER2, LAYER3 }; enum ChannelMode { STEREO, JOINT_STEREO, DUAL_CHANNEL, SINGLE_CHANNEL }; enum { MPEG_FRAME_HDR_SIZE = 4 }; private: //void init(const unsigned char* pHeader, std::istream* pIn, std::streampos pos, int nRelPos); //bool hasXingHdr(); //bool hasVbriHdr(); // Amarok doesn't seem to care about this, so the Xing header must be used anyway for VBR void init(NoteColl& notes, const char* bfr); char m_header[4]; protected: Version m_eVersion; Layer m_eLayer; int m_nBitrate; int m_nFrequency; int m_nPadding; ChannelMode m_eChannelMode; int m_nSize; bool m_bCrc; std::streampos m_pos; public: //MpegFrame(const unsigned char* pHeader, std::istream& in, std::streampos pos, int nRelPos); //MpegFrame(const char* pHeader, std::istream& in, std::streampos pos, int nRelPos); //MpegFrame(std::istream& in, std::streampos pos); //MpegFrame(std::istream& in, std::streampos pos); MpegFrameBase(NoteColl& notes, std::istream& in); MpegFrameBase(); MpegFrameBase(NoteColl& notes, std::streampos pos, const char* bfr); std::ostream& write(std::ostream& out) const; Version getVersion() const { return m_eVersion; } Layer getLayer() const { return m_eLayer; } ChannelMode getChannelMode() const { return m_eChannelMode; } int getBitrate() const { return m_nBitrate; } int getFrequency() const { return m_nFrequency; } int getSize() const { return m_nSize; } // total size, including the header bool getCrcUsage() const { return m_bCrc; } std::streampos getPos() const { return m_pos; } const char* getSzVersion() const; const char* getSzLayer() const; const char* getSzChannelMode() const; const char* getHeader() const { return m_header; } int getSideInfoSize() const; // needed by Xing #ifdef GENERATE_TOC MpegFrameBase getBigBps() const; // returns a "similar" frame to "this" but with a bigger bps, so it can hold a Xing TOC #endif struct NotMpegFrame {}; // exception private: friend class boost::serialization::access; template void serialize(Archive& ar, const unsigned int nVersion) { if (nVersion > 0) { throw std::runtime_error("invalid version of serialized file"); } //ar & boost::serialization::base_object(*this); ar & m_header; ar & m_eVersion; ar & m_eLayer; ar & m_nBitrate; ar & m_nFrequency; ar & m_nPadding; ar & m_eChannelMode; ar & m_nSize; ar & m_bCrc; ar & m_pos; } }; struct MpegFrame : public MpegFrameBase { struct PrematurelyEndedMpegFrame // exception { std::string m_strInfo; PrematurelyEndedMpegFrame(const std::string& strInfo) : m_strInfo(strInfo) {} }; MpegFrame(NoteColl& notes, std::istream& in); MpegFrame() {} }; std::ostream& operator<<(std::ostream& out, const MpegFrameBase& frm); #endif // ifndef MpegFrameH MP3Diags-1.2.02/src/translations/0000755000175000001440000000000012477147651015475 5ustar ciobiusersMP3Diags-1.2.02/src/translations/mp3diags_cs.ts0000644000175000001440000110426312265753762020251 0ustar ciobiusers AboutDlg About MP3 Diags O programu MP3 Diags MP3 Diags x.y.z MP3 Diags x.y.z About O programu System info Informace o systému GPL V2 (for the program) GPL V2 (pro program) LGPL V3 (for the icons) LGPL V3 (pro ikony) GPL V3 (for the icons) GPL V3 (pro ikony) LGPL V2.1 (for Qt) LGPL V2.1 (pro Qt) Boost license Povolení pro Boost zlib license Povolení pro zlib OK OK AboutDlgImpl Written by %1, %2 Napsáno %1, %2 Command-line mode by %1, %2 Režim příkazového řádku %1, %2 %1 translation by %2, %3 PÅ™eklad do %1 %2, %3 Czech ÄeÅ¡tiny German nÄ›mÄiny French francouzÅ¡tiny Distributed under %1 Šířeno pod %1 Using %1, released under %2 Používá %1, vydáno pod %2 Using %1, released under the %2zlib License%3 Používá %1, vydáno pod %2zlib licence%3 Using %1 and %2, distributed under the %3Boost Software License%4 Používá %1 a %2, šířeno pod %3Boost Software License%4 Using original and modified icons from the %1 for %2, distributed under %3LGPL V3%4 Používá původní a upravené ikony z %1 pro %2, šířeno pod %3LGPL V3%4 Using web services provided by %1 to retrieve album data Používá internetové služby poskytované %1 pro získávání dat o albech Home page and documentation: %1 Domovské stránky a dokumentace: %1 Feedback and support: %1 or %2 at SourceForge ZpÄ›tná vazba a podpora: %1 nebo %2 na SourceForge Bug reports and feature requests: %1 at SourceForge Hlášení chyb a žádosti o vlastnosti: %1 na SourceForge Change log for the latest version: %1 Seznam zmÄ›n pro poslední verzi: %1 AlbumInfoDownloaderDlg WWW Internet Search Hledat Artist UmÄ›lec Album Album Match count PoÄet shod Results Výsledky Released Vydáno Genre Žánr Use Užití Format Formát Amazon Amazon Image Obrázek Image size Velikost obrázku ResultNo Výsledek Äíslo Previous album PÅ™edchozí album ... ... Previous image or album PÅ™edchozí obrázek nebo album p P Next image or album Další obrázek nebo album n N Next album Další album Filter: Filtr: CD CD Track count PoÄet stop Volume Hlasitost Save image Uložit obrázek Save all Uložit vÅ¡e Cancel ZruÅ¡it AlbumInfoDownloaderDlgImpl not found at amazon.com Nenalezeno na amazon.com searching ... Hledá se... Error Chyba You cannot save the results now, because a request is still pending Nemůžete výsledky uložit nyní, protože požadavek stále Äeká You cannot save the results now, because no album is loaded Nemůžete výsledky uložit nyní, protože není nahráno žádné album You may want to use a different volume selection on this multi-volume release. Možná budete chtít použít jiný výbÄ›r svazku na tomto vícesvazkovém vydání. A number of %1 tracks were expected, but your selection contains %2. Additional tracks will be discarded. %3Save anyway? Byl oÄekáván urÄitý poÄet skladeb %1, ale váš výbÄ›r obsahuje %2. DodateÄné skladby budou zahozeny. %3 PÅ™esto uložit? A number of %1 tracks were expected, but your selection only contains %2. Remaining tracks will get null values. %3Save anyway? Byl oÄekáván urÄitý poÄet skladeb %1, ale váš výbÄ›r obsahuje %2. Zbývající skladby dostanou nulové hodnoty. %3 PÅ™esto uložit? Count inconsistency Nesrovnalost v poÄtu &Save &Uložit Cancel ZruÅ¡it You cannot save any image now, because there is no image loaded Nyní nemůžete uložit žádný obrázek, protože není nahrán žádný obrázek request error Chyba požadavku received %1 bytes Obdrženo %1 bytů received very short response; aborting request ... Obdržena velmi krátká odpovÄ›Ä. Požadavek zruÅ¡en... Original: %1kB, %2x%3 Původní: %1kB, %2x%3 Recompressed to: %1kB, %2x%3 PÅ™ebaleno do: %1kB, %2x%3 Not recompressed NepÅ™ebaleno do Failed to load the image NepodaÅ™ilo se nahrát obrázek Error loading image Chyba pÅ™i nahrávání obrázku init error Chyba inicializace unexpected result NeoÄekávaný výsledek empty string received Obdržen prázdný Å™etÄ›zec search results received Obdrženy výsledky hledání Couldn't process the search result. (Usually this means that the server is busy, so trying later might work.) NepodaÅ™ilo se zpracovat výsledek hledání. (Obvykle to znamená, že je server zaneprázdnÄ›n, takže pozdÄ›jší pokus by mohl zabrat.) No results found Nenalezeny žádné výsledky album info received Obdrženy informace o albu Couldn't process the album information. (Usually this means that the server is busy, so trying later might work.) NepodaÅ™ilo se zpracovat informace o albu. (Obvykle to znamená, že je server zaneprázdnÄ›n, takže pozdÄ›jší pokus by mohl zabrat.) image received Obdržen obrázek <All> <VÅ¡e> Album %1/%2%3, image %4/%5 Album %1/%2%3, obrázek %4/%5 No image Žádný obrázek getting album info ... Získávají se informace o albu... getting image ... Získává se obrázek... ApeItem Ape stream whose items have unsupported flags. Proud APE, jehož položky mají nepodporované příznaky. Ape Item seems too big. Although the size may be any 32-bit integer, 256 bytes should be enough in practice. If this message is determined to be mistaken, it will be removed in the future. Item key: %1; item size: %2 Položka APE je příliÅ¡ velká. AÄkoli může být velikost jakékoli 32 bitové celé Äíslo, 256 bytů by v praxi mÄ›l být dostateÄné. Pokud je tato zpráva mylná, bude v budoucnu odstranÄ›na. KlÃ­Ä položky: %1; velikost položky: %2 ApeStream Tag missing header or footer. Záhlaví nebo zápatí postrádá znaÄku CommonData Info Informace The font changes will only be used after restarting the application. ZmÄ›ny písma se použijí až po znovuspuÅ¡tÄ›ní programu. Error Chyba There was an error setting up the directories containing MP3 files. You will have to define them again. PÅ™i nastavování adresářů obsahujících soubory MP3 se vyskytla chyba. Budete je muset urÄit znovu. ConfigDlg Configuration Nastavení Files Soubory Simple view Jednoduchý pohled Full view Plný pohled Removable TextLabel Odstranitelný textový Å¡títek Tab 1 Karta 1 Don't create backup for modified files Nevytvářet zálohu zmÄ›nÄ›ných souborů Create backup for modified files in VytvoÅ™it zálohu zmÄ›nÄ›ných souborů v ... ... Custom settings (go to full view to make changes) Vlastní nastavení (jít na úplný pohled pro udÄ›lání zmÄ›n) Tab 2 Karta 2 Original file that would be changed Původní soubor, který by byl zmÄ›nÄ›n Don't touch Nedotýkat se Erase Vymazat Move and change the name PÅ™esunout a zmÄ›nit název Move but change the name only if a name collision would occur PÅ™esunout, ale název zmÄ›nit jen v případÄ› výskytu stÅ™etu názvů Change name ZmÄ›nit název Move if the destination doesn't already exist; erase if it does PÅ™esunout, pokud cíl již neexistuje; vymazat pokud ano Name change params Parametry pro zmÄ›nu názvu Identifying label Rozpoznávací Å¡títek Don't use an identifying label Nepoužívat rozpoznávací Å¡títek Use "orig" as an identifying label Použít "orig" jako rozpoznávací Å¡títek Counter PoÄítadlo Always use a counter Vždy používat poÄítadlo Only use a counter when a name collision would occur PoÄítadlo použít jen v případÄ› výskytu stÅ™etu názvů Destination Cíl Original file that would not be changed Původní soubor, který by nebyl zmÄ›nÄ›n Changed file ZmÄ›nÄ›ný soubor Don't create Nevytvářet Create and change the name VytvoÅ™it a zmÄ›nit název Create but change the name only if a name collision would occur VytvoÅ™it, ale název zmÄ›nit jen v případÄ› výskytu stÅ™etu názvů Use "proc" as an identifying label Použít "proc" jako rozpoznávací Å¡títek Use the source dir Použít zdrojový adresář Use Použít Temporary files DoÄasné soubory Source directory Zdrojový adresář Create in VytvoÅ™it v Compare files Porovnat soubory Ignored notes PÅ™ehlížené poznámky Custom transformation lists Vlastní proměňovací seznamy Transformation params Parametry promÄ›ny Locale for text conversion Jazyk pro pÅ™evod textu Case transformation PromÄ›na velikosti písmen Artists UmÄ›lci Others Ostatní Keep original modification time when changing a file PÅ™i zmÄ›nÄ› souboru zachovat původní Äas zmÄ›ny Keep a single image for a file Mít jeden obrázek pro soubor Visible Transformations Viditelné promÄ›ny Quality thresholds Jakostní prahy If the audio bitrate falls below these limits, warnings are generated. For VBR a low bitrate is less of an indicatior of poor quality, because if the signal is simple there's no need for higher bitrates. Pokud datový tok zvuku spadne pod tyto hranice, jsou vytvoÅ™ena varování. Pro promÄ›nlivý datový tok (PDT; Variable Bitrate - VBR) je nízký datový tok ukazatelem bídné jakosti ménÄ›, protože pokud je signál jednoduchý, není žádná potÅ™eba vyššího datového toku. The bitrate is not the best quality indicator. Values from a "Lame header" (if present) are generally better, but these headers are not processed in the current version. Datový tok není nejlepším ukazatelem jakosti. Hodnoty z hlaviÄky LAME (je-li přítomna), jsou obecnÄ› vzato lepší, ale tyto hlaviÄky souÄasná verze programu nezpracovává. Note that for mono streams, half of the value for "Dual Channel" is used. VÅ¡imnÄ›te si, že pro monofonní proudy se používá polovina hodnoty používané pro dvojitý kanál. Stereo CBR Stereo SDT (CBR) Joint Stereo CBR Smíšené stereo SDT (CBR) Dual Channel CBR Dvojkanálový SDT (CBR) Stereo VBR Stereo PDT (VBR) Joint Stereo VBR Smíšené stereo PDT (VBR) Dual Channel VBR Dvojkanálový PDT (VBR) Colors Barvy Audio Zvuk Xing and LAME Xing a LAME VBRI ID3V2 APIC ID3V2.3.0 ID3V2.4.0 ID3V1 Broken streams PoÅ¡kozené proudy Truncated streams Zkrácené proudy Unknown streams Neznámé proudy Lyrics Text písnÄ› Ape Ape Reset to default colors Nastavit znovu výchozí barvy Shell Shell Enable temporary session per folder Povolit doÄasné sezení na složku Enable hidden session per folder Povolit skryté sezení na složku Enable visible session per folder Povolit viditelné sezení na složku ErrorShell ChybaShell External tools VnÄ›jší nástroje Name Název Command Příkaz Wait PoÄkat Don't wait NeÄekat Wait for external tool to finish, then keep launch window open PoÄkejte, až vnÄ›jší nástroj skonÄí, pak ponechejte spouÅ¡tÄ›cí okno otevÅ™ené Wait for external tool to finish, then close launch window PoÄkejte, až vnÄ›jší nástroj skonÄí, pak zavÅ™ete spouÅ¡tÄ›cí okno Confirm launch Potvrdit spuÅ¡tÄ›ní Confirm Potvrdit Add PÅ™idat Update Aktualizovat Delete Smazat Discard changes Zahodit zmÄ›ny Tag editor Editor znaÄek Warn when the tag editor enters albums with non-sequential track numbers Varovat, když editor znaÄek vejde do alb s Äísly skladeb, která nejdou postupnÄ› po sobÄ› Warn when pasting track information in the tag editor for albums with non-sequential track numbers Varovat, když se v editoru znaÄek vkládá informace o skladbÄ› do alb s Äísly skladeb, která nejdou postupnÄ› po sobÄ› Use "fast save" in the tag editor Použít v editoru znaÄek rychlé uložení Maximum image size, in kB NejvÄ›tší velikost obrázku v kB If an image size is above this value, it will be recompressed before storing it inside MP3 files Pokud je velikost obrázku nad touto hodnotou, bude pÅ™ed uložením do souborů MP3 znovu zkomprimován Handle "various artists" for Vyřídit různé umÄ›lce pro iTunes iTunes Windows Media Player Windows Media Player If a field is marked as "assigned" when exiting an album Pokud je pole pÅ™i opuÅ¡tÄ›ní alba oznaÄeno jako "pÅ™iÅ™azené" Save automatically Uložit automaticky Discard Zahodit Ask Zeptat se If a field's value is different from that in the ID3V2 tag when exiting an album Pokud je hodnota pole pÅ™i opuÅ¡tÄ›ní alba jiná než je hodnota ve znaÄce ID3V2 Misc Různé Scan for new, modified, and deleted files at startup PÅ™i spuÅ¡tÄ›ní hledat nové, zmÄ›nÄ›né a smazané soubory Show "Export" button Ukázat tlaÄítko Vyvést Show "Debug" button Ukázat tlaÄítko Ladit Show "Sessions" button Ukázat tlaÄítko Sezení Show Gnome 3 close buttons Ukázat zavírací tlaÄítka Gnome 3 Log program state to "_trace" and "_step" files Zapsat stav programu do souborů "_trace" a "_step" Check for new version at startup PÅ™i spuÅ¡tÄ›ní ověřit dostupnost nové verze Language Jazyk General font: Obecné písmo: TextLabel Textový Å¡títek Change ... ZmÄ›nit... Label font smaller by: Písmo Å¡títku menší o: Fixed font: Pevná šířka znaků: Icon size Velikost ikon Auto-size icons based on the width of the main window Automaticky mÄ›nit velikost ikon podle šířky hlavního okna Normalizer Normalizátor Command (file names will get added at the end) Příkaz (názvy souborů budou pÅ™idány na konec) Keep the window open after completion Po doplnÄ›ní ponechat okno otevÅ™ené File renamer PÅ™ejmenovávaÄ souborů Invalid characters Neplatné znaky Replace with Nahradit: O&K &OK Cancel ZruÅ¡it ConfigDlgImpl <all notes> <VÅ¡echny poznámky> If you don't know exactly what codepage you want, it's better to make current a file having an ID3V2 tag that contains text frames using the Latin-1 encoding and having non-ASCII characters. Then the content of those frames will replace this text, allowing you to decide which codepage is a match for your file. ID3V1 tags are supported as well, if you want to copy data from them to ID3V2. Pokud pÅ™esnÄ› nevíte, jakou kódovou stránku chcete, je lepší udÄ›lat soubor se znaÄkou ID3V2, který obsahuje textová pole za použití kódování Latin-1 a se znaky ne-ASCII. Potom nahradí obsah tÄ›ch polí tento text, Äímž vám umožní se rozhodnout, která kódová stránka odpovídá vaÅ¡emu souboru. Jsou podporovány i znaÄky ID3V1, pokud chcete data zkopírovat z nich do ID3V2. Other notes Jiné poznámky Ignore notes PÅ™ehlížet poznámky lower case Malá písmena UPPER CASE Velká písmena Title Case Velikost písmen názvu Sentence case Velikost písmen vÄ›ty Invisible transformations Neviditelné promÄ›ny Visible transformations Viditelné promÄ›ny Characters in this list get replaced with the string below, in "Replace with" An underlined font is used to allow spaces to be seen Znaky v tomto seznamu budou nahrazeny Å™etÄ›zcem níže, v "Nahradit". Podtržené písmo je dovoleno, aby byly vidÄ›ny mezery This string replaces invalid characters in the file renamer" An underlined font is used to allow spaces to be seen Tento Å™etÄ›zec nahradí neplatné znaky v pÅ™ejmenovávaÄi souborů Podtržené písmo je dovoleno, aby byly vidÄ›ny mezery All transformations VÅ¡echny promÄ›ny Used transformations Použité promÄ›ny Confirm Potvrdit You modified the external tool information but you didn't save your changes. Discard the changes or cancel closing of the options window? ZmÄ›nil jste informace o vnÄ›jším nástroji, ale neuložil jste své zmÄ›ny. Zahodit zmÄ›ny nebo zruÅ¡it zavÅ™ení okna s volbami? &Discard &Zahodit &Cancel Z&ruÅ¡it Error Chyba You can't have '%1' in both the list of invalid characters and the string that invalid characters are replaced with. Nemůžete mít '%1' jak v seznamu neplatných znaků tak v Å™etÄ›zci, kterým jsou neplatné znaky nahrazeny. Info Informace You need to restart the program to use the new language. Aby se projevila zmÄ›na jazyka, je tÅ™eba program spustit znovu. Invalid folder name Neplatný název složky A folder name is incorrect. Název složky je neplatný. Add selected note(s) PÅ™idat vybranou(é) poznámku(y) Remove selected note(s) Odstranit vybranou(é) poznámku(y) Add all notes PÅ™idat vÅ¡echny poznámky Remove all notes Odstranit vÅ¡echny poznámky Restore lists to their default value Obnovit seznamy na jejich výchozí hodnotu Restore lists to the configuration they had when the window was open Obnovit seznamy na nastavení, které mÄ›ly, když bylo okno otevÅ™eno Select folder Vybrat složku All files VÅ¡echny soubory CustomTransfListPainter Action Krok Description Popis Add selected transformation(s) PÅ™idat vybranou(é) promÄ›nu(y) Remove selected transformation(s) Odstranit vybranou(é) promÄ›nu(y) Restore current list to its default value Obnovit nynÄ›jší seznam na jeho výchozí hodnotu Restore current list to the configuration it had when the window was open Obnovit nynÄ›jší seznam na nastavení, které mÄ›l, když bylo okno otevÅ™eno DataStream begins with: ZaÄíná: Broken %1 PoÅ¡kozený %1 Unsupported %1 Nepodporovaný %1 Unknown Neznámý Truncated MPEG Zkrácený MPEG Null Nulový MPEG Audio Zvuk MPEG Xing Header HlaviÄka Xing Lame Header HlaviÄka Lame VBRI Header HlaviÄka VBRI padding= Vycpávka= unsynch= Nesynch= frames Snímky frame count= PoÄet snímků= last frame removed; it was located at 0x%1 Poslední snímek odstranÄ›n, byl umístÄ›n na 0x%1 last frame located at 0x%1 Poslední snímek umístÄ›n na 0x%1 Xing header info: Informace o hlaviÄce Xing: frame count= PoÄet snímků= byte count= PoÄet bytů= TOC present Přítomný obsah (TOC) quality= Kvalita= MPEG-1 MPEG-1 MPEG-2 MPEG-2 Layer I Vrstva I Layer II Vrstva II Layer III Vrstva III Stereo Stereo Joint stereo Smíšené stereo Dual channel Dva kanály Single channel Jeden kanál Not an MPEG frame. Synch missing. Není snímek MPEG. Seřízení chybí. Not an MPEG frame. Unsupported version (2.5). Není snímek MPEG. Nepodporovaná verze (2.5). Not an MPEG frame. Invalid version. Není snímek MPEG. Neplatná verze. Not an MPEG frame. Invalid layer. Není snímek MPEG. Neplatná vrstva. Not an MPEG frame. Invalid bitrate. Není snímek MPEG. Neplatný datový tok. Not an MPEG frame. Invalid frequency for MPEG1. Není snímek MPEG. Neplatný kmitoÄet pro MPEG1. Not an MPEG frame. Invalid frequency for MPEG2. Není snímek MPEG. Neplatný kmitoÄet pro MPEG2. %1 %2%3%4%5%6Hz%7%8bps%9CRC=%10%11length %12 (0x%13)%14padding=%15 %1 %2%3%4%5%6Hz%7%8bps%9CRC=%10%11délka %12 (0x%13)%14vycpávka=%15 length= Délka= N/A Nedostupné DebugDlg Debug Ladit Enable tracing Povolit stopování Save trace messages Uložit sledovací zprávy ... ... Decode MPEG Audio frame header Dekódovat hlaviÄku snímku zvuku MPEG dec Dek Test VyzkouÅ¡et tst ZkouÅ¡ka Close Zavřít Use all notes Použít vÅ¡echny poznámky Log transformations Zápis promÄ›n Save downloaded data Uložit stažená data Trace messages: Sledovat zprávy: DebugDlgImpl If this is checked, ignored notes and trace notes are shown in the note list and exported, regardless of the "Ignored" settings. Note that if this is not checked, the trace notes are discarded during file scanning, so checking it later won't bring them back. A new scan is needed to see them. this is a multiline tooltip Pokud je toto zaÅ¡krtnuto, jsou ukázány pÅ™ehlížené poznámky a stopovací poznámky v seznamu poznámek a vyvedeny, bez ohledu na nastavení "PÅ™ehlíženo". VÅ¡imnÄ›te si, že pokud to zaÅ¡krtnuto není, jsou sledovací poznámky bÄ›hem prohledávání souboru zahozeny, takže pozdÄ›jší zaÅ¡krtnutí je zpÄ›t nepÅ™ivede. Pro jejich zobrazení je potÅ™eba nové prohledání. Choose destination file Vybrat cílový soubor Text files (*.txt) Textové soubory (*.txt) Decoded MPEG frame header Dékódovaná hlaviÄka snímku MPEG DirFilterDlg Folder filter Filtr složek OK OK Cancel ZruÅ¡it DirFilterDlgImpl <all folders> <VÅ¡echny složky> Available folders Dostupné složky Include folders Zahrnout složky Add selected folders PÅ™idat vybrané složky Remove selected folders Odstranit vybrané složky Restore lists to the configuration they had when the window was open Obnovit seznamy na nastavení, které mÄ›ly, když bylo okno otevÅ™eno Folder Složka DiscogsDownloader Download album data from Discogs.com Stáhnout data o albu z Discogs.com Genres Žánry Genres, Styles Žánry, styly Genres (Styles) Žánry (styly) Styles Styly DoubleListWdg Form Formulář Include elems: Zahrnout prvky: Available elems: Dostupné prvky: < < > > >> >> ... ... ExportDlg Export Vyvést File name: Název souboru: ... ... Format Formát XML XML Text Text M3U M3U Remove root: Odstranit koÅ™en: When creating an M3U file, this text gets removed from the beginning of the file names. Meant to be used for creating M3U files containing relative paths. PÅ™i vytváření souboru M3U bude tento text odstranÄ›n ze zaÄátku názvů souborů. Zamýšleno pro používání pÅ™i vytváření souborů M3U obsahujících relativní cesty. Locale: Jazyk: Files to save Soubory k uložení Visible files Viditelné soubory Selected files Vybrané soubory Sort by short names Třídit podle krátkých názvů Close Zavřít ExportDlgImpl Error Chyba The file name cannot be empty. Exiting ... Název souboru nemůže být prázdný. UkonÄuje se... You need to specify an absolute file name when exporting to formats other than .m3u. Exiting ... Je potÅ™eba stanovit absolutní název souboru, když se dÄ›lá vyvedení do formátů jiných než je .m3u. UkonÄuje se... The root cannot be empty if the file name is relative. Exiting ... KoÅ™en nemůže zůstat prázdný, pokud je název souboru relativní. UkonÄuje se... The root must be an absolute directory name. Exiting ... KoÅ™en musí být absolutní název adresáře. UkonÄuje se... The root doesn't exist. Exiting ... KoÅ™en neexistuje. UkonÄuje se... Warning Varování A file called "%1" already exists. Do you want to overwrite it? Soubor s názvem "%1" již existuje. Chcete jej pÅ™epsat? Cancel ZruÅ¡it &Overwrite &PÅ™epsat Info Informace Successfully created file "%1" ÚspěšnÄ› vytvoÅ™en soubor "%1" O&K &OK EWST the letters are the initials of the 4 severity levels: Error, Warning, Support, Trace ChybVarPodpSled There was an error writing to the file "%1" PÅ™i zápisu do souboru "%1" se vyskytla chyba Choose destination file Vybrat cílový soubor XML files (*.xml);;Text files (*.txt);;M3U files (*.m3u) Soubory XML (*.xml);;Textové soubory (*.txt);;Soubory M3U (*.m3u) The file named "%1" isn't inside the specified root. Exiting ... Soubor pojmenovaný "%1" není v zadaném koÅ™eni. UkonÄuje se... The file named "%1" cannot be encoded in the selected locale. Exiting ... Soubor pojmenovaný "%1" nelze zakódovat ve vybraném jazyce. UkonÄuje se... ExternalToolDlg External tool VnÄ›jší nástroj Keep window open after completion Ponechat okno po doplnÄ›ní otevÅ™ené Abort ZruÅ¡it Close Zavřít ExternalToolDlgImpl Error Chyba Cannot start process. Check that the executable name and the parameters are correct. Nelze spustit proces. Ověřte, zda jsou název spustitelného souboru a parametry správné. Finished Hotovo Warning Varování Cannot close while "%1" is running. Nelze zavřít, když běží "%1". Confirm Potvrdit Stopping "%1" may leave the files in an inconsistent state or may prevent temporary files from being deleted. Are you sure you want to abort "%1"? Zastavení "%1" může soubory zanechat v nesoudržném stavu nebo může zabránit tomu, aby byly doÄasné soubory smazány. Jste si jistý, že chcete zruÅ¡it "%1"? Yes, abort Ano. ZruÅ¡it Don't abort NeruÅ¡it ExternalToolsModel Name Název Command Příkaz Wait PoÄkat Confirm launch Potvrdit spuÅ¡tÄ›ní FileRenamer::HndlrListModel << missing ID3V2 >> << chybí ID3V2 >> << no pattern defined >> << nestanoven žádný vzor >> << missing fields >> << chybí pole >> File name Název souboru New file name Nový název souboru FileRenamerDlg MP3 Diags - File renamer MP3 Diags - PÅ™ejmenovávaÄ souborů Previous [Ctrl+P] PÅ™edchozí [Ctrl+P] < < Ctrl+P Ctrl+P Folder Složka Next [Ctrl+N] Další [Ctrl+N] > > Ctrl+N Ctrl+N If this is checked, a copy of the file is created, and both original and copy are kept Pokud je toto zaÅ¡krtnuto, je vytvoÅ™ena kopie souboru, a jsou zachovány jak původní soubor tak jeho kopie Keep the original file Zachovat původní soubor Mark unrated as duplicates OznaÄit nehodnocené jako kopie Edit patterns Upravit vzory ... ... Rename PÅ™ejmenovat Close Zavřít alb type Typ alba FileRenamerDlgImpl Source or destination is a directory Zdroj nebo cíl je adresář Error during copying Chyba pÅ™i kopírování Error during renaming Chyba pÅ™i pÅ™ejmenovávání Destination already exists Cíl již existuje Source not found Zdroj nenalezen Cannot create folder %1 Nelze vytvoÅ™it složku %1 Unknown error Neznámá chyba No patterns exist Neexistuje žádný vzor You must create at least a pattern before you can start renaming files. Musíte vytvoÅ™it alespoň vzor, pÅ™edtím než můžete zaÄít pÅ™ejmenovávat soubory. Copy all the files? Kopírovat vÅ¡echny soubory? Copy the selected files? Kopírovat vybrané soubory? Rename all the files? PÅ™ejmenovat vÅ¡echny soubory? Rename the selected files? PÅ™ejmenovat vybrané soubory? Confirm Potvrdit &Yes &Ano Cancel ZruÅ¡it Error Chyba Operation aborted because file "%1" doesn't have an ID3V2 tag. Operace zruÅ¡ena, protože soubor "%1" nemá znaÄku ID3V2. Operation aborted because file "%1" is missing some required fields in its ID3V2 tag. Operace zruÅ¡ena, protože souboru "%1" v jeho znaÄce ID3V2 chybí nÄ›která požadovaná pole. Operation aborted because it would create 2 copies of a file called "%1" Operace zruÅ¡ena, protože by vytvoÅ™ila dvÄ› kopie souboru s názvem "%1" Operation aborted because a file called "%1" already exists. Operace zruÅ¡ena, protože soubor s názvem "%1" již existuje. Copying all the files in the current album Kopírování vÅ¡ech souborů v nynÄ›jším albu Copying the selected files in the current album Kopírování vybraných souborů v nynÄ›jším albu Renaming all the files in the current album PÅ™ejmenování vÅ¡ech souborů v nynÄ›jším albu Renaming the selected files in the current album PÅ™ejmenování vybraných souborů v nynÄ›jším albu Single artist Jeden umÄ›lec Various artists Různí umÄ›lci Error setting up patterns Chyba pÅ™i nastavování vzorů An invalid value was found in the configuration file. You'll have to set up the patterns manually. V souboru s nastavením byla nalezena neplatná hodnota. Budete muset vzory nastavit ruÄnÄ›. FilesModel File name Název souboru FixedAddrRemover Remove stream %1 at address 0x%2 Odstranit proud %1 na adrese 0x%2 GlobalTranslHlp yes ano no ne Don't wait NeÄekat Wait for external tool to finish, then close launch window PoÄkejte, až vnÄ›jší nástroj skonÄí, pak zavÅ™ete spouÅ¡tÄ›cí okno Wait for external tool to finish, then keep launch window open PoÄkejte, až vnÄ›jší nástroj skonÄí, pak ponechejte spouÅ¡tÄ›cí okno otevÅ™ené These settings cannot currently be changed. In order to make changes you should probably run the program as an administrator. Tato nastavení nelze v souÄasnosti zmÄ›nit. Abyste mohl tyto zmÄ›ny provést, budete pravdÄ›podobnÄ› muset program spustit jako správce systému. Platform not supported Platforma nepodporována O&K &OK HtmlMsg I got the message; don't show this again Zpráva obdržena. Znovu ji neukazovat Id3V230Frame Truncated ID3V2.3.0 tag. Zkrácená znaÄka ID3V2.3.0. Broken ID3V2.3.0 tag. PoÅ¡kozená znaÄka ID3V2.3.0. ID3V2.3.0 tag containing a frame with an invalid name: %1. ZnaÄka ID3V2.3.0 obsahuje pole s neplatným názvem: %1. ID3V2.3.0 tag containing a frame with an unsupported flag. ZnaÄka ID3V2.3.0 obsahuje pole s nepodporovaným příznakem. %1 (Frame: %2) %1 (Pole: %2) INVALID Neplatný ID3V2.3.0 tag containing a broken text frame named %1. ZnaÄka ID3V2.3.0 obsahuje poÅ¡kozené textové pole s názvem %1. ID3V2.3.0 tag containing a text frame named %1 using unsupported characters. ZnaÄka ID3V2.3.0 obsahuje pole s názvem %1 používající nepodporované znaky. Id3V230Stream Unsupported version of ID3V2 tag%1 Nepodporovaná verze znaÄky ID3V2 %1 ID3V2 tag with unsupported flag. ZnaÄka ID3V2 s nepodporovaným příznakem. Id3V240Frame Truncated ID3V2.4.0 tag. Zkrácená znaÄka ID3V2.4.0. ID3V2.4.0 tag containing a frame with an invalid name: %1. ZnaÄka ID3V2.4.0 obsahuje pole s neplatným názvem: %1. ID3V2.4.0 tag containing a frame with an unsupported flag. ZnaÄka ID3V2.4.0 obsahuje pole s nepodporovaným příznakem. Broken ID3V2.4.0 tag. PoÅ¡kozená znaÄka ID3V2.4.0. %1 (Frame: %2) %1 (Pole: %2) INVALID Neplatný ID3V2.4.0 tag containing a broken text frame named %1. ZnaÄka ID3V2.4.0 obsahuje poÅ¡kozené textové pole s názvem %1. ID3V2.4.0 tag containing a text frame named %1 using unsupported characters or unsupported text encoding. ZnaÄka ID3V2.4.0 obsahuje textové pole s názvem %1 používající nepodporované znaky nebo nepodporované kódování textu. Id3V240Stream ID3V2 tag with unsupported flag. ZnaÄka ID3V2 s nepodporovaným příznakem. Id3V2Frame << error decoding string >> << chyba pÅ™i dekódování Å™etÄ›zce >> << unsupported encoding >> << nepodporované kódování >> size= Velikost= invalid text encoding Neplatné kódování textu unsupported text encoding Nepodporované kódování textu <encoding error> <Chyba v kódování> invalid data Neplatná data MIME="%1" File="%2" Descr="%3" Binary data size=%4 MIME="%1" Soubor="%2" Popis="%3" Velikost binárních dat=%4 Begins with: %1 ZaÄíná: %1 Content: %1 Obsah: %1 status=%1 Stav=%1 link Odkaz non-cover Ne-obal error Chyba cover Obal Id3V2StreamBase %1 (Frame: %2) %1 (Pole: %2) ImageInfoPanel <error> <Chyba> Click to see larger image KlepnÄ›te, abyste uvidÄ›li vÄ›tší obrázek ImageInfoPanelWdg Form Formulář Thumb Náhled Pos Poloha Dim RozmÄ›ry Size Velikost View full-size image Zobrazit obrázek v plné velikosti ... ... Assign picture to songs PÅ™iÅ™adit obrázek k písním Erase local file Vymazat místní soubor ImageInfoPanelWdgImpl Erase these files: Smazat tyto soubory: Erase file %1 Smazat soubory %1 InvalidPattern Pattern "%1" is invalid. %2 Vzor "%1" je neplatný. %2 LogModel Message Hlášení MainFormDlg Dialog Dialog Scan folders for MP3 files [Ctrl+S] Prohledat složky na soubory MP3 [Ctrl+S] prc Ctrl+S Ctrl+S Close this window and open the Session editor Zavřít toto okno a otevřít editor sezení ... ... Export ... Vyvést... Filter by notes Filtrovat podle poznámek nflt Poznámky Filter by folders Filtrovat podle složek dflt Složky Show the full list of files (after applying the filters) Ukázat úplný seznam souborů (po použití filtrů) Show one album (i.e. folder) at a time Ukázat jedno album (tj. složku) souÄasnÄ› d d Show one song at a time Ukázat jednu píseň souÄasnÄ› f f Previous [Ctrl+P] PÅ™edchozí [Ctrl+P] < < Ctrl+P Ctrl+P Folder Složka Next [Ctrl+N] Další [Ctrl+N] > > Ctrl+N Ctrl+N Apply a single transformation Použít jednoduchou promÄ›nu rep Apply custom set of transforms #1 Použít vlastní množinu promÄ›n #1 Apply custom set of transforms #2 Použít vlastní množinu promÄ›n #2 Apply custom set of transforms #3 Použít vlastní množinu promÄ›n #3 Tag editor Editor znaÄek Normalize Normalizovat Reload Nahrát znovu Rename files PÅ™ejmenovat soubory Configuration Nastavení cfg Debug Ladit About O File info Informace o souboru All notes VÅ¡echny poznámky Tag details Podrobnosti ke znaÄce Removable TextLabel Odstranitelný textový Å¡títek MainFormDlgImpl Assertion failure NeúspÄ›ch prohlaÅ¡ování Plese report this problem to the project's Issue Tracker at %1 NahlaÅ¡te, prosím, potíže sledování potíží projektu na %1 Please restart the application for instructions about how to report this issue SpusÅ¥te, prosím, program znovu pro pokyny k tomu, jak nahlásit tuto vÄ›c Exit UkonÄit Because MP3 Diags changes the content of your MP3 files if asked to, it has a significant destructive potential, especially in cases where the user doesn't read the documentation and simply expects the program to do other things than what it was designed to do. Protože MP3 Diags mÄ›ní obsah vaÅ¡ich souborů MP3, když je o to požádáte, má znaÄné pustoÅ¡ivé schopnosti, zvláštÄ› v případech, kdy uživatel neÄte dokumentaci a jednoduÅ¡e oÄekává, že program bude dÄ›lat jiné vÄ›ci, než pro jaké byl navržen, aby je dÄ›lal. Therefore, it is highly advisable to back up your files first. Takže se velice doporuÄuje zazálohovat nejprve vaÅ¡e data. Also, although MP3 Diags is very stable on the developer's computer, who hasn't experienced a crash in a long time and never needed to restore MP3 files from a backup, there are several crash reports that haven't been addressed, as the developer couldn't reproduce the crashes and those who reported the crashes didn't answer the developer's questions that might have helped isolate the problem. I když je program MP3 Diags velmi stabilní na poÄítaÄi svého vývojáře, který už dlouho nezakusil jeho pád a nikdy nepotÅ™eboval soubory MP3 obnovovat ze zálohy, vyskytlo se nÄ›koli hlášení o pádech, které ale nebyly urÄeny, protože se vývojáři nepodaÅ™ilo tyto pády zopakovat a ti, kteří je nahlásili zase nezodpovÄ›dÄ›li vývojářovy otázky, které by pomohly s oddÄ›lením problému. An unknown note was found in the configuration. This note is unknown: %1 V nastavení byla nalezena neznámá poznámka. Tato poznámka je neznámá: %1 Unknown notes were found in the configuration. These notes are unknown: %1 V nastavení byly nalezeny neznámé poznámky. Tyto poznámky jsou neznámé: %1 Error setting up the "ignored notes" list Chyba pÅ™i nastavování seznamu "PÅ™ehlížené poznámky" You may want to check again the list and add any notes that you want to ignore. (If you didn't change the settings file manually, this is probably due to a code enhanement that makes some notes no longer needed, and you can safely ignore this message.) Možná budete chtít prověřit seznam znovu a pÅ™idat vÅ¡echny poznámky, které se mají pÅ™ehlížet. (Pokud soubor s nastavením nezmÄ›níte ruÄnÄ›, je to pravdÄ›podobnÄ› kvůli vylepÅ¡ení kódu, které vedlo k tomu, že nÄ›které poznámky už nejsou potÅ™eba, a vy můžete bezpeÄnÄ› takovouto zprávu ignorovat.) If there is a name of an MP3 file at the end of <b>%1</b>, that might be a file that consistently causes a crash. Please check if it is so. Then, if confirmed, please make that file available by mailing it to %2 or by putting it on a file sharing site. Pokud je na konci <b>%1</b> název souboru MP3, který by mohl být souborem působícím pád. Prověřte, prosím, jestli to tak je. Potom, potvrdí-li se to, udÄ›lejte, prosím, takový soubor dostupným jeho posláním %2 nebo jeho umístÄ›ním na stránky pro sdílení souborů. Error Chyba MP3 Diags crashed while reading song data from the disk. The whole collection will be rescanned. MP3 Diags spadl pÅ™i Ätení dat písnÄ› z disku. Celá sbírka bude prohlédnuta znovu. An error occured while loading the MP3 information. Your files will be rescanned. PÅ™i nahrávání informací o MP3 se vyskytla chyba. VaÅ¡e soubory budou prohlédnuty znovu. Warning Varování Restarting after crash OpÄ›tovné spuÅ¡tÄ›ní po pádu Note Poznámka If you simply left-click, all the visible files get processed. However, it is possible to process only the selected files. To do that, either keep SHIFT pressed down while clicking or use the right button, as described at %1 Pokud jednoduÅ¡e klepnete levým tlaÄítkem myÅ¡i, budou vÅ¡echny viditelné soubory zpracovány. Je ale možné zpracovat pouze vybrané soubory. Abyste je oznaÄil, buÄ držte pÅ™i klepání na položky stisknutu klávesu Shift, nebo použijte pravé tlaÄítko myÅ¡i, jak je to popsáno v %1 MP3 Diags is restarting after a crash. MP3 Diags se po pádu spouÅ¡tí znovu. Information in the file %1%5%2 may help identify the cause of the crash so please make it available to the developer by mailing it to %3, by reporting an issue to the project's Issue Tracker at %4 and attaching the files to the report, or by some other means (like putting it on a file sharing site.) %1 and %2 are HTML elements Informace v %1%5%2 mohou pomoci s urÄením příÄiny pádu, takže je, prosím, dejte dál vývojáři jejich posláním na %3, nahlášením potíží sledování chyb v projektu %4 a pÅ™ipojením souborů ke zprávÄ›, nebo jinými prostÅ™edky (jako je jejich nahrání na službu pro sdílení souborů) Information in the files %1%5%2 and %1%6%2 may help identify the cause of the crash so please make them available to the developer by mailing them to %3, by reporting an issue to the project's Issue Tracker at %4 and attaching the files to the report, or by some other means (like putting them on a file sharing site.) %1 and %2 are HTML elements Informace v %1%5%2 a %1%6%2 mohou pomoci s urÄením příÄiny pádu, takže je, prosím, dejte dál vývojáři jejich posláním na %3, nahlášením potíží sledování chyb v projektu %4 a pÅ™ipojením souborů ke zprávÄ›, nebo jinými prostÅ™edky (jako je jejich nahrání na službu pro sdílení souborů) Information in the files %1%5%2, %1%6%2, and %1%7%2 may help identify the cause of the crash so please make them available to the developer by mailing them to %3, by reporting an issue to the project's Issue Tracker at %4 and attaching the files to the report, or by some other means (like putting them on a file sharing site.) %1 and %2 are HTML elements Informace v %1%5%2, %1%6%2 a %1%7%2 mohou pomoci s urÄením příÄiny pádu, takže je, prosím, dejte dál vývojáři jejich posláním na %3, nahlášením potíží sledování chyb v projektu %4 a pÅ™ipojením souborů ke zprávÄ›, nebo jinými prostÅ™edky (jako je jejich nahrání na službu pro sdílení souborů) These are plain text files, which you can review before sending, if you have privacy concerns. Jsou tu soubory s prostým textem, na které byste se mÄ›l pÅ™ed odesláním podívat, pokud máte zájem o soukromí. After getting the files, the developer will probably want to contact you for more details, so please check back on the status of your report. Po obdržení souborů se s vámi bude vývojář pravdÄ›podobnÄ› chtít spojit kvůli dalším podrobnostem, takže se, prosím, obÄas podívejte na stav své zprávy. Note that these files <b>will be removed</b> when you close this window. UvÄ›domte si, že tyto soubory <b>budou odstranÄ›ny</b>, až zavÅ™ete toto okno. Please also try to <b>repeat the steps that led to the crash</b> before reporting the crash, which will probably result in a new set of files being generated; these files are more likely to contain relevant information than the current set of files, because they will also have information on what happened before the crash, while the current files only tell where the crash occured. Také se, prosím, pokuste o <b>zopakování kroků vedoucích k pádu</b>, než pád nahlásíte. To pravdÄ›podobnÄ› povede k novému souboru vytvoÅ™ených souborů. Tyto soubory budou pravdÄ›podobnÄ› obsahovat závažnÄ›jší informace než běžný soubor informací, protože budou také obsahovat informace o tom, co se stalo pÅ™ed pádem, zatímco nynÄ›jší soubory jen Å™eknou, kde k pádu doÅ¡lo. You should include in your report any other details that seem relevant (what might have caused the failure, steps to reproduce it, ...) Do své zprávy byste mÄ›li zahrnout jakékoli další podrobnosti, které se zdají být důležité (co mohlo zapříÄinit selhání, kroky pro jeho zopakování, ...) Remove these files and continue Odstranit tyto soubory a pokraÄovat MP3 Diags is restarting after a crash. There was supposed to be some information about what led to the crash in the file <b>%1</b>, but that file cannot be found. Please report this issue to the project's Issue Tracker at %2. MP3 Diags se znovuspouÅ¡tí po pádu. NÄ›jaké informace o tom, co vedlo k pádu, by mÄ›ly být v souboru <b>%1</b>, ale tento soubor nejde najít. NahlaÅ¡te, prosím, tuto vÄ›c systému pro sledování chyb v projektu na %2. The developer will probably want to contact you for more details, so please check back on the status of your report.</p><p style="margin-bottom:8px; margin-top:1px; ">Make sure to include the data below, as well as any other detail that seems relevant (what might have caused the failure, steps to reproduce it, ...) Vývojář se s vámi bude chtít pravdÄ›podobnÄ› spojit kvůli dalším podrobnostem, takže se, prosím, dívejte na stav vaší zprávy.</p><p style="margin-bottom:8px; margin-top:1px; ">UjistÄ›te se, že jste zahrnul data níže, a také jakékoli další informace, které se zdají být důležité (co mohlo zapříÄinit selhání, kroky pro jeho zopakování, ...) MP3 Diags is restarting after a crash. To help determine the reason for the crash, the <i>Log program state to _trace and _step files</i> option has been activated. This logs to 3 files what the program is doing, which might make it slightly slower. MP3 Diags se znovuspouÅ¡tí po pádu. Abyste pomohli urÄit důvod pádu, byla zapnuta volba <i>Zapsat stav programu do souborů _trace a _step</i>. Toto zapíše do tří souborů, co program dÄ›lá, což jej může udÄ›lat mírnÄ› pomalejší. It is recommended to not process more than several thousand MP3 files while this option is turned on. You can turn it off manually, in the configuration dialog, in the <i>Others</i> tab, but keeping it turned on may provide very useful feedback to the developer, should the program crash again. With this feedback, future versions of MP3 Diags will get closer to being bug free. DoporuÄuje se nezpracovávat více než nÄ›kolk tisíc souborů MP3, když je tato volba zapnuta. Můžete ji ruÄnÄ› vypnout v dialogu pro nastavení na kartÄ› <i>Jiné</i>, ale její ponechání zapnuté může vývojáři poskytnout velmi užiteÄnou zpÄ›tnou vazbu, kdyby program znovu spadl. S touto zpÄ›tnou vazbou se budoucí verze MP3 Diags dostanou blíže k tomu, aby byly bez chyb. Loading data Nahrávání dat It seems that MP3 Diags is restarting after a crash. Your files will be rescanned. (Since this may take a long time for large collections, you may want to abort the full rescanning and apply a filter to include only the files that you changed since the last time the program closed correctly, then manually rescan only those files.) Zdá se, že se MP3 Diags po pádu spouÅ¡tí znovu. VaÅ¡e soubory budou znovu prohlédnuty. (Protože to u vÄ›tších sbírek může trvat déle, můžete chtít zruÅ¡it úplné znovuprohledání a použít filtr, aby se zahrnuly jen ty soubory, které jste zmÄ›nil od té doby, kdy byl program zavÅ™en správnÄ›, a pak ruÄnÄ› znovuprohledat pouze tyto soubory.) Saving data Ukládání dat An error occured while saving the MP3 information. You will have to scan your files again. %1 PÅ™i ukládání informací o MP3 se vyskytla chyba. VaÅ¡e soubory se budou muset prohlédnout znovu. %1 Scanning MP3 files Prohledávání souborů MP3 Info Informace Your files are not fully supported by the current version of MP3 Diags. The main reason for this is that the developer is aware of some MP3 features but doesn't have actual MP3 files to implement support for those features and test the code. VaÅ¡e soubory nejsou nynÄ›jší verzí MP3 Diags plnÄ› podporovány. Hlavním důvodem pro to je to, že vývojář si je vÄ›dom nÄ›kterých vlastností MP3, ale nemá skuteÄné soubory MP3, aby zavedl podporu pro tyto vlastnosti a zkouÅ¡el kód. You can help improve MP3 Diags by making files with unsupported notes available to the developer. The preferred way to do this is to report an issue on the project's Issue Tracker at %1, after checking if others made similar files available. To actually send the files, you can mail them to %2 or put them on a file sharing site. It would be a good idea to make sure that you have the latest version of MP3 Diags. Můžete pomoci zlepÅ¡it MP3 Diags tím, že uÄiníte soubory s nepodporovanými poznámkami dostupnými vývojáři. UpÅ™ednostňovaným přístupem pro to je nahlášení vÄ›ci pÅ™es systém pro sledování potíží v projektu na %1, až když prověříte, jestli snad nÄ›kdo jiný neudÄ›lal podobné soubory dostupnými. Soubory můžete poslat %2 nebo je umístit na stránky pro sdílení souborů. Dobrý nápad je ujistit se, že máte poslední verzi MP3 Diags. You can identify unsupported notes by the blue color that is used for their labels. Můžete urÄit nepodporované poznámky podle modré barvy, která je použita na jejich Å¡títky. Error setting up custom transformations Chyba pÅ™i nastavování vlastních promÄ›n Couldn't find a transformation with the name "%1". The program will proceed, but you should review the custom transformations lists. NepodaÅ™ilo se najít žádnou promÄ›nu s názvem "%1". Program bude pokraÄovat, ale mÄ›l byste se podívat na seznam vlastních promÄ›n. Error setting up visible transformations Chyba pÅ™i nastavování viditelných promÄ›n Couldn't find a transformation with the name "%1". The program will proceed, but you should review the visible transformations list. NepodaÅ™ilo se najít žádnou promÄ›nu s názvem "%1". Program bude pokraÄovat, ale mÄ›l byste se podívat na seznam viditelných promÄ›n. Error setting up external tools Chyba pÅ™i nastavování vnÄ›jších nástrojů Unable to parse "%1". The program will proceed, but you should review the external tools list. Nelze zpracovat "%1". Program bude pokraÄovat, ale mÄ›l byste se podívat na seznam vnÄ›jších nástrojů. There are no files to normalize. Nejsou žádné soubory k normalizaci. you are requesting to normalize only some of the files Požadujete normalizaci jen nÄ›kterých ze souborů the "Album" mode is not selected Režim Album není vybrán filters are applied Filtry jsou použity a filter is applied Filtr je použit the normalization will process more than 50 files, which is more than what an album usually has PÅ™i normalizaci bude zpracováno více než 50 souborů, což je více, než album obvykle má Normalization should process one whole album at a time, so it should only be run in "Album" mode, when no filters are active, and it should be applied to all the files in that album. But in the current case %1. Normalizace by mÄ›la najednou zpracovat jedno celé album, takže by mÄ›la běžet jen v režimu Album, kdy nejsou v Äinnosti žádné filtry, a mÄ›la by se použít na vÅ¡echny soubory na tom albu. Ale v nynÄ›jším případÄ› %1. Normalization should process one whole album at a time, so it should only be run in "Album" mode, when no filters are active, and it should be applied to all the files in that album. But in the current case there are some issues: %1 Normalizace by mÄ›la najednou zpracovat jedno celé album, takže by mÄ›la běžet jen v režimu Album, kdy nejsou v Äinnosti žádné filtry, a mÄ›la by se použít na vÅ¡echny soubory na tom albu. Ale v nynÄ›jším případÄ› jsou tu urÄité potíže: %1 Normalize Normalizovat Cancel ZruÅ¡it Normalize anyway? PÅ™esto normalizovat? Confirm Potvrdit Normalize all the files in the current album? (Note that normalization is done "in place", by an external program, so it doesn't care about the transformation settings for original and modified files.) Normalizovat vÅ¡echny soubory na nynÄ›jším albu? (UvÄ›domte si, že normalizace je dÄ›lána vnÄ›jším programem, takže se nestará o nastavení promÄ›ny pro původní a zmÄ›nÄ›né soubory.) The file list is empty, therefore no transformations can be applied. Exiting ... Seznam se soubory je prázdný, proto nelze použít žádných promÄ›n. UkonÄuje se... all the files shown in the file list VÅ¡echny soubory ukázané v seznamu se soubory all %1 files shown in the file list VÅ¡ech %1 souborů ukázaných v seznamu se soubory No file is selected, therefore no transformations can be applied. Exiting ... Není vybrán žádný soubor, proto nelze použít žádných promÄ›n. UkonÄuje se... and the other selected file A další vybrané soubory and the other %1 selected files A další %1 vybrané soubory The transformation list is empty. Based on the configuration, it is possible for changes to the files in the list to be performed, even in this case (the files may still be moved, renamed or erased). However, the current settings are to leave the original files unchanged, so currently there's no point in applying an empty transformation list. Exiting ... Seznam s promÄ›nami je prázdný. V závislosti na nastavení je možné provést zmÄ›ny v souborech v seznamu, i v tomto případÄ› (soubory lze stále pÅ™esunout, pÅ™ejmenovat nebo smazat). NicménÄ› souÄasná nastavení ponechávají původní soubory beze zmÄ›n, takže nyní není důvod použít prázdný seznam promÄ›n. UkonÄuje se... Apply an empty transformation list to all the files shown in the file list? (Note that even if no transformations are performed, the files may still be moved, renamed or erased, based on the current settings.) Použít prázdný seznam promÄ›n na vÅ¡echny soubory ukázané v seznamu souborů? (UvÄ›domte si, že i když nebyly provedeny žádné promÄ›ny, soubory lze stále pÅ™esunout, pÅ™ejmenovat nebo smazat, založeno na nynÄ›jších nastaveních.) Apply transformation "%1" to %2? Použít promÄ›nu "%1" na %2? Apply the following transformations to %1? Použít následující promÄ›nu na %1? don't change NemÄ›nit erase Vymazat move PÅ™esunout rename PÅ™ejmenovat move if destination doesn't exist PÅ™esunout, pokud cíl neexistuje Actions to be taken: ÄŒinnosti k provedení: original file that has been transformed: %1 Původní soubor, který byl promÄ›nÄ›n: %1 original file that has not been transformed: %1 Původní soubor, který nebyl promÄ›nÄ›n: %1 &Yes &Ano &No &Ne Applying transformations to MP3 files Použití promÄ›n na soubory MP3 Apply custom transformation list #%1 Použít vlastní seznam promÄ›n #%1 <empty list> (you can edit the list in the Settings dialog) <prázdný seznam> (seznam můžete upravit v dialogu nastavení) The file list is empty. You need to populate it before opening the tag editor. Seznam souborů je prázdný. Musíte jej zaplnit pÅ™ed otevÅ™ením v editoru znaÄek. The file list is empty. You need to populate it before opening the file rename tool. Seznam souborů je prázdný. Musíte jej zaplnit pÅ™ed otevÅ™ením v nástroji na pÅ™ejmenovávání. Delete %1? Smazat %1? Cannot delete file %1 Nelze smazat soubor %1 MP3 Diags can check at startup if a new version of the program has been released. Here's how this is supposed to work: MP3 Diags může pÅ™i spuÅ¡tÄ›ní ověřit, zda byla vydána nová verze programu. Zde je, jak se poÄítá, že to bude pracovat: The check is done in the background, when the program starts, so there should be no performance penalties Ověření je udÄ›láno na pozadí, když se program spouÅ¡tí, takže by tu nemÄ›l být pokles výkonu A notification message is displayed only if there's a new version available Zpráva s oznámením je zobrazena, pokud je dostupná nová verze The update is manual. You are told that there is a new version and are offered links to see what's new, but nothing gets downloaded and / or installed automatically Aktualizace je ruÄní: Je vám sdÄ›leno, že je dostupná nová verze a poskytnut odkaz, abyste se podívali, co je nového, ale nic není staženo nebo nainstalováno automaticky There is no System Tray process checking periodically for updates Není žádný proces v oznamovací oblasti panelu ověřující pravidelnÄ› dostupnost novÄ›jší verze You can turn the notifications on and off from the configuration dialog Můžete zapnout a vypnout oznamování v dialogu pro nastavení If you restart the program within a day after a check, no new check is done Pokud program spustíte znovu bÄ›hem dne následujícího po ověření, nedÄ›lá se žádné nové ověření Disable checking for new versions Zakázat ověřování dostupnosti nových verzí Enable checking for new versions Povolit ověřování dostupnosti nových verzí Version %1 has been published. You are running %2. You can see what's new in %3. A more technical list with changes can be seen in %4. Verze %1 byla vydána. Provozujete %2. Můžete se podívat, co je nového v %3. TechniÄtÄ›jší seznam zmÄ›n lze shlédnout na %4. the %1MP3 Diags blog%2 arguments are HTML elements stránky %1MP3 Diags%2 the %1change log%2 arguments are HTML elements %1seznam zmÄ›n%2 This notification is about the availability of the source code. Binaries may or may not be available at this time, depending on your particular platform. Toto oznámení se týká dostupnosti zdrojového kódu. Spustitelné soubory tentokrát nemusí být dostupné, to v závislosti na systému, který používáte. You should review the changes and decide if you want to upgrade or not. MÄ›l byste se podívat na zmÄ›ny a rozhodnout se, zda chcete provést povýšení verze nebo nikoli. Note: if you want to upgrade, you should %1close MP3 Diags%2 first. arguments are HTML elements Poznámka: Pokud hodláte provést povýšení verze, nejprve %1zavÅ™ete MP3 Diags%2. Choose what do you want to do: Vyberte, co chcete dÄ›lat: Just close this message Pouze zavřít tuto zprávu Don't tell me about version %1 again Znovu o verzi %1 neříkat Open containing folder ... Otevřít obsahující složku... Run "%1" on %2? Spustit "%1" na %2? Cannot start process. Check that the executable name and the parameters are correct. Nelze spustit proces. Ověřte, zda jsou název spustitelného souboru a parametry správné. %1 and %2 %1 a %2 %1, %2 and %3 %1, %2 a %3 %1, %2 and %3 other files %1, %2 a %3 jiné soubory Folder "%1" doesn't exist. The program will exit ... Složka "%1" neexistuje. Program se ukonÄí... O&K &OK Cannot write to file "%1". The program will exit ... Nelze zapisovat do souboru "%1". Program se ukonÄí... Mp3Transformer Error Chyba There was an error writing to the following file: %1 Make sure that you have write permissions and that there is enough space on the disk. Processing aborted. PÅ™i zápisu do následujícího souboru se vyskytla chyba: %1 UjistÄ›te se, že máte oprávnÄ›ní k zápisu a že je na disku dost místa. Zpracování zruÅ¡eno. The file "%1" seems to have been modified since the last scan. You need to rescan it before continuing. Processing aborted. Zdá se, že soubor "%1" byl od poslední prohlídky zmÄ›nÄ›n. PÅ™ed pokraÄováním je potÅ™eba provést prohledání znovu. Zpracování zruÅ¡eno. There was an error processing the following file: %1 Probably the file was deleted or modified since the last scan, in which case you should reload / rescan your collection. Or it may be used by another program; if that's the case, you should stop the other program first. This may also be caused by access restrictions or a full disk. Processing aborted. PÅ™i zpracování následujícího souboru se vyskytla chyba: %1 PravdÄ›podobnÄ› byl soubor od poslední prohlídky zmÄ›nÄ›n nebo smazán. V tom případÄ› byste mÄ›l svou sbírku prohledat/nahrát znovu. Nebo to může být způsobeno jiným programem. V tom případÄ› byste mÄ›l tento program nejprve zastavit. Může to být způsobeno omezeními, co se týÄe přístupu k souboru, nebo plným diskem. Zpracování zruÅ¡eno. There was an error processing the following file: %1 The following folder couldn't be created: %2 Processing aborted. PÅ™i zpracování následujícího souboru se vyskytla chyba: %1 Následující složku se nepodaÅ™ilo vytvoÅ™it. %2 Zpracování zruÅ¡eno. MusicBrainzDownloader Download album data from MusicBrainz.org Stáhnout data o albu z MusicBrainz.org waiting %1ms ÄŒeká se %1ms <a href="%1">view at amazon.com</a> <a href="%1">podívat se na amazon.com</a> NoteFilterDlg Note filter Filtr poznámek OK OK Cancel ZruÅ¡it NoteFilterDlgImpl <all notes> <VÅ¡echny poznámky> Available notes Dostupné poznámky Include notes Zahrnout poznámky Add selected note(s) PÅ™idat vybranou(é) poznámku(y) Remove selected note(s) Odstranit vybranou(é) poznámku(y) Add all notes PÅ™idat vÅ¡echny poznámky Remove all notes Odstranit vÅ¡echny poznámky Restore lists to the configuration they had when the window was open Obnovit seznamy na nastavení, které mÄ›ly, když bylo okno otevÅ™eno L L Note Poznámka Notes <Placeholder for a note that can no longer be found, most likely as a result of a software upgrade. You should rescan the file.> <Zástupný znak pro poznámku, kterou už nejde najít, nejpravdÄ›podobnÄ›ji jako výsledek povýšení programu. MÄ›l byste prohlédnout soubor znovu.> Low quality MPEG audio stream. (What is considered "low quality" can be changed in the configuration dialog, under "Quality thresholds".) Zvukový proud MPEG o nízké jakosti. (To, za co se považuje "nízká jakost", jde zmÄ›nit v doalogu nastavení pod "Jakostní prahy".) No MPEG audio stream found. Nenalezen žádný zvukový proud MPEG. VBR with audio streams other than MPEG1 Layer III might work incorrectly. PromÄ›nlivé datové toky (VBR) se zvukovými proudy jinými než MPEG1 Vrstva III mohou pracovat nesprávnÄ›. Incomplete MPEG frame at the end of an MPEG stream. Neúplný snímek MPEG na konci proudu MPEG. Valid frame with a different version found after an MPEG stream. Platný snímek s jinou verzí nalezen po proudu MPEG. Valid frame with a different layer found after an MPEG stream. Platný snímek s jinou vrstvou nalezen po proudu MPEG. Valid frame with a different channel mode found after an MPEG stream. Platný snímek s jiným kanálem nalezen po proudu MPEG. Valid frame with a different frequency found after an MPEG stream. Platný snímek s jiným kmitoÄtem nalezen po proudu MPEG. Valid frame with a different CRC policy found after an MPEG stream. Platný snímek s jinou politikou CRC nalezen po proudu MPEG. Invalid MPEG stream. Stream has fewer than 10 frames. Neplatný proud MPEG. Proud má ménÄ› než 10 snímků. Invalid MPEG stream. First frame has different bitrate than the rest. Neplatný proud MPEG. První snímek má jiný datový tok než zbytek. No normalization undo information found. The song is probably not normalized by MP3Gain or a similar program. As a result, it may sound too loud or too quiet when compared to songs from other albums. Nenalezena žádná informace o vrácení normalizace. Píseň pravdÄ›podobnÄ› není normalizována MP3Gain nebo podobným programem. Ve výsledku může znít příliÅ¡ hlasitÄ› nebo příliÅ¡ tiÅ¡e, pÅ™i srovnání s písnÄ›mi z jiných alb. Found audio stream in an encoding other than "MPEG-1 Layer 3" or "MPEG-2 Layer 3." While MP3 Diags understands such streams, very few tests were run on files containing them (because they are not supposed to be found inside files with the ".mp3" extension), so there is a bigger chance of something going wrong while processing them. Nalezen zvukový proud v kódování jiném než "MPEG-1 Layer 3" nebo "MPEG-2 Layer 3." I když MP3 Diags rozumí takovým proudům, bylo udÄ›láno velmi málo zkouÅ¡ek na souborech je obsahujících (protože se nepÅ™edpokládá, že budou nalezeny v souborech s příponou ".mp3"), takže pÅ™i jejich zpracování je vÄ›tší Å¡ance, že se s nimi stane nÄ›co Å¡patného. Two Lame headers found, but a file should have at most one of them. Nalezeny dvÄ› hlaviÄky Lame, ale soubor má mít nejvíce jednu z nich. Xing header seems added by Mp3Fixer, which makes the first frame unusable and causes a 16-byte unknown or null stream to be detected next. Zdá se, že Mp3Fixer byla pÅ™idána hlaviÄka Xing, což dÄ›lá první snímek nepoužitelným a způsobuje, že dále bude zjiÅ¡tÄ›n 16 bytů neznámý nebo nulový proud. Frame count mismatch between the Xing header and the audio stream. Nesoulad v poÄtu snímků mezi hlaviÄkou Xing a zvukovým proudem. Two Xing headers found, but a file should have at most one of them. Nalezeny dvÄ› hlaviÄky Xing, ale soubor má mít nejvíce jednu z nich. The Xing header should be located immediately before the MPEG audio stream. HlaviÄka Xing má být umístÄ›na hned pÅ™ed zvukovým proudem MPEG. The Xing header should be compatible with the MPEG audio stream, meaning that their MPEG version, layer and frequency must be equal. HlaviÄka Xing má být sluÄitelná se zvukovým proudem MPEG, což znamená, že jejich verze MPEG, vrstva a kmitoÄet se musí rovnat. The MPEG audio stream uses VBR but a Xing header wasn't found. This will confuse some players, which won't be able to display the song duration or to seek. Zvukový proud MPEG používá VBR, ale hlaviÄka Xing nebyla nalezena. To zmate nÄ›které pÅ™ehrávaÄe, které nebudou schopny zobrazit dobu trvání písnÄ› nebo hledat. Two VBRI headers found, but a file should have at most one of them. Nalezeny dvÄ› hlaviÄky VBRI, ale soubor má mít nejvíce jednu z nich. VBRI headers aren't well supported by some players. They should be replaced by Xing headers. HlaviÄky VBRI nejsou nÄ›kterými pÅ™ehrávaÄi dobÅ™e podporovány. MÄ›ly by se nahradit hlaviÄkami Xing. VBRI header found alongside Xing header. The VBRI header should probably be removed. HlaviÄka VBRI nalezena spoleÄnÄ› s hlaviÄkou Xing. HlaviÄka VBRI by se pravdÄ›podobnÄ› mÄ›la odstranit. Invalid ID3V2 frame. File too short. Neplatné pole ID3V2. Soubor příliÅ¡ krátký. Invalid frame name in ID3V2 tag. Neplatný název pole ve znaÄce ID3V2. Flags in the first flag group that are supposed to always be 0 are set to 1. They will be ignored. Příznak v první skupinÄ› příznaků, u nichž se pÅ™edpokládá, že budou vždy nastaveny na 0, jsou nastaveny na 1. Budou se pÅ™ehlížet. Flags in the second flag group that are supposed to always be 0 are set to 1. They will be ignored. Příznak v druhé skupinÄ› příznaků, u nichž se pÅ™edpokládá, že budou vždy nastaveny na 0, jsou nastaveny na 1. Budou se pÅ™ehlížet. Error decoding the value of a text frame while reading an Id3V2 Stream. Chyba pÅ™i dekódování hodnoty textového pole pÅ™i Ätení proudu ID3V2. ID3V2 tag has text frames using Latin-1 encoding that contain characters with a code above 127. While this is valid, those frames may have their content set or displayed incorrectly by software that uses the local code page instead of Latin-1. Conversion to Unicode (UTF16) is recommended. ZnaÄka ID3V2 má textová pole používající kódování Latin-1, které obsahuje znaky s kódem nad 127. Zatímco toto je platné, tato pole mohou mít jejich obsah nastaven nebo zobrazen nesprávnÄ› programem, který používá místní kódovou stránku namísto Latin-1. PÅ™evod do Unicode (UTF16) se doporuÄuje. Empty genre frame (TCON) found. Nalezeno prázdné pole žánrů (TCON). Multiple frame instances found, but only the first one will be used. Nalezeno více instancí polí, ale pouze první bude použita. The padding in the ID3V2 tag is too large, wasting space. (Large padding improves the tag editor saving speed, if fast saving is enabled, so you may want to delay compacting the tag until after you're done with the tag editor.) Vycpávka ve znaÄce ID3V2 je příliÅ¡ velká, plýtvá místem. (Velká vycpávka zlepÅ¡uje rychlost ukládání editoru znaÄek, pokud je povoleno rychlé ukládání, takže můžete chtít zpozdit stlaÄení znaÄky, až když budete hotov s editerem znaÄek) Unsupported ID3V2 version. Nepodporovaná verze ID3V2. Unsupported ID3V2 tag. Unsupported flag. Nepodporovaná znaÄka ID3V2. Nepodporovaný příznak. Unsupported value for Flags1 in ID3V2 frame. (This may also indicate that the file contains garbage where it was supposed to be zero.) Nepodporovaná hodnota pro Flags1 v poli ID3V2. (To také může naznaÄovat, že soubor obsahuje zbyteÄnosti tam, kde se pÅ™edpokládá, že bude nula) Unsupported value for Flags2 in ID3V2 frame. (This may also indicate that the file contains garbage where it was supposed to be zero.) Nepodporovaná hodnota pro Flags2 v poli ID3V2. (To také může naznaÄovat, že soubor obsahuje zbyteÄnosti tam, kde se pÅ™edpokládá, že bude nula) Multiple instances of the POPM frame found in ID3V2 tag. The current version discards all the instances except the first when processing this tag. Nalezeno více instancí POPM pole ve znaÄce ID3V2. SouÄasná verze pÅ™i zpracovávání této znaÄky zahazuje vÅ¡echny instance kromÄ› první. ID3V2 tag contains no frames, which is invalid. This note will disappear once you add track information in the tag editor. ZnaÄka ID3V2 neobsahuje žádná pole, což je neplatné. Tato poznámka zmizí, jakmile pÅ™idáte informace o skladbÄ› v editoru znaÄek. ID3V2 tag contains an empty text frame, which is invalid. ZnaÄka ID3V2 obsahuje prázdné textové pole, což je neplatné. ID3V2 tag doesn't have an APIC frame (which is used to store images). ZnaÄka ID3V2 nemá pole APIC (které se používá pro ukládání obrázků). ID3V2 tag has an APIC frame (which is used to store images), but the image couldn't be loaded. ZnaÄka ID3V2 má pole APIC (které se používá pro ukládání obrázků), ale obrázek se nepodaÅ™ilo nahrát. ID3V2 tag has at least one valid APIC frame (which is used to store images), but no frame has a type that is associated with an album cover. ZnaÄka ID3V2 má alespoň jedno platné pole APIC (které se používá pro ukládání obrázků), ale žádný obrázek nemá typ, který je spojen s obalem alba. Error loading image in APIC frame. Chyba pÅ™i nahrávání obrázku v poli APIC. Error loading image in APIC frame. The frame is too short anyway to have space for an image. Chyba pÅ™i nahrávání obrázku v poli APIC. Pole je tak Äi tak příliÅ¡ krátké, aby mÄ›lo místo na nÄ›jaký obrázek. ID3V2 tag has multiple APIC frames with the same picture type. ZnaÄka ID3V2 má více polí APIC se stejným typem obrázku. ID3V2 tag has multiple APIC frames. While this is valid, players usually use only one of them to display an image, discarding the others. ZnaÄka ID3V2 má více polí APIC. Zatímco toto je platné, pÅ™ehrávaÄe obvykle používají jen jeden z nich pro zobrazení obrázku, zahazují ostatní. Unsupported text encoding for APIC frame in ID3V2 tag. Nepodporované kódování textu pro pole APIC ve znaÄce ID3V2. APIC frame uses a link to a file as a MIME Type, which is not supported. Pole APIC používá odkaz na soubor jako MIME typ, což není podporováno. Picture description is ignored in the current version. Popis obrázku se v nynÄ›jší verzi pÅ™ehlíží. No ID3V2.3.0 tag found, although this is the most popular tag for storing song information. Nebyla nalezena žádná znaÄka ID3V2.3.0, aÄkoli toto je nejoblíbenÄ›jší znaÄka pro ukládání informací o písni. Two ID3V2.3.0 tags found, but a file should have at most one of them. Nalezeny dvÄ› znaÄky ID3V2.3.0, ale soubor má mít nejvíce jednu z nich. Both ID3V2.3.0 and ID3V2.4.0 tags found, but there should be only one of them. Nalezeny jako znaÄka ID3V2.3.0 tak ID3V2.4.0, ale má tam být pouze jedna z nich. The ID3V2.3.0 tag should be the first tag in a file. ZnaÄka ID3V2.3.0 má být první znaÄka v souboru. ID3V2.3.0 tag contains a text frame encoded as UTF-8, which is valid in ID3V2.4.0 but not in ID3V2.3.0. ZnaÄka ID3V2.4.0 obsahuje textové pole kódované jako UTF-8, což je platné v ID3V2.4.0 ale ne v ID3V2.3.0. Unsupported value of text frame while reading an Id3V2 Stream. Nepodporovaná hodnota textového pole pÅ™i Ätení proudu ID3V2. Invalid ID3V2.3.0 frame. Incorrect frame size or file too short. Neplatné pole ID3V2.3.0. Nesprávná velikost pole nebo je soubor příliÅ¡ krátký. Two ID3V2.4.0 tags found, but a file should have at most one of them. Nalezeny dvÄ› znaÄky ID3V2.4.0, ale soubor má mít nejvíce jednu z nich. Invalid ID3V2.4.0 frame. Incorrect frame size or file too short. Neplatné pole ID3V2.4.0. Nesprávná velikost pole nebo je soubor příliÅ¡ krátký. Invalid ID3V2.4.0 frame. Frame size is supposed to be stored as a synchsafe integer, which uses only 7 bits in a byte, but the size uses all 8 bits, as in ID3V2.3.0. This will confuse some applications Neplatné pole ID3V2.4.0. Velikost souboru se pÅ™edpokládá, že bude uložena v synchronnÄ› bezpeÄném celém Äísle, které používá pouze 7 bitů, ale velikost používá vÅ¡ech 8 bitů, jako v ID3V2.3.0. To nÄ›které aplikace zmate Deprecated TYER frame found in 2.4.0 tag alongside a TDRC frame. Ve znaÄce 2.4.0 bylo nalezeno nesouhlasné pole TYER spoleÄnÄ› s polem TDRC. Deprecated TYER frame found in 2.4.0 tag. It's supposed to be replaced by a TDRC frame. Ve znaÄce 2.4.0 bylo nalezeno nesouhlasné pole TYER. PÅ™edpokládá se, že bude nahrazeno polem TDRC. Deprecated TDAT frame found in 2.4.0 tag alongside a TDRC frame. Ve znaÄce 2.4.0 bylo nalezeno nesouhlasné pole TDAT spoleÄnÄ› s polem TDRC. Deprecated TDAT frame found in 2.4.0 tag. It's supposed to be replaced by a TDRC frame. Ve znaÄce 2.4.0 bylo nalezeno nesouhlasné pole TDAT. PÅ™edpokládá se, že bude nahrazeno polem TDRC. Invalid ID3V2.4.0 frame. Mismatched Data length indicator. Frame value is probably incorrect Neplatné pole ID3V2.4.0. Nesouhlasící indikátor délky dat. Hodnota pole je pravdÄ›podobnÄ› nesprávná Invalid ID3V2.4.0 frame. Incorrect unsynchronization bit. Neplatné pole ID3V2.4.0. Nesprávný neseřízený bit. Unsupported value of text frame while reading an Id3V2.4.0 stream. It may be using an unsupported text encoding. Nepodporovaná hodnota textového pole pÅ™i Ätení proudu ID3V2.4.0. Možná používá nepodporované kódování textu. The only supported tag found that is capable of storing song information is ID3V1, which has pretty limited capabilities. Jediná nalezená podporovaná znaÄka, která je schopna ukládání informací o písni je ID3V1, která má dost omezené možnosti. The ID3V1 tag should be located after the MPEG audio stream. ZnaÄka ID3V1 má být umístÄ›na po zvukovém proudu MPEG. Invalid ID3V1 tag. File too short. Neplatná znaÄka ID3V1. Soubor příliÅ¡ krátký. Two ID3V1 tags found, but a file should have at most one of them. Nalezeny dvÄ› znaÄky ID3V1, ale soubor má mít nejvíce jednu z nich. ID3V1 tag contains fields padded with spaces alongside fields padded with zeroes. The standard only allows zeroes, but some tools use spaces. Even so, zero-padding and space-padding shouldn't be mixed. ZnaÄka ID3V1 obsahuje pole vycpaná mezerami (volnými místy) spoleÄnÄ› s poli vycpanými nulami. Standard povoluje pouze nuly, ale nÄ›které nástroje používají mezery - volná místa. Tak jako tak by se nemÄ›lo míchat vycpání volným místem s vycpáním nulami. ID3V1 tag contains fields that are padded with spaces mixed with zeroes. The standard only allows zeroes, but some tools use spaces. Even so, one character should be used for padding for the whole tag. ZnaÄka ID3V1 obsahuje pole vycpaná mezerami (volnými místy) spoleÄnÄ› s poli vycpanými nulami. Standard povoluje pouze nuly, ale nÄ›které nástroje používají mezery - volná místa. Tak jako tak by se mÄ›l pro vycpání celé znaÄky používat jeden znak. Invalid ID3V1 tag. Invalid characters in Name field. Neplatná znaÄka ID3V1. Neplatné znaky v poli Název. Invalid ID3V1 tag. Invalid characters in Artist field. Neplatná znaÄka ID3V1. Neplatné znaky v poli UmÄ›lec. Invalid ID3V1 tag. Invalid characters in Album field. Neplatná znaÄka ID3V1. Neplatné znaky v poli Album. Invalid ID3V1 tag. Invalid characters in Year field. Neplatná znaÄka ID3V1. Neplatné znaky v poli Rok. Invalid ID3V1 tag. Invalid characters in Comment field. Neplatná znaÄka ID3V1. Neplatné znaky v poli Poznámka. Broken stream found. Nalezen poÅ¡kozený proud. Broken stream found. Since other streams follow, it is possible that players and tools will have problems using the file. Removing the stream is recommended. Nalezen poÅ¡kozený proud. Protože následují další proudy, je možné, že pÅ™ehrávaÄe a nástroje budou mít potíže pÅ™i používání souboru. DoporuÄuje se proud odstranit. Truncated MPEG stream found. The cause for this seems to be that the file was truncated. Nalezen zkrácený proud MPEG. PříÄinou toho je, jak se zdá, že byl zkrácen soubor. Truncated MPEG stream found. Since other streams follow, it is possible that players and tools will have problems using the file. Removing the stream or padding it with 0 to reach its declared size is strongly recommended. Nalezen zkrácený proud MPEG. Protože následují další proudy, je možné, že pÅ™ehrávaÄe a nástroje budou mít potíže pÅ™i používání souboru. Velice se doporuÄuje proud odstranit nebo jej vycpat nulami, aby se dosáhlo jeho prohlášené velikosti. Not enough remaining bytes to create an UnknownDataStream. Nezůstává dost bytů na vytvoÅ™ení NeznáméhoDatovéhoProudu. Unknown stream found. Nalezen neznámý proud. Unknown stream found. Since other streams follow, it is possible that players and tools will have problems using the file. Removing the stream is recommended. Nalezen neznámý proud. Protože následují další proudy, je možné, že pÅ™ehrávaÄe a nástroje budou mít potíže pÅ™i používání souboru. DoporuÄuje se proud odstranit. File contains null streams. Soubor obsahuje nulové proudy. Invalid Lyrics stream tag. File too short. Neplatná znaÄka proudu textu písnÄ›. Soubor příliÅ¡ krátký. Two Lyrics tags found, but only one is supported. Nalezeny dvÄ› znaÄky textu písnÄ›, ale je podporována pouze jedna. Invalid Lyrics stream tag. Unexpected characters found. Neplatná znaÄka proudu textu písnÄ›. Nalezeny neoÄekávané znaky. Multiple fields with the same name were found in a Lyrics tag, but only one is supported. Ve znaÄce textu písnÄ› bylo nalezeno více polí se stejným názvem, ale je podporováno pouze jedno. Currently INF fields in Lyrics tags are not fully supported. V souÄasnosti nejsou pole INF ve znaÄkách s texty písnÄ› plnÄ› podporována. Invalid Ape Item. File too short. Neplatná položka APE. Soubor příliÅ¡ krátký. Ape Item seems too big. Although the size may be any 32-bit integer, 256 bytes should be enough in practice. If this note is determined to be incorrect, it will be removed in the future. Položka APE je příliÅ¡ velká. AÄkoli může být velikost jakékoli 32 bitové celé Äíslo, 256 bytů by v praxi mÄ›l být dostateÄné. Pokud je tato poznámka nesprávná, bude v budoucnu odstranÄ›na. Invalid Ape Item. Terminator not found for item name. Neplatná položka APE. Pro název položky nebyl ukonÄovatel nalezen. Invalid Ape tag. Header expected but footer found. Neplatná znaÄka APE. Byla oÄekávána hlaviÄka, ale byla nalezena patiÄka. Not an Ape tag. File too short. Není to znaÄka APE. Soubor příliÅ¡ krátký. Invalid Ape tag. Footer expected but header found. Neplatná znaÄka APE. Byla oÄekávána patiÄka, ale byla nalezena hlaviÄka. Invalid Ape tag. Mismatch between header and footer. Neplatná znaÄka APE. Nesoulad mezi hlaviÄkou a patiÄkou. Two Ape tags found, but only one is supported. Nalezeny dvÄ› znaÄky APE, ale je podporována pouze jedna. Ape item flags not supported. Příznaky pro položku APE nepodporovány. Unsupported Ape tag. Currently a missing header or footer are not supported. Nepodporovaná znaÄka APE. Nyní nejsou chybÄ›jící hlaviÄky a patiÄky podporovány. The file seems to have been changed in the (short) time that passed between parsing it and the initial search for pictures. If you think that's not the case, report a bug. Zdá se, že soubor byl bÄ›hem krátké doby, která probÄ›hla mezi jeho zpracováním a poÄátkem hledání obrázků, zmÄ›nÄ›n. Pokud si myslíte, že toto není ten případ, nahlaÅ¡te chybu. No supported tag found that is capable of storing song information. Nenalezena žádná podporovaná znaÄka schopná ukládání informací o písni. Too many TRACE notes added. The rest will be discarded. PÅ™idáno příliÅ¡ mnoho poznámek TRACE. Zbytek bude zahozen. Too many notes added. The rest will be discarded. PÅ™idáno příliÅ¡ mnoho poznámek. Zbytek bude zahozen. Too many streams found. Aborting processing. Nalezeno příliÅ¡ mnoho proudů. Zpracování zruÅ¡eno. Unsupported stream found. It may be supported in the future if there's a real need for it. Nalezen nepodporovaný proud. V budoucnu bude možná podporován, pokud pro to bude skuteÄná potÅ™eba. The file was saved using the "fast" option. While this improves the saving speed, it may leave the notes in an inconsistent state, so you should rescan the file. Soubor byl uložen za použití "rychlé" volby. I když to zlepÅ¡uje rychlost ukládání, může to zanechat poznámky v nesoudržném stavu, takže byste mÄ›l soubor prohlédnout znovu. ERROR Chyba WARNING Varování SUPPORT Podpora Two MPEG audio streams found, but a file should have exactly one. Nalezeny dva zvukové proudy MPEG, ale soubor má mít pÅ™esnÄ› jeden. NotesModel L L Note Poznámka Address Adresa PaletteDlg Background colors Barvy pozadí Album Album Field equal to the ID3V2 field or missing Pole stejné s polem ID3V2 nebo chybÄ›jící Field is different from the corresponding ID3V2 value (or no ID3V2 field exists) Pole se liší od odpovídající hodnoty ID3V2 (nebo žádné pole ID3V2 neexistuje) Value marked as "assigned" Hodnota oznaÄena jako pÅ™iÅ™azená Song Píseň Tag and field present ZnaÄka a pole jsou přítomny Tag not present ZnaÄka není přítomna Tag present but field is not supported ZnaÄka přítomna, ale pole není podporováno Tag present, field is supported but missing ZnaÄka přítomna, pole je podporováno, ale chybí OK OK PatternsDlg Patterns Vzory Add predefined patterns PÅ™idat pÅ™ednastavené vzory Line 1, Col 1 Řádek 1, Sloupec 1 O&K &OK Cancel ZruÅ¡it Renamer A pattern cannot be empty Vzor nemůže být prázdný A pattern must either begin with '%1' or contain no '%1' at all Vzor musí zaÄínat buÄ '%1' nebo nesmí vůbec obsahovat '%1' A pattern must either begin with "<drive>:\" or contain no '\' at all Vzor musí zaÄínat buÄ <drive>:\" nebo nesmí vůbec obsahovat '\' Error in column %1. Chyba ve sloupci %1. Nested optional elements are not allowed VnoÅ™ené volitelné prvky nejsou povoleny Trying to close and optional element although none is open Pokus o zavÅ™ení volitelného prvku, aÄkoli žádný není otevÅ™en Optional element must be closed Musí se zavřít volitelný prvek Title entry (%t) must be present Položka názvu (%t) musí být přítomna RenamerPatternsDlgImpl %n track number %a artist %t title %b album %y year %g genre %r rating (a lowercase letter) %c composer To include the special characters "%", "[" and "]", precede them by a "%": "%%", "%[" and "%]" The path should be either a full path, starting with a "%1", or it should contain no "%1", if what is wanted is for the renamed files to remain in their original directories %n Äíslo stopy %a umÄ›lec %t název %b album %y rok %g žánr %r hodnocení (malé písmeno) %c skladatel Pro zahrnutí zvláštních znaků "%", "[" a "]" je uveÄte "%": "%%", "%[" a "%]" Cesta by mÄ›la být buÄ Ãºplnou cestou zaÄínající "%1", nebo by nemÄ›la obsahovat "%1", pokud to, co se chce, je, aby pÅ™ejmenované soubory zůstaly ve svých původních adresářích %n track number %a artist %t title %b album %y year %g genre %r rating (a lowercase letter) %c composer To include the special characters "%", "[" and "]", precede them by a "%": "%%", "%[" and "%]" The path should be either a full path, starting with a drive letter followed by ":\", or it should contain no "\", if what is wanted is for the renamed files to remain in their original directories %n Äíslo stopy %a umÄ›lec %t název %b album %y rok %g žánr %r hodnocení (malé písmeno) %c skladatel Pro zahrnutí zvláštních znaků "%", "[" a "]" je uveÄte "%": "%%", "%[" a "%]" Cesta by mÄ›la být buÄ Ãºplnou cestou zaÄínající písmenem diskové jednotky následovaným":\", nebo by nemÄ›la obsahovat "\", pokud to, co se chce, je, aby pÅ™ejmenované soubory zůstaly ve svých původních adresářích Error Chyba Line %1, Col %2 Řádek %1, Sloupec %2 ScanDlg Scan Prohledat Rescan files that seem unchanged Prohledat soubory, které se jeví být nezmÄ›nÄ›ny, znovu &Scan &Prohledat Cancel ZruÅ¡it SessionEditorDlg pppppppppppppp Language Jazyk Include directories Zahrnout adresáře Backup Záloha Don't create backup for modified files Nevytvářet zálohu zmÄ›nÄ›ných souborů Create backup for modified files in VytvoÅ™it zálohu zmÄ›nÄ›ných souborů v ... ... Settings file name Název souboru s nastavením Scan for new, modified, and deleted files at startup PÅ™i spuÅ¡tÄ›ní hledat nové, zmÄ›nÄ›né a smazané soubory Open last session at s&tartup PÅ™i &spuÅ¡tÄ›ní otevřít poslední sezení Open the main "Sessions" dialog, to load an existing session Otevřít dialog hlavního sezení pro nahrání existujícího sezení O&K &OK Cancel ZruÅ¡it SessionEditorDlgImpl This is the name of the "settings file" It is supposed to be a file that doesn't already exist. You don't need to set it up. MP3 Diags will store its settings in this file. The name was generated automatically. If you want to choose a different name, simply click on the button at the right to change it. this is a multiline tooltip Toto je název "souboru s nastavením" PÅ™edpokládá se, že je to soubor, který již neexistuje. Není tÅ™eba jej vytvářet. MP3 Diags bude uchovávat svá nastavení v tomto souboru. Název byl vytvoÅ™en automaticky. Pokud chcete vybrat jiný název, jednoduÅ¡e klepnÄ›te na tlaÄítko napravo pro jeho zmÄ›nu. Here you need to specify the name of a "settings file" This is supposed to be a file that doesn't already exist. You don't need to set it up. MP3 Diags will store its settings in this file. To change it, simply click on the button at the right to choose the name of the settings file. this is a multiline tooltip Zde zadejte název "souboru s nastavením" PÅ™edpokládá se, že je to soubor, který již neexistuje. Není tÅ™eba jej vytvářet. MP3 Diags bude uchovávat svá nastavení v tomto souboru. Pro jeho zmÄ›nu jednoduÅ¡e klepnÄ›te na tlaÄítko napravo pro pojmenování souboru s nastavením. MP3 Diags - Create new session MP3 Diags - VytvoÅ™it nové sezení MP3 Diags - Edit session MP3 Diags - Upravit sezení Error Chyba You need to specify the name of the settings file. This is supposed to be a file that doesn't already exist. You don't need to set it up, but just to pick a name for it. MP3 Diags will store its settings in this file. Zadejte název "souboru s nastavením. PÅ™edpokládá se, že to je soubor, který již neexistuje. Není tÅ™eba jej vytvářet, jen pro nÄ›j vyberte název. MP3 Diags bude uchovávat svá nastavení v tomto souboru. You need to select at least a directory to be included in the session. Musíte vybrat alespoň adresář, který má být zahrnut v tomto sezení. If you want to create backups, you must select an existing directory to store them. Pokud chcete vytvoÅ™it zálohy, musíte vybrat existující adresář pro jejich uložení. Failed to write to file %1 NepodaÅ™ilo se zapsat do souboru %1 Select folder Vybrat složku All files (*) VÅ¡echny soubory (*) Enter configuration file Zadat soubor s nastavením MP3 Diags session files (*%1) Soubory se sezením MP3 Diags (*%1) SessionsDlg MP3 Diags - Sessions MP3 Diags - Sezení Language Jazyk &Open &Otevřít &New ... &Nové... &Edit ... &Upravit... Save &as ... Uložit &jako... E&rase &Vymazat &Load ... &Nahrát... &Hide S&krýt Close Zavřít Temporary session template PÅ™edloha pro doÄasné sezení Persistent session template PÅ™edloha pro trvalé sezení At s&tartup open the last session automatically PÅ™i &spuÅ¡tÄ›ní otevřít poslední sezení automaticky SessionsDlgImpl <last session> <poslední sezení> Confirm Potvrdit Do you really want to erase the current session? Opravdu chcete smazat nynÄ›jší sezení? Erase Vymazat Cancel ZruÅ¡it Error Chyba Failed to remove the data files associated with this session NepodaÅ™ilo se odstranit soubory s daty spojené s tímto sezením Save session as ... Uložit sezení jako... Do you really want to hide the current session? Opravdu chcete skrýt nynÄ›jší sezení? Hide Skrýt Choose a session file Vybrat soubor se sezením The session named "%1" is already part of the session list Sezení s názvem "%1" je již souÄástí seznamu sezení The session list is empty. You must create a new session or load an existing one. Seznam sezení je prázdný. Musíte vytvoÅ™it nové sezení nebo nahrát existující. SessionsModel File name Název souboru ShellIntegrator %1 - %2 %1 - %2 Error setting up shell integration Chyba pÅ™i nastavování integrace shellu It appears that setting up shell integration didn't complete successfully. You might have to configure it manually. Zdá se, že nastavování integrace shellu nebylo dokonÄeno úspěšnÄ›. Je možné, že to budete muset udÄ›lat ruÄnÄ›. This message will not be shown again until the program is restarted, even if more errors occur. Tato zpráva nebude znovu zobrazena, dokud program nebude spuÅ¡tÄ›n znovu, i kdyby se vyskytly další chyby. O&K &OK temporary folder DoÄasná složka hidden folder Skrytá složka visible folder Viditelná složka StreamsModel Address Adresa Size (dec) Velikost (dec) Size (hex) Velikost (hex) Type Typ Stream details Podrobnosti proudu TagEditor::CurrentAlbumDelegate Warning Varování You are editing data in a cell. If you proceed that change will be lost. Proceed and lose the data? Upravujete data v buňce. Pokud budete pokraÄovat bude tato zmÄ›na ztracena. PokraÄovat nebo ztratit data? Proceed PokraÄovat Cancel ZruÅ¡it TagEditor::CurrentAlbumModel N/A N/A File name Název souboru Error Chyba The data contained errors and couldn't be saved Data obsahovala chyby a nepodaÅ™ilo se je uložit TagEditor::CurrentFileModel N/A N/A TagEditorDlg MP3 Diags - Tag editor MP3 Diags - Editor znaÄek Save Uložit S S Reset Nastavit znovu R R Previous [Ctrl+P] PÅ™edchozí [Ctrl+P] < < Ctrl+P Ctrl+P Folder Složka Next [Ctrl+N] Další [Ctrl+N] > > Ctrl+N Ctrl+N Query Discogs Dotázat se Discogs Q Q Query MusicBrainz Dotázat se MusicBrainz ... ... Toggle "Various Artists" PÅ™epnout Různí umÄ›lci V V Change case automatically ZmÄ›nit velikost písmen automaticky Copy field(s) from first line Kopírovat pole z prvního řádku C C Sort by track number Třídit podle Äísla skladby Toggle "assigned" state PÅ™epnout "pÅ™iÅ™azený" stav A A Paste Vložit Ctrl+V Ctrl+V Edit file patterns Upravit souborové vzory F F Background colors Barvy pozadí Configuration Nastavení Close Zavřít TextLabel Textový Å¡títek TagEditorDlgImpl Toggle "Various Artists" PÅ™epnout Různí umÄ›lci To enable "Various Artists" you need to open the configuration dialog, go to the "Others" tab and check the corresponding checkbox(es) this is a multi-line tooltip Pro povolení "Různí umÄ›lci" potÅ™ebujete otevřít dialog pro nastavení. JdÄ›te na kartu "Jiné" a zaÅ¡krtnÄ›te odpovídající zaÅ¡krtávací okénka Error setting up the tag order Chyba pÅ™i nastavování poÅ™adí karet An invalid value was found in the configuration file. You'll have to sort the tags again. V souboru s nastavením byla nalezena neplatná hodnota. Budete muset znaÄky třídit znovu. Error setting up patterns Chyba pÅ™i nastavování vzorů An invalid value was found in the configuration file. You'll have to set up the patterns manually. V souboru s nastavením byla nalezena neplatná hodnota. Budete muset vzory nastavit ruÄnÄ›. Warning Varování Reloading the current album causes all unsaved changes to be lost. Really reload? Znovunahrání nynÄ›jšího alba povede k tomu, že vÅ¡echny neuložené zmÄ›ny budou ztraceny. SkuteÄnÄ› je nahrát znovu? Reload Nahrát znovu Cancel ZruÅ¡it Artists - %1 UmÄ›lci - %1 Others - %1 Jiní - %1 Confirm Potvrdit You added %1 images but then you didn't assign them to any songs. What do you want to do? PÅ™idal jste %1 obrázků, ale pak jste je nepÅ™iÅ™adil k žádné písni. Co chcete dÄ›lat? You added an image but then you didn't assign it to any song. What do you want to do? PÅ™idal jste jeden obrázek, ale pak jste jej nepÅ™iÅ™adil k žádné písni. Co chcete dÄ›lat? &Discard &Zahodit &Cancel Z&ruÅ¡it There are unsaved fields that you assigned a value to, as well as fields whose value doesn't match the ID3V2 value. What do you want to do? Jsou tu neuložená pole, kterým jste pÅ™iÅ™adil hodnotu, i pole, jejichž hodnota neodpovídá hodnotÄ› ID3V2. Co chcete dÄ›lat? &Save &Uložit There are unsaved fields that you assigned a value to. What do you want to do? Jsou tu neuložená pole, kterým jste pÅ™iÅ™adil hodnotu. Co chcete dÄ›lat? There are fields whose value doesn't match the ID3V2 value. What do you want to do? Jsou tu pole, jejichž hodnota neodpovídá hodnotÄ› ID3V2. Co chcete dÄ›lat? Saving ID3V2.3.0 tags Ukládání znaÄek ID3V2.3.0 Some fields are missing or may be incomplete. While this is usually solved by downloading correct information, there are a cases when this approach doesn't work, like custom compilations, rare albums, or missing tracks. NÄ›která pole chybí nebo jsou neúplná. Toto je obvykle vyÅ™eÅ¡eno stažením správných informací, ale jsou případy, kdy takový přístup nepracuje, jako u vlastních kompilací, vzácných alb nebo když chybí skladby. Info Informace If your current folder fits one of these cases or you simply have consistently named files that you would prefer to use as a source of track info, you may want to take a look at the tag editor's patterns, at %1 Pokud vaÅ¡e souÄasná složka odpovídá jednomu z tÄ›chto případů nebo jednoduÅ¡e máte soubory důslednÄ› pojmenovany, protože je rád používáte jako zdroj informací o skladbÄ›, možná se budete chtít podívat na vzory v editoru znaÄek, v %1 O&K &OK TagEdtPatternsDlgImpl %n track number %a artist %t title %b album %y year %g genre %r rating (a lowercase letter) %c composer %i ignored To include the special characters "%", "[", "]" and "%1", preced them by a "%": "%%", "%[", "%]" and "%%1" For a pattern to be considered a "file pattern" (as opposed to a "table pattern"), it must contain at least a "%1", even if you don't care about what's in the file's parent directory (see the fourth predefined pattern for an example.) Leading and trailing spaces are removed automatically from unbound fields after matching, so "-[ ]%t" is equivalent to "-%t" (but "-[ ]%n" is not equivalent to "-%n", because %n is a fixed format field). However, all non-optional characters matter in the matching phase, including spaces. %n Äíslo stopy %a umÄ›lec %t název %b album %y rok %g žánr %r hodnocení (malé písmeno) %c skladatel Pro zahrnutí zvláštních znaků "%", "[", "]" and "%1" je uveÄte "%": "%%", "%[", "%]" and "%%1 Aby byl vzor považován za "souborový vzor" (jako protiklad k "tabulkovému vzoru"), musí obsahovat alespoň "%1", i když byste se nestaral o to, co je v rodiÄovském adresáři souboru (podívejte se například na Ätvrtý pÅ™ednastavený vzor) Mezery na zaÄátku a na konci jsou po srovnání z nesvázaných polí odstranÄ›ny automaticky, takže "-[ ]%t" je obdoba pro "-%t" (ale "-[ ]%n" není obdoba pro "-%n", protože %n je pevné pole formátu). NicménÄ› na vÅ¡ech nevolitelných znacích záleží v porovnávací fázi, vÄetnÄ› mezer. Error Chyba Line %1, Col %2 Řádek: %1 Sloupec: %2 TagReadPanel Track# Stopa# Artist UmÄ›lec Title Název Album Album VA Time ÄŒas Genre Žánr Rating Hodnocení Composer Skladatel TagReader Title Název Artist UmÄ›lec Track # Stopa# Time ÄŒas Genre Žánr Picture Obrázek Album Album Rating Hodnocení Composer Skladatel VA (file) (soubor) (table) (tabulka) Pattern Vzor Web Web Additional %1 field: %2 DodateÄné %1 pole: %2 %1 field: %2 %1 pole: %2 Comment: %1 Poznámka: %1 <non-text value> <netextová hodnota> <error loading frame> <chyba pÅ™i nahrávání pole> <error decoding frame> <chyba pÅ™i dekódování pole> other jiné 32x32 icon Ikona 32x32 other file icon Jiná souborová ikona front cover PÅ™ední strana obalu back cover Zadní strana obalu leaflet page Strana letáku media Media lead artist Hlavní umÄ›lec artist UmÄ›lec conductor Dirigent band Skupina composer Skladatel lyricist TextaÅ™ recording location Místo nahrávky during recording BÄ›hem nahrávání during performance BÄ›hem vystoupení screen capture Zachycení obrazu illustration Ukázka band/artist logotype Logotyp hudební skupiny/umÄ›lce publisher/studio logotype Logotyp vydavatele/nahrávacího studia unknown Neznámý <no change> <žádná zmÄ›na> lower case Malá písmena UPPER CASE Velká písmena Title Case Velikost písmen názvu Sentence case Velikost písmen vÄ›ty TagWriter Do you want to erase these files?%1 Chcete smazat tyto soubory? %1 Do you want to erase %1? Chcete smazat %1? Confirm Potvrdit Erase Vymazat Cancel ZruÅ¡it Currently pasting multiple file names is not supported, so only the first one is considered. Nyní není vkládání více souborových názvů podporováno, takže se bere jen první. The pasted value couldn't be assigned to some fields Vloženou hodnotu se nepodaÅ™ilo pÅ™iÅ™adit k nÄ›kterým polím The pasted value couldn't be assigned to any field Vloženou hodnotu se nepodaÅ™ilo pÅ™iÅ™adit k žádnému poli The number of lines in the clipboard is different from the number of files. Paste anyway? PoÄet řádků ve schránce se liší od poÄtu souborů. PÅ™esto vložit? Paste Vložit The track numbers aren't consecutive numbers starting at 1, so the pasted track information might not match the tracks. Paste anyway? Čísla skladeb nejsou Äísla jdoucí po sobÄ› zaÄínající Äíslem 1, takže vložené informace o skladbách nemusí odpovídat skladbám. PÅ™esto vložit? Unrecognized clipboard content Nerozpoznaný obsah schránky Error Chyba Track numbers are supposed to be consecutive numbers from 1 to the total number of tracks. This is not the case with the current album, which may lead to incorrect assignments when pasting information. PÅ™edpokládá se, že Äísla skladeb jsou Äísla jdoucí po sobÄ› zaÄínající Äíslem 1 z celkového poÄtu skladeb. Toto není případ nynÄ›jšího alba, což může pÅ™i vkládání informací vést k nesprávným pÅ™iÅ™azení. Some files have been modified by an external tool after the last scan. You won't be able to save any changes to those files until you rescan them. NÄ›které soubory byly po posledním prohledání zmÄ›nÄ›ny vnÄ›jším nástrojem. Nebudete do tÄ›chto souborů moci uložit žádné zmÄ›ny, dokud je znovu neprohlédnete. You cannot erase image files if there are unsaved values. Do you want to save? Nemůžete vymazat soubory s obrázky, pokud jsou tu neuložené hodnoty. Chcete je uložit? Save, then erase file Uložit, a pak soubor smazat Warning Varování Erasing image files triggers a full reload, which results in downloaded and pasted data being lost. Erase anyway? Vymazání souborů s obrázky spouÅ¡tí plné znovunahrání, což vede k tomu, že stažená a vložená data jsou ztracena. PÅ™esta smazat? Couldn't erase file "%1" NepodaÅ™ilo se smazat soubor "%1" ThreadRunnerDlg Thread Runner Provozovatel vlákna TextLabel Textový Å¡títek &Pause &Pozastavit &Abort &ZruÅ¡it ThreadRunnerDlgImpl Completed Hotovo &Resume &PokraÄovat &Pause &Pozastavit Total time: %1 Running time: %2 Celkový Äas: %1 ÄŒas bÄ›hu: %2 Time: %1 ÄŒas: %1 TrackTextParser "%1" is not a valid pattern. Error in column %2. "%1" není platný vzor. Chyba ve sloupci %2. TransfConfig Error Chyba Invalid value found for file settings. Reverting to default settings. Pro nastavení souboru nalezena neplatná hodnota. Návrat k výchozímu nastavení. Transformation Convert non-ASCII ID3V2 text frames to Unicode assuming codepage %1 PÅ™evést ne-ASCII ID3V2 textová pole na Unicode za pÅ™edpokladu kódové stránky %1 Change case for ID3V2 text frames: Artists - %1; Others - %2 ZmÄ›nit velikost písmen pro ID3V2 textová pole: UmÄ›lec - %1; Jiní - %2 Copy missing ID3V2 frames from ID3V1 assuming codepage %1 Kopírovat chybÄ›jící ID3V2 textové rámeÄky z ID3V1 za pÅ™edpokladu kódové stránky %1 Removes all ID3V2 frames that aren't used by MP3 Diags. You normally don't want to do such a thing, but it may help if some other program misbehaves because of invalid or unknown frames in an ID3V2 tag. Odstraní vÅ¡echny rámeÄky ID3V2, které nejsou MP3 Diags používány. ObyÄejnÄ› nÄ›co takového dÄ›lat nechcete, ale může to pomoci, když se nÄ›jaký jiný program chová Å¡patnÄ› z důvodu neplatných nebo neznámých polí ve znaÄce ID3V2. Remove non-basic ID3V2 frames Odstranit ne-základní ID3V2 pole Copies only ID3V2 frames that seem valid or can be made valid, discarding those that are invalid and can't be fixed (e.g. an APIC frame claiming to hold a picture although it doesn't.) Handles both loadable and broken ID3V2 tags, in the latter case copying being stopped when a fatal error occurs. Kopíruje pouze pole ID3V2, která se zdají být platná nebo je lze udÄ›lat platnými, zahazuje ta, která jsou neplatná a nelze je opravit (napÅ™. pole APIC tvrdící, že drží obrázek, i když to nedÄ›lá). Zachází jak s nahratelnými tak s poÅ¡kozenými znaÄkami ID3V2. Ve druhém případÄ› je kopírování zastaveno, když se objeví vážná chyba. Discard invalid ID3V2 data Zahodit neplatná data ID3V2 Transforms text frames in ID3V2 encoded as Latin1 to Unicode (UTF16.) The reason to do this is that sometimes non-conforming software treats these frames as they are encoded in a different code page, causing other programs to display unexpected data. PromÄ›ní textová pole v ID3V2 kódovaná v Latin-1 na Unicode (UTF16). Důvod pro to je v tom, že nÄ›kdy nevyhovující software zachází s tÄ›mito poli, jako by byla kódována v jiné kódové stránce, Äímž způsobí, že jiné programy zobrazí neoÄekávaná data. Convert non-ASCII ID3V2 text frames to Unicode PÅ™evést ne-ASCII ID3V2 textová pole na Unicode Transforms the case of text frames in ID3V2 tags, according to global settings. PromÄ›ní velikost textových polí ve znaÄkách ID3V2 podle celkových nastavení. Change case for ID3V2 text frames ZmÄ›nit velikost písmen pro ID3V2 textová pole Copies frames from ID3V1 to ID3V2 if those frames don't exist in the destination or if the destination doesn't exist at all. Kopíruje pole z ID3V1 do ID3V2, pokud tato pole neexistují v cíli, nebo pokud cíl neexistuje vůbec. Copy missing ID3V2 frames from ID3V1 Kopírovat chybÄ›jící ID3V2 textová pole z ID3V1 Adds the value of the composer field to the beginning of the artist field in ID3V2 frames. Useful for players that don't use the composer field. PÅ™idá hodnotu pole skladatele na zaÄátek pole umÄ›lce v polích ID3V2. UžiteÄné pro pÅ™ehrávaÄe, které nepoužívají pole skladatele. Add composer field to the artist field in ID3V2 frames PÅ™idat pole skladatele do pole umÄ›lce v polích ID3V2 "Undo" for "adding composer field." Removes the value of the composer field from the beginning of the artist field in ID3V2 frames, if it was previously added. Krok ZpÄ›t pro pÅ™idání pole skladatele. Odstraní hodnotu pole skladatele ze zaÄátku pole umÄ›lce v polích ID3V2, pokud bylo pÅ™edtím pÅ™idáno. Remove composer field from the artist field in ID3V2 frames Odstranit pole skladatele z pole umÄ›lce v polích ID3V2 Copies to the "Composer" field the beginning of an "Artist" field that is formatted as "Composer [Artist]". Does nothing if the "Artist" field doesn't have this format. Kopíruje do skladatelova pole zaÄátek pole umÄ›lce, které je formátováno jako "Skladatel [UmÄ›lec]". NedÄ›lá nic, pokud nemá pole UmÄ›lec tento formát. Fill in composer field based on artist in ID3V2 frames Vyplnit pole skladatele vycházeje z pole umÄ›lce v polích ID3V2 Keeps only the biggest (and supposedly the best) image in a file. The image type is set to Front Cover. (This may result in the replacement of the Front Cover image.) Ponechá v souboru pouze nejvÄ›tší obrázek (pÅ™edpokládá se, že je nejlepší). Typ obrázku je nastaven na obal pÅ™ední strany. (To může vést k nahrazení obrázku s obalem pÅ™ední stranu) Make the largest image "Front Cover" and remove the rest UdÄ›lat nejvÄ›tší obrázek obalem pÅ™ední strany a odstranit zbytek Adds extra spacing to the ID3V2 tag. This allows subsequent saving from the tag editor to complete quicker. PÅ™idá do znaÄky ID3V2 volné místo navíc. To umožní rychlejší dokonÄení pozdÄ›jšího uložení z editoru znaÄek. Reserve space in ID3V2 for fast tag editing Vyhradit místo v ID3V2 pro rychlejší upravování znaÄek Removes large unused blocks from ID3V2 tags. (Usually these have been reserved for fast tag editing, in which case they should be removed only after the ID3V2 tag has all the right values.) Odstraní velké nepoužívané bloky ze znaÄek ID3V2. (Obvykle byly vyhrazeny pro rychlé úpravy znaÄek. V tom případÄ› by mÄ›ly být odstranÄ›ny až poté, co má znaÄka ID3V2 vÅ¡echny správné hodnoty) Remove extra space from ID3V2 Odstranit místo navíc z ID3V2 Removes selected streams. Odstraní vybrané proudy. Remove selected stream(s) Odstranit vybraný(é) proud(y) Removes specified stream. Odstraní zadaný proud. Remove specified stream Odstranit zadaný proud Sometimes a bit gets flipped in a file. This tries to identify places having this issue in audio frame headers. If found, it fixes the problem. It is most likely to apply to files that have 2 audio streams. NÄ›kdy je bit v souboru pÅ™evrácen. Toto zkouší urÄit místa mající tento problém v hlaviÄkách zvukových snímků. Pokud je najde, opraví problém. NejpravdÄ›podobnÄ›ji se to použije na soubory, které mají dva zvukové proudy. Restore flipped bit in audio Obnovit pÅ™evrácený bit v audio Removes all non-audio data that is found between audio streams. In this context, VBRI streams are considered audio streams (while Xing streams are not.) Odstraní vÅ¡echna ne-audio data, která jsou nalezena mezi zvukovými proudy. VBRI proudy se považují za zvukové proudy (zatímco Xing proudy ne) Remove inner non-audio Odstranit vnitÅ™ní ne-audio Removes all unknown streams. Odstraní vÅ¡echny neznámé proudy. Remove unknown streams Odstranit neznámé proudy Removes all broken streams. Odstraní vÅ¡echny poÅ¡kozené proudy. Remove broken streams Odstranit poÅ¡kozené proudy Removes all unsupported streams. Odstraní vÅ¡echny nepodporované proudy. Remove unsupported streams Odstranit nepodporované proudy Removes all truncated audio streams. Odstraní vÅ¡echny zkrácené zvukové proudy. Remove truncated audio streams Odstranit zkrácené zvukové proudy Removes all null streams. Odstraní vÅ¡echny nulové proudy. Remove null streams Odstranit nulové proudy Removes all streams that are broken, truncated, unsupported or have some errors making them unusable. Odstraní vÅ¡echny proudy, které jsou poÅ¡kozené, zkrácené, nepodporované nebo mají nÄ›jaké chyby, což je dÄ›lá nepoužitelnými. Removes broken ID3V2 streams. Odstraní poÅ¡kozené proudy ID3V2. Remove broken ID3V2 streams Odstranit poÅ¡kozené proudy ID3V2 Removes unsupported ID3V2 streams. Odstraní nepodporované proudy ID3V2. Remove unsupported ID3V2 streams Odstranit nepodporované proudy ID3V2 If a file has multiple ID3 streams it keeps only the last ID3V1 and the first ID3V2 stream. Pokud má soubor více proudů ID3, zachová pouze poslední proud ID3V1 a první ID3V2. Remove multiple ID3 streams Odstranit více proudů ID3 Sometimes the number of frames in an audio stream is different from the number of frames in a preceding Xing (or Lame) header, usually because the audio stream was damaged. It's probably best for the Xing header to be removed in this case. If the audio is VBR, you may want to try "Repair VBR data" first. NÄ›kdy je poÄet polí ve zvukovém proudu odliÅ¡ný od poÄtu polí v pÅ™edchozí hlaviÄce Xing (nebo Lame), obvykle proto, že byl zvukový proud poÅ¡kozen. V tom případÄ› je u Xing pravdÄ›podobnÄ› nejlepší hlaviÄku odstranit. Pokud je zvuk VBR (promÄ›nlivý datový tok), můžete chtít nejprve zkusit "Opravit data VBR". Remove mismatched Xing headers Odstranit neodpovídající hlaviÄky Xing Removes all ID3V1 streams. Odstraní vÅ¡echny proudy ID3V1. Remove all ID3V1 streams Odstranit vÅ¡echny proudy ID3V1 Removes all APE streams. Odstraní vÅ¡echny proudy APE. Remove all APE streams Odstranit vÅ¡echny proudy APE Removes all non-audio streams. Odstraní vÅ¡echny ne-audio proudy. Remove all non-audio streams Odstranit vÅ¡echny ne-audio proudy Pads truncated audio frames with 0 to the right. Its usefulness hasn't been determined yet (it might be quite low.) Vycpe zkrácené zvukové snímky s 0 vpravo. Jeho užiteÄnost jeÅ¡tÄ› nebyla urÄena (může být docela malá) Pad truncated audio Vycpat zkrácený zvuk If a file contains VBR audio but doesn't have a Xing header, one such header is added. If a VBRI header exists, it is removed. If a Xing header exists but is determined to be incorrect, it is corrected or replaced. Only the first audio stream is considered; if a file contains more than one audio stream, this should be fixed first. Pokud soubor obsahuje zvuk s promÄ›nlivým datovým tokem (VBR), ale nemá hlaviÄku Xing, je jedna taková hlaviÄka pÅ™idána. Pokud existuje VBRI hlaviÄka, je odstranÄ›na. Pokud existuje hlaviÄka Xing, ale je urÄena jako nesprávná, je opravena nebo nahrazena. Je zvažován pouze první zvukový proud, pokud soubor obsahuje více než jeden zvukový proud, tento by mÄ›l být nejprve opraven. Repair VBR data Opravit data VBR If a file contains VBR audio, any existing VBRI or Xing headers are removed and a new Xing header is created. Only the first audio stream is considered; if a file contains more than one audio stream, this should be fixed first. Pokud soubor obsahuje zvuk s promÄ›nlivým datovým tokem (VBR), jsou existující hlaviÄky VBRI nebo Xing odstranÄ›ny a je vytvoÅ™ena nová hlaviÄka Xing. Je zvažován pouze první zvukový proud, pokud soubor obsahuje více než jeden zvukový proud, tento by mÄ›l být nejprve opraven. Rebuild VBR data Obnovit data VBR Saves user-edited ID3V2.3.0 tags. Uloží uživatelem upravené znaÄky ID3V2.3.0. Save ID3V2.3.0 tags Uložit znaÄky ID3V2.3.0 Doesn't actually change the file, but it creates a temporary copy and it reports that it does change it. This is not as meaningless as it might first seem: if the configuration settings indicate some action (i.e. rename, move or erase) to be taken for processed files, then that action will be performed for these files. While the same can be achieved by changing the settings for unprocessed files, this is easier to use when it is executed on a subset of all the files (filtered or selected). Ve skuteÄnosti nezmÄ›ní soubor, ale vytvoří doÄasnou kopii a nahlásí, že ji zmÄ›nilo. Není to tak bezvýznamné, jak se to nejprve zdá: Pokud naznaÄí nastavení nastavení pro zpracovávané soubory nÄ›jakou Äinnost (napÅ™. pÅ™ejmenovat, pÅ™esunout nebo smazat), pak tato Äinnost bude pro tyto soubory provedena. Zatímco lze totéž dosáhnout zmÄ›nou nastavení pro nezpracované soubory, snadnÄ›ji se to používá, když je to vykonáno na podmnožinÄ› vÅ¡ech souborů (filtrovaných nebo vybraných). No change Žádná zmÄ›na UniqueNotesModel L L Note Poznámka VisibleTransfPainter Action Krok Description Popis Add selected transformation(s) PÅ™idat vybranou(é) promÄ›nu(y) Remove selected transformation(s) Odstranit vybranou(é) promÄ›nu(y) Restore current list to its default value Obnovit nynÄ›jší seznam na jeho výchozí hodnotu Restore current list to the configuration it had when the window was open Obnovit nynÄ›jší seznam na nastavení, které mÄ›l, když bylo okno otevÅ™eno WebDwnldModel Pos Poloha Title Název Artist UmÄ›lec Composer Skladatel MP3Diags-1.2.02/src/translations/mp3diags_de_DE.ts0000644000175000001440000111315212265753762020601 0ustar ciobiusers AboutDlg About MP3 Diags Über MP3 Diags MP3 Diags x.y.z MP3 Diags x.y.z About Über System info Systeminformation GPL V2 (for the program) GPL v2 (Hauptprogramm) LGPL V3 (for the icons) LGPL v3 (für Symbole) GPL V3 (for the icons) GPL v3 (für Symbole) LGPL V2.1 (for Qt) LGPL v2.1 (für Qt) Boost license Boost Lizenz zlib license zLib Lizenz OK OK AboutDlgImpl Written by %1, %2 Geschrieben von %1, %2 Command-line mode by %1, %2 Befehlszeilenmodus von %1, %2 %1 translation by %2, %3 %1 Übersetzung von %2, %3 Czech Tschechische German Deutsche French Französisch Distributed under %1 ?? Verbreitet unter %1 Using %1, released under %2 Benutzt %1, veröffentlicht unter %2 Using %1, released under the %2zlib License%3 Benutzt %1, veröffentlicht unter der %2zLib Lizenz%3 Using %1 and %2, distributed under the %3Boost Software License%4 Benutzt %1 und %2, verbreitet unter der %3Boost Software Lizenz%4 Using original and modified icons from the %1 for %2, distributed under %3LGPL V3%4 Benutzt originale und abgeänderte Symbole von %1 für %2, verbreitet unter %3LGPL v3%4 Using web services provided by %1 to retrieve album data Benutzt Webdienste welche von %1 bereit gestellt werden um Albumdaten abzurufen Home page and documentation: %1 Homepage und Dokumentation: %1 Feedback and support: %1 or %2 at SourceForge Feedback und Supportanfragen: %1 oder %2 auf SourceForge Bug reports and feature requests: %1 at SourceForge Fehlerberichte und Verbesserungsvorschläge: %1 auf SourceForge Change log for the latest version: %1 Änderungsprotokoll für die neueste Version: %1 AlbumInfoDownloaderDlg WWW WWW Search Suchen Artist Künstler Album Album Match count Trefferzahl Results Ergebnisse Released Erscheinungsdatum Genre Genre Use verwende Format Format Amazon Amazon Image Bild Image size Bildgröße ResultNo ?? Ergebnisnummer Previous album vorheriges Album ... ... Previous image or album vorheriges Bild oder Album p p Next image or album nächstes Bild oder Album n n Next album nächstes Album Filter: Filter: CD CD Track count Titelzahl Volume ?? Volume Save image Bild speichern Save all Alles speichern Cancel Abbrechen AlbumInfoDownloaderDlgImpl not found at amazon.com auf Amazon.com nicht gefunden searching ... suche ... Error Fehler You cannot save the results now, because a request is still pending Sie können die Ergebnisse noch nicht speichern da noch eine Anfrage aussteht You cannot save the results now, because no album is loaded Sie können die Ergebnisse noch nicht speichern da noch kein Album geladen wurde You may want to use a different volume selection on this multi-volume release. Sie sollten vielleicht einen anderen Teil (andere CD) dieser mehrteiligen Veröffentlichung benutzen. A number of %1 tracks were expected, but your selection contains %2. Additional tracks will be discarded. %3Save anyway? Es wurden %1 Titel erwartet, aber Ihre Auswahl beinhaltet %2. Überzählige Titel werden verworfen. %3Trotzdem speichern? A number of %1 tracks were expected, but your selection only contains %2. Remaining tracks will get null values. %3Save anyway? Es wurden %1 Titel erwartet, aber Ihre Auswahl beinhaltet %2. Fehlende Titel werden mit leeren Feldern aufgefüllt. %3Trotzdem speichern? Count inconsistency Inkonsistente Anzahl &Save &Speichern Cancel Abbrechen You cannot save any image now, because there is no image loaded Sie können jetzt kein Bild abspeichern da noch keines geladen wurde request error Anfragefehler received %1 bytes %1 Bytes empfangen received very short response; aborting request ... Sehr kurze Antwort erhalten; die Anfrage wird abgebrochen ... Original: %1kB, %2x%3 Original: %1kB, %2x%3 Recompressed to: %1kB, %2x%3 Neu komprimiert: %1kB, %2x%3 Not recompressed Nicht neu komprimiert Failed to load the image Bild konnte nicht geladen werden Error loading image Fehler beim Laden des Bildes init error Initialisierungsfehler unexpected result unerwartetes Ergebnis empty string received Leere Zeichenfolge empfangen search results received Suchergebnisse erhalten Couldn't process the search result. (Usually this means that the server is busy, so trying later might work.) Konnte das Suchergebnis nicht verarbeiten (das heisst meistens dass der Server zu beschäftigt war. Bitte später noch einmal probieren.) No results found Keine Ergebnisse gefunden album info received Albuminformation erhalten Couldn't process the album information. (Usually this means that the server is busy, so trying later might work.) Konnte die Albuminformation nicht verarbeiten (das heisst meistens dass der Server zu beschäftigt war. Bitte später noch einmal probieren.) image received Bild empfangen <All> <Alle> Album %1/%2%3, image %4/%5 Album %1/%2%3, Bild %4/%5 No image kein Bild getting album info ... hole Albuminformation ... getting image ... hole Bild ... ApeItem Ape stream whose items have unsupported flags. APE Stream hat Elemente mit nicht unterstützten Flags. Ape Item seems too big. Although the size may be any 32-bit integer, 256 bytes should be enough in practice. If this message is determined to be mistaken, it will be removed in the future. Item key: %1; item size: %2 APE Element erscheint zu groß. Obwohl die Größe bis zu 4.294.967.295 Bytes sein darf sollten 256 Byte in der Praxis genügen. Falls diese Meldung sich als unzutreffend herausstellt so wird sie in zukünftigen Versionen entfernt. Elementschlüssel: %1; Elementgröße: %2 ApeStream Tag missing header or footer. Tag hat keinen Header oder Footer. CommonData Info Information The font changes will only be used after restarting the application. Die Schriftartänderungen werden erst nach einem Programmneustart wirksam. Error Fehler There was an error setting up the directories containing MP3 files. You will have to define them again. Beim Einrichten der MP3 Verzeichnisse ist ein Fehler ist aufgetreten. Sie müssen sie erneut definieren. ConfigDlg Configuration Konfiguration Files Dateien Simple view Einfache Ansicht Full view Vollansicht Removable TextLabel Removable TextLabel Tab 1 Tab 1 Don't create backup for modified files Kein Backup für geänderte Dateien erstellen Create backup for modified files in Backup für geänderte Dateien erstellen in ... ... Custom settings (go to full view to make changes) Benutzerdefinierte Einstellungen (zur Vollansicht wechseln um Änderungen vorzunehmen) Tab 2 Tab 2 Original file that would be changed Originale Datei würde geändert werden Don't touch nicht anfassen Erase löschen Move and change the name verschieben und umbenennen Move but change the name only if a name collision would occur verschieben. Nur dann umbenennen wenn ein Namenskonflikt auftritt Change name umbenennen Move if the destination doesn't already exist; erase if it does verschieben falls die Zieldatei noch nicht existiert; ansonsten löschen Name change params Parameter zum Umbenennen Identifying label Bezeichnung Don't use an identifying label Keine Bezeichnung benutzen Use "orig" as an identifying label Benutze "Orig" als Bezeichnung Counter Zähler Always use a counter Zähler immer verwenden Only use a counter when a name collision would occur Zähler nur dann verwenden wenn ein Namenskonflikt auftritt Destination Ziel Original file that would not be changed Original Datei würde nicht geändert werden Changed file Geänderte Datei Don't create nicht erstellen Create and change the name erstellen und umbenennen Create but change the name only if a name collision would occur Erstellen aber den Namen nur ändern wenn ein Namenskonflikt auftritt Use "proc" as an identifying label Benutze "proc" als Bezeichnung Use the source dir Benutze Quellverzeichnis Use benutze Temporary files temporäre Dateien Source directory Quellverzeichnis Create in Erstellen in Compare files Dateien vergleichen Ignored notes ignorierte Hinweise Custom transformation lists Liste benutzerdefinierter Transformationen Transformation params Transformationsparameter Locale for text conversion Sprache für Textkonvertierung Case transformation Groß-/Kleinschreibung umwandeln Artists Künstler Others Sonstiges Keep original modification time when changing a file die originale letzten Änderung Zeit beibehalten wenn die Datei geändert wird Keep a single image for a file nur ein Bild pro Datei behalten Visible Transformations Sichtbare Transformationen Quality thresholds Schwellenwerte für Qualität If the audio bitrate falls below these limits, warnings are generated. For VBR a low bitrate is less of an indicatior of poor quality, because if the signal is simple there's no need for higher bitrates. Wenn die Audiobitrate unter dieses Limit absinkt so wird eine Warnung ausgegeben. Bei VBR Ist eine geringe Bitrate nicht unbedingt ein Zeichen für geringe Qualität da bei einem einfachen Signal kein Bedarf für eine höhere Bitrate besteht. The bitrate is not the best quality indicator. Values from a "Lame header" (if present) are generally better, but these headers are not processed in the current version. Die Bitrate ist nicht der bester Indikator für Qualität. Die Werte aus einem "LAME Header" (sofern vorhanden) sind generell besser aber diese Header werden in der aktuellen Version noch nicht verarbeitet. Note that for mono streams, half of the value for "Dual Channel" is used. Für Monostreams wird der halbierte Wert von "Dual Channel" benutzt. Stereo CBR Stereo CBR Joint Stereo CBR Joint Stereo CBR Dual Channel CBR Zweikanal CBR Stereo VBR Stereo VBR Joint Stereo VBR Joint Stereo VBR Dual Channel VBR Zweikanal VBR Colors Farben Audio Audio Xing and LAME Xing und LAME VBRI VBRI ID3V2 ID3v2 APIC APIC ID3V2.3.0 ID3v2.3.0 ID3V2.4.0 ID3v2.4.0 ID3V1 ID3v1 Broken streams Fehlerhafte Streams Truncated streams abgeschnittene Streams Unknown streams unbekannte Streams Lyrics Liedtexte Ape APE Reset to default colors Zurücksetzen aud Standardfarben Shell Shell Enable temporary session per folder Temporäre Sitzung pro Verzeichnis einschalten Enable hidden session per folder Versteckte Sitzung pro Verzeichnis einschalten Enable visible session per folder Sichtbare Sitzung pro Verzeichnis einschalten ErrorShell ShellFehler External tools externe Tools Name Name Command Befehlszeile Wait Warten Don't wait nicht warten Wait for external tool to finish, then keep launch window open Warten bis das externe Tool endet, danach das Fenter offen lassen Wait for external tool to finish, then close launch window Warten bis das externe Tool endet, danach das Fenter schließen Confirm launch Start bestätigen Confirm Bestätigen Add hinzufügen Update aktualisieren Delete löschen Discard changes Änderung verwerfen Tag editor Tag Editor Warn when the tag editor enters albums with non-sequential track numbers warnen wenn der Tag Editor ein Album mit nicht sequentiellen Titelnummern öffnet Warn when pasting track information in the tag editor for albums with non-sequential track numbers Warnen wenn Daten für Alben mit nicht sequenziellen Titelnummern in den Tag Editor eingefügt werden Use "fast save" in the tag editor "Schnelles Speichern" im Tag Editor verwenden Maximum image size, in kB maximale Bildgröße in kB If an image size is above this value, it will be recompressed before storing it inside MP3 files Wenn ein Bild größer ist als dieser Wert dann wird es neu komprimiert bevor es in die MP3 Datei hineingespeichert wird Handle "various artists" for Verwende "Verschiedene Künstler" für iTunes iTunes Windows Media Player Windows Media Player If a field is marked as "assigned" when exiting an album Wenn beim Schliessen eines Albums ein Feld als "zugewiesen" markiert ist Save automatically automatisch speichern Discard verwerfen Ask nachfragen If a field's value is different from that in the ID3V2 tag when exiting an album Wenn sich beim Schliessen eines Albums ein Wert für ein Feld unterscheidet vom ID3v2 Tag Misc Sonstiges Scan for new, modified, and deleted files at startup Bei Programmstart nach neuen, veränderten und gelöschten Dateien scannen Show "Export" button "Export" Schaltfläche anzeigen Show "Debug" button "Debuggen" Schaltfläche anzeigen Show "Sessions" button "Sitzungen" Schaltfläche anzeigen Show Gnome 3 close buttons Gnome3 Schliessen Schaltfläche anzeigen Log program state to "_trace" and "_step" files Programmstatus in den "_trace" und "_step" Dateien loggen Check for new version at startup Beim Programmstart nach neuer Version suchen Language Sprache General font: allgemeine Schriftart: TextLabel ????? Textfeld Change ... Ändern ... Label font smaller by: ?????? Schrift verkleinern um: Fixed font: Schriftart mit fester Breite: Icon size Symbolgröße Auto-size icons based on the width of the main window Symbolgröße nach der Größe des Hauptfensters anpassen Normalizer Normalisierer Command (file names will get added at the end) Befehlszeile (Dateinamen werden angehängt) Keep the window open after completion nach Beendigung das Fenter offen lassen File renamer Dateien umbenennen Invalid characters ungültige Zeichen Replace with ersetzen durch O&K O&K Cancel Abbrechen ConfigDlgImpl <all notes> <alle Hinweise> If you don't know exactly what codepage you want, it's better to make current a file having an ID3V2 tag that contains text frames using the Latin-1 encoding and having non-ASCII characters. Then the content of those frames will replace this text, allowing you to decide which codepage is a match for your file. ID3V1 tags are supported as well, if you want to copy data from them to ID3V2. Wenn Sie nicht sicher sind welche CodePage die Richtige ist dann ist es empfehlenswert für die aktuelle Datei einen ID3V2 Tag zu nehmen welcher Textframes in Latin-1 mit non-ASCII Zeichen kodiert. Dann wird der Inhalt dieser Frames mit diesem Text ersetzt, was Ihnen erlaubt eine passende CodePage für die Datei festzulegen. ID3v1 Tags sind ebenfalls unterstützt falls Sie die Daten von dort nach ID3v2 kopieren wollen. Other notes angezeigte Hinweise Ignore notes ignorierte Hinweise lower case kleinschreibung UPPER CASE GROßSCHREIBUNG Title Case Ersten Buchstaben im Wort groß schreiben Sentence case Ersten Buchstaben im Satz groß schreiben Invisible transformations Versteckte Transformationen Visible transformations Sichtbare Transformationen Characters in this list get replaced with the string below, in "Replace with" An underlined font is used to allow spaces to be seen Die Zeichen in dieser Liste werden mit der Zeichenfolge von "Ersetzen durch" ersetzt Eine unterstrichene Schrift wird benutzt um Leerzeichen sichtbar zu machen This string replaces invalid characters in the file renamer" An underlined font is used to allow spaces to be seen Diese Zeichenfolge ersetzt ungültige Zeichen beim Datei umbenennen Eine unterstrichene Schrift wird benutzt um Leerzeichen sichtbar zu machen All transformations Alle Transformationen Used transformations benutzte Transformationen Confirm Bestätigen You modified the external tool information but you didn't save your changes. Discard the changes or cancel closing of the options window? Sie haben die "Externe Tools" Informationen geändert, aber nicht gesichert. Wollen Sie die Änderungen verwerfen oder das Schließen des Fensters abbrechen? &Discard &Verwerfen &Cancel &Abbrechen Error Fehler You can't have '%1' in both the list of invalid characters and the string that invalid characters are replaced with. Sie können nicht "%1" sowohl in der Liste der ungültigen Zeichen als auch in der Liste der Ersatzzeichen haben. Info Information You need to restart the program to use the new language. Das Programm muss neu gestartet werden um die Sprache wechseln. Invalid folder name Ungültiger Verzeichnisname A folder name is incorrect. Ein Verzeichnisname ist inkorrekt. Add selected note(s) Füge ausgewählte Hinweis(e) hinzu Remove selected note(s) Entferne ausgewählte Hinweis(e) Add all notes alle Hinweise hinzufügen Remove all notes alle Hinweise entfernen Restore lists to their default value Listen auf Standardwerte zurücksetzen Restore lists to the configuration they had when the window was open Listen zu der Konfiguration zurücksetzen welche beim Fensteröffnen vorlag Select folder Verzeichnis auswählen All files alle Dateien CustomTransfListPainter Action Aktion Description Beschreibung Add selected transformation(s) Ausgewählte Transformation(en) hinzufügen Remove selected transformation(s) Ausgewählte Transformation(en) entfernen Restore current list to its default value Aktuelle Liste auf Standardwert zurücksetzen Restore current list to the configuration it had when the window was open Aktuelle Liste zu der Konfiguration zurücksetzen welche beim Fensteröffnen vorlag DataStream begins with: beginnt mit: Broken %1 Fehlerhafte %1 Unsupported %1 nicht unterstützt %1 Unknown unbekannt Truncated MPEG abgeschnittenes MPEG Null ?? context ... Null MPEG-1 MPEG-1 MPEG-2 MPEG-2 Layer I Layer I Layer II Layer II Layer III Layer III Stereo Stereo Joint stereo Joint stereo Dual channel Dual channel Single channel Single channel Not an MPEG frame. Synch missing. Kein MPEG frame. Synch fehlt. Not an MPEG frame. Unsupported version (2.5). Kein MPEG Frame. Nicht unterstützte Version (2.5). Not an MPEG frame. Invalid version. Kein MPEG Frame. Ungültige Version. Not an MPEG frame. Invalid layer. Kein MPEG Frame. Ungültiger Layer. Not an MPEG frame. Invalid bitrate. Kein MPEG Frame. Ungültige Bitrate. Not an MPEG frame. Invalid frequency for MPEG1. Kein MPEG Frame. Ungültige Frequenz für MPEG1. Not an MPEG frame. Invalid frequency for MPEG2. Kein MPEG Frame. Ungültige Frequenz für MPEG2. %1 %2%3%4%5%6Hz%7%8bps%9CRC=%10%11length %12 (0x%13)%14padding=%15 %1 %2%3%4%5%6Hz%7%8bps%9CRC=%10%11Länge %12 (0x%13)%14Fülldaten=%15 padding= Fülldaten= unsynch= nicht sync= frames Frames length= Länge= frame count= Frameanzahl= last frame removed; it was located at 0x%1 letzter Frame entfernt, Position war 0x%1 last frame located at 0x%1 letzten Frame befindet sich an 0x%1 Xing header info: Xing Header Information: frame count= Frameanzahl= byte count= Bytezahl= TOC present TOC gefunden quality= Qualität= MPEG Audio MPEG Audio Xing Header Xing Header Lame Header Lame Header VBRI Header VBRI Header N/A N/A DebugDlg Debug Debuggen Enable tracing Tracing einschalten Save trace messages Tracenachrichten abspeichern ... ... Decode MPEG Audio frame header Dekodiere MPEG Audioframe Header dec dez Test Test tst tst Close Schliessen Use all notes Verwende alle Hinweise Log transformations Transformationen loggen Save downloaded data heruntergeladene Daten speichern Trace messages: Tracenachrichten: DebugDlgImpl If this is checked, ignored notes and trace notes are shown in the note list and exported, regardless of the "Ignored" settings. Note that if this is not checked, the trace notes are discarded during file scanning, so checking it later won't bring them back. A new scan is needed to see them. this is a multiline tooltip Wenn diese Option ausgewählt ist dann werden ignorierte und Tracehinweise in der Hinweisliste angezeigt und exportiert unabhängig von den "Ignorieren" Einstellungen. Beachten Sie dass wenn diese Option NICHT ausgewählt ist dann werden Tracehinweise während des Dateiscannens verworfen. Ein späteres Auswählen birngt diese Hinweise nicht zurück sondern ein erneuter Scan ist notwendig. Choose destination file Zieldatei auswählen Text files (*.txt) Textdateien (*.txt) Decoded MPEG frame header Dekodierter MPEG Frame Header DirFilterDlg Folder filter Verzeichnisfilter OK OK Cancel Abbrechen DirFilterDlgImpl <all folders> <alle Verzeichnisse> Available folders Verfügbare Verzeichnisse Include folders Verzeichnisse einschliessen Add selected folders Füge ausgewählte Verzeichnisse hinzu Remove selected folders Entferne ausgewählte Verzeichnisse Restore lists to the configuration they had when the window was open Listen zu der Konfiguration zurücksetzen welche beim Fenster öffnen vorlag Folder Verzeichnis DiscogsDownloader Download album data from Discogs.com Albumdaten von Discogs.com holen Genres Genres Genres, Styles Genres, Stile Genres (Styles) Genres (Stile) Styles Stile DoubleListWdg Form Form Include elems: enthaltene Elemente: Available elems: verfügbare Elemente: < > > >> >> ... ... ExportDlg Export Exportieren File name: Dateiname: ... ... Format Format XML XML Text Text M3U M3U Remove root: Stammverzeichnis entfernen: When creating an M3U file, this text gets removed from the beginning of the file names. Meant to be used for creating M3U files containing relative paths. Wenn eine M3U Datei erstellt wird dann wird dieser Text vom Anfang der Dateinamen entfernt. Dies ist dafür gedacht um die Erstellung von M3U Dateien mit relativen Pfaden zu ermöglichen. Locale: Gebietsschema: Files to save zu speichernde Dateien Visible files Sichtbare Dateien Selected files Ausgewählte Dateien Sort by short names Nach Kurznamen sortieren Close Schliessen ExportDlgImpl Error Fehler The file name cannot be empty. Exiting ... Der Dateiname kann nicht leer sein. Beenden ... You need to specify an absolute file name when exporting to formats other than .m3u. Exiting ... Beim Exportieren nach anderen Formaten als .m3u muß ein absoluter Dateiname angegeben werden. Beenden ... The root cannot be empty if the file name is relative. Exiting ... Das Stammverzeichnis kann nicht leer sein wenn der Dateiname relativ ist. Beenden ... The root must be an absolute directory name. Exiting ... Das Stammverzeichnis muss ein absoluter Verzeichnisname sein. Beenden ... The root doesn't exist. Exiting ... Das Stammverzeichnis existiert nicht. Beenden ... Warning Warnung A file called "%1" already exists. Do you want to overwrite it? Eine Datei namens "%1" existiert bereits. Soll die Datei überschrieben werden? &Overwrite &Überschreiben Cancel Abbrechen Info Information Successfully created file "%1" Datei "%1" erfolgreich erstellt O&K O&K There was an error writing to the file "%1" Ein Fehler ist beim Schreiben der Datei "%1" aufgetreten Choose destination file Zieldatei auswählen XML files (*.xml);;Text files (*.txt);;M3U files (*.m3u) XML Dateien (*.xml);;Textdateien (*.txt);;M3U Dateien (*.m3u) EWST the letters are the initials of the 4 severity levels: Error, Warning, Support, Trace to translate ??? EWST The file named "%1" isn't inside the specified root. Exiting ... Der Dateiname "%1" befindet sich nicht innerhalb des angegebenen Stammverzeichnisses. Beenden ... The file named "%1" cannot be encoded in the selected locale. Exiting ... Die Datei "%1" kann nicht in dem gewählten Gebietsschema kodiert werden. Beenden ... ExternalToolDlg External tool Externes Tool Keep window open after completion nach Beendigung das Fenter offen lassen Abort Abbrechen Close Schliessen ExternalToolDlgImpl Error Fehler Cannot start process. Check that the executable name and the parameters are correct. Kann den Prozess nicht starten. Bitte den Namen des Programmsi und die Parameter überprüfen. Finished Fertig Warning Warnung Cannot close while "%1" is running. Kann nicht geschlossen werden solange "%1" noch läuft. Confirm Bestätigen Stopping "%1" may leave the files in an inconsistent state or may prevent temporary files from being deleted. Are you sure you want to abort "%1"? Das Abbrechen von "%1" kann Dateien in einem inkonsistentem Zustand lassen oder temporäre Dateien zurücklassen. Sind Sie sicher dass Sie "%1" abbrechen wollen? Yes, abort Ja, abbrechen Don't abort Nein, nicht abbrechen ExternalToolsModel Name Name Command Befehlszeile Wait Warten Confirm launch Start bestätigen FileRenamer::HndlrListModel << missing ID3V2 >> << fehlende ID3V2 >> << no pattern defined >> << kein Muster definiert >> << missing fields >> << fehlende Felder >> File name Dateiname New file name neuer Dateiname FileRenamerDlg MP3 Diags - File renamer MP3 Diags - Dateien umbennen Previous [Ctrl+P] Vorherige [Strg+V] < < Ctrl+P Ctrl+P Folder Verzeichnis Next [Ctrl+N] Nächste [Strg+N] > > Ctrl+N Ctrl+N If this is checked, a copy of the file is created, and both original and copy are kept Wenn diese Option gewählt ist dann wird eine Kopie der Datei erstellt und sowohl Original als auch die Kopie bleiben erhalten Keep the original file Originaldatei beibehalten Mark unrated as duplicates ??? doesnt make sense to me Dateien ohne Bewertung als Duplikate markieren Edit patterns Muster editieren ... ... Rename Umbenennen Close Schliessen alb type ??? Albumtype FileRenamerDlgImpl Source or destination is a directory Quelle oder Ziel ist ein Verzeichnis Error during copying Fehler beim Kopieren Error during renaming Fehler beim Umbenennen Destination already exists Ziel existiert bereits Source not found Quelle nicht gefunden Cannot create folder %1 Kann Verzeichnis %1 nicht erstellen Unknown error unbekannter Fehler No patterns exist kein Muster vorhanden You must create at least a pattern before you can start renaming files. Sie müssen wenigstens ein Muster erstellen bevor Sie Dateien umbenennen können. Confirm Bestätigen Copy all the files? Alle Dateien kopieren? Copy the selected files? Ausgewählte Dateien kopieren? Rename all the files? Alle Dateien umbenennen? Rename the selected files? Ausgewählte Dateien umbenennen? &Yes &Ja Cancel Abbrechen Error Fehler Operation aborted because file "%1" doesn't have an ID3V2 tag. Die Operation wurde abgebrochen da die Datei "%1" kein ID3v2 Tag aufweist. Operation aborted because file "%1" is missing some required fields in its ID3V2 tag. Die Operation wurde abgebrochen da in der Datei "%1" einige notwendige Felder im ID3v2 Tag fehlen. Operation aborted because it would create 2 copies of a file called "%1" Die Operation wurde abgebrochen da sonst 2 Kopien der Datei "%1" erstellt worden wären Operation aborted because a file called "%1" already exists. Die Operation wurde abgebrochen da die Datei "%1" bereits existiert. Copying all the files in the current album Alle Dateien des aktuellen Albums werden kopiert Copying the selected files in the current album Alle ausgewählten Dateien des aktuellen Albums werden kopiert Renaming all the files in the current album Alle Dateien des aktuellen Albums werden umbenannt Renaming the selected files in the current album Alle ausgewählten Dateien des aktuellen Albums werden umbenannt Single artist einzelner Künstler Various artists verschiedene Künstler Error setting up patterns Fehler beim Erstellen der Muster An invalid value was found in the configuration file. You'll have to set up the patterns manually. Ein ungültiger Wert wurde in der Konfigurationsdatei gefunden. Sie müssen die Muster manuell erstellen. FilesModel File name Dateiname FixedAddrRemover Remove stream %1 at address 0x%2 Stream %1 ab Adresse 0x%2 wird entfernt GlobalTranslHlp Don't wait Nicht warten Wait for external tool to finish, then close launch window Warten bis das externe Tool endet, danach das Fenter schließen Wait for external tool to finish, then keep launch window open Warten bis das externe Tool endet, danach das Fenter offen lassen These settings cannot currently be changed. In order to make changes you should probably run the program as an administrator. Diese Einstellungen können im Moment nicht geändert werden. Dafür Sie müssen das Programm als Administrator ausführen. Platform not supported Plattform nicht unterstützt yes Ja no Nein O&K O&K HtmlMsg I got the message; don't show this again Nachricht verstanden, nicht noch einmal anzeigen Id3V230Frame Truncated ID3V2.3.0 tag. Abgeschnittener ID3v2.3.0 Tag. Broken ID3V2.3.0 tag. Fehlerhafter ID3v2.3.0 Tag. ID3V2.3.0 tag containing a frame with an invalid name: %1. ID3v2.3.0 Tag beinhaltet ein Frame mit einem ungültigem Namen: %1. ID3V2.3.0 tag containing a frame with an unsupported flag. ID3v2.3.0 Tag beinhaltet ein Frame mit einem nicht unterstütztem Flag. %1 (Frame: %2) %1 (Frame: %2) INVALID UNGÜLTIG ID3V2.3.0 tag containing a broken text frame named %1. ID3v2.3.0 Tag beinhaltet einen fehlerhaften Textframe namens %1. ID3V2.3.0 tag containing a text frame named %1 using unsupported characters. ID3v2.3.0 Tag beinhaltet einen Textframe mit nicht unterstützten Zeichen.Der Textframe heißt %1. Id3V230Stream Unsupported version of ID3V2 tag%1 Nicht unterstützte Version des ID3v2 Tags%1 ID3V2 tag with unsupported flag. ID3v2 Tag mit nicht unterstütztem Flag. Id3V240Frame Truncated ID3V2.4.0 tag. Abgeschnittener ID3v2.4.0 Tag. ID3V2.4.0 tag containing a frame with an invalid name: %1. ID3v2.4.0 Tag beinhaltet ein Frame mit einem ungültigem Namen: %1. ID3V2.4.0 tag containing a frame with an unsupported flag. ID3v2.4.0 Tag beinhaltet ein Frame mit einem nicht unterstützem Flag. Broken ID3V2.4.0 tag. Fehlerhafter ID3v2.4.0 Tag. %1 (Frame: %2) %1 (Frame: %2) INVALID UNGÜLTIG ID3V2.4.0 tag containing a broken text frame named %1. ID3v2.4.0 Tag beinhaltet einen fehlerhaften Textframe mit dem Namen %1. ID3V2.4.0 tag containing a text frame named %1 using unsupported characters or unsupported text encoding. ID3v2.4.0 Tag beinhaltet einen Textframe namens %1 welcher nicht unterstützte Zeichen oder eine nicht unterstützte Textkodierung beinhaltet. Id3V240Stream ID3V2 tag with unsupported flag. ID3v2 Tag mit nicht unterstütztem Flag. Id3V2Frame << error decoding string >> <Fehler beim Zeichenfolge dekodieren> << unsupported encoding >> << nicht unterstützte Kodierung >> size= Größe= invalid text encoding ungültige Textkodierung unsupported text encoding nicht unterstützte Textkodierung <encoding error> <Kodierungsfehler> invalid data ungültige Daten MIME="%1" File="%2" Descr="%3" Binary data size=%4 MIME="%1" Datei="%2" Beschreibung="%3" Binäre Datengröße=%4 Begins with: %1 Beginnt mit: %1 Content: %1 Inhalt: %1 status=%1 Status=%1 link Link non-cover nicht-Titelbild error Fehler cover Titelbild Id3V2StreamBase %1 (Frame: %2) %1 (Frame: %2) ImageInfoPanel <error> ?? <Fehler> Click to see larger image Anklicken um vergrößertes Bild zu sehen ImageInfoPanelWdg Form Form Thumb ?? Vorschau Pos Position Dim Abmaße Size Größe View full-size image Bild in Originalgröße anzeigen ... ... Assign picture to songs Dieses Bild den Liedern zuordnen Erase local file Lokale Datei löschen ImageInfoPanelWdgImpl Erase these files: Diese Dateien löschen: Erase file %1 Lösche Datei %1 InvalidPattern Pattern "%1" is invalid. %2 Muster "%1" ist ungültig. %2 LogModel Message Nachricht MainFormDlg Dialog Dialog Scan folders for MP3 files [Ctrl+S] Scanne Verzeichnisse nach MP3 Dateien [Strg+S] prc ??? prc Ctrl+S Ctrl+S Close this window and open the Session editor dieses Fenster schliessen und den Session Editor öffnen ... ... Export ... Exportieren ... Filter by notes Filtern nach Hinweisen nflt ??? nflt Filter by folders Filter nach Verzeichnissen dflt ??? dflt Show the full list of files (after applying the filters) komplette Dateiliste anzeigen (nach dem Filtern) Show one album (i.e. folder) at a time Zeige Alben (oder Verzeichnisse) einzeln an d d Show one song at a time zeige Titel einzeln an f f Previous [Ctrl+P] Vorherige [Strg+P] < < Ctrl+P Ctrl+P Folder Verzeichnis Next [Ctrl+N] Nächste [Strg+N] > > Ctrl+N Ctrl+N Apply a single transformation Eine einzelne Transformation anwenden rep rep Apply custom set of transforms #1 Anwenden der benutzerdefinierten Serie von Umwandlungen #1 Apply custom set of transforms #2 Anwenden der benutzerdefinierten Serie von Umwandlungen #2 Apply custom set of transforms #3 Anwenden der benutzerdefinierten Serie von Umwandlungen #3 Tag editor Tag Editor Normalize Normalisieren Reload Neu laden Rename files Dateien umbenennen Configuration Konfiguration cfg cfg Debug debuggen About Über File info Datei Information All notes Alle Hinweise Tag details ?? Tag Details Removable TextLabel ??? Removable TextLabel MainFormDlgImpl Assertion failure ?????? Assertionsfehler Plese report this problem to the project's Issue Tracker at %1 Bitte melden Sie dieses Problem auf dem Project Issue Tracker auf %1 Please restart the application for instructions about how to report this issue Für Anweisungen wie Sie dieses Problem melden können starten Sie bitte das Programm erneut Exit Beenden Restarting after crash Neustart nach einem Absturz Warning Warnung Because MP3 Diags changes the content of your MP3 files if asked to, it has a significant destructive potential, especially in cases where the user doesn't read the documentation and simply expects the program to do other things than what it was designed to do. Da MP3 Diags den Inhalt Ihrer MP3 Dateien verändert (sofern erlaubt) besteht eine signifikante Gefahr das Schaden ensteht. Dies trifft besonders dann zu wenn der Benutzer die Dokumentation nicht gelesen hat und erwartet daß das Programm andere Dinge tut als es konzipiert wurde zu tun. Therefore, it is highly advisable to back up your files first. Daher wird es dringend empfohlen von allen Dateien zuerst ein Backup zu erstellen. Also, although MP3 Diags is very stable on the developer's computer, who hasn't experienced a crash in a long time and never needed to restore MP3 files from a backup, there are several crash reports that haven't been addressed, as the developer couldn't reproduce the crashes and those who reported the crashes didn't answer the developer's questions that might have helped isolate the problem. MP3 Diags läuft sehr stabil auf dem Computer des Entwicklers und er hat seit einer langen Zeit keinen Absturz mehr erlebt und brauchte auch nie MP3 Dateien von einem Backup zurückholen. Trotzdem existieren mehrere Berichte von Abstürzen welche nicht adressiert werden konnten da der Entwickler diese Abstürze nicht nachvollziehen konnte oder die Nutzer welche die Abstürze meldeten nicht auf Nachfragen geantwortet haben. O&K O&K Note Hinweis If you simply left-click, all the visible files get processed. However, it is possible to process only the selected files. To do that, either keep SHIFT pressed down while clicking or use the right button, as described at %1 Mit Links-klick werden alle sichtbaren Dateien verarbeitet. Es ist aber auch möglich nur die ausgewählten Dateien zu verarbeiten. Dazu halten Sie die Umschalttaste gedrückt während Sie rechts klicken; mehr Information unter %1 An unknown note was found in the configuration. This note is unknown: %1 Der folgende unbekannte Hinweis wurde in der Konfiguration gefunden: %1 Unknown notes were found in the configuration. These notes are unknown: %1 Die folgenden unbekannten Hinweise wurden in der Konfiguration gefunden: %1 Error setting up the "ignored notes" list Fehler beim Einrichten der "ignorierte Hinweise" Liste You may want to check again the list and add any notes that you want to ignore. (If you didn't change the settings file manually, this is probably due to a code enhanement that makes some notes no longer needed, and you can safely ignore this message.) Sie sollten die Liste überprüfen und alle Hinweise hinzufügen welche ignoriert werden sollen. (Falls Sie die Einstellungsdatei nicht manuell verändert haben dann kam dieses Problem vermutlich durch eine Programmupdate zustande welche einige Hinweise überflüssig gemacht hat. In diesem Falle können Sie diese Fehlermeldung ignorieren.) MP3 Diags is restarting after a crash. MP3 Diags startet neu nach einem Absturz. Information in the file %1%5%2 may help identify the cause of the crash so please make it available to the developer by mailing it to %3, by reporting an issue to the project's Issue Tracker at %4 and attaching the files to the report, or by some other means (like putting it on a file sharing site.) %1 and %2 are HTML elements Die Information in der Datei %1%5%2 kann helfen die Ursache für den Absturz zu finden, bitte schicken Sie die Datei per Email an den Entwickler %3, oder melden Sie das Problem auf der Projectwebseite beim Issue Tracker %4 und hängen Sie die Datei an den Report oder machen Sie sie anderweitig zugängig (z.B. über eine FileSharing Seite) Information in the files %1%5%2 and %1%6%2 may help identify the cause of the crash so please make them available to the developer by mailing them to %3, by reporting an issue to the project's Issue Tracker at %4 and attaching the files to the report, or by some other means (like putting them on a file sharing site.) %1 and %2 are HTML elements Die Information in der Dateien %1%5%2 und %1%6%2 können helfen die Ursache für den Absturz zu finden, bitte schicken Sie die Dateien per Email an den Entwickler %3, oder melden Sie das Problem auf der Projectwebseite beim Issue Tracker %4 und hängen Sie die Dateien an den Report oder machen Sie sie anderweitig zugängig (z.B. über eine FileSharing Seite) Information in the files %1%5%2, %1%6%2, and %1%7%2 may help identify the cause of the crash so please make them available to the developer by mailing them to %3, by reporting an issue to the project's Issue Tracker at %4 and attaching the files to the report, or by some other means (like putting them on a file sharing site.) %1 and %2 are HTML elements Die Information in den Dateien %1%5%2, %1%6%2 und %1%7%2 können helfen die Ursache für den Absturz zu finden, bitte schicken Sie die Dateien per Email an den Entwickler %3, oder melden Sie das Problem auf der Projectwebseite beim Issue Tracker %4 und hängen Sie die Dateien an den Report oder machen Sie sie anderweitig zugängig (z.B. über eine FileSharing Seite) These are plain text files, which you can review before sending, if you have privacy concerns. Dies sind einfache Textdateien, Sie können sie verifizieren bevor Sie sie versenden falls Sie Datenschutzbedenken haben. After getting the files, the developer will probably want to contact you for more details, so please check back on the status of your report. Nach dem Empfang der Dateien wird der Entwickler vermutlich versuchen mit Ihnen in Kontakt zu treten. Bitte überprüfen Sie den Status Ihres Reports regelmäßig. Note that these files <b>will be removed</b> when you close this window. Beachten Sie daß diese Dateien <b>gelöscht</b> werden wenn Sie das Fenster schliessen. If there is a name of an MP3 file at the end of <b>%1</b>, that might be a file that consistently causes a crash. Please check if it is so. Then, if confirmed, please make that file available by mailing it to %2 or by putting it on a file sharing site. Falls der Name einer MP3 Datei am Ende von <b>%1</b> steht dann is dies vermutlich die Datei welche den Absturz verursacht hat. Bitte überprüfen Sie dieses. Falls dies zutrifft schicken Sie diese Datei per Email an %2 oder stellen Sie sie über eine FileSharing Seite zur Verfügung. Please also try to <b>repeat the steps that led to the crash</b> before reporting the crash, which will probably result in a new set of files being generated; these files are more likely to contain relevant information than the current set of files, because they will also have information on what happened before the crash, while the current files only tell where the crash occured. Bitte versuchen Sie <b>die Schritte bis zum Absturz zu reproduzieren</b> bevor Sie das Problem melden. Dies wird vorraussichtlich einen neuen Satz von Dateien erzeugen welche mehr relevante Informationen enthalten (die neuen Dateien zeigen nur den Absturz, die älteren Dateien auch alles was vorher getan wurde). You should include in your report any other details that seem relevant (what might have caused the failure, steps to reproduce it, ...) Sie sollten in Ihrem Bericht alles erwähnen was für das Problem relevant sein könnte (vermutete Ursachen für den Fehler, Schritte zum Reproduzieren, usw.) Remove these files and continue Diese Dateien entfernen und fortfahren MP3 Diags is restarting after a crash. There was supposed to be some information about what led to the crash in the file <b>%1</b>, but that file cannot be found. Please report this issue to the project's Issue Tracker at %2. MP3 Diags startet neu nach einem Absturz. Die Datei <b>%1</b> sollte Informationen über den Absturz haben aber die Datei kann nicht gefunden werden. Bitte melden Sie dieses Problem beim Projekts Issue Tracker auf %2. The developer will probably want to contact you for more details, so please check back on the status of your report.</p><p style="margin-bottom:8px; margin-top:1px; ">Make sure to include the data below, as well as any other detail that seems relevant (what might have caused the failure, steps to reproduce it, ...) Der Entwickler vermutlich versuchen mit Ihnen in Kontakt zu treten. Bitte überprüfen Sie den Status Ihres Reports regelmäßig.</p><p style="margin-bottom:8px; margin-top:1px; "> Stellen Sie sicher dass die unten aufgeführten Daten erhältlich sind, als auch alle anderen relevanten Einzelheiten (vermutete Absturzursache, Schritte zum Reproduzieren, etc.) MP3 Diags is restarting after a crash. To help determine the reason for the crash, the <i>Log program state to _trace and _step files</i> option has been activated. This logs to 3 files what the program is doing, which might make it slightly slower. MP3 Diags startet neu nach einem Absturz. Um die Absturzursache herauszufinden wurden die <i>Logge den Programstatus in die _trace und _step Dateien</i> Option aktiviert. Dies schreibt in 3 Logdateien was das Program gerade tut. Dies kann Programm geringfügig langsamer macht. It is recommended to not process more than several thousand MP3 files while this option is turned on. You can turn it off manually, in the configuration dialog, in the <i>Others</i> tab, but keeping it turned on may provide very useful feedback to the developer, should the program crash again. With this feedback, future versions of MP3 Diags will get closer to being bug free. Es wird empfohlen nicht mehrere Tausend MP3 Dateien zu verarbeiten solange diese Option aktiv ist. Sie können sie manuell im Konfigurationsdialog im <i>Sonstige</i> Tab ausschalten. Solange die Option eingeschalten ist können wertvolle Informationen für den Entwickler gesammelt werden sollte das Programm erneut abstürzen. Mit Hilfe diese Informationen werden zukünftige Versionen von MP3 Diags stabiler laufen. Error Fehler MP3 Diags crashed while reading song data from the disk. The whole collection will be rescanned. MP3 Diags ist abgestürzt während Musikdaten von der Platte gelesen wurden. Die ganze Sammlung muss noch einmal gescannt werden. Loading data Lade Daten An error occured while loading the MP3 information. Your files will be rescanned. Beim Laden der MP3 Informationen ist ein Fehler aufgetreten. Die Dateien werden erneut gescannt. It seems that MP3 Diags is restarting after a crash. Your files will be rescanned. (Since this may take a long time for large collections, you may want to abort the full rescanning and apply a filter to include only the files that you changed since the last time the program closed correctly, then manually rescan only those files.) Es scheint als wenn MP3 Diags nach einem Programmabsturz neu gestartet wurde. Ihre Dateien werden erneut gescannt. (Da dies bei großen Sammlungen sehr lange dauern kann, sollten Sie vielleicht den Komplettscan abbrechen, einen Filter setzen welcher nur die Dateien einschliesst die seit dem letzten erfolgreichen Programmlauf verändert wurden oder hinzu kamen.Dann sollten Sie nur diese Dateien manuell scannen.) Saving data Daten speichern An error occured while saving the MP3 information. You will have to scan your files again. %1 Beim Speichern der MP3 Informationen trat ein Fehler auf. Sie müssen Ihre Dateien noch einmal scannen. %1 Scanning MP3 files Scanne MP3 Dateien Info Information Your files are not fully supported by the current version of MP3 Diags. The main reason for this is that the developer is aware of some MP3 features but doesn't have actual MP3 files to implement support for those features and test the code. Ihre Dateien werden von der aktuellen Version von MP3 Diags nicht vollständig unterstützt. Hauptgrund dafür ist dass der Entwickler zwar bestimmte MP3 Merkmale kennt aber keine Dateien hat die diese auch verwenden und somit Unterstützung für diese Merkmale nicht einbauen und testen kann. You can help improve MP3 Diags by making files with unsupported notes available to the developer. The preferred way to do this is to report an issue on the project's Issue Tracker at %1, after checking if others made similar files available. To actually send the files, you can mail them to %2 or put them on a file sharing site. It would be a good idea to make sure that you have the latest version of MP3 Diags. Sie können helfen MP3 Diags zu verbessern indem Sie dem Entwickler Dateien mit nicht unterstützten Hinweisen zur Verfügung stellen. Die bevorzugte Methode wäre dass Sie einen Problemreport an den Issue Tracker des Projektes auf %1 schicken nachdem Sie geprüft haben ob vielleicht schon jemand andere dasselbe Problem gemeldet hat. Sie können dann die Dateien per Email an %2 schicken oder auf eine FileSharingseite hochladen. Sie sollten sicherstellen dass Sie die neueste Version von MP3 Diags verwenden. You can identify unsupported notes by the blue color that is used for their labels. ?? Sie können nicht unterstützte Hinweise anhand der blauen Schriftfarbe erkennen. Error setting up custom transformations Fehler beim Einrichten der benutzerdefinierten Transformationen Couldn't find a transformation with the name "%1". The program will proceed, but you should review the custom transformations lists. Konnte keine Transformation namens "%1" finden. Das Programm wird fortfahren aber Sie sollten die Liste mit den benutzerdefinierten Transformationen überprüfen. Error setting up visible transformations Fehler beim Einrichten der sichtbaren Transformationen Couldn't find a transformation with the name "%1". The program will proceed, but you should review the visible transformations list. Konnte keine Transformation namens "%1" finden. Das Programm wird fortfahren aber Sie sollten die Liste mit den sichtbaren Transformation überprüfen. Error setting up external tools Fehler beim Einrichten der externen Tools Unable to parse "%1". The program will proceed, but you should review the external tools list. Konnte "%1" nicht analysieren. Das Programm wird fortfahren, Sie sollten aber die Liste der externen Tools überprüfen. There are no files to normalize. Keine Dateien zum Normalisieren. you are requesting to normalize only some of the files Sie wollen nur einige der Dateien normalisieren the "Album" mode is not selected Der "Album"modus ist nicht gewählt filters are applied Filter wurden angewandt a filter is applied ein Filter wurde angewandt the normalization will process more than 50 files, which is more than what an album usually has die Normalisierung wird mehr als 50 Dateien bearbeiten, dies ist mehr als ein Album üblicherweise hat Normalization should process one whole album at a time, so it should only be run in "Album" mode, when no filters are active, and it should be applied to all the files in that album. But in the current case %1. Sie sollten immer ein komplettes Album zu einem Zeitpunkt normalisieren. Am besten tun Sie dies im "Album" Modus, ohne aktive Filter und es sollte alle Dateien des Albums einschliessen. Aber die momentane Einstellung ist %1. Normalization should process one whole album at a time, so it should only be run in "Album" mode, when no filters are active, and it should be applied to all the files in that album. But in the current case there are some issues: %1 Sie sollten immer ein komplettes Album zu einem Zeitpunkt normalisieren. Am besten tun Sie dies im "Album" Modus, ohne aktive Filter und es sollte alle Dateien des Albums einschliessen. Im aktuellen Falle sind ein paar Probleme: %1 Normalize anyway? Trotzdem normalisieren? Normalize Normalisieren Cancel Abbrechen Confirm Bestätigen Normalize all the files in the current album? (Note that normalization is done "in place", by an external program, so it doesn't care about the transformation settings for original and modified files.) Alle Dateien im aktuellen Album normalisieren? (Beachten Sie dass Normalisierung "an Ort und Stelle" stattfindet und von einem externen Programm durchgeführt wird welches Transformationseinstellungen für originale und veränderte Dateien nicht beachtet) The file list is empty, therefore no transformations can be applied. Exiting ... Die Dateiliste ist leer, daher können keine Transformationen durchgeführt werden. Beenden ... all the files shown in the file list alle in der Dateiliste angezeigten Dateien all %1 files shown in the file list alle in der Dateiliste angezeigten %1 Dateien No file is selected, therefore no transformations can be applied. Exiting ... Es wurden keine Dateien ausgewählt, daher kann auch keine Transformation erfolgen. Beenden ... and the other selected file und die andere ausgewählte Datei and the other %1 selected files und die anderen ausgewählten %1 Dateien The transformation list is empty. Based on the configuration, it is possible for changes to the files in the list to be performed, even in this case (the files may still be moved, renamed or erased). However, the current settings are to leave the original files unchanged, so currently there's no point in applying an empty transformation list. Exiting ... Die Transformationsliste ist leer. Abhängig von der Konfiguration ist es trotzdem möglich dass Änderungen an den Dateien in der Liste vorgenommen werden (verschieben, umbenennen oder löschen). Sie aktuellen Einstellungen abee lassen die Originaldateien unverändert. Damit ist es sinnlos die leere Transformationsliste anzuwenden. Beenden ... Apply an empty transformation list to all the files shown in the file list? (Note that even if no transformations are performed, the files may still be moved, renamed or erased, based on the current settings.) Die leere Transformationsliste auf alle Dateien in der Dateiliste anwenden? (Selbst wenn keine Transformationen stattfinden so können die Dateien abhängig von den Einstellungen doch verschoben, umbenannt oder gelöscht werden) Apply transformation "%1" to %2? Transformation %1 bis %2 anwenden? Apply the following transformations to %1? Die folgenden Transformationen auf %1 anwenden? don't change nicht ändern erase löschen move verschieben rename umbenennen move if destination doesn't exist verschieben falls das Ziel noch nicht existiert Actions to be taken: Durchzuführende Aktionen: original file that has been transformed: %1 Originaldatei welche transformiert wurde: %1 original file that has not been transformed: %1 Originaldatei welche nicht transformiert wurde: %1 &Yes &Ja &No &Nein Applying transformations to MP3 files Transformationen auf MP3 Dateien anwenden Apply custom transformation list #%1 Benutzerdefinierte Transformationsliste #%1 anwenden <empty list> (you can edit the list in the Settings dialog) <leere Liste> (Sie können die Liste im Einstellungsdialog ändern) The file list is empty. You need to populate it before opening the tag editor. Die Dateiliste ist leer. Sie müssen sie füllen bevor Sie den Tag Editor öffnen. The file list is empty. You need to populate it before opening the file rename tool. Die Dateiliste ist leer. Sie müssen sie füllen bevor Sie das "Umbenennen" Werkzeug öffnen. Delete %1? %1 löschen? Cannot delete file %1 Kann Datei %1 nicht löschen MP3 Diags can check at startup if a new version of the program has been released. Here's how this is supposed to work: MP3 Diags kann beim Programmstart nach einer neuen Version suchen. Dies funktioniert wie folgt: The check is done in the background, when the program starts, so there should be no performance penalties Die Überprüfung erfogt im Hintergrund wärend des Programmstarts. Es sollten keine Performanceeinbußen auftreten A notification message is displayed only if there's a new version available Eine Benachrichtigung wird nur dann ausgegeben wenn eine neue Version vorliegt The update is manual. You are told that there is a new version and are offered links to see what's new, but nothing gets downloaded and / or installed automatically Das Update verläuft manuell. Sie werden informiert dass eine neue Version vorliegt und ein Link wird angeboten um die Änderungen zu sehen aber es wird nichts automatisch heruntergeladen und/oder installiert There is no System Tray process checking periodically for updates Es gibt keinen Taskleistenprozess welcher periodisch nach Updates schaut You can turn the notifications on and off from the configuration dialog Sie können die Benachrichtigungen in der Konfiguration ein- und ausschalten If you restart the program within a day after a check, no new check is done Das Programm sucht nur beim ersten Programmstart des Tages nach Updates Disable checking for new versions Suche nach neuer Version ausschalten Enable checking for new versions Suche nach neuer Version einschalten Version %1 has been published. You are running %2. You can see what's new in %3. A more technical list with changes can be seen in %4. Version %1 wurde veröffentlicht. Sie benutzen %2. Einen Überblick über die Änderung gibt es hier: %3. Ein technisch detailiertere Änderungsliste befindet sich hier: %4. the %1MP3 Diags blog%2 arguments are HTML elements Der %1MP3 Diags Blog%2 the %1change log%2 arguments are HTML elements Das %1Änderungsprotokoll%2 This notification is about the availability of the source code. Binaries may or may not be available at this time, depending on your particular platform. Die Nachricht betrifft die Verfügbarkeit des Quelltextes. Binärdateien könnten abhängig von Ihrer Platform ebenfalls bereits zur Verfügung stehen. You should review the changes and decide if you want to upgrade or not. Sie sollten sich die Änderungsliste anschauen und dann entscheiden ob Sie upgraden wollen oder nicht. Note: if you want to upgrade, you should %1close MP3 Diags%2 first. arguments are HTML elements Falls Sie upgraden wollen dann sollten Sie %1 MP3 Diags %2 zuerst beenden. Choose what do you want to do: Was wollen Sie tun: Just close this message Meldung schliessen Don't tell me about version %1 again Bitte nicht mehr über Version %1 informieren Open containing folder ... Öffne Verzeichnis ... Run "%1" on %2? Starte "%1" auf %2? Cannot start process. Check that the executable name and the parameters are correct. Kann den Prozess nicht starten. Bitte den Namen des Programms und die Parameter überprüfen. %1 and %2 %1 und %2 %1, %2 and %3 %1, %2 und %3 %1, %2 and %3 other files %1, %2 und %3 andere Dateien Folder "%1" doesn't exist. The program will exit ... Verzeichnis "%1" existiert nicht. Das Programm wird beendet ... Cannot write to file "%1". The program will exit ... Kann nicht in Datei "%1" schreiben. Das Programm wird beendet ... Mp3Transformer Error Fehler There was an error writing to the following file: %1 Make sure that you have write permissions and that there is enough space on the disk. Processing aborted. Beim Schreiben der folgenden Datei ist ein Fehler aufgetreten: %1 Stellen Sie sicher dass Sie für das Verzeichnis schreibberechtigt sind und das genug Speicherplatz zur Verfügung steht. Der Vorgang wurde abgebrochen. The file "%1" seems to have been modified since the last scan. You need to rescan it before continuing. Processing aborted. Die Datei "%1" wurde verändert seit dem letztem Scan. Sie müssen erneut scannen bevor Sie fortfahren können. Verarbeitung abgebrochen. There was an error processing the following file: %1 Probably the file was deleted or modified since the last scan, in which case you should reload / rescan your collection. Or it may be used by another program; if that's the case, you should stop the other program first. This may also be caused by access restrictions or a full disk. Processing aborted. Beim Verarbeiten der folgenden Datei trat ein Fehler auf: %1 Vermutlich wurde die Datei seit dem letzten Scannen gelöscht oder verändert. Sie sollten die Sammlung erneut laden oder scannen. Es kann auch sein daß die Datei gerade von einem anderen Programm benutzt wird, in diesem Fall sollten Sie das andere Programm stoppen Es kann auch sein das keine Zugriffrechte vorliegen oder die Platte voll ist. Verarbeitung abgebrochen. There was an error processing the following file: %1 The following folder couldn't be created: %2 Processing aborted. Beim Verarbeiten der folgenden Datei trat ein Fehler auf: %1 Der folgende Ordner konnte nicht angelegt werden: %2 Verarbeitung abgebrochen. MusicBrainzDownloader Download album data from MusicBrainz.org Albumdaten von MusicBrainz.org holen waiting %1ms warte %1ms <a href="%1">view at amazon.com</a> <a href="%1">anschauen auf amazon.com</a> NoteFilterDlg Note filter Filter für Hinweise OK OK Cancel Abbrechen NoteFilterDlgImpl <all notes> <alle Hinweise> Available notes verfügbare Hinweise Include notes enthaltene Hinweise Add selected note(s) Füge ausgewählte Hinweise hinzu Remove selected note(s) Entferne ausgewählte Hinweise Add all notes alle Hinweise hinzufügen Remove all notes alle Hinweise entfernen Restore lists to the configuration they had when the window was open Listen zu der Konfiguration zurücksetzen welche beim Öffnen des Fensters vorlag L L Note Hinweis Notes <Placeholder for a note that can no longer be found, most likely as a result of a software upgrade. You should rescan the file.> <Platzhalter für einen Hinweis welcher nicht mehr gefunden werden kann, vermutlich aufgrund eines Softwareupdates. Die Datei sollte neu gescannt werden.> ERROR FEHLER WARNING WARNUNG SUPPORT SUPPORT Two MPEG audio streams found, but a file should have exactly one. Es wurden 2 MPEG Audio Streams gefunden, aber es sollte nur ein Stream pro Datei existieren. Low quality MPEG audio stream. (What is considered "low quality" can be changed in the configuration dialog, under "Quality thresholds".) MPEG Audio Stream mit geringer Qualität (Im Konfigurationsdialog unter Qualitätsgrenzwerte kann definiert werden was als "geringe Qualität" zählt.) No MPEG audio stream found. Kein MPEG Audio Stream gefunden. VBR with audio streams other than MPEG1 Layer III might work incorrectly. VBR mit einem anderen Audiostream als MPEG1 Layer III kann unter Umständen nicht funktionieren. Incomplete MPEG frame at the end of an MPEG stream. Unvollständiger MPEG Frame am Ende des MPEG Streams. Valid frame with a different version found after an MPEG stream. Es wurde ein gültiger Frame mit einer anderen Version nach dem MPEG Stream gefunden. Valid frame with a different layer found after an MPEG stream. Es wurde ein gültiger Frame mit einem anderen Layer nach dem MPEG Stream gefunden. Valid frame with a different channel mode found after an MPEG stream. Es wurde ein gültiger Frame mit einem anderen Channel Modus nach dem MPEG Stream gefunden. Valid frame with a different frequency found after an MPEG stream. Es wurde ein gültiger Frame mit einer anderen Frequenz nach dem MPEG Stream gefunden. Valid frame with a different CRC policy found after an MPEG stream. Es wurde ein gültiger Frame mit einer anderen CRC Regel nach dem MPEG Stream gefunden. Invalid MPEG stream. Stream has fewer than 10 frames. Ungültiger MPEG Stream. Der Stream hat weniger als 10 Frames. Invalid MPEG stream. First frame has different bitrate than the rest. Ungültiger MPEG Stream. Der erste Frame hat eine andere Bitrate als der Rest. No normalization undo information found. The song is probably not normalized by MP3Gain or a similar program. As a result, it may sound too loud or too quiet when compared to songs from other albums. Keine Information gefunden um eine Normalisierung zurückzusetzen. Der Titel wurde vermutlich nicht mit MP3Gain o.ä. normalisiert. Das kann dazu führen das dieser Titel lauter oder leiser klingt im Vergleich mit Titeln von anderen Alben. Found audio stream in an encoding other than "MPEG-1 Layer 3" or "MPEG-2 Layer 3." While MP3 Diags understands such streams, very few tests were run on files containing them (because they are not supposed to be found inside files with the ".mp3" extension), so there is a bigger chance of something going wrong while processing them. Ein Audio Stream mit einer andere Kodierung als MPEG-1 Layer 3 oder MPEG-2 Layer 3 wurde gefunden. MP3 Diags kann diese Streams verarbeiten. Allerdings wurden wenige Tests mit derartigen Dateien unternommen (solche Streams sollten nicht in ".mp3" Dateien auftreten). Daher ist die Gefahr größer dass mit diesen Dateien etwas schief läuft. Two Lame headers found, but a file should have at most one of them. Es wurden zwei Lame Header gefunden, es sollte aber maximal einer pro Datei existieren. Xing header seems added by Mp3Fixer, which makes the first frame unusable and causes a 16-byte unknown or null stream to be detected next. Ein Xing Header wurde anscheinend mit MP3Fixer dazugefügt. Dadurch wurde der erste Frame unbenutzbar und verursacht einen nachfolgenden 16 Byte Unknown/Null Stream. Frame count mismatch between the Xing header and the audio stream. Die Framezahl zwischen XING Header und Audiostream stimmt nicht überein. Two Xing headers found, but a file should have at most one of them. Es wurden zwei Xing Header gefunden, es sollte aber maximal einer pro Datei existieren. The Xing header should be located immediately before the MPEG audio stream. Der Xing Header sollte sich unmittelbar vor dem MPEG Audio Stream befinden. The Xing header should be compatible with the MPEG audio stream, meaning that their MPEG version, layer and frequency must be equal. Der Xing Header sollte mit dem MPEG Audiostream kompatibel sein, das heißt das die MPEG Version, die Layer und die Frequenz übereinstimmen müssen. The MPEG audio stream uses VBR but a Xing header wasn't found. This will confuse some players, which won't be able to display the song duration or to seek. Der MPEG Audio Stream benutzt VBR, es ist aber kein Xing Header vorhanden. Dies kann einige Abspielprogramme verwirren. Two VBRI headers found, but a file should have at most one of them. Es wurden zwei VBRI Header gefunden, es sollte aber maximal einer pro Datei existieren. VBRI headers aren't well supported by some players. They should be replaced by Xing headers. Einige Abspielprogramme unterstützen VBRI Headers nicht besonders gut. Diese Headers sollten durch Xing Headers ersetzt werden. VBRI header found alongside Xing header. The VBRI header should probably be removed. Es wurde sowohl ein VBRI Header als auch ein Xing Header gefunden. Der VBRI Header sollte entfernt werden. Invalid ID3V2 frame. File too short. Ungültiger ID3v2 Frame. Die Datei ist zu kurz. Invalid frame name in ID3V2 tag. Ungültiger Framename im ID3v2 Tag. Flags in the first flag group that are supposed to always be 0 are set to 1. They will be ignored. In der ersten Gruppe befinden sich Flags welche immer auf 0 gesetzt sein sollten aber auf 1 gesetzt sind. Diese Flags werden ignoriert. Flags in the second flag group that are supposed to always be 0 are set to 1. They will be ignored. In der zweiten Gruppe befinden sich Flags welche immer auf 0 gesetzt sein sollten aber auf 1 gesetzt sind. Diese Flags werden ignoriert. Error decoding the value of a text frame while reading an Id3V2 Stream. Fehler beim Dekodieren des Inhalts eines Testframes während des Lesens des ID3v2 Streams. ID3V2 tag has text frames using Latin-1 encoding that contain characters with a code above 127. While this is valid, those frames may have their content set or displayed incorrectly by software that uses the local code page instead of Latin-1. Conversion to Unicode (UTF16) is recommended. Ein ID3v2 Tag hat ein Textframe welches in Latin-1 kodiert ist und Zeichen mit einem Wert von über 127 beinhaltet. Dies ist zwar zulässig; solche Frames können aber unter Umständen falsch angezeigt werden wenn eine Software die lokale Spracheinstellung benutzt anstelle von Latin-1. Es wird empfohlen den Frame nach UniCode (UTF16) umzuwandeln. Empty genre frame (TCON) found. Leeres Genre Frame (TCON) gefunden. Multiple frame instances found, but only the first one will be used. Mehrere Frameinstanzen gefunden, nur die Erste wird benutzt. The padding in the ID3V2 tag is too large, wasting space. (Large padding improves the tag editor saving speed, if fast saving is enabled, so you may want to delay compacting the tag until after you're done with the tag editor.) Zuviele Fülldaten im ID3v2 Tag, Platzverschwendung. (Fülldaten bescheunigen den Tag Editor beim Abspeichern falls Schnelles Speichern aktiviert wurde. Daher könnte es sinnvoll sein das Verkleinern des Tags erst zu vollziehen wenn Sie mit dem Tag Editor fertig sind) Unsupported ID3V2 version. Nicht unterstützte ID3v2 Tag Version. Unsupported ID3V2 tag. Unsupported flag. Nicht unterstütztes ID3v2 Tag. Nicht unterstütztes Flag. Unsupported value for Flags1 in ID3V2 frame. (This may also indicate that the file contains garbage where it was supposed to be zero.) Nicht unterstützte Werte für Flags1 im ID3v2 Frame (Dies kann bedeuten dass die Datei Müll enthält an Stellen wo Nullen sein sollten.) Unsupported value for Flags2 in ID3V2 frame. (This may also indicate that the file contains garbage where it was supposed to be zero.) Nicht unterstützte Werte für Flags2 im ID3v2 Frame (Dies kann bedeuten dass die Datei Müll enthält an Stellen wo Nullen sein sollten.) Multiple instances of the POPM frame found in ID3V2 tag. The current version discards all the instances except the first when processing this tag. Mehrere POPM Frames im ID3v2 Tag gefunden. Die aktuelle Version verwirft alle Instanzen außer der Ersten beim Verarbeiten des Tags. ID3V2 tag contains no frames, which is invalid. This note will disappear once you add track information in the tag editor. ID3v2 Tag beinhaltet keine Frames, dies ist nicht zulässig. Diese Warnung wird verschwinden sobald Sie Titelinformationen im Tag Editor hizufügen. ID3V2 tag contains an empty text frame, which is invalid. ID3v2 Tag beinhaltet ein leeren Textframe. Dies ist nicht zulässig. ID3V2 tag doesn't have an APIC frame (which is used to store images). ID3v2 Tag hat keinen APIC Frame (welcher zum Speichern von Bildern dient). ID3V2 tag has an APIC frame (which is used to store images), but the image couldn't be loaded. ID3v2 Tag hat einen APIC Frame (welcher zum Speichern von Bildern dient) aber das Bild konnte nicht geladen werden. ID3V2 tag has at least one valid APIC frame (which is used to store images), but no frame has a type that is associated with an album cover. ID3v2 Tag hat wenigstens einen APIC Frame (welcher zum Speichern von Bildern dient) aber diese Frames sind nicht mit einem Titelbild assoziiert. Error loading image in APIC frame. Fehler beim Lades des Bildes im APIC Frame. Error loading image in APIC frame. The frame is too short anyway to have space for an image. Fehler beim Lades des Bildes im APIC Frame. Der Frame ist ausserdem zu klein für ein Bild. ID3V2 tag has multiple APIC frames with the same picture type. ID3v2 Tag hat mehrere APIC Frames mit demselben Bildtyp. ID3V2 tag has multiple APIC frames. While this is valid, players usually use only one of them to display an image, discarding the others. ID3v2 Tag hat mehrere APIC Frames. Dies ist zwar zulässig, Abspielprogramme benutzen normalerweise aber nur ein Bild und ignorieren alle anderen. Unsupported text encoding for APIC frame in ID3V2 tag. NIcht unterstützte Textkodierung im APIC Frame des ID3v2 Tags. APIC frame uses a link to a file as a MIME Type, which is not supported. APIC Frame benutzt eine Verknüpfung zu einer Datei als MIME Type. Dies ist nicht unterstützt. Picture description is ignored in the current version. Bildbeschreibungen werden in der aktuellen Version ignoriert. No ID3V2.3.0 tag found, although this is the most popular tag for storing song information. Kein ID3v2.3.0 Tag gefunden, obwohl dies das verbreitetste Tag zum Speichern von Liedinformationen ist. Two ID3V2.3.0 tags found, but a file should have at most one of them. Es wurden zwei ID3v2 Tags gefunden, es sollte aber maximal einer pro Datei existieren. Both ID3V2.3.0 and ID3V2.4.0 tags found, but there should be only one of them. Es wurde sowohl ein ID2v2.3.0 als auch ein ID3v2.4.0 Tag gefunden, es sollte aber maximal einer davon pro Datei existieren. The ID3V2.3.0 tag should be the first tag in a file. Der ID3v2.3.0 Tag sollte der erste Tag in einer Datei sein. ID3V2.3.0 tag contains a text frame encoded as UTF-8, which is valid in ID3V2.4.0 but not in ID3V2.3.0. ID3V2.3.0 Tag hat einen mit UTF-8 kodierten Textframe, was zulässig ist für ID3V2.4.0 aber nicht für ID3V2.3.0. Unsupported value of text frame while reading an Id3V2 Stream. Nicht unterstützter Wert in einem Textframe gefunden während des Lesens des ID3v2 Streams. Invalid ID3V2.3.0 frame. Incorrect frame size or file too short. Ungültiger ID3v2.3.0 Frame. Falsche Framegröße oder Datei zu klein. Two ID3V2.4.0 tags found, but a file should have at most one of them. Es wurden zwei ID3v2.4.0 Tags gefunden, es sollte aber maximal einer pro Datei existieren. Invalid ID3V2.4.0 frame. Incorrect frame size or file too short. Ungültiger ID3v2.4.0 Frame. Falsche Framegröße oder Datei zu klein. Invalid ID3V2.4.0 frame. Frame size is supposed to be stored as a synchsafe integer, which uses only 7 bits in a byte, but the size uses all 8 bits, as in ID3V2.3.0. This will confuse some applications Ungültiger ID3v2.4.0 Frame. Die Framegröße sollte sollte mit nur 7Bits pro Byte gespeichert sein, es wurden aber alle 8Bits benutzt (wie im ID3v2.3.0). Dies kann einige Programme verwirren Deprecated TYER frame found in 2.4.0 tag alongside a TDRC frame. Veralteter TYER Frame zusammen mit einem TDRC Frame im ID3v2.4.0 Frame gefunden. Deprecated TYER frame found in 2.4.0 tag. It's supposed to be replaced by a TDRC frame. Veralteter TYER Frame im ID3v2.4.0 Frame gefunden.Dieser sollte durch einen TDRC Frame ersetzt werden. Deprecated TDAT frame found in 2.4.0 tag alongside a TDRC frame. Veralteter TDAT Frame zusammen mit einem TDRC Frame im ID3v2.4.0 Frame gefunden. Deprecated TDAT frame found in 2.4.0 tag. It's supposed to be replaced by a TDRC frame. Veralteter TDAT Frame im ID3v2.4.0 Frame gefunden.Dieser sollte durch einen TDRC Frame ersetzt werden. Invalid ID3V2.4.0 frame. Mismatched Data length indicator. Frame value is probably incorrect Ungültiger ID3v2.2.0 Frame. Nicht übereinstimmende Datenlängenindikatoren. Framewerte sind vermutlich falsch Invalid ID3V2.4.0 frame. Incorrect unsynchronization bit. Ungültiger ID3v2.4.0 Frame. Falsch gesetztes "UnSync"Bit. Unsupported value of text frame while reading an Id3V2.4.0 stream. It may be using an unsupported text encoding. Nicht unterstützter Wert in einem Textframe beim Einlesen des ID3v2.4.0 Streams gefunden. Es könnte eine nicht unterstützte Textkodierung vorliegen. The only supported tag found that is capable of storing song information is ID3V1, which has pretty limited capabilities. Der einzige gefundene Tag mit Support für Liedinformation ist ID3v1, dieser hat aber nur sehr beschränkte Fähigkeiten. The ID3V1 tag should be located after the MPEG audio stream. Der ID3v1 Tag sollte hinter dem MPEG Audiostream stehen. Invalid ID3V1 tag. File too short. Ungültiger ID3v1 Frame. Die Datei ist zu kurz. Two ID3V1 tags found, but a file should have at most one of them. Es wurden zwei ID3v1 Tags gefunden, es sollte aber maximal einer pro Datei existieren. ID3V1 tag contains fields padded with spaces alongside fields padded with zeroes. The standard only allows zeroes, but some tools use spaces. Even so, zero-padding and space-padding shouldn't be mixed. ID3v1 Tag hat Felder die mit Leerzeichen aufgefüllt wurden als auch solche wo Nullen verwendet wurden. Der Standard erlaubt nur Nullen, aber einige Tools benutzen Leerzeichen. Es sollte aber in keinem Fall gemischt auftreten. ID3V1 tag contains fields that are padded with spaces mixed with zeroes. The standard only allows zeroes, but some tools use spaces. Even so, one character should be used for padding for the whole tag. ID3v1 Tag hat Felder welche mit Leerzeichen und mit Nullen gemischt aufgefüllt wurden. Der Standard erlaubt nur Nullen, aber einige Tools benutzen Leerzeichen. Es sollte aber in keinem Fall gemischt auftreten. Invalid ID3V1 tag. Invalid characters in Name field. Ungültiger ID3v1 Tag. Ungültige Zeichen im Name Feld. Invalid ID3V1 tag. Invalid characters in Artist field. Ungültiger ID3v1 Tag. Ungültige Zeichen im Künstler Feld. Invalid ID3V1 tag. Invalid characters in Album field. Ungültiger ID3v1 Tag. Ungültige Zeichen im Album Feld. Invalid ID3V1 tag. Invalid characters in Year field. Ungültiger ID3v1 Tag. Ungültige Zeichen im Jahr Feld. Invalid ID3V1 tag. Invalid characters in Comment field. Ungültiger ID3v1 Tag. Ungültige Zeichen im Kommentar Feld. Broken stream found. Fehlerhaften Stream gefunden. Broken stream found. Since other streams follow, it is possible that players and tools will have problems using the file. Removing the stream is recommended. Fehlerhaften Stream gefunden. Es folgen noch andere Streams, damit könnten Abspieler oder andere Tools Probleme haben. Es ist empfohlen diesen Stream zu entfernen. Truncated MPEG stream found. The cause for this seems to be that the file was truncated. Abgeschnittenen MPEG Stream gefunden. Es scheint als wenn die Datei am Ende abgeschitten wurde. Truncated MPEG stream found. Since other streams follow, it is possible that players and tools will have problems using the file. Removing the stream or padding it with 0 to reach its declared size is strongly recommended. Abgeschnittenen MPEG Stream gefunden. Es folgen andere Streams, Abspieler oder andere Tools könnten mit dieser Datei Probleme haben. Es ist empfohlen diesen Stream zu entfernen oder mit Nullen bis zur vorgegebenen Größe aufzufüllen. Not enough remaining bytes to create an UnknownDataStream. Nicht genügend Bytes übrig um einen UnknownDataStream zu erstellen. Unknown stream found. Unbekannter Stream gefunden. Unknown stream found. Since other streams follow, it is possible that players and tools will have problems using the file. Removing the stream is recommended. Unbekannter Stream gefunden. Es folgen noch andere Streams, Abspieler oder andere Tools könnten mit dieser Datei Probleme haben. Es ist empfohlen diesen Stream zu entfernen. File contains null streams. Datei hat leere Streams. Invalid Lyrics stream tag. File too short. Ungültiger Liedtext Tag. Die Datei ist zu kurz. Two Lyrics tags found, but only one is supported. Es wurden zwei LiedText Tags gefunden, es wird aber nur einer pro Datei unterstützt. Invalid Lyrics stream tag. Unexpected characters found. Ungültiger LiedTextstream Tag. Unerwartete Zeichen gefunden. Multiple fields with the same name were found in a Lyrics tag, but only one is supported. Mehrere Felder mit dem gleichen Namen wurden im LiedText Tag gefunden, aber nur eines wird unterstützt. Currently INF fields in Lyrics tags are not fully supported. Zur Zeit werden INF Felder im LiedText Tag noch nicht vollständig unterstützt. Invalid Ape Item. File too short. Ungültiger Ape Tag. Die Datei ist zu kurz. Ape Item seems too big. Although the size may be any 32-bit integer, 256 bytes should be enough in practice. If this note is determined to be incorrect, it will be removed in the future. APE Element erscheint zu groß. Obwohl die Größe bis zu 4294967295 Bytes sein darf sollten 256 Byte in der Praxis genügen. Falls dieser Hinweis sich als unzutreffend herausstellt so wird er in zukünftigen Versionen entfernt. Invalid Ape Item. Terminator not found for item name. Ungültiges Ape Element. Kein Terminator für Elementnamen gefunden. Invalid Ape tag. Header expected but footer found. Ungültiges Ape Tag. Header erwartet aber Footer gefunden. Not an Ape tag. File too short. Kein Ape Tag. Die Datei ist zu kurz. Invalid Ape tag. Footer expected but header found. Ungültiger Ape Tag. Footer erwartet aber Header gefunden. Invalid Ape tag. Mismatch between header and footer. Ungültiger Ape Tag. Diskrepanz zwischen Header und Footer gefunden. Two Ape tags found, but only one is supported. Es wurden zwei Ape Tags gefunden, es wird aber nur einer pro Datei unterstützt. Ape item flags not supported. Ape Flag nicht unterstützt. Unsupported Ape tag. Currently a missing header or footer are not supported. Nicht unterstützter Ape Tag. Zur Zeit werden fehlende Header oder Footer nicht unterstützt. The file seems to have been changed in the (short) time that passed between parsing it and the initial search for pictures. If you think that's not the case, report a bug. Die Datei scheint verändert worden zu sein in der (kurzen) Zeit zwischen Analyse und der Suche nach Bildern. Falls Sie denken dass dies nicht der Fall sein kann dann melden Sie bitte einen Fehler. No supported tag found that is capable of storing song information. Kein unterstützter Tag gefunden der in der Lage wäre Liedinformationen aufzunehmen. Too many TRACE notes added. The rest will be discarded. Zu viele TRACE Hinweise hinzugefügt, der Rest wird verworfen. Too many notes added. The rest will be discarded. Zu viele Hinweise hinzugefügt, der Rest wird verworfen. Too many streams found. Aborting processing. Zu viele Streams gefunden. Verarbeitung abgebrochen. Unsupported stream found. It may be supported in the future if there's a real need for it. Nicht unterstützter Stream gefunden. Er könnte in der Zukunft unterstützt werden falls genug Bedarf besteht. The file was saved using the "fast" option. While this improves the saving speed, it may leave the notes in an inconsistent state, so you should rescan the file. Die Datei wurde mit er "Schnell Speichern" Option gesichert. Dies beschleunigt zwar das Abspeichern kann aber die Hinweise in einem inkonsistentem Zustand hinterlassen. Sie sollten die Datei neu scannen. NotesModel L L Note Hinweis Address Addresse PaletteDlg Background colors HIntergrundfarben Album Album Field equal to the ID3V2 field or missing ??? Feld gleicht dem ID3v2 Feld oder fehlt Field is different from the corresponding ID3V2 value (or no ID3V2 field exists) Feld gleicht nicht dem korrespondierendem ID3v2 Wert (oder kein ID3v2 Feld existiert) Value marked as "assigned" Wert ist als "zugewiesen" markiert Song Titel Tag and field present Tag und Feld sind vorhanden Tag not present Tag fehlt Tag present but field is not supported Tag is vorhanden aber Feld ist nicht unterstützt Tag present, field is supported but missing Tag is vorhanden, Feld ist unterstützt aber nicht vorhanden OK OK PatternsDlg Patterns Muster Add predefined patterns vordefinierte Muster hinzufügen Line 1, Col 1 Zeile 1, Spalte 1 O&K O&K Cancel Abbrechen Renamer A pattern cannot be empty Ein Muster kann nicht leer sein A pattern must either begin with '%1' or contain no '%1' at all Ein Muster muss entweder mit %1 anfangen oder kann %1 gar nicht beinhalten A pattern must either begin with "<drive>:\" or contain no '\' at all Ein Muster muss entweder mit "<Laufwerk>:\" anfangen oder gar kein '\' beinhalten Error in column %1. Fehler in Spalte %1. Nested optional elements are not allowed Verschachtelte optionale Elemente sind nicht erlaubt Trying to close and optional element although none is open Es wurde versucht ein optionales Element zu schliessen obwohl keines geöffnet wurde Optional element must be closed Optionales Element muss geschlossen werden Title entry (%t) must be present Titel Eintrag (%t) muss angegeben werden RenamerPatternsDlgImpl %n track number %a artist %t title %b album %y year %g genre %r rating (a lowercase letter) %c composer To include the special characters "%", "[" and "]", precede them by a "%": "%%", "%[" and "%]" The path should be either a full path, starting with a "%1", or it should contain no "%1", if what is wanted is for the renamed files to remain in their original directories %n Titelnummer %a Künstler %t Titel %b Album %y Jahr %g Genre %r Bewertung (Kleinbuchstabe) %c Komponist Um diese Spezialzeichen einzufügen "%", "[" und "]" muss davor ein "%": "%%", "%[" und "%]" stehen Der Pfad sollte entweder ein vollständiger Pfad sein, mit einem "%1" anfangen, oder sollte garkein "%1" beinhalten falls es gewünscht ist daß die umbenannten Dateien in den originalen Verzeichnissen verbleiben %n track number %a artist %t title %b album %y year %g genre %r rating (a lowercase letter) %c composer To include the special characters "%", "[" and "]", precede them by a "%": "%%", "%[" and "%]" The path should be either a full path, starting with a drive letter followed by ":\", or it should contain no "\", if what is wanted is for the renamed files to remain in their original directories %n Titelnummer %a Künstler %t Titel %b Album %y Jahr %g Genre %r Bewertung (Kleinbuchstabe) %c Komponist Um diese Spezialzeichen einzufügen "%", "[" und "]" muss davor ein "%": "%%", "%[" und "%]" stehen Der Pfad sollte entweder ein vollständiger Pfad sein, mit einem Laufwerksbuchstaben gefolgt von einem ":\" anfangen, oder sollte gar kein "\" beinhalten falls es gewünscht ist daß die umbenannten Dateien i den originalen Verzeichnissen verbleiben Error Fehler Line %1, Col %2 Zeile %1, Spalte %2 ScanDlg Scan Scannen Rescan files that seem unchanged Dateien welche unverändert erscheinen erneut scannen &Scan &Scannen Cancel Abbrechen SessionEditorDlg pppppppppppppp ??? pppppppppppppp Language Sprache Include directories Verzeichnisse mit erfassen Backup Backup Don't create backup for modified files Kein Backup für veränderte Dateien erstellen Create backup for modified files in Backup für veränderte Dateien erstellen in ... ... Settings file name Dateiname für die Einstellungen Scan for new, modified, and deleted files at startup Bei Programmstart nach neuen, veränderten und gelöschten Dateien suchen Open last session at s&tartup Die letzte &Sitzung beim Programmstart wieder öffnen Open the main "Sessions" dialog, to load an existing session den "Sitzungen" Dialog öffnen um eine existierende Sitzung zu laden O&K O&K Cancel Abbrechen SessionEditorDlgImpl This is the name of the "settings file" It is supposed to be a file that doesn't already exist. You don't need to set it up. MP3 Diags will store its settings in this file. The name was generated automatically. If you want to choose a different name, simply click on the button at the right to change it. this is a multiline tooltip Dies ist der Name für die "Einstellungsdatei" Es sollte eine Datei sein welche noch nicht existiert. Sie brauchen sie nicht anzulegen. MP3 Diags wird die Einstellungen in dieser Datei absichern. Der Name wurde automatisch generiert. Wenn Sie einen anderen Namen wählen wollen dann klicken Sie einfach auf den Knopf rechts um ihn zu ändern. Here you need to specify the name of a "settings file" This is supposed to be a file that doesn't already exist. You don't need to set it up. MP3 Diags will store its settings in this file. To change it, simply click on the button at the right to choose the name of the settings file. this is a multiline tooltip Hier können Sie den Namen für die "Einstellungsdatei" angeben Es sollte eine Datei sein welche noch nicht existiert. Sie brauchen sie nicht anzulegen. MP3 Diags wird die Einstellungen in dieser Datei absichern. Um den Namen zu ändern klicken Sie einfach auf den Knopf rechts um einen Namen anzugeben. MP3 Diags - Create new session MP3 Diags - eine neue Sitzung anlegen MP3 Diags - Edit session MP3 Diags - Sitzung ändern Error Fehler You need to specify the name of the settings file. This is supposed to be a file that doesn't already exist. You don't need to set it up, but just to pick a name for it. MP3 Diags will store its settings in this file. Sie müssen den Namen für die "Einstellungsdatei" angeben. Es sollte eine Datei sein welche noch nicht existiert. Sie brauchen sie nicht anzulegen, nur einen Namen angeben. MP3 Diags wird die Einstellungen in dieser Datei absichern. You need to select at least a directory to be included in the session. Die Sitzung muss wenigstens ein Verzeichnis beinhalten. If you want to create backups, you must select an existing directory to store them. Wenn Sie Backups anlegen wollen dann müssen Sie ein bereits existierendes Verzeichnis dafür auswählen. Failed to write to file %1 Konnte Datei %1 nicht schreiben Select folder Verzeichnis auswählen All files (*) alle Dateien (*) Enter configuration file Konfigurationsdatei angeben MP3 Diags session files (*%1) MP3 Diags Sitzungsdateien (*%1) SessionsDlg MP3 Diags - Sessions MP3 Diags - Sitzungen Language Sprache &Open &Öffnen &New ... &Neu ... &Edit ... &Ändern ... Save &as ... Speichern &als ... E&rase &Löschen &Load ... L&aden ... &Hide &Verstecken Close Schliessen Temporary session template Vorlage für temporäre Sitzung Persistent session template Vorlage für normale Sitzung At s&tartup open the last session automatically &Beim Programmstart die letzte Sitzung automatisch wieder öffnen SessionsDlgImpl <last session> ?? translate <letzte Sitzung> Confirm Bestätigen Do you really want to erase the current session? Wollen Sie die aktuelle Sitzung wirklich löschen? Erase Löschen Cancel Abbrechen Error Fehler Failed to remove the data files associated with this session Konnte die mit dieser Sitzung verknüpften Datendateien nicht löschen Save session as ... Speichere Sitzung als ... Do you really want to hide the current session? Wollen Sie die aktuelle Sitzung wirklich verstecken? Hide Verstecken Choose a session file Eine Sitzung auswählen The session named "%1" is already part of the session list Eine Sitzung namens "%1" ist bereits in der Sitzungsliste The session list is empty. You must create a new session or load an existing one. Die Sitzungsliste ist leer. Sie müssen eine neue Sitzung erstellen oder eine existierende laden. SessionsModel File name Dateiname ShellIntegrator %1 - %2 %1 - %2 Error setting up shell integration Fehler beim Einrichten der Shellintegration It appears that setting up shell integration didn't complete successfully. You might have to configure it manually. Es sieht so aus als wenn die Shellintegration nicht erfolgreich verlief. Sie müssen dies vermutlich manuell konfigurieren. This message will not be shown again until the program is restarted, even if more errors occur. Die Meldung wird bis zum nächsten Programmstart nicht mehr angezeigt, auch wenn der Fehler erneut auftritt. O&K O&K temporary folder temporäres Verzeichnis hidden folder verstecktes Verzeichnis visible folder sichbares Verzeichnis StreamsModel Address Adresse Size (dec) Größe (dez) Size (hex) Größe (hex) Type Typ Stream details Streamdetails TagEditor::CurrentAlbumDelegate Warning Warnung You are editing data in a cell. If you proceed that change will be lost. Proceed and lose the data? Sie editieren Daten in einer Zelle. Wenn Sie fortfahren dann gehen diese Änderungen verloren. Fortfahren und Änderungen verwerfen? Proceed Fortfahren Cancel Abbrechen TagEditor::CurrentAlbumModel N/A N/A File name Dateiname Error Fehler The data contained errors and couldn't be saved Die Daten waren fehlerhaft und konnten nicht gespeichert werden TagEditor::CurrentFileModel N/A N/A TagEditorDlg MP3 Diags - Tag editor MP3 Diags - Tag Editor Save Speichern S S Reset Zurücksetzen R Pls check Z Previous [Ctrl+P] Vorherige [Strg+P] < < Ctrl+P Ctrl+P Folder Verzeichnis Next [Ctrl+N] Nächste [Strg+N] > > Ctrl+N Ctrl+N Query Discogs Suchen bei Discogs Q A Query MusicBrainz Suchen bei MusicBrainz ... ... Toggle "Various Artists" Schalte "Verschiedene Künstler" um V V Change case automatically Ändere Groß-/Kleinschreibung automatisch Copy field(s) from first line Feld(er) von der ersten Zeile kopieren C C Sort by track number Nach Titelnummer sortieren Toggle "assigned" state Schalte "Zugewiesen" Status um A Z Paste Einfügen Ctrl+V Ctrl+V Edit file patterns Dateimuster editieren F F Background colors Hintergrundfarben Configuration Konfiguration Close Schliessen TextLabel Beschriftung TagEditorDlgImpl Toggle "Various Artists" "Verschiedene Künstler" umschalten To enable "Various Artists" you need to open the configuration dialog, go to the "Others" tab and check the corresponding checkbox(es) this is a multi-line tooltip Um "Verschiedene Künstler" einzuschalten müssen Sie in den Konfigurationsdialog gehen, zum "Sonstiges" Tab und die ensprechenden Boxen markieren Error setting up the tag order Fehler beim Einrichten der Tagreihenfolge An invalid value was found in the configuration file. You'll have to sort the tags again. Ein ungülter Wert wurde in der Konfigurationsdatei gefunden. SIe müssen die Tags nochmal sortieren. Error setting up patterns Fehler beim Einrichten der Muster An invalid value was found in the configuration file. You'll have to set up the patterns manually. Ein ungülter Wert wurde in der Konfigurationsdatei gefunden. Sie müssen die Muster manuell erstellen. Warning Warnung Reloading the current album causes all unsaved changes to be lost. Really reload? Wenn das aktuelle Album noch einmal geladen wird dann gehen alle ungesicherten Änderungen verloren. Wollen Sie wirklich neu laden? Reload Neu laden Cancel Abbrechen Artists - %1 Künstler - %1 Others - %1 Andere - %1 Confirm Bestätigen You added %1 images but then you didn't assign them to any songs. What do you want to do? Es wurden %1 Bilder hinzugefügt aber nicht zu einem Lied zugeordnet. Was wollen Sie tun? You added an image but then you didn't assign it to any song. What do you want to do? Es wurde ein Bild hinzugefügt aber nicht zu einem Lied zugeordnet. Was wollen Sie tun? &Discard &Verwerfen &Cancel &Abbrechen There are unsaved fields that you assigned a value to, as well as fields whose value doesn't match the ID3V2 value. What do you want to do? Einige Felder mit neu zugewiesenen Werten wurden noch nicht gesichert und einige Felder haben Werte welche nicht mit dem ID3v2 Wert übereinstimmen. Was wollen Sie tun? &Save &Speichern There are unsaved fields that you assigned a value to. What do you want to do? Einige Felder mit neu zugewiesenen Werten wurden noch nicht gesichert. Was wollen Sie tun? There are fields whose value doesn't match the ID3V2 value. What do you want to do? Einige Felder haben Werte welche nicht mit dem ID3v2 Wert übereinstimmen. Was wollen Sie tun? Saving ID3V2.3.0 tags Speichere ID3v2.3.0 Tags Info Information Some fields are missing or may be incomplete. While this is usually solved by downloading correct information, there are a cases when this approach doesn't work, like custom compilations, rare albums, or missing tracks. Einige Felder fehlen oder sind unvollständig. Dies kann normalerweise durch Herunterladen der korrekten Daten gelöst werden, aber es gibt Fälle wo dieser Ansatz nicht funktioniert (z.B. eigene Zusammenstellungen, seltene Alben oder fehlende Titel). If your current folder fits one of these cases or you simply have consistently named files that you would prefer to use as a source of track info, you may want to take a look at the tag editor's patterns, at %1 Falls Ihr aktuelles Verzeichnis passt oder falls Sie einfach konsistent benannte Dateinamen verwenden und Sie diese als Quelle für die Titelinformationen benutzen wollen dann sollten Sie einen Blick auf die Muster im Tag Editor werfen, hier: %1 O&K O&K TagEdtPatternsDlgImpl %n track number %a artist %t title %b album %y year %g genre %r rating (a lowercase letter) %c composer %i ignored To include the special characters "%", "[", "]" and "%1", preced them by a "%": "%%", "%[", "%]" and "%%1" For a pattern to be considered a "file pattern" (as opposed to a "table pattern"), it must contain at least a "%1", even if you don't care about what's in the file's parent directory (see the fourth predefined pattern for an example.) Leading and trailing spaces are removed automatically from unbound fields after matching, so "-[ ]%t" is equivalent to "-%t" (but "-[ ]%n" is not equivalent to "-%n", because %n is a fixed format field). However, all non-optional characters matter in the matching phase, including spaces. whats an unbound field? %n Titelnummer %a Künstler %t Titel %b Album %y Jahr %g Genre %r Bewertung (Kleinbuchstabe) %c Komponist %i ignoriert Um diese Spezialzeichen einzufügen "%", "[", "]" und "%1", muss davor ein "%": "%%", "%[", "%]" und "%%1" stehen Damit ein Muster als Dateimuster erkannt wird (im Gegensatz zu einem "Tabellenmuster"), muß es mindestens ein "%1" enthalten, selbst dann wenn der Inhalt des übergeordneten Verzeichnisses unwichtig ist (vergleiche das vierte vordefinierte Muster als Beispiel.) Leerzeichen am Anfang und Ende werden automatisch nach dem Vergleich von ungebundenen Feldern entfernt, so "-[ ]%t" ist gleichwertig zu "-%t" (aber "-[ ]%n" ist nicht gleichwertig zu "-%n", da %n ein Feld mit festem Format ist). Jedoch alle nicht-optionalen Zeichen sind relevant in der Vergleichsphase, auch Leerzeichen. Error Fehler Line %1, Col %2 Zeile %1, Spalte %2 TagReadPanel Track# Titelnummer Artist Künstler Title Titel Album Album VA VA Time Zeit Genre Genre Rating Wertung Composer Komponist TagReader <non-text value> <nicht-Text Wert> other andere 32x32 icon 32x32 Symbol other file icon anderes Dateisymbol front cover Titelseite back cover Rückseite leaflet page Begleitheftseite media ??? Datenträger lead artist Hauptinterpret artist Künstler conductor Dirigent band Band composer Komponist lyricist Texter recording location Aufnahmeort during recording während der Aufnahme during performance während der Vorstellung screen capture ??? Bildschirmphoto illustration Illustration band/artist logotype Band/Künstler Logo publisher/studio logotype Verlag/Studio Logo unknown unbekannt <no change> <keine Änderung> lower case kleinschreibung UPPER CASE GROßSCHREIBUNG Title Case Ersten Buchstaben im Wort groß schreiben Sentence case Ersten Buchstaben im Satz groß schreiben Title Titel Artist Künstler Track # Titelnummer Time Länge Genre Genre Picture Bild Album Album Rating Wertung Composer Komponist VA VA <error loading frame> <Fehler beim Frame laden> <error decoding frame> <Fehler beim Frame dekodieren> Additional %1 field: %2 Zusätzlich %1 Feld: %2 %1 field: %2 %1 Feld: %2 Comment: %1 Kommentar: %1 (file) (Datei) (table) (Tabelle) Pattern Muster Web Web TagWriter Warning Warnung Track numbers are supposed to be consecutive numbers from 1 to the total number of tracks. This is not the case with the current album, which may lead to incorrect assignments when pasting information. Titelnummern sollten aufeinanderfolgende Zahlen von 1 bis zur Gesamtzahl der Titel sein. Dies ist nicht der Fall beim aktuellen Album. Dies kann zur inkorrekten Zuordnung von eingefügten Informationen führen. Error Fehler Some files have been modified by an external tool after the last scan. You won't be able to save any changes to those files until you rescan them. Einige Dateien wurden nach dem letztem Scan mit einem externen Tool verändert.Für diese Dateien können keine Änderungen gespeichert werden bevor sie nicht erneut gescannt werden. Do you want to erase these files?%1 Wollen Sie diese Dateien wirklich löschen?%1 Do you want to erase %1? Wollen Sie folgende Datei wirklich löschen: %1? Confirm Bestätigen Erase Löschen Cancel Abbrechen You cannot erase image files if there are unsaved values. Do you want to save? Sie können Bilddateien nicht löschen solange noch ungesicherte Werte vorliegen. Wollen Sie diese jetzt absichern? Save, then erase file Speichern, danach Datei löschen Erasing image files triggers a full reload, which results in downloaded and pasted data being lost. Erase anyway? Das Löschen von Bilddateien verursacht ein komplettes Neuladen was wiederum dazu führt dass heruntergeladene und eingefügte Daten verloren gehen. Wollen Sie trotzdem löschen? Couldn't erase file "%1" Die folgende Datei konnte nicht gelöscht werden: "%1" Currently pasting multiple file names is not supported, so only the first one is considered. Im Moment ist das Einfügen von mehreren Dateinamen noch nicht unterstützt. Daher wird nur der erste Dateiname berücksichtigt. The pasted value couldn't be assigned to some fields Der eingefügte Wert konnte für einige Felder nicht zugewiesen werden The pasted value couldn't be assigned to any field Der eingefügte Wert konnte für kein Feld zugewiesen werden The number of lines in the clipboard is different from the number of files. Paste anyway? Die Zeilenzahl in der Zwischenablage stimmt nicht mit der Anzahl der Dateien überein. Trotzdem einfügen? Paste Einfügen The track numbers aren't consecutive numbers starting at 1, so the pasted track information might not match the tracks. Paste anyway? Die Titelnummern sind nicht aufeinenderfolgende Nummern beginnend mit 1. Eingefügte Titelinformationen werden eventuell nicht den richtigen Titeln zugeordnet. Daten trotzdem einfügen? Unrecognized clipboard content Zwischenablageinhalt nicht erkannt ThreadRunnerDlg Thread Runner ???? Thread Runner TextLabel TextLabel &Pause &Pause &Abort &Abbrechen ThreadRunnerDlgImpl Completed Fertiggestellt &Resume &Fortfahren &Pause &Pause Total time: %1 Running time: %2 Gesamtlänge: %1 Laufzeit: %2 Time: %1 Zeit: %1 TrackTextParser "%1" is not a valid pattern. Error in column %2. "%1" ist kein gültiges Muster. Fehler in Spalte %2. TransfConfig Error Fehler Invalid value found for file settings. Reverting to default settings. Ungültiger Wert in den Dateieinstellungen gefunden. Zurückgesetzt auf Standardeinstellung. Transformation Convert non-ASCII ID3V2 text frames to Unicode assuming codepage %1 Konvertiere nicht-ASCII ID3v2 Textframes zu UniCode unter der Annahme von Codeseite %1 Change case for ID3V2 text frames: Artists - %1; Others - %2 Ändere Groß/Kleinschreibung für ID3v2 Textframes: Künstler - %1; Andere - %2 Copy missing ID3V2 frames from ID3V1 assuming codepage %1 Kopiere fehlende ID3V2 Frames vom ID3V1 under Annahme von CodeSeite %1 Removes all ID3V2 frames that aren't used by MP3 Diags. You normally don't want to do such a thing, but it may help if some other program misbehaves because of invalid or unknown frames in an ID3V2 tag. Enferne all ID3v2 Tags die MP3 Diags nicht benutzt. Normalerweise ist dies nicht erforderlich aber es kann helfen falls des Programm Schwierigkeiten aufgrund eines unbekannten oder ungültigen Frames in einem ID3v2 Tag hat. Remove non-basic ID3V2 frames ???? check ... Entferne alle nicht regulären ID3v2 Tags Copies only ID3V2 frames that seem valid or can be made valid, discarding those that are invalid and can't be fixed (e.g. an APIC frame claiming to hold a picture although it doesn't.) Handles both loadable and broken ID3V2 tags, in the latter case copying being stopped when a fatal error occurs. Kopiere nur ID3v2 Frames welche gültig sind oder repariert werden können, entferne alle ungültigen und nicht reparierbaren (z.B. ein APIC Frame welches anzeigt ein Bild zu haben aber keines hat). Dies betrifft ladbare und fehlerhafte ID3v2 Tags, im letzteren Falle wird das Kopieren abgebrochen sobald ein fataler Fehler auftritt. Discard invalid ID3V2 data Ungültige ID3v2 Daten verwerfen Transforms text frames in ID3V2 encoded as Latin1 to Unicode (UTF16.) The reason to do this is that sometimes non-conforming software treats these frames as they are encoded in a different code page, causing other programs to display unexpected data. Konvertiere ID3v2 Textframes welche in Latin-1 vorliegen nach UniCode(UTF16). Grund hierfür ist das manche Programme diese Frames in einer andere Sprache dekodieren und dann falsche Daten anzeigen. Convert non-ASCII ID3V2 text frames to Unicode Convertiere nicht-ASCII ID3v2 Textframes nach UniCode Transforms the case of text frames in ID3V2 tags, according to global settings. Wandle die Groß/Kleinschreibung für Textframes in ID3v2 Tags entsprechend den globalen Einstellungen um. Change case for ID3V2 text frames Ändere Groß/Kleinschreibung für ID3v2 Textframes Copies frames from ID3V1 to ID3V2 if those frames don't exist in the destination or if the destination doesn't exist at all. Kopiere Frames von ID3v1 nach ID3v2 wenn diese Frames dort nicht existieren oder das Ziel komplett fehlt. Copy missing ID3V2 frames from ID3V1 Kopiere fehlende ID3v2 Frames von ID3v1 Adds the value of the composer field to the beginning of the artist field in ID3V2 frames. Useful for players that don't use the composer field. Kopiere den Wert des Komponist Feldes an den Anfang des Künstler Feldes im ID3v2 Tag. Dies is sinnvoll für Abspieler welche das Komponist Feld ignorieren. Add composer field to the artist field in ID3V2 frames Komponist Feld zum Künstler Feld im ID3v2 Tag hinzufügen "Undo" for "adding composer field." Removes the value of the composer field from the beginning of the artist field in ID3V2 frames, if it was previously added. Rückgängig machen fuer das Hinzufügen des Komponist Feldes. Dies entfernt den Wert des Komponist Feldes vom Anfang des Künstler Feldes im ID3v2 Tag falls dieses zuvor dazugefügt wurde. Remove composer field from the artist field in ID3V2 frames Entferne Komponist Feld vom Künstler Feld in den ID3v2 Frames Copies to the "Composer" field the beginning of an "Artist" field that is formatted as "Composer [Artist]". Does nothing if the "Artist" field doesn't have this format. Kopiert den Anfang des "Künstler" Feldes nach "Komponist" falls das Feld als "Komponist [Künstler]" formatiert ist. Falls das Feld nicht in diesem Format vorliegt dann passiert gar nichts. Fill in composer field based on artist in ID3V2 frames Fülle das Komponist Feld basierend auf dem Künstler Feld in ID3v2 Frames Keeps only the biggest (and supposedly the best) image in a file. The image type is set to Front Cover. (This may result in the replacement of the Front Cover image.) Behalte nur das größte (und damit vermutlich beste) Bild in der Datei. Der Bildtyp wird auf "Titelseite" gesetzt (dies wiederum kann zum Ersetzen eines anderen "Titelseite" Bildes führen) Make the largest image "Front Cover" and remove the rest Markiere das größte Bild als "Titelseite" und entferne den Rest Adds extra spacing to the ID3V2 tag. This allows subsequent saving from the tag editor to complete quicker. Zusätzlichen Speicherplatz in den ID3v2 Tag einfügen. Dies erlaubt später das "Schnelle Speichern" im Tag Editor. Reserve space in ID3V2 for fast tag editing Reserviere Platz in ID3v2 für Schnelles Speichern im Tag Editor Removes large unused blocks from ID3V2 tags. (Usually these have been reserved for fast tag editing, in which case they should be removed only after the ID3V2 tag has all the right values.) Entfernt große unbenutzte Blöcke von ID3v2 Tags. (Diese sind meist fuer "schnelles Speichern" im Tag Editor reserviert. Sie sollten sie erst entfernen wenn alle ID3v2 Tags die korrekten Werte haben.) Remove extra space from ID3V2 Entferne extra Platz von ID3v2 Removes selected streams. Entferne ausgewählte Streams. Remove selected stream(s) Entferne ausgewählte Stream(s) Removes specified stream. Entfernt angegebenen Stream. Remove specified stream Entferne angegebenen Stream Sometimes a bit gets flipped in a file. This tries to identify places having this issue in audio frame headers. If found, it fixes the problem. It is most likely to apply to files that have 2 audio streams. Manchmal kippt ein Bit in einer Datei. Es wird versucht solche Stellen im Audioframe Header zu erkennen. Falls gefunden wird das Problem behoben. Dies passiert hauptsächlich bei Dateien welche zwei Audiostreams haben. Restore flipped bit in audio Gekipptes Bit im Audio wiederherstellen Removes all non-audio data that is found between audio streams. In this context, VBRI streams are considered audio streams (while Xing streams are not.) Entferne alle nicht-Audio Daten welche zwischen Audiostreams gefunden werden. In diesem Kontext, VBRI Streams zählen als Audiostreams (Xing Streams dagegen nicht) Remove inner non-audio Enferne innere nicht-Audio Daten Removes all unknown streams. Entferne alle unbekannten Streams. Remove unknown streams Entferne unbekannte Streams Removes all broken streams. Entferne alle fehlerhaften Streams. Remove broken streams Entferne fehlerhafte Streams Removes all unsupported streams. Entferne alle nicht unterstützten Streams. Remove unsupported streams Entferne nicht unterstützte Streams Removes all truncated audio streams. Entferne alle abgeschnittenen Audiostreams. Remove truncated audio streams Entferne abgeschnittenen Audiostreams Removes all null streams. Entferne alle leeren Streams. Remove null streams Entferne leere Streams Removes all streams that are broken, truncated, unsupported or have some errors making them unusable. Entferne alle Streams welche entweder fehlerhaft, abgeschnitten oder nicht unterstützt sind als auch solche welche Fehler aufweisen die sie unbrauchbar machen. Removes broken ID3V2 streams. Entferne fehlerhafte ID3v2 Streams. Remove broken ID3V2 streams Entferne fehlerhafte ID3v2 Streams Removes unsupported ID3V2 streams. Entferne nicht unterstützte ID3v2 Streams. Remove unsupported ID3V2 streams Entferne nicht unterstützte ID3v2 Streams If a file has multiple ID3 streams it keeps only the last ID3V1 and the first ID3V2 stream. Falls eine Datei mehrere ID3 Streams hat wird nur der letzte ID3v1 und der erste ID3v2 Stream beibehalten. Remove multiple ID3 streams Enferne mehrfache ID3 Streams Sometimes the number of frames in an audio stream is different from the number of frames in a preceding Xing (or Lame) header, usually because the audio stream was damaged. It's probably best for the Xing header to be removed in this case. If the audio is VBR, you may want to try "Repair VBR data" first. Manchmal ist die Anzahl der Frames in einem Audiostream unterschiedlich zur Zahl im vorangehenden Xing (oder LAME) Header, meistens weil der Audiostream beschädigt wurde. Es ist vermutlich das Beste in so einem Fall den Xing Header zu entfernen. Falls der Audiostream in VBR vorliegt dann können Sie zuerst "VBR Daten reparieren" probieren. Remove mismatched Xing headers Entferne nicht übereinstimmende Xing Header Removes all ID3V1 streams. Entferne alle ID3v1 Streams. Remove all ID3V1 streams Entferne alle ID3v1 Streams Removes all APE streams. Entferne alle APE Streams. Remove all APE streams Entferne alle APE Streams Removes all non-audio streams. Entferne alle nicht-Audio Streams. Remove all non-audio streams Entferne alle nicht-Audio Streams Pads truncated audio frames with 0 to the right. Its usefulness hasn't been determined yet (it might be quite low.) Füllt abgeschnittene Audio Frames mit 00 nach rechts auf. Es ist nicht klar ob dies wirklich hilfreich ist (wahrscheinlich nicht.) Pad truncated audio Abgeschittenes Audio auffüllen If a file contains VBR audio but doesn't have a Xing header, one such header is added. If a VBRI header exists, it is removed. If a Xing header exists but is determined to be incorrect, it is corrected or replaced. Only the first audio stream is considered; if a file contains more than one audio stream, this should be fixed first. Wenn eine Datei VBR Audio enthält aber keinen XING Header so wird dieser Header hinzugefügt. Falls ein VBRI Header existiert so wird dieser entfernt. Falls ein Xing Header existiert aber inkorrekt erscheint so wird er repariert oder ersetzt. Nur der erste Audio Stream wird berücksichtigt. Falls die Datei mehr als einen Audiostream beinhalted so sollte dies zuerst bereinigt werden. Repair VBR data repariere VBR Daten If a file contains VBR audio, any existing VBRI or Xing headers are removed and a new Xing header is created. Only the first audio stream is considered; if a file contains more than one audio stream, this should be fixed first. Wenn eine Datei VBR Daten enthält dann werden alle existierenden VBRI und Xing Header entfernt und ein neuer Xing Header erstellt. Nur der erste Audiostream wird berücksichtigt. Wenn die Datei mehrere Audiostreams hat dann sollte dies zuerst bereinigt werden. Rebuild VBR data VBR Daten wiederherstellen Saves user-edited ID3V2.3.0 tags. Speichere benutzerveränderte ID3v2.3.0 Tags. Save ID3V2.3.0 tags Speichere ID3v2.3.0 Tags Doesn't actually change the file, but it creates a temporary copy and it reports that it does change it. This is not as meaningless as it might first seem: if the configuration settings indicate some action (i.e. rename, move or erase) to be taken for processed files, then that action will be performed for these files. While the same can be achieved by changing the settings for unprocessed files, this is easier to use when it is executed on a subset of all the files (filtered or selected). Die Datei wird nicht wirklich verändert sondern es wird eine temporäre Kopie erstellt und eine Änderung gemeldet. Dies ist nicht so sinnlos wie es klingt: wenn die Konfigurationseinstellungen irgendeine Aktion (umbennen, verschieben, löschen etc) für bearbeitete Dateien vorsehen dann werden diese Aktionen in dem Moment ausgelöst. Dasselbe kann natürlich auch durch das Ändern der Einstellungen für nicht bearbeitete Dateien erreicht werden aber dies hier ist einfacher zu benutzen wenn nur eine Teilmenge aller Dateien betroffen ist (durch Filter oder Auswahl). No change Keine Änderung UniqueNotesModel L L Note ?? Hinweis VisibleTransfPainter Action Aktion Description Beschreibung Add selected transformation(s) Ausgewählte Transformation(en) hinzufügen Remove selected transformation(s) Ausgewählte Transformation(en) entfernen Restore current list to its default value Aktuelle Liste auf Standardwert zurücksetzen Restore current list to the configuration it had when the window was open Aktuelle Liste zu der Konfiguration zurücksetzen welche beim Fenster öffnen vorlag WebDwnldModel Pos Pos Title Titel Artist Künstler Composer Komponist MP3Diags-1.2.02/src/translations/mp3diags_fr_FR.ts0000644000175000001440000112011412265753762020633 0ustar ciobiusers AboutDlg About MP3 Diags A propos de MP3 Diags MP3 Diags x.y.z MP3 Diags x.y.z About A propos System info Infos système GPL V2 (for the program) GPL V2 (pour le logiciel) LGPL V3 (for the icons) LGPL V3 (pour les icônes) GPL V3 (for the icons) GPL V3 (pour les icônes) LGPL V2.1 (for Qt) LGPL V2.1 (pour Qt) Boost license Licence Boost zlib license Licence zlib OK OK AboutDlgImpl Written by %1, %2 Ecrit par %1, %2 Command-line mode by %1, %2 Mode console par %1, %2 %1 translation by %2, %3 Traduction %1 par %2, %3 Czech tchèque German allemande French française Distributed under %1 Distribué sous licence %1 Using %1, released under %2 Ce logiciel utilise %1, publié sous licence %2 Using %1, released under the %2zlib License%3 Ce logiciel utilise %1, publié sous %2licence zlib%3 Using %1 and %2, distributed under the %3Boost Software License%4 I am not sure if license names should really be translated... Ce logiciel utilise %1 et %2, publié sous %3licence logiciel Boost%4 Using original and modified icons from the %1 for %2, distributed under %3LGPL V3%4 Ce logiciel utilise des icônes originaux et modifiés du %1 pour %2, distribué sous %3LGPL V3%4 Using web services provided by %1 to retrieve album data Ce logiciel utilise les services web fournis par %1 pour récupérer les informations d'album Home page and documentation: %1 Site web et documentation: %1 Feedback and support: %1 or %2 at SourceForge Impressions et support technique : %1 ou %2 sur SourceForge Bug reports and feature requests: %1 at SourceForge Rapports de bogues et nouvelles fonctionnalités : %1 sur SourceForge Change log for the latest version: %1 Journal des modifications de la dernière version : %1 AlbumInfoDownloaderDlg WWW WWW Search Rechercher Artist Artiste Album Album Match count Nb de résultats Results Résultats Released Date de sortie Genre Genre Use Utiliser Format Format Amazon Amazon Image Image Image size Taille de l'image ResultNo Résultat n° Previous album Album précédent ... ... Previous image or album Image ou album précédent p p Next image or album Image ou album suivant n n Next album Album suivant Filter: Filtre : CD CD Track count Nombre de pistes Volume Volume Save image Enregistrer l'image Save all Tout enregistrer Cancel Annuler AlbumInfoDownloaderDlgImpl not found at amazon.com non trouvé sur amazon.com searching ... recherche en cours... Error Erreur You cannot save the results now, because a request is still pending Impossible de sauvegarder les résultats maintenant car une requête est encore en attente You cannot save the results now, because no album is loaded Impossible de sauvegarder les résultats maintenant car aucun album n'est chargé You may want to use a different volume selection on this multi-volume release. Vous souhaiterez peut-être utiliser une autre sélection de volume sur cet album multi-volume. A number of %1 tracks were expected, but your selection contains %2. Additional tracks will be discarded. %3Save anyway? %1 pistes étaient attendues, mais votre sélection en contient %2. Les pistes supplémentaires seront ignorées. %3Sauvegarder quand même ? A number of %1 tracks were expected, but your selection only contains %2. Remaining tracks will get null values. %3Save anyway? %1 pistes étaient attendues, mais votre sélection n'en contient que %2. Les pistes restantes auront des valeurs nulles. %3Sauvegarder quand même ? Count inconsistency Incohérence du comptage &Save &Sauvegarder Cancel Annuler You cannot save any image now, because there is no image loaded Impossible de sauvegarder une image maintenant car aucune image n'est chargée request error erreur de requête received %1 bytes %1 octets reçus received very short response; aborting request ... réponse anormalement courte reçue, annulation de la requête... Original: %1kB, %2x%3 Original: %1ko, %2x%3 Recompressed to: %1kB, %2x%3 Recompressé à : %1 ko, %2x%3 Not recompressed Non recompressé Failed to load the image Le chargement de l'image a échoué Error loading image Erreur lors du chargement de l'image init error erreur d'initialisation unexpected result résultat inattendu empty string received chaîne vide reçue search results received résultats de la recherche reçus Couldn't process the search result. (Usually this means that the server is busy, so trying later might work.) Impossible de traiter le résultat de la recherche. (Ceci veut généralement dire que le serveur est saturé, donc réessayer plus tard peut fonctionner.) No results found Aucun résultat album info received informations d'album reçues Couldn't process the album information. (Usually this means that the server is busy, so trying later might work.) Impossible de traiter les informations d'album. (Ceci veut généralement dire que le serveur est saturé, donc réessayer plus tard peut fonctionner.) image received image reçue <All> <Tout> Album %1/%2%3, image %4/%5 Album %1/%2%3, image %4/%5 No image Pas d'image getting album info ... obtention des informations d'album... getting image ... obtention de l'image... ApeItem Ape stream whose items have unsupported flags. Flux Ape dont les éléments contiennent des drapeaux non supportés. Ape Item seems too big. Although the size may be any 32-bit integer, 256 bytes should be enough in practice. If this message is determined to be mistaken, it will be removed in the future. Item key: %1; item size: %2 L'item Ape semble trop grand. Bien que la taille puisse être n'importe quel entier sur 32 bits, 256 octets devraient suffire en pratique. Si ce message s'avère induire en erreur, il sera supprimé. Clé item : %1 ; taille item : %2 ApeStream Tag missing header or footer. Tag avec en-tête ou 'footer'. CommonData Info Information The font changes will only be used after restarting the application. La nouvelle police ne sera utilisée qu'après avoir redémarré l'application. Error Erreur There was an error setting up the directories containing MP3 files. You will have to define them again. Une erreur est survenue lors du paramétrage des dossiers contenant les fichiers MP3. Veuillez les définir à nouveau. ConfigDlg Configuration Configuration Files Fichiers Simple view Vue simple Full view Vue étendue Removable TextLabel TextLabel supprimable Tab 1 Onglet 1 Don't create backup for modified files Ne pas créer de sauvegarde pour les fichiers modifiés Create backup for modified files in Créer une sauvegarde pour les fichiers modifiés dans ... ... Custom settings (go to full view to make changes) Paramètres personnalisés (aller à la vue complète pour effectuer des modifications) Tab 2 Onglet 2 Original file that would be changed Fichier original qui devrait être modifié Don't touch Ne pas toucher Erase Effacer Move and change the name Déplacer et renommer Move but change the name only if a name collision would occur Déplacer mais ne renommer que si un fichier de même nom existe déjà Change name Renommer Move if the destination doesn't already exist; erase if it does Déplacer si la destination n'existe pas encore, effacer sinon Name change params Paramètres de renommage Identifying label Etiquette distinctive Don't use an identifying label Ne pas utiliser d'étiquette distinctive Use "orig" as an identifying label Utiliser "orig" comme étiquette distinctive Counter Compteur Always use a counter Toujours utiliser un compteur Only use a counter when a name collision would occur N'utiliser un compteur que si un fichier avec le même nom existe déjà Destination Destination Original file that would not be changed Fichier original qui ne devrait pas être modifié Changed file Fichier modifié Don't create Ne pas créer Create and change the name Créer et renommer Create but change the name only if a name collision would occur Créer mais ne renommer que si un fichier de même nom existe déjà Use "proc" as an identifying label Utiliser "proc" comme étiquette distinctive Use the source dir Utiliser le répertoire source Use Utiliser Temporary files Fichiers temporaires Source directory Répertoire source Create in Créer dans Compare files Comparer les fichiers Ignored notes Notes ignorées Custom transformation lists Listes de transformations personnalisées Transformation params Paramètres de transformation Locale for text conversion Encodage pour la conversion de texte Case transformation Transformation de casse Artists Artistes Others Autres Keep original modification time when changing a file Conserver la date de modification originale lors de la modification d'un fichier Keep a single image for a file Ne garder qu'une seule image par fichier Visible Transformations Transformations visibles Quality thresholds Seuils de qualité If the audio bitrate falls below these limits, warnings are generated. For VBR a low bitrate is less of an indicatior of poor quality, because if the signal is simple there's no need for higher bitrates. Si le débit audio passe en dessous de ces limites, des avertissements sont générés. Pour le VBR, un faible débit n'indique pas forcément une mauvaise qualité, car si le signal est simple un débit élevé n'est pas nécessaire. The bitrate is not the best quality indicator. Values from a "Lame header" (if present) are generally better, but these headers are not processed in the current version. Le débit n'est pas le meilleur indicateur de qualité. Les valeurs d'un "en-tête Lame" (s'il existe) sont généralement meilleures, mais ces en-têtes ne sont pas traités dans la version actuelle. Note that for mono streams, half of the value for "Dual Channel" is used. Veuillez noter que pour les flux mono, la moitié de la valeur pour "Canal double" est utilisée. Stereo CBR CBR Stéréo Joint Stereo CBR CBR Joint Stereo Dual Channel CBR CBR canal double Stereo VBR VBR Stéréo Joint Stereo VBR VBR Joint stereo Dual Channel VBR VBR canal double Colors Couleurs Audio Audio Xing and LAME Xing et LAME VBRI VBRI ID3V2 ID3V2 APIC APIC ID3V2.3.0 ID3V2.3.0 ID3V2.4.0 ID3V2.4.0 ID3V1 ID3V1 Broken streams Flux endommagés Truncated streams Flux tronqués Unknown streams Flux inconnus Lyrics Paroles Ape Ape Reset to default colors Couleurs par défaut Shell Shell Enable temporary session per folder Activer les sessions temporaires par dossier Enable hidden session per folder Activer les sessions cachées par dossier Enable visible session per folder Activer les sessions visibles par dossier ErrorShell ErrorShell External tools Outils externes Name Nom Command Commande Wait Attendre Don't wait Ne pas attendre Wait for external tool to finish, then keep launch window open Attendre la fin de l'outil externe, puis garder la fenêtre de lancement ouverte Wait for external tool to finish, then close launch window Attendre la fin de l'outil externe, puis fermer la fenêtre Confirm launch Confirmer le lancement Confirm Confirmer Add Ajouter Update Mettre à jour Delete Supprimer Discard changes Ignorer les modifications Tag editor Editeur de tags Warn when the tag editor enters albums with non-sequential track numbers Avertir lorsque l'éditeur de tags rencontre des albums avec des numéros de pistes non consécutifs Warn when pasting track information in the tag editor for albums with non-sequential track numbers Avertir lors du collage des informations de piste dans l'éditeur de tags pour des albums avec des numéros de pistes non consécutifs Use "fast save" in the tag editor Utiliser la "sauvegarde rapide" dans l'éditeur de tags Maximum image size, in kB Taille d'image maximum, en ko If an image size is above this value, it will be recompressed before storing it inside MP3 files Si une image a une taille supérieure à cette valeur, elle sera recompressée avant d'être incluse dans les fichiers MP3 Handle "various artists" for Supporter "various artists" pour iTunes iTunes Windows Media Player Windows Media Player If a field is marked as "assigned" when exiting an album Si un champ est marqué comme "assigné" lorsqu'on quitte un album Save automatically Sauvegarder automatiquement Discard Ignorer Ask Me demander If a field's value is different from that in the ID3V2 tag when exiting an album Si la valeur d'un champ diffère de celle du tag ID3V2 lorsqu'un album est quitté Misc Divers Scan for new, modified, and deleted files at startup Rechercher au démarrage les fichiers nouveaux, modifiés ou supprimés Show "Export" button Afficher le bouton "Exporter" Show "Debug" button Afficher le bouton "Débogage" Show "Sessions" button Afficher le bouton "Sessions" Show Gnome 3 close buttons Afficher les boutons de fermeture Gnome 3 Log program state to "_trace" and "_step" files Journaliser l'état du programme dans des fichiers "_trace" et "_step" Check for new version at startup Vérifier au démarrage si une nouvelle version est disponible Language Langage General font: Police générale : TextLabel TextLabel Change ... Modifier... Label font smaller by: Police des labels réduite de : Fixed font: Police à largeur fixe : Icon size Taille d'icône Auto-size icons based on the width of the main window Redimensionner automatiquement les icônes par rapport à la largeur de la fenêtre principale Normalizer Normaliseur Command (file names will get added at the end) Commande (les noms de fichiers seront ajoutés à la fin) Keep the window open after completion Garder la fenêtre ouverte une fois terminé File renamer Renommeur de fichiers Invalid characters Caractères non valides Replace with Remplacer par O&K O&K Cancel Annuler ConfigDlgImpl <all notes> <toutes les notes> If you don't know exactly what codepage you want, it's better to make current a file having an ID3V2 tag that contains text frames using the Latin-1 encoding and having non-ASCII characters. Then the content of those frames will replace this text, allowing you to decide which codepage is a match for your file. ID3V1 tags are supported as well, if you want to copy data from them to ID3V2. Si vous ne savez pas quelle page de code vous désirez, il est préférable de rendre courant un fichier avec un tag ID3V2 contenant des trames de texte encodées en Latin-1 et comportant des caractères non ASCII. Le contenu de ces trames remplacera ce texte, vous permettant ainsi de décider quelle page de code convient à votre fichier. Les tags ID3V1 sont aussi supportés si vous souhaitez en copier les données vers des tags ID3V2. Other notes Autres notes Ignore notes Ignorer les notes lower case minuscules UPPER CASE MAJUSCULES Title Case Casse Des Titres Sentence case Casse normale Invisible transformations Transformations invisibles Visible transformations Transformations visibles Characters in this list get replaced with the string below, in "Replace with" An underlined font is used to allow spaces to be seen Les caractères de cette liste sont remplacés par la chaîne ci-dessous, dans "Remplacer avec" Une police soulignée est utilisée afin de pouvoir voir les espaces This string replaces invalid characters in the file renamer" An underlined font is used to allow spaces to be seen Cette chaîne de caractères remplace les caractères non valides dans le renommeur de fichiers Une police soulignée est utilisée afin de pouvoir voir les espaces All transformations Toutes les transformations Used transformations Transformations utilisées Confirm Confirmer You modified the external tool information but you didn't save your changes. Discard the changes or cancel closing of the options window? Vous avez modifié les informations d'outil externe sans sauvegarder vos changements. Ignorer les changements, ou annuler la fermeture de la fenêtre des options ? &Discard &Ignorer &Cancel &Annuler Error Erreur You can't have '%1' in both the list of invalid characters and the string that invalid characters are replaced with. '%1' ne peut pas se trouver à la fois dans la liste de caractères non valides et dans la chaîne qui les remplace. Info Information You need to restart the program to use the new language. perhaps "langue" would be better Vous devez redémarrer le programme pour utiliser le nouveau langage. Invalid folder name Nom de dossier non valide A folder name is incorrect. Un nom de dossier est incorrect. Add selected note(s) Ajouter la(les) note(s) sélectionné(es) Remove selected note(s) Enlever la(les) note(s) sélectionné(es) Add all notes Ajouter toutes les notes Remove all notes Supprimer toutes les notes Restore lists to their default value Restaurer les listes à leur valeur par défaut Restore lists to the configuration they had when the window was open Restaurer les listes à leur configuration à l'ouverture de la fenêtre Select folder Sélectionnez le dossier All files Tous les fichiers CustomTransfListPainter Action Action Description Description Add selected transformation(s) Ajouter la(les) transformation(s) sélectionné(es) Remove selected transformation(s) Enlever la(les) transformation(s) sélectionné(es) Restore current list to its default value Restaurer la liste courante à sa valeur par défaut Restore current list to the configuration it had when the window was open Restaurer la liste courante à sa configuration à l'ouverture de la fenêtre DataStream begins with: commence par : Broken %1 %1 endommagé Unsupported %1 %1 non supporté Unknown Inconnu Truncated MPEG MPEG tronqué Null Null MPEG-1 MPEG-1 MPEG-2 MPEG-2 Layer I Layer I Layer II Layer II Layer III Layer III Stereo Stéréo Joint stereo Joint stereo Dual channel I have a doubt if this is the correct translation... Canal double Single channel Canal simple Not an MPEG frame. Synch missing. N'est pas une trame MPEG. Informations de synchronisation manquantes. Not an MPEG frame. Unsupported version (2.5). N'est pas une trame MPEG. Version non supportée (2.5). Not an MPEG frame. Invalid version. N'est pas une trame MPEG. Version non valide. Not an MPEG frame. Invalid layer. N'est pas une trame MPEG. Couche (layer) non valide. Not an MPEG frame. Invalid bitrate. N'est pas une trame MPEG. Débit non valide. Not an MPEG frame. Invalid frequency for MPEG1. N'est pas une trame MPEG. Fréquence non valide pour le MPEG1. Not an MPEG frame. Invalid frequency for MPEG2. N'est pas une trame MPEG. Fréquence non valide pour le MPEG2. %1 %2%3%4%5%6Hz%7%8bps%9CRC=%10%11length %12 (0x%13)%14padding=%15 %1 %2%3%4%5%6Hz%7%8bps%9CRC=%10%11longueur %12 (0x%13)%14bourrage=%15 padding= bourrage= unsynch= désynchro= frames trames length= longueur= frame count= nb trames= last frame removed; it was located at 0x%1 dernière trame supprimée ; elle se trouvait en 0x%1 last frame located at 0x%1 dernière trame située en 0x%1 Xing header info: Infos en-tête Xing : frame count= nb trames= byte count= nb octets= TOC present TOC présente quality= qualité= MPEG Audio Audio MPEG Xing Header En-tête Xing Lame Header En-tête Lame VBRI Header En-tête VBRI N/A N/A DebugDlg Debug Débogage Enable tracing Activer le traçage Save trace messages Sauvegarder les messages trace ... ... Decode MPEG Audio frame header Décoder l'en-tête de trame MPEG dec dec Test Test tst tst Close Fermer Use all notes Utiliser toutes les notes Log transformations Journaliser les transformations Save downloaded data Sauvegarder les données téléchargées Trace messages: Messages trace : DebugDlgImpl If this is checked, ignored notes and trace notes are shown in the note list and exported, regardless of the "Ignored" settings. Note that if this is not checked, the trace notes are discarded during file scanning, so checking it later won't bring them back. A new scan is needed to see them. this is a multiline tooltip Si cette case est cochée, les notes ignorées et les notes trace sont affichées dans la liste des notes quel que soit le réglage "Ignoré". Veuillez noter que si elle n'est pas cochée, les notes trace sont ignorées durant le scan des fichiers, donc cocher ensuite cette case ne les fera pas réapparaître. Un nouveau scan sera nécessaire. Choose destination file Choisir le fichier de destination Text files (*.txt) Fichiers texte (*.txt) Decoded MPEG frame header En-tête de trame MPEG décodé DirFilterDlg Folder filter Filtre de dossier OK OK Cancel Annuler DirFilterDlgImpl <all folders> <tous les dossiers> Available folders Dossiers disponibles Include folders Dossiers inclus Add selected folders Ajouter les dossiers sélectionnés Remove selected folders Enlever les dossiers sélectionnés Restore lists to the configuration they had when the window was open Restaurer les listes à leur configuration à l'ouverture de la fenêtre Folder Dossier DiscogsDownloader Download album data from Discogs.com Télécharger les informations d'album depuis Discogs.com Genres Genres Genres, Styles Genres, Styles Genres (Styles) Genres (Styles) Styles Styles DoubleListWdg Form Fenêtre Include elems: Eléments affichés : Available elems: Eléments disponibles : < < > > >> >> ... ... ExportDlg Export Exporter File name: Nom du fichier : ... ... Format Format XML XML Text Texte M3U M3U Remove root: Suppr. préfixe : When creating an M3U file, this text gets removed from the beginning of the file names. Meant to be used for creating M3U files containing relative paths. Lors de la création d'un fichier M3U, ce texte sera supprimé du début du nom des fichiers. Ceci est utile pour créer des fichiers M3U contenant des chemins relatifs. Locale: Encodage : Files to save Fichiers à enregistrer Visible files Fichiers visibles Selected files Fichiers sélectionnés Sort by short names Trier par noms courts Close Fermer ExportDlgImpl Error Erreur The file name cannot be empty. Exiting ... Le nom de fichier ne peut être vide. Arrêt... You need to specify an absolute file name when exporting to formats other than .m3u. Exiting ... Vous devez spécifier un nom de fichier absolu lors de l'exportation aux formats autres que .m3u. Arrêt... The root cannot be empty if the file name is relative. Exiting ... La racine ne doit pas être vide si le nom de fichier est relatif. Arrêt... The root must be an absolute directory name. Exiting ... La racine doit être un nom de répertoire absolu. Arrêt... The root doesn't exist. Exiting ... La racine n'existe pas. Arrêt... Warning Avertissement A file called "%1" already exists. Do you want to overwrite it? Un fichier nommé "%1" existe déjà. Voulez-vous l'écraser ? &Overwrite &Ecraser Cancel Annuler Info Information Successfully created file "%1" Fichier "%1" créé avec succès O&K O&K There was an error writing to the file "%1" Erreur lors de l'écriture du fichier "%1" Choose destination file Choisir le dossier de destination XML files (*.xml);;Text files (*.txt);;M3U files (*.m3u) Fichiers XML (*.xml);;Fichiers texte (*.txt);;Fichiers M3U (*.m3u) EWST the letters are the initials of the 4 severity levels: Error, Warning, Support, Trace ces lettres sont les initiales des 4 niveaux de garvité : Erreur, Avertissement, Support, Trace EAST The file named "%1" isn't inside the specified root. Exiting ... Le fichier "%1" n'est pas dans la racine spécifiée. Arrêt... The file named "%1" cannot be encoded in the selected locale. Exiting ... Le fichier "%1" ne peut être encodé avec l'encodage sélectionné. Arrêt... ExternalToolDlg External tool Outil externe Keep window open after completion Garder la fenêtre ouverte une fois terminé Abort Interrompre Close Fermer ExternalToolDlgImpl Error Erreur Cannot start process. Check that the executable name and the parameters are correct. Impossible de démarrer le processus. Vérifiez que le nom du programme et les paramètres sont corrects. Finished Terminé Warning Avertissement Cannot close while "%1" is running. Impossible de fermer tant que "%1" s'exécute. Confirm Confirmer Stopping "%1" may leave the files in an inconsistent state or may prevent temporary files from being deleted. Are you sure you want to abort "%1"? Arrêter "%1" peut laisser les fichiers dans un état inexploitable ou empêcher la suppression de fichiers temporaires. Etes-vous sûr de vouloir interrompre "%1" ? Yes, abort Oui, interrompre Don't abort Ne pas interrompre ExternalToolsModel Name Nom Command Commande Wait Attendre Confirm launch Confirmer le lancement FileRenamer::HndlrListModel << missing ID3V2 >> << ID3V2 manquant >> << no pattern defined >> << aucun motif défini >> << missing fields >> << champs manquants >> File name Nom du fichier New file name Nouveau nom de fichier FileRenamerDlg MP3 Diags - File renamer MP3 Diags - Renommage de fichiers Previous [Ctrl+P] Précédent [Ctrl+P] < < Ctrl+P Ctrl+P Folder Dossier Next [Ctrl+N] Suivant [Ctrl+N] > > Ctrl+N Ctrl+N If this is checked, a copy of the file is created, and both original and copy are kept Si cette case est cochée, une copie du fichier est créée, et original et copie sont tous deux conservés Keep the original file Conserver le fichier original Mark unrated as duplicates unsure of the meaning of unrated and duplicate Marquer les non notés comme des copies Edit patterns Editer les motifs ... ... Rename Renommer Close Fermer alb type alb type FileRenamerDlgImpl Source or destination is a directory La source ou la destination est un dossier Error during copying Erreur pendant la copie Error during renaming Erreur pendant le renommage Destination already exists La destination existe déjà Source not found Source non trouvée Cannot create folder %1 Impossible de créer le dossier %1 Unknown error Erreur inconnue No patterns exist Aucun motif n'existe You must create at least a pattern before you can start renaming files. Vous devez créer au moins un motif avant de pouvoir renommer des fichiers. Confirm Confirmer Copy all the files? Copier tous les fichiers ? Copy the selected files? Copier les fichiers sélectionnés ? Rename all the files? Renommer tous les fichiers ? Rename the selected files? Renommer tous les fichiers sélectionnés ? &Yes &Oui Cancel Annuler Error Erreur Operation aborted because file "%1" doesn't have an ID3V2 tag. Opération annulée car le fichier "%1" n'a pas de tag ID3V2. Operation aborted because file "%1" is missing some required fields in its ID3V2 tag. Opération annulée car il manque des champs requis dans le tag ID3V2 du fichier "%1". Operation aborted because it would create 2 copies of a file called "%1" Opération annulée car elle créerait 2 copies d'un fichier nommé "%1" Operation aborted because a file called "%1" already exists. Opération annulée car un fichier nommé "%1" existe déjà. Copying all the files in the current album Copie de tous les fichiers de l'album courant Copying the selected files in the current album Copie des fichiers sélectionnés de l'album courant Renaming all the files in the current album Renommage de tous les fichiers de l'album courant Renaming the selected files in the current album Renommage des fichiers sélectionnés de l'album courant Single artist Artiste unique Various artists Various artists Error setting up patterns Erreur lors de la configuration des motifs An invalid value was found in the configuration file. You'll have to set up the patterns manually. Une valeur non valide a été rencontrée dans le fichier de configuration. Vous devrez configurer les motifs manuellement. FilesModel File name Nom du fichier FixedAddrRemover Remove stream %1 at address 0x%2 Supprime le flux %1 à l'adresse 0x%2 GlobalTranslHlp Don't wait Ne pas attendre Wait for external tool to finish, then close launch window Attendre la fin de l'outil externe, puis fermer la fenêtre Wait for external tool to finish, then keep launch window open Attendre la fin de l'outil externe, garder la fenêtre ouverte These settings cannot currently be changed. In order to make changes you should probably run the program as an administrator. Ces paramètres ne peuvent pas être modifiés actuellement. Pour effectuer des modifications, vous devez lancer le programme en tant qu'administrateur. Platform not supported Plate-forme non supportée yes oui no non O&K O&K HtmlMsg I got the message; don't show this again Message reçu, ne plus afficher à nouveau Id3V230Frame Truncated ID3V2.3.0 tag. Tag ID3V2.3.0 tronqué. Broken ID3V2.3.0 tag. Tag ID3V2.3.0 endommagé. ID3V2.3.0 tag containing a frame with an invalid name: %1. Tag ID3V2.3.0 contenant une trame avec un nom non valide : %1. ID3V2.3.0 tag containing a frame with an unsupported flag. Tag ID3V2.3.0 contenant une trame avec un drapeau non supporté. %1 (Frame: %2) %1 (Trame: %2) INVALID NON VALIDE ID3V2.3.0 tag containing a broken text frame named %1. Tag ID3V2.3.0 contenant une trame de texte endommagée nommée : %1. ID3V2.3.0 tag containing a text frame named %1 using unsupported characters. Tag ID3V2.3.0 contenant une trame de texte nommée %1 utilisant des caractères non supportés. Id3V230Stream Unsupported version of ID3V2 tag%1 Version non supportée de l'étiquette ID3V2%1 ID3V2 tag with unsupported flag. Tag ID3V2 comportant un drapeau non supporté. Id3V240Frame Truncated ID3V2.4.0 tag. Tag ID3V2.4.0 tronqué. ID3V2.4.0 tag containing a frame with an invalid name: %1. Tag ID3V2.4.0 contenant une trame avec un nom non valide : %1. ID3V2.4.0 tag containing a frame with an unsupported flag. Tag ID3V2.4.0 contenant une trame avec un drapeau non supporté. Broken ID3V2.4.0 tag. Tag ID3V2.4.0 endommagé. %1 (Frame: %2) %1 (Trame: %2) INVALID NON VALIDE ID3V2.4.0 tag containing a broken text frame named %1. Tag ID3V2.4.0 contenant une trame de texte endommagée nommée : %1. ID3V2.4.0 tag containing a text frame named %1 using unsupported characters or unsupported text encoding. Tag ID3V2.4.0 contenant une trame de texte nommée %1 utilisant des caractères ou un encodage non supportés. Id3V240Stream ID3V2 tag with unsupported flag. Translation of flag is uncertain. Tag ID3V2 comportant un drapeau non supporté. Id3V2Frame << error decoding string >> << erreur lors du décodage de la chaîne >> << unsupported encoding >> << encodage non supporté >> size= taille= invalid text encoding encodage du texte non valide unsupported text encoding encodage du texte non supporté <encoding error> <erreur d'encodage> invalid data données non valides MIME="%1" File="%2" Descr="%3" Binary data size=%4 MIME="%1" Fichier="%2" Descr="%3" Taille des données binaires=%4 Begins with: %1 Commence par : %1 Content: %1 Contenu : %1 status=%1 statut=%1 link lien non-cover unsure of the context... pas de pochette error erreur cover pochette Id3V2StreamBase %1 (Frame: %2) %1 (Trame: %2) ImageInfoPanel <error> <erreur> Click to see larger image Cliquer pour agrandir l'image ImageInfoPanelWdg Form Fenêtre Thumb Not really sure of this... Aperçu Pos Pos Dim Dimension Size Taille View full-size image Voir l'image taille réelle ... ... Assign picture to songs Affecter l'image aux titres Erase local file Effacer le fichier local ImageInfoPanelWdgImpl Erase these files: Effacer ces fichiers : Erase file %1 Effacer le fichier %1 InvalidPattern Pattern "%1" is invalid. %2 Le motif "%1" n'est pas valide. %2 LogModel Message Message MainFormDlg Dialog Boîte de dialogue Scan folders for MP3 files [Ctrl+S] Rechercher des fichiers MP3 dans les dossiers [Ctrl+S] prc prc Ctrl+S Ctrl+S Close this window and open the Session editor Fermer cette fenêtre et ouvrir l'éditeur de Sessions ... ... Export ... Exporter... Filter by notes Filtrer par notes nflt nflt Filter by folders Filtrer par dossiers dflt dflt Show the full list of files (after applying the filters) Afficher la liste de fichiers complète (après filtrage) Show one album (i.e. folder) at a time N'afficher qu'un seul album (dossier) à la fois d d Show one song at a time Ne montrer qu'une chanson à la fois f f Previous [Ctrl+P] Précédent [Ctrl+P] < < Ctrl+P Ctrl+P Folder Dossier Next [Ctrl+N] Suivant [Ctrl+N] > > Ctrl+N Ctrl+N Apply a single transformation Appliquer une transformation rep rep Apply custom set of transforms #1 Appliquer le jeu de transformations personnalisé #1 Apply custom set of transforms #2 Appliquer le jeu de transformations personnalisé #2 Apply custom set of transforms #3 Appliquer le jeu de transformations personnalisé #3 Tag editor Editeur de tags Normalize Normaliser Reload Recharger Rename files Renommer les fichiers Configuration Configuration cfg cfg Debug Débogage About A propos File info Informations fichier All notes Toutes les notes Tag details Détails du tag Removable TextLabel TextLabel supprimable MainFormDlgImpl Assertion failure Problème d'assertion Plese report this problem to the project's Issue Tracker at %1 Veuillez rapporter le problème sur le système de suivi d'incidents du projet situé à %1 Please restart the application for instructions about how to report this issue Veuillez redémarrer l'application pour savoir comment signaler ce problème Exit Quitter Restarting after crash Redémarrage après plantage Warning Avertissement Because MP3 Diags changes the content of your MP3 files if asked to, it has a significant destructive potential, especially in cases where the user doesn't read the documentation and simply expects the program to do other things than what it was designed to do. Dans la mesure où MP3 Diags modifie le contenu de vos fichiers MP3 si vous le lui demandez, il possède un potentiel de destruction significatif, particulièrement dans les cas où l'utilisateur ne lit pas la documentation et attend du programme qu'il fasse des choses autres que celles pour lesquelles il a été conçu. Therefore, it is highly advisable to back up your files first. C'est pourquoi il vous est fortement conseillé de faire préalablement une sauvegarde de vos fichiers. Also, although MP3 Diags is very stable on the developer's computer, who hasn't experienced a crash in a long time and never needed to restore MP3 files from a backup, there are several crash reports that haven't been addressed, as the developer couldn't reproduce the crashes and those who reported the crashes didn't answer the developer's questions that might have helped isolate the problem. Aussi, bien que MP3 Diags soit très stable sur l'ordinateur de son développeur, qui n'a jamais eu de plantage depuis très longtemps et n'a jamais eu besoin de restaurer des fichiers MP3 depuis une sauvegarde, de nombreux rapports de plantage non traités existent, car le développeur n'a pas pu reproduire ces plantages et que les auteurs des rapports n'ont pas répondu aux questions du développeur qui auraient pu l'aider à isoler le problème. O&K O&K Note Note If you simply left-click, all the visible files get processed. However, it is possible to process only the selected files. To do that, either keep SHIFT pressed down while clicking or use the right button, as described at %1 Si vous faites simplement un clic gauche, tous les fichiers visibles sont traités. Cependant, il est possible de ne traiter que les fichiers sélectionnés. Pour ce faire, maintenez MAJ enfoncé durnat le clic ou utilisez le bouton droit comme décrit dans %1 An unknown note was found in the configuration. This note is unknown: %1 Une note inconnue a été trouvée dans la configuration. Cette note est inconnue : %1 Unknown notes were found in the configuration. These notes are unknown: %1 Des notes inconnues ont été trouvées dans la configuration. Ces notes sont inconnues : %1 Error setting up the "ignored notes" list Erreur lors de la configuration de la liste des "notes ignorées" You may want to check again the list and add any notes that you want to ignore. (If you didn't change the settings file manually, this is probably due to a code enhanement that makes some notes no longer needed, and you can safely ignore this message.) MP3 Diags is restarting after a crash. MP3 Diags redémarre après un plantage. Information in the file %1%5%2 may help identify the cause of the crash so please make it available to the developer by mailing it to %3, by reporting an issue to the project's Issue Tracker at %4 and attaching the files to the report, or by some other means (like putting it on a file sharing site.) %1 and %2 are HTML elements Les informations contenues dans le fichier %1%5%2 peuvent aider à l'identification de la cause du plantage donc merci de bien vouloir les rendre accessibles au développeur en les envoyant à %3, en rapportant un problème dans le système de suivi d'erreurs du projet sur %4 et en joignant les fichiers au rapport, ou par d'autres moyens (comme l'envoi sur un service de partage de fichiers) Information in the files %1%5%2 and %1%6%2 may help identify the cause of the crash so please make them available to the developer by mailing them to %3, by reporting an issue to the project's Issue Tracker at %4 and attaching the files to the report, or by some other means (like putting them on a file sharing site.) %1 and %2 are HTML elements Les informations contenues dans les fichiers %1%5%2 et %1%6%2 peuvent aider à l'identification de la cause du plantage donc merci de bien vouloir les rendre accessibles au développeur en les envoyant à %3, en rapportant un problème dans le système de suivi d'erreurs du projet sur %4 et en joignant les fichiers au rapport, ou par d'autres moyens (comme l'envoi sur un service de partage de fichiers) Information in the files %1%5%2, %1%6%2, and %1%7%2 may help identify the cause of the crash so please make them available to the developer by mailing them to %3, by reporting an issue to the project's Issue Tracker at %4 and attaching the files to the report, or by some other means (like putting them on a file sharing site.) %1 and %2 are HTML elements Les informations contenues dans les fichiers %1%5%2, %1%6%2 et %1%7%2 peuvent aider à l'identification de la cause du plantage donc merci de bien vouloir les rendre accessibles au développeur en les envoyant à %3, en rapportant un problème dans le système de suivi d'erreurs du projet sur %4 et en joignant les fichiers au rapport, ou par d'autres moyens (comme l'envoi sur un service de partage de fichiers) These are plain text files, which you can review before sending, if you have privacy concerns. Ce sont des fichiers de texte brut, que vous pouvez consulter avant de les envoyer si vous avez des inquiétudes relatives à la confidentialité. After getting the files, the developer will probably want to contact you for more details, so please check back on the status of your report. Une fois les fichiers récupérés, le développeur souhaitera probablement vous contacter pour davantage de détails, donc merci de bien vouloir suivre le statut de votre rapport. Note that these files <b>will be removed</b> when you close this window. Veuillez noter que ces fichiers <b>seront supprimés</b> lorsque vous fermerez cette fenêtre. If there is a name of an MP3 file at the end of <b>%1</b>, that might be a file that consistently causes a crash. Please check if it is so. Then, if confirmed, please make that file available by mailing it to %2 or by putting it on a file sharing site. Si un nom de fichier MP3 se trouve à la fin de <b>%1</b>, ce peut être un fichier responsable d'un plantage. Veuillez vérifier si c'est le cas. Si oui, merci de rendre ce fichier disponible en l'envoyant à %2 ou sur un site de partage de fichiers. Please also try to <b>repeat the steps that led to the crash</b> before reporting the crash, which will probably result in a new set of files being generated; these files are more likely to contain relevant information than the current set of files, because they will also have information on what happened before the crash, while the current files only tell where the crash occured. Essayez si possible de <b>répéter les étapes qui ont conduit au plantage</b> avant de rapporter le plantage, ce qui conduira probablement à la génération d'un nouveau jeu de fichiers ; ceux-ci contiendront probablement davantage d'informations que le jeu actuel, car ils comporteront des inforlations sur ce qui s'est passé avant le plantage, alors que les fichiers actuels ne donnent que le moment où le plantage a eu lieu. You should include in your report any other details that seem relevant (what might have caused the failure, steps to reproduce it, ...) Vous devriez inclure dans votre rapport tous les détails qui semblent importants (causes possibles de l'erreur, étapes pour la reproduire, ...) Remove these files and continue Enlever ces fichiers et continuer MP3 Diags is restarting after a crash. There was supposed to be some information about what led to the crash in the file <b>%1</b>, but that file cannot be found. Please report this issue to the project's Issue Tracker at %2. MP3 Diags redémarre après un plantage. Il y aurait dû y avoir des informations sur les causes du plantage dans le fichier <b>%1</b>, mais ce fichier est introuvable. Veuillez rapporter ce problème au système de suivi d'erreurs du projet à %2. The developer will probably want to contact you for more details, so please check back on the status of your report.</p><p style="margin-bottom:8px; margin-top:1px; ">Make sure to include the data below, as well as any other detail that seems relevant (what might have caused the failure, steps to reproduce it, ...) Le développeur souhaitera probablement vous recontacter pour plus de détails, donc suivez bien le statut de votre rapport.</p><p style="margin-bottom:8px; margin-top:1px; ">Assurez-vous d'inclure les données ci-dessous, ainsi que tous les autres détails qui semblent importants (causes possibles de l'erreur, étapes pour la reproduire, ...) MP3 Diags is restarting after a crash. To help determine the reason for the crash, the <i>Log program state to _trace and _step files</i> option has been activated. This logs to 3 files what the program is doing, which might make it slightly slower. MP3 Diags redémarre après un plantage. Pour aider à déterminer les raisons du plantage, l'option <i>Journaliser l'état du programme dans des fichiers _trace et _step</i> a été activée. Ceci journalise dans 3 fichiers les actions du programme, ce qui peut légèrement le ralentir. It is recommended to not process more than several thousand MP3 files while this option is turned on. You can turn it off manually, in the configuration dialog, in the <i>Others</i> tab, but keeping it turned on may provide very useful feedback to the developer, should the program crash again. With this feedback, future versions of MP3 Diags will get closer to being bug free. Il est recommandé de ne pas traiter plus de quelques milliers de fichiers MP3 lorsque cette option est activée. Vous pouvez la désactiver manuellement dans la fenêtre de configuration, onglet <i>Autres</i>, mais la garder activée peut fournir un retour très utile au développeur en cas de nouveau plantage. Avec ces retours utilisateur, les prochaines versions de MP3 Diags tendront davantage vers le zéro bogue. Error Erreur MP3 Diags crashed while reading song data from the disk. The whole collection will be rescanned. MP3 Diags a planté pendant la lecture des données de titre depuis le disque. La collection va être intégralement scannée à nouveau. Loading data Chargement des données An error occured while loading the MP3 information. Your files will be rescanned. Une erreur s'est produite pendant le chargement des informations MP3. Vos fichiers vont être scannés à nouveau. It seems that MP3 Diags is restarting after a crash. Your files will be rescanned. (Since this may take a long time for large collections, you may want to abort the full rescanning and apply a filter to include only the files that you changed since the last time the program closed correctly, then manually rescan only those files.) Il semble que MP3 Diags redémarre après un plantage. Vos fichiers vont être à nouveau scannés. (Cette opération pouvant prendre beaucoup de temps sur des collections étendues, vous souhaiterez peut-être annuler le scan complet et appliquer un filtre pour n'inclure que les fichiers modifiés depuis la dernière fermeture correcte du programme, puis scanner manuellement ces fichiers.) Saving data Sauvegarde des données An error occured while saving the MP3 information. You will have to scan your files again. %1 Une erreur s'est produite pendant la sauvegarde des informations MP3. Vous devrez à nouveau scanner vos fichiers. %1 Scanning MP3 files Scan des fichiers MP3 Info Information Your files are not fully supported by the current version of MP3 Diags. The main reason for this is that the developer is aware of some MP3 features but doesn't have actual MP3 files to implement support for those features and test the code. Vos fichiers ne sont pas totalement supportés dans la version actuelle de MP3 Diags. La raison principale en est que le développeur est au courant de certaines fonctionnalités MP3 mais n'a pas de fichiers MP3 adéquats pour coder et tester ces fonctionnalités. You can help improve MP3 Diags by making files with unsupported notes available to the developer. The preferred way to do this is to report an issue on the project's Issue Tracker at %1, after checking if others made similar files available. To actually send the files, you can mail them to %2 or put them on a file sharing site. It would be a good idea to make sure that you have the latest version of MP3 Diags. Vous pouvez participer à l'amélioration de MP3 Diags en rendant des fichiers avec des notes non supportées accessibles au développeur. La manière privilégiée est de rapporter un problème sur le système de suivi d'erreurs du projet à %1, après avoir vérifié si d'autres personnes ont déjà rendu disponibles des fichiers semblables. Pour réellement envoyer les fichiers, vous pouvez les envoyer par mail à %2 ou les poster sur un site de partage de fichiers. Une idée judicieuse est de vous assurer auparavant que vous avez bien la dernière version de MP3 Diags. You can identify unsupported notes by the blue color that is used for their labels. Vous pouvez identifier les notes non supportées à la couleur bleue de leur texte. Error setting up custom transformations Erreur lors de la configuration des transformations personnalisées Couldn't find a transformation with the name "%1". The program will proceed, but you should review the custom transformations lists. Impossible de trouver une transformation nommée "%1". Le programme va continuer, mais vous devriez vérifier la liste des transformations personnalisées. Error setting up visible transformations Erreur lors de la configuration des transformations visibles Couldn't find a transformation with the name "%1". The program will proceed, but you should review the visible transformations list. Impossible de trouver une transformation nommée "%1". Le programme va continuer, mais vous devriez vérifier la liste des transformations visibles. Error setting up external tools Erreur lors de la configuration des outils externes Unable to parse "%1". The program will proceed, but you should review the external tools list. Impossible d'évaluer "%1". Le programme va continuer, mais vous devriez vérifier la liste des outils externes. There are no files to normalize. Aucun fichier à normaliser. you are requesting to normalize only some of the files vous ne demandez la normalisation que d'une partie des fichiers the "Album" mode is not selected le mode "Album" n'est pas sélectionné filters are applied plusieurs filtres sont appliqués a filter is applied un filtre est appliqué the normalization will process more than 50 files, which is more than what an album usually has la normalisation va traiter plus de 50 fichiers, ce qui est plus que le nombre habituel pour un album Normalization should process one whole album at a time, so it should only be run in "Album" mode, when no filters are active, and it should be applied to all the files in that album. But in the current case %1. La normalisation ne devrait traiter qu'un album complet à la fois, c'est pourquoi elle ne devrait être lancée qu'en mode "Album" sans aucun filtre actif, et elle devrait être appliquée à tous les fichiers de l'album. Mais dans ce cas précis %1. Normalization should process one whole album at a time, so it should only be run in "Album" mode, when no filters are active, and it should be applied to all the files in that album. But in the current case there are some issues: %1 La normalisation ne devrait traiter qu'un album complet à la fois, c'est pourquoi elle ne devrait être lancée qu'en mode "Album" sans aucun filtre actif, et elle devrait être appliquée à tous les fichiers de l'album. Mais dans ce cas précis il y a quelques problèmes : %1 Normalize anyway? Normaliser quand même ? Normalize Normaliser Cancel Annuler Confirm Confirmer Normalize all the files in the current album? (Note that normalization is done "in place", by an external program, so it doesn't care about the transformation settings for original and modified files.) Normaliser tous les fichiers de l'album courant ? (Veuillez noter que la normalisation se fait "en place", à l'aide d'un programme externe, donc elle ne tient pas compte des réglages de transformation pour les fichiers originaux et modifiés.) The file list is empty, therefore no transformations can be applied. Exiting ... La liste de fichiers est vide, donc aucune transformation ne peut être appliquée. Arrêt... all the files shown in the file list tous les fichiers de la liste de fichiers all %1 files shown in the file list les %1 fichiers de la liste de fichiers No file is selected, therefore no transformations can be applied. Exiting ... Aucun fichier sélectionné, donc aucune transformation ne peut être appliquée. Arrêt... and the other selected file et l'autre fichier sélectionné and the other %1 selected files et les %1 autres fichiers sélectionnés The transformation list is empty. Based on the configuration, it is possible for changes to the files in the list to be performed, even in this case (the files may still be moved, renamed or erased). However, the current settings are to leave the original files unchanged, so currently there's no point in applying an empty transformation list. Exiting ... La liste de transformations est vide. Selon la configuration, il est possible que même dans ce cas des changements soient apportés aux fichiers (déplacement, renommage ou suppression). Cependant, les réglages actuels sont de ne pas toucher aux fichiers originaux, donc il n'y a aucune raison d'appliquer une liste de transformations vide. Arrêt... Apply an empty transformation list to all the files shown in the file list? (Note that even if no transformations are performed, the files may still be moved, renamed or erased, based on the current settings.) Appilquer une liste de transformations vide à tous les fichiers présents dans la liste ? (Veuillez noter que même si aucune transformation n'est effectuée, les fichiers peuvent cependant être déplacés, renommés ou effacés selon les paramètres actuels.) Apply transformation "%1" to %2? Appliquer la transformation "%1" à %2 ? Apply the following transformations to %1? Appliquer les transformations suivantes à %1 ? don't change ne pas modifier erase effacer move déplacer rename renommer move if destination doesn't exist déplacer si la destination n'existe pas Actions to be taken: Actions à effectuer : original file that has been transformed: %1 fichier original transformé : %1 original file that has not been transformed: %1 fichier original non transformé : %1 &Yes &Oui &No &Non Applying transformations to MP3 files Application des transformations aux fichiers MP3 Apply custom transformation list #%1 Appliquer la liste de transformations personnalisée #%1 <empty list> (you can edit the list in the Settings dialog) <liste vide> (vous pouvez éditer la liste dans la fenêtre de configuration) The file list is empty. You need to populate it before opening the tag editor. La liste de fichiers est vide. Vous devez la remplir avant d'ouvrir l'éditeur de tags. The file list is empty. You need to populate it before opening the file rename tool. La liste de fichiers est vide. Vous devez la remplir avant d'ouvrir l'outil de renommage de fichiers. Delete %1? Supprimer %1 ? Cannot delete file %1 Impossible de supprimer %1 MP3 Diags can check at startup if a new version of the program has been released. Here's how this is supposed to work: MP3 Diags peut vérifier au démarrage si une nouvelle version est disponible. Le fonctionnement est le suivant : The check is done in the background, when the program starts, so there should be no performance penalties La vérification se fait en arrière-plan au démarrage, donc ceci ne devrait pas impacter les performances A notification message is displayed only if there's a new version available Une notification n'est affichée que si une nouvelle version est disponible The update is manual. You are told that there is a new version and are offered links to see what's new, but nothing gets downloaded and / or installed automatically La mise à jour est manuelle. Vous êtes averti qu'une nouvelle version existe avec des liens vers les nouveautés, mais rien n'est téléchargé et/ou installé automatiquement There is no System Tray process checking periodically for updates Il n'y a pas de processus de barre des tâches qui vérifie périodiquement les mises à jour You can turn the notifications on and off from the configuration dialog Vous pouvez activer et désactiver les notifications depuis la fenêtre de configuration If you restart the program within a day after a check, no new check is done Si vous redémarrez le programme moins d'un jour après une vérification, elle n'est pas effectuée à nouveau Disable checking for new versions Ne pas vérifier les nouvelles versions Enable checking for new versions Vérifier les nouvelles versions Version %1 has been published. You are running %2. You can see what's new in %3. A more technical list with changes can be seen in %4. La version %1 vient d'être publiée. Vous exécutez %2. Vous pouvez consulter les nouveautés sur %3. Une liste plus technique avec les modifications se trouve dans %4. the %1MP3 Diags blog%2 arguments are HTML elements le %1blog de MP3 Diags%2 the %1change log%2 arguments are HTML elements le %1journal des modifications%2 This notification is about the availability of the source code. Binaries may or may not be available at this time, depending on your particular platform. Cette notification concerne la disponibilité du code source. Les versions compilées peuvent selon votre plate-forme ne pas être encore disponibles. You should review the changes and decide if you want to upgrade or not. Vous devriez regarder les modifications et décidez si vous souhaitez ou non mettre à jour. Note: if you want to upgrade, you should %1close MP3 Diags%2 first. arguments are HTML elements Note : si vous souhaitez mettre à jour, vous devez d'abord %1fermer MP3 Diags%2. Choose what do you want to do: Choisissez ce que vous souhaitez faire : Just close this message Fermer simplement ce message Don't tell me about version %1 again Ne plus m'informer concernant la version %1 Open containing folder ... Ouvrir le dossier contenant ... Run "%1" on %2? Exécuter "%1" sur %2 ? Cannot start process. Check that the executable name and the parameters are correct. Impossible de démarrer le processus. Vérifiez que le nom de l'exécutable ainsi que les paramètres sont corrects. %1 and %2 %1 et %2 %1, %2 and %3 %1, %2 et %3 %1, %2 and %3 other files %1, %2 et %3 autres fichiers Folder "%1" doesn't exist. The program will exit ... Le fichier "%1" n'existe pas. Le programme va s'arrêter... Cannot write to file "%1". The program will exit ... Impossible d'écrire dans le fichier "%1". Le programme va s'arrêter... Mp3Transformer Error Erreur There was an error writing to the following file: %1 Make sure that you have write permissions and that there is enough space on the disk. Processing aborted. Une erreur est survenue lors de l'écriture du fichier suivant : %1 Assurez-vous d'avoir les droits en écriture et qu'il y a suffisamment d'espace libre sur le disque. Traitement annulé. The file "%1" seems to have been modified since the last scan. You need to rescan it before continuing. Processing aborted. Le fichier "%1" semble avoir été modifié depuis le dernier scan. Vous devez le scanner à nouveau pour pouvoir continuer. Traitement annulé. There was an error processing the following file: %1 Probably the file was deleted or modified since the last scan, in which case you should reload / rescan your collection. Or it may be used by another program; if that's the case, you should stop the other program first. This may also be caused by access restrictions or a full disk. Processing aborted. Une erreur est survenue lors du traitement du fichier suivant : %1 Le fichier a probablement été modifié ou supprimé depuis le dernier scan, dans ce cas vous devriez recharger / rescanner votre collection. Sinon, il est peut-être utilisé par un autre programme; dans ce cas, veuillez arrêter cet autre programme d'abord. Ceci peut également être dû à des restrictions d'accès ou un disque plein. Traitement annulé. There was an error processing the following file: %1 The following folder couldn't be created: %2 Processing aborted. Une erreur est survenue lors du traitement du fichier : %1 Le dossier suivant n'a pas pu être créé : %2 Traitement annulé. MusicBrainzDownloader Download album data from MusicBrainz.org Télécharger les informations d'album depuis MusicBrainz.org waiting %1ms patiente %1ms <a href="%1">view at amazon.com</a> <a href="%1">voir sur amazon.com</a> NoteFilterDlg Note filter Filtre de notes OK OK Cancel Annuler NoteFilterDlgImpl <all notes> <toutes les notes> Available notes Notes disponibles Include notes Notes incluses Add selected note(s) Ajouter la(les) note(s) sélectionné(es) Remove selected note(s) Enlever la(les) note(s) sélectionné(es) Add all notes Ajouter toutes les notes Remove all notes Supprimer toutes les notes Restore lists to the configuration they had when the window was open Restaurer les listes courantes à leur configuration à l'ouverture de la fenêtre L L Note Note Notes <Placeholder for a note that can no longer be found, most likely as a result of a software upgrade. You should rescan the file.> <Emplacement pour une note introuvable, vraisemblablement après une mise à jour logicielle. Veuillez scanner le fichier à nouveau.> ERROR ERREUR WARNING AVERTISSEMENT SUPPORT SUPPORT Two MPEG audio streams found, but a file should have exactly one. Deux flux audio MPEG détectés, mais un fichier ne peut en avoir qu'un seul. Low quality MPEG audio stream. (What is considered "low quality" can be changed in the configuration dialog, under "Quality thresholds".) Flux audio MPEG de qualité médiocre (Ce qui est considéré comme "qualité médiocre" peut être modifié dans la configuration, onglet "Seuils de qualité".) No MPEG audio stream found. Aucun flux audio MPEG trouvé. VBR with audio streams other than MPEG1 Layer III might work incorrectly. Le VBR avec des flux audio autres que le MPEG1 Layer III peut ne pas fonctionner correctement. Incomplete MPEG frame at the end of an MPEG stream. Trame MPEG incomplète en fin d'un flux MPEG. Valid frame with a different version found after an MPEG stream. Trame valide avec une version différente détectée après un flux MPEG. Valid frame with a different layer found after an MPEG stream. Trame valide avec une couche (layer) différente détectée après un flux MPEG. Valid frame with a different channel mode found after an MPEG stream. Trame valide avec un mode de canaux différent détectée après un flux MPEG. Valid frame with a different frequency found after an MPEG stream. Trame valide avec une fréquence différente détectée après un flux MPEG. Valid frame with a different CRC policy found after an MPEG stream. Trame valide avec une politique CRC différente détectée après un flux MPEG. Invalid MPEG stream. Stream has fewer than 10 frames. Flux MPEG non valide. Le flux comporte moins de 10 trames. Invalid MPEG stream. First frame has different bitrate than the rest. Flux MPEG non valide. La première trame a un débit différent du reste du fichier. No normalization undo information found. The song is probably not normalized by MP3Gain or a similar program. As a result, it may sound too loud or too quiet when compared to songs from other albums. Aucune donnée pour annuler la normalisation détectée. Le titre n'a probablement pas été normalisé par un logiciel tel que MP3Gain. En conséquence, il peut sembler trop bas ou trop fort par rapport aux titres issus d'autres albums. Found audio stream in an encoding other than "MPEG-1 Layer 3" or "MPEG-2 Layer 3." While MP3 Diags understands such streams, very few tests were run on files containing them (because they are not supposed to be found inside files with the ".mp3" extension), so there is a bigger chance of something going wrong while processing them. Flux audio détecté utilisant un encodage autre que "MPEG-1 Layer 3" ou "MPEG-2 Layer 3". Même si MP3 Diags reconnaît de tels flux, très peu de tests ont été effectués sur des fichiers les contenant (puisqu'ils ne sont pas supposés se trouver dans des fichiers portant l'extension ".mp3"), donc il y a de fortes chances que leur traitement échoue. Two Lame headers found, but a file should have at most one of them. Deux en-têtes Lame détectés, mais un fichier ne devrait en avoir qu'un au plus. Xing header seems added by Mp3Fixer, which makes the first frame unusable and causes a 16-byte unknown or null stream to be detected next. L'en-tête Xing semble avoir été ajouté par Mp3Fixer, qui rend la première trame inutilisable et entraîne la détection d'un flux inconnu ou vide de 16 octets juste après. Frame count mismatch between the Xing header and the audio stream. Nombre de trames différent entre l'en-tête Xing et le flux audio. Two Xing headers found, but a file should have at most one of them. Deux en-têtes Xing détectés, mais un fichier ne devrait en avoir qu'un au plus. The Xing header should be located immediately before the MPEG audio stream. L'en-tête Xing devrait se trouver juste après le flux audio MPEG. The Xing header should be compatible with the MPEG audio stream, meaning that their MPEG version, layer and frequency must be equal. L'en-tête Xing devrait être compatible avec le flux audio MPEG, c'est-à-dire que leur fréquence, version MPEG et couche doivent être identiques. The MPEG audio stream uses VBR but a Xing header wasn't found. This will confuse some players, which won't be able to display the song duration or to seek. Le flux audio MPEG utilise le VBR mais aucun en-tête Xing n'a été trouvé. Ceci peut induire en erreur certains lecteurs, qui ne pourront pas afficher la durée du titre ou faire de recherche. Two VBRI headers found, but a file should have at most one of them. Deux en-têtes VBRI détectés, mais un fichier ne devrait en avoir qu'un au plus. VBRI headers aren't well supported by some players. They should be replaced by Xing headers. Les en-têtes VBRI sont mal supportés par certains lecteurs. Il est préférable de les remplacer par des en-têtes Xing. VBRI header found alongside Xing header. The VBRI header should probably be removed. En-têtes VBRI et Xing détectés. L'en-tête VBRI devrait probablement être supprimé. Invalid ID3V2 frame. File too short. Trame ID3V2 non valide. Fichier trop court. Invalid frame name in ID3V2 tag. Nom de trame non valide dans le tag ID3V2. Flags in the first flag group that are supposed to always be 0 are set to 1. They will be ignored. Les drapeaux du premier groupe supposés être toujours à 0 sont à 1. Ils seront ignorés. Flags in the second flag group that are supposed to always be 0 are set to 1. They will be ignored. Les drapeaux du second groupe supposés être toujours à 0 sont à 1. Ils seront ignorés. Error decoding the value of a text frame while reading an Id3V2 Stream. Erreur lors du décodage de la valeur d'une trame texte pendant la lecture d'un flux ID3V2. ID3V2 tag has text frames using Latin-1 encoding that contain characters with a code above 127. While this is valid, those frames may have their content set or displayed incorrectly by software that uses the local code page instead of Latin-1. Conversion to Unicode (UTF16) is recommended. Le tag ID3V2 comporte des trames de texte utilisant l'encodage Latin-1 avec des caractères possédant un code supérieur à 127. Même si cela est valide, ces trames peuvent voir leur contenu modifié ou affiché de façon incorrecte par des logiciels utilisant la page de code locale au lieu du Latin-1. La conversion en Unicode (UTF16) est recommandée. Empty genre frame (TCON) found. Trame de genre vide (TCON) détectée. Multiple frame instances found, but only the first one will be used. Plusieurs instances de trame détectées, mais seule la première sera utilisée. The padding in the ID3V2 tag is too large, wasting space. (Large padding improves the tag editor saving speed, if fast saving is enabled, so you may want to delay compacting the tag until after you're done with the tag editor.) Le bourrage du tag ID3V2 est trop important et gaspille de l'espace. (Un bourrage important améliore la vitesse de sauvegarde de l'éditeur de tags si la sauvegarde rapide est activée, donc vous souhaiterez peut-être retarder la compaction du tag une fois que vous aurez terminé avec l'éditeur de tags.) Unsupported ID3V2 version. Version ID3V2 non supportée. Unsupported ID3V2 tag. Unsupported flag. Tag ID3V2 non supporté. Drapeau non supporté. Unsupported value for Flags1 in ID3V2 frame. (This may also indicate that the file contains garbage where it was supposed to be zero.) Valeur non supportée pour Flags1 dans la trame ID3V2. (Ceci peut aussi indiquer que le fichier contient des données incorrectes là où il devrait y avoir des zéros.) Unsupported value for Flags2 in ID3V2 frame. (This may also indicate that the file contains garbage where it was supposed to be zero.) Valeur non supportée pour Flags2 dans la trame ID3V2. (Ceci peut aussi indiquer que le fichier contient des données incorrectes là où il devrait y avoir des zéros.) Multiple instances of the POPM frame found in ID3V2 tag. The current version discards all the instances except the first when processing this tag. Instances multiples d'une trame POPM détectée dans le tag ID3V2. La version actuelle ignore toutes les instances sauf la première lorsqu'elle traite ce tag. ID3V2 tag contains no frames, which is invalid. This note will disappear once you add track information in the tag editor. Le tag ID3V2 ne contient aucune trame, ce qui n'est pas valide. Cette note disparaîtra dès que vous aurez ajouté des informations sur la piste dans l'éditeur de tags. ID3V2 tag contains an empty text frame, which is invalid. Le tag ID3V2 contient une trame de texte vide, ce qui n'est pas valide. ID3V2 tag doesn't have an APIC frame (which is used to store images). Le tag ID3V2 ne possède pas de trame APIC (utilisée pour stocker les images). ID3V2 tag has an APIC frame (which is used to store images), but the image couldn't be loaded. Le tag ID3V2 possède une trame APIC (utilisée pour stocker les images), mais l'image n'a pas pu être chargée. ID3V2 tag has at least one valid APIC frame (which is used to store images), but no frame has a type that is associated with an album cover. Le tag ID3V2 a au moins une trame APIC (utilisée pour stocker les images) valide, mais aucune n'a un type associé avec une pochette d'album. Error loading image in APIC frame. Erreur lors du chargement de l'image depuis la trame APIC. Error loading image in APIC frame. The frame is too short anyway to have space for an image. Erreur lors du chargement de l'image depuis la trame APIC. Cette dernière est cependant bien trop courte pour contenir une image. ID3V2 tag has multiple APIC frames with the same picture type. Le tag ID3V2 a plusieurs trames APIC avec le même type d'image. ID3V2 tag has multiple APIC frames. While this is valid, players usually use only one of them to display an image, discarding the others. Le tag ID3V2 a plusieurs trames APIC. Si cela est valide, les lecteurs n'en utilisent généralement qu'une pour afficher une image, en ignorant les autres. Unsupported text encoding for APIC frame in ID3V2 tag. Encodage de texte de la trame APIC non supporté dans le tag ID3V2. APIC frame uses a link to a file as a MIME Type, which is not supported. La trame APIC utilise un lien vers un fichier en tant que type MIME, ce qui n'est pas supporté. Picture description is ignored in the current version. La description des images est ignorée dans la version actuelle. No ID3V2.3.0 tag found, although this is the most popular tag for storing song information. Aucun tag ID3V2.3.0 détecté, bien que ce soit le tag le plus populaire pour le stockage d'informations sur les titres. Two ID3V2.3.0 tags found, but a file should have at most one of them. Deux tags ID3V2.3.0 détectés, mais un fichier ne devrait en avoir qu'un au plus. Both ID3V2.3.0 and ID3V2.4.0 tags found, but there should be only one of them. Tags ID3V2.3.0 et ID3V2.4.0 détectés, mais il ne devrait y en avoir qu'un des deux. The ID3V2.3.0 tag should be the first tag in a file. Le tag ID3V2.3.0 devrait être le premier tag du fichier. ID3V2.3.0 tag contains a text frame encoded as UTF-8, which is valid in ID3V2.4.0 but not in ID3V2.3.0. Tag ID3V2.3.0 contenant une trame de texte encodée en UTF-8, ce qui est valide en ID3V2.4.0 mais pas en ID3V2.3.0. Unsupported value of text frame while reading an Id3V2 Stream. Valeur de trame de texte non supportée lors de la lecture d'un flux ID3V2. Invalid ID3V2.3.0 frame. Incorrect frame size or file too short. Trame ID3V2.3.0 non valide. Taille de trame incorrecte ou fichier trop court. Two ID3V2.4.0 tags found, but a file should have at most one of them. Deux tags ID3V2.4.0 détectés, mais un fichier ne devrait en avoir qu'un au plus. Invalid ID3V2.4.0 frame. Incorrect frame size or file too short. Trame ID3V2.4.0 non valide. Taille de trame incorrecte ou fichier trop court. Invalid ID3V2.4.0 frame. Frame size is supposed to be stored as a synchsafe integer, which uses only 7 bits in a byte, but the size uses all 8 bits, as in ID3V2.3.0. This will confuse some applications Frame ID3V2.4.0 non valide. La taille de trame devrait être stockée en tant qu'entier 'synchsafe', qui n'utilise que 7 bits de l'octet, mais elle utilise la totalité des 8 bits, comme en ID3V2.3.0. Ceci risque d'induire en erreur certaines applications Deprecated TYER frame found in 2.4.0 tag alongside a TDRC frame. Trame TYER obsolète détectée dans le tag 2.4.0 avec une trame TDRC. Deprecated TYER frame found in 2.4.0 tag. It's supposed to be replaced by a TDRC frame. Trame TYER obsolète détectée dans un tag 2.4.0. Elle devrait être remplacée par une trame TDRC. Deprecated TDAT frame found in 2.4.0 tag alongside a TDRC frame. Trame TDAT obsolète détectée dans le tag 2.4.0 avec une trame TDRC. Deprecated TDAT frame found in 2.4.0 tag. It's supposed to be replaced by a TDRC frame. Trame TDAT obsolète détectée dans le tag 2.4.0. Elle devrait être remplacée par une trame TDRC. Invalid ID3V2.4.0 frame. Mismatched Data length indicator. Frame value is probably incorrect Trame ID3V2.4.0 non valide. L'indicateur de longueur de données ne correspond pas. La valeur de la trame est sans doute incorrecte Invalid ID3V2.4.0 frame. Incorrect unsynchronization bit. Trame ID3V2.4.0 non valide. Bit de désynchronisation incorrect. Unsupported value of text frame while reading an Id3V2.4.0 stream. It may be using an unsupported text encoding. Valeur de trame texte non supportée durant la lecture d'un flux ID3V2.4.0. Cela provient peut-être d'un encodage de texte non supporté. The only supported tag found that is capable of storing song information is ID3V1, which has pretty limited capabilities. Le seul tag supporté détcté qui soit capable de stocker des informations sur le titre est un ID3V1, dont les capacités sont relativement limitées. The ID3V1 tag should be located after the MPEG audio stream. Le tag ID3V1 devrait se trouver juste après le flux audio MPEG. Invalid ID3V1 tag. File too short. Tag ID3V1 non valide. Fichier trop court. Two ID3V1 tags found, but a file should have at most one of them. Deux tags ID3V1 détectés, mais un fichier ne devrait en avoir qu'un au plus. ID3V1 tag contains fields padded with spaces alongside fields padded with zeroes. The standard only allows zeroes, but some tools use spaces. Even so, zero-padding and space-padding shouldn't be mixed. Le tag ID3V1 contient des champs bourrés avec des espaces et d'autres champs bourrés avec des zéros. Le standard n'autorise que des zéros, mais certains outils utilisent des espaces. Quoi qu'il en soit, les deux types de bourrage ne devraient pas être mélangés. ID3V1 tag contains fields that are padded with spaces mixed with zeroes. The standard only allows zeroes, but some tools use spaces. Even so, one character should be used for padding for the whole tag. Le tag ID3V1 contient des champs bourrés avec des espaces et zéros mélangés. Le standard n'autorise que des zéros, mais certains outils utilisent des espaces. Quoi qu'il en soit, un unique caractère devrait être utilisé pour le bourrage du tag complet. Invalid ID3V1 tag. Invalid characters in Name field. Tag ID3V1 non valide. Caractères non valides dans le champ Nom. Invalid ID3V1 tag. Invalid characters in Artist field. Tag ID3V1 non valide. Caractères non valides dans le champ Artiste. Invalid ID3V1 tag. Invalid characters in Album field. Tag ID3V1 non valide. Caractères non valides dans le champ Album. Invalid ID3V1 tag. Invalid characters in Year field. Tag ID3V1 non valide. Caractères non valides dans le champ Année. Invalid ID3V1 tag. Invalid characters in Comment field. Tag ID3V1 non valide. Caractères non valides dans le champ Commentaire. Broken stream found. Flux endommagé détecté. Broken stream found. Since other streams follow, it is possible that players and tools will have problems using the file. Removing the stream is recommended. Flux endommagé détecté. Puisque d'autres flux suivent, il se peut que certains outils ou lecteurs aient des problèmes pour utiliser ce fichier. Il est recommandé de supprimer le flux. Truncated MPEG stream found. The cause for this seems to be that the file was truncated. Flux MPEG tronqué détecté. La cause semble être la troncature du fichier. Truncated MPEG stream found. Since other streams follow, it is possible that players and tools will have problems using the file. Removing the stream or padding it with 0 to reach its declared size is strongly recommended. Flux MPEG tronqué détecté. Puisque d'autres flux suivent, il se peut que certains outils ou lecteurs aient des problèmes pour utiliser ce fichier. Il est fortement recommandé de supprimer le flux ou de le bourrer avec des 0 pour atteindre sa taille déclarée. Not enough remaining bytes to create an UnknownDataStream. Nombre d'octets restant insuffisant pour créer un flux de données inconnu (UnknownDataStream). Unknown stream found. Flux inconnu détecté. Unknown stream found. Since other streams follow, it is possible that players and tools will have problems using the file. Removing the stream is recommended. Flux inconnu détecté. Puisque d'autres flux suivent, il se peut que certains outils ou lecteurs aient des problèmes pour utiliser ce fichier. Il est recommandé de supprimer le flux. File contains null streams. Le fichier contient des flux nuls. Invalid Lyrics stream tag. File too short. Tag de flux Paroles non valide. Fichier trop court. Two Lyrics tags found, but only one is supported. Deux tags Paroles détectés, mais un seul est supporté. Invalid Lyrics stream tag. Unexpected characters found. Tag de flux Paroles non valide. Caractères inattendus trouvés. Multiple fields with the same name were found in a Lyrics tag, but only one is supported. Des champs multiples avec le même nom ont été détectés dans un tag Paroles, mais un seul est supporté. Currently INF fields in Lyrics tags are not fully supported. Les champs INF dans les tags Paroles ne sont pas totalement supportés actuellement. Invalid Ape Item. File too short. Element APE non valide. Fichier trop court. Ape Item seems too big. Although the size may be any 32-bit integer, 256 bytes should be enough in practice. If this note is determined to be incorrect, it will be removed in the future. L'item Ape semble trop grand. Bien que la taille puisse être n'importe quel entier sur 32 bits, 256 octets devraient suffire en pratique. Si cette note s'avère incorrecte, elle sera supprimée. Invalid Ape Item. Terminator not found for item name. Elément APE non valide. Terminaison pour le nom d'élément introuvable. Invalid Ape tag. Header expected but footer found. Tag APE non valide. En-tête attendu mais pied de page trouvé. Not an Ape tag. File too short. N'est pas un tag APE. Fichier trop court. Invalid Ape tag. Footer expected but header found. Tag APE non valide. Pied de page attendu mais en-tête trouvé. Invalid Ape tag. Mismatch between header and footer. Tag APE non valide. Incohérence entre l'en-tête et le pied de page. Two Ape tags found, but only one is supported. Deux tags APE détectés, mais un seul est supporté. Ape item flags not supported. Drapeaux de l'élément APE non supportés. Unsupported Ape tag. Currently a missing header or footer are not supported. Tag APE non supporté. Actuellement un en-tête ou pied de page manquant n'est pas supporté. The file seems to have been changed in the (short) time that passed between parsing it and the initial search for pictures. If you think that's not the case, report a bug. Le fichier semble avoir changé pendant le (court) laps de temps entre son analyse et la recherche initiale d'images. Si vous pensez que ce n'est pas le cas, veuillez signaler un bogue. No supported tag found that is capable of storing song information. Aucun tag supporté capable de stocker des informations sur le titre n'a été détecté. Too many TRACE notes added. The rest will be discarded. Trop de notes TRACE ajoutées. Le reste sera ignoré. Too many notes added. The rest will be discarded. Trop de notes ajoutées. Le reste sera ignoré. Too many streams found. Aborting processing. Trop de flux détectés. Annulation du traitement. Unsupported stream found. It may be supported in the future if there's a real need for it. Flux non supporté détecté. Il pourrait être supporté ultérieurement si un réel besoin s'en fait sentir. The file was saved using the "fast" option. While this improves the saving speed, it may leave the notes in an inconsistent state, so you should rescan the file. Le fichier a été sauvegardé en utilisant l'option "rapide". Si la vitesse de sauvegarde est améliorée, il se peut aussi que les notes soient laissées dans un état incohérent, c'est pourquoi vous devriez scanner à nouveau le fichier. NotesModel L L Note Note Address Adresse PaletteDlg Background colors Couleurs d'arrière-plan Album Album Field equal to the ID3V2 field or missing Champ identique au champ ID3V2 ou manquant Field is different from the corresponding ID3V2 value (or no ID3V2 field exists) Le champ diffère de la valeur ID3V2 correspondante (ou aucun champ ID3V2 n'existe) Value marked as "assigned" Valeur marquée comme "assignée" Song Titre Tag and field present Tag et champ présents Tag not present Tag absent Tag present but field is not supported Tag présent mais champ non supporté Tag present, field is supported but missing Tag présent, champ supporté mais manquant OK OK PatternsDlg Patterns Motifs Add predefined patterns Ajouter des motifs prédéfinis Line 1, Col 1 Ligne 1, Colonne 1 O&K O&K Cancel Annuler Renamer A pattern cannot be empty Un motif ne peut être vide A pattern must either begin with '%1' or contain no '%1' at all Un motif doit commencer par '%1' ou ne contenir aucun '%1' A pattern must either begin with "<drive>:\" or contain no '\' at all Un motif doit commencer par "<lecteur>:\" ou ne contenir aucun '\' Error in column %1. Erreur colonne %1. Nested optional elements are not allowed Les éléments optionnels imbriqués ne sont pas autorisés Trying to close and optional element although none is open Tentative de fermeture des éléments optionnels bien qu'aucun ne soit ouvert Optional element must be closed L'élément optionnel doit être fermé Title entry (%t) must be present L'entrée de titre (%t) doit être présente RenamerPatternsDlgImpl %n track number %a artist %t title %b album %y year %g genre %r rating (a lowercase letter) %c composer To include the special characters "%", "[" and "]", precede them by a "%": "%%", "%[" and "%]" The path should be either a full path, starting with a "%1", or it should contain no "%1", if what is wanted is for the renamed files to remain in their original directories %n numéro de piste %a artiste %t titre %b album %y année %g genre %r note (1 lettre minuscule) %c compositeur Pour inclure les caractères spéciaux "%", "[" et "]", préfixez-les par "%": "%%", "%[" et "%]" Le chemin d'accès doit être soit un chemin complet, commençant par "%1", ou ne doit contenir aucun "%1", si le but recherché est de conserver les fichiers renommés dans le répertoire original %n track number %a artist %t title %b album %y year %g genre %r rating (a lowercase letter) %c composer To include the special characters "%", "[" and "]", precede them by a "%": "%%", "%[" and "%]" The path should be either a full path, starting with a drive letter followed by ":\", or it should contain no "\", if what is wanted is for the renamed files to remain in their original directories %n numéro de piste %a artiste %t titre %b album %y année %g genre %r note (1 lettre minuscule) %c compositeur Pour inclure les caractères spéciaux "%", "[" et "]", préfixez-les par "%": "%%", "%[" et "%]" Le chemin d'accès doit être soit un chemin complet, commençant par une lettre de lecteur suivie par ":\", ou ne doit contenir aucun "\", si le but recherché est de conserver les fichiers renommés dans le répertoire original Error Erreur Line %1, Col %2 Ligne %1, Colonne %2 ScanDlg Scan Scan Rescan files that seem unchanged Scanner à nouveau les fichiers apparemment non modifiés &Scan &Scanner Cancel Annuler SessionEditorDlg pppppppppppppp pppppppppppppp Language Langage Include directories Inclure les répertoires Backup Sauvegarde Don't create backup for modified files Ne pas créer de sauvegarde pour les fichiers modifiés Create backup for modified files in Créer une sauvegarde des fichiers modifiés dans ... ... Settings file name Nom du fichier de configuration Scan for new, modified, and deleted files at startup Rechercher au démarrage les fichiers nouveaux, modifiés ou supprimés Open last session at s&tartup Ouvrir la dernière session au &démarrage Open the main "Sessions" dialog, to load an existing session Ouvrir la fenêtre "Sessions" pour charger une session existante O&K O&K Cancel Annuler SessionEditorDlgImpl This is the name of the "settings file" It is supposed to be a file that doesn't already exist. You don't need to set it up. MP3 Diags will store its settings in this file. The name was generated automatically. If you want to choose a different name, simply click on the button at the right to change it. this is a multiline tooltip Ceci est le nom du "fichier de configuration". Ceci est supposé être un fichier qui n'existe pas encore. Vous n'avez pas besoin de le créer. MP3 Diags stockera ses réglages dans ce fichier. Le nom a été généré automatiquement. Si vous souhaitez choisir un autre nom, cliquez simplement sur le bouton à droite pour le changer. Here you need to specify the name of a "settings file" This is supposed to be a file that doesn't already exist. You don't need to set it up. MP3 Diags will store its settings in this file. To change it, simply click on the button at the right to choose the name of the settings file. this is a multiline tooltip Ici vous devez spécifier le nom d'un "fichier de configuration". Ceci est supposé être un fichier qui n'existe pas encore. Vous n'avez pas besoin de le créer. MP3 Diags stockera ses réglages dans ce fichier. Pour le modifier, cliquez simplement sur le bouton à droite pour choisir le nom du fichier de configuration. MP3 Diags - Create new session MP3 Diags - Créer une nouvelle session MP3 Diags - Edit session MP3 Diags - Modifier la session Error Erreur You need to specify the name of the settings file. This is supposed to be a file that doesn't already exist. You don't need to set it up, but just to pick a name for it. MP3 Diags will store its settings in this file. Vous devez spécifier le nom du fichier de configuration. Ceci est supposé être un fichier qui n'existe pas encore. Vous n'avez pas besoin de le créer, juste de lui donner un nom. MP3 Diags stockera ses réglages dans ce fichier. You need to select at least a directory to be included in the session. Vous devez sélectionner au moins un dossier à inclure dans la session. If you want to create backups, you must select an existing directory to store them. Si vous souhaitez faire des sauvegardes, vous devez sélectionner un répertoire existant pour les stocker. Failed to write to file %1 L'écriture dans le fichier %1 a échoué Select folder Sélectionnez le dossier All files (*) Tous les fichiers (*) Enter configuration file Sélectionnez le fichier de configuration MP3 Diags session files (*%1) Fichiers de session MP3 Diags (*%1) SessionsDlg MP3 Diags - Sessions MP3 Diags - Sessions Language Langage &Open &Ouvrir &New ... &Nouveau... &Edit ... &Modifier ... Save &as ... Enregistrer &sous ... E&rase &Effacer &Load ... &Charger ... &Hide M&asquer Close Fermer Temporary session template Modèle pour les sessions temporaires Persistent session template Modèle pour les sessions persistantes At s&tartup open the last session automatically Ouvrir automatiquement la dernière session au &démarrage SessionsDlgImpl <last session> <dernière session> Confirm Confirmer Do you really want to erase the current session? Voulez-vous vraiemnt effacer la session courante ? Erase Effacer Cancel Annuler Error Erreur Failed to remove the data files associated with this session Impossible de supprimer les fichiers de données associés à cette session Save session as ... Enregistrer la session sous... Do you really want to hide the current session? Voulez-vous vraiment masquer la session courante ? Hide Masquer Choose a session file Sélectionnez un fichier de session The session named "%1" is already part of the session list La session nommée "%1" fait déjà partie de la liste des sessions The session list is empty. You must create a new session or load an existing one. La liste de sessions est vide. Vous devez créer une nouvelle session ou charger une session existante. SessionsModel File name Nom du fichier ShellIntegrator %1 - %2 %1 - %2 Error setting up shell integration Erreur lors de la configuration de l'intégration au shell It appears that setting up shell integration didn't complete successfully. You might have to configure it manually. Il semble que la configuration de l'intégration au shell ne se soit pas terminée correctement. Vous aurez sans doute à la configurer manuellement. This message will not be shown again until the program is restarted, even if more errors occur. Ce message n'apparaîtra plus jusqu'au redémarrage du programme, même si d'autres erreurs se produisent. O&K O&K temporary folder dossier temporaire hidden folder dossier caché visible folder dossier visible StreamsModel Address Adresse Size (dec) Taille (déc.) Size (hex) Taille (hexa.) Type Type Stream details Détails du flux TagEditor::CurrentAlbumDelegate Warning Avertissement You are editing data in a cell. If you proceed that change will be lost. Proceed and lose the data? Vous éditez les données d'une cellule. Si vous continuez, vos changements seront perdus. Continuer et perdre les données entrées ? Proceed Continuer Cancel Annuler TagEditor::CurrentAlbumModel N/A N/A File name Nom du fichier Error Erreur The data contained errors and couldn't be saved Les données contenaient des erreurs et n'ont pu être sauvegardées TagEditor::CurrentFileModel N/A N/A TagEditorDlg MP3 Diags - Tag editor MP3 Diags - Editeur de tags Save Sauvegarder S S Reset Réinitialiser R R Previous [Ctrl+P] Précédent [Ctrl+P] < < Ctrl+P Ctrl+P Folder Dossier Next [Ctrl+N] Suivant [Ctrl+N] > > Ctrl+N Ctrl+N Query Discogs Contacter Discogs Q Q Query MusicBrainz Contacter MusicBrainz ... ... Toggle "Various Artists" Afficher/masquer "Various Artists" V V Change case automatically Changer la casse automatiquement Copy field(s) from first line Copier le(s) champ(s) à partir de la première ligne C C Sort by track number Trier par numéro de piste Toggle "assigned" state Afficher/masquer l'état "assigné" A A Paste Coller Ctrl+V Ctrl+V Edit file patterns Editer les motifs fichier F F Background colors Couleurs d'arrière-plan Configuration Configuration Close Fermer TextLabel TextLabel TagEditorDlgImpl Toggle "Various Artists" Afficher/masquer "Various Artists" To enable "Various Artists" you need to open the configuration dialog, go to the "Others" tab and check the corresponding checkbox(es) this is a multi-line tooltip Pour activer "Various Artists", vous devez ouvrir la fenêtre de configuration, puis cocher la(les) case(s) correspondante(s) de l'onglet "Autres" Error setting up the tag order Erreur lors de la configuration de l'ordre des tags An invalid value was found in the configuration file. You'll have to sort the tags again. Une valeur non valide a été rencontrée dans le fichier de configuration. Vous devrez trier les tags à nouveau. Error setting up patterns Erreur lors de la configuration des motifs An invalid value was found in the configuration file. You'll have to set up the patterns manually. Une valeur non valide a été rencontrée dans le fichier de configuration. Vous devrez configurer les motifs manuellement. Warning Avertissement Reloading the current album causes all unsaved changes to be lost. Really reload? Recharger l'album courant entraîne la perte de tous les changements non sauvegardés. Vraiment recharger ? Reload Recharger Cancel Annuler Artists - %1 Artistes - %1 Others - %1 Autres - %1 Confirm Confirmer You added %1 images but then you didn't assign them to any songs. What do you want to do? Vous avez ajouté %1 images mais vous ne les avez assignées à aucun morceau. Que souhaitez-vous faire ? You added an image but then you didn't assign it to any song. What do you want to do? Vous avez ajouté une image mais vous ne l'avez assignée à aucun morceau. Que souhaitez-vous faire ? &Discard &Ignorer &Cancel &Annuler There are unsaved fields that you assigned a value to, as well as fields whose value doesn't match the ID3V2 value. What do you want to do? Il existe des champs non sauvegardés auxquels vous avez affecté une valeur, ainsi que des champs dont la valeur ne correspond pas à celle de l'ID3V2. Que souhaitez-vous faire ? &Save &Sauvegarder There are unsaved fields that you assigned a value to. What do you want to do? Il existe des champs non sauvegardés auxquels vous avez affecté une valeur. Que souhaitez-vous faire ? There are fields whose value doesn't match the ID3V2 value. What do you want to do? Il existe des champs dont la valeur ne correspond pas à celle de l'ID3V2. Que souhaitez-vous faire ? Saving ID3V2.3.0 tags Sauvegarde des tags ID3V2.3.0 Info Information Some fields are missing or may be incomplete. While this is usually solved by downloading correct information, there are a cases when this approach doesn't work, like custom compilations, rare albums, or missing tracks. Certains champs manquent ou peuvent être incomplets. Si le téléchargement d'informations correctes permet souvent de résoudre le problème, il existe des cas comme les compilations personnalisées, albums rares ou pistes manquantes où cette approche ne fonctionne pas. If your current folder fits one of these cases or you simply have consistently named files that you would prefer to use as a source of track info, you may want to take a look at the tag editor's patterns, at %1 Si le dossier actuel est dans un tel cas ou si vous avez nommé de façon cohérente les fichiers pour les utiliser comme source d'infos sur les pistes, les motifs de l'éditeur de tags sur %1 peuvennt vous être utiles O&K O&K TagEdtPatternsDlgImpl %n track number %a artist %t title %b album %y year %g genre %r rating (a lowercase letter) %c composer %i ignored To include the special characters "%", "[", "]" and "%1", preced them by a "%": "%%", "%[", "%]" and "%%1" For a pattern to be considered a "file pattern" (as opposed to a "table pattern"), it must contain at least a "%1", even if you don't care about what's in the file's parent directory (see the fourth predefined pattern for an example.) Leading and trailing spaces are removed automatically from unbound fields after matching, so "-[ ]%t" is equivalent to "-%t" (but "-[ ]%n" is not equivalent to "-%n", because %n is a fixed format field). However, all non-optional characters matter in the matching phase, including spaces. %n numéro de piste %a artiste %t titre %b album %y année %g genre %r note (1 lettre minuscule) %c compositeur %i ignoré Pour inclure les caractères spéciaux "%", "[", "]" et "%1", préfixez-les d'un "%": "%%", "%[", "%]" et "%%1" Pour qu'un motif soit considéré comme un "motif fichier" (par opposition à un "motif tableau"), il doit contenir au moins un "%1", même si le contenu du répertoire parent vous importe peu (voyez par exemple le quatrième motif prédéfini.) Les espaces de début et de fin sont automatiquement supprimés des champs non liés après correspondance, donc "-[ ]%t" est équivalent à "-%t" (mais "-[ ]%n" n'est pas équivalent à "-%n", car %n est un champ à format fixé). Cependant, tous les caractères non optionnels, espaces compris, sont pris en compte lors de la mise en correspondance. Error Erreur Line %1, Col %2 Ligne %1, Colonne %2 TagReadPanel Track# Piste# Artist Artiste Title Titre Album Album VA VA Time Durée Genre Genre Rating Note Composer Compositeur TagReader <non-text value> <valeur non texte> other autre 32x32 icon Icône 32x32 other file icon autre fichier icône front cover pochette avant back cover pochette arrière leaflet page page de livret media media lead artist artiste principal artist artiste conductor chef d'orchestre band groupe composer compositeur lyricist parolier recording location lieu d'enregistrement during recording not sure if this is the best translation here en enregistrement during performance not sure if this is the best translation here en live screen capture capture d'écran illustration illustration band/artist logotype logo artiste/groupe publisher/studio logotype logo studio/label unknown inconnu <no change> <inchangé> lower case minuscules UPPER CASE MAJUSCULES Title Case Casse Des Titres Sentence case Casse normale Title Titre Artist Artiste Track # Piste # Time Durée Genre Genre Picture Image Album Album Rating Note Composer Compositeur VA VA <error loading frame> <erreur lors du chargement de trame> <error decoding frame> <erreur lors du décodage de trame> Additional %1 field: %2 Champ additionnel %1 : %2 %1 field: %2 Champ %1 : %2 Comment: %1 Commentaire : %1 (file) (fichier) (table) (tableau) Pattern Motif Web Web TagWriter Warning Avertissement Track numbers are supposed to be consecutive numbers from 1 to the total number of tracks. This is not the case with the current album, which may lead to incorrect assignments when pasting information. Les numéros de piste sont censés être des nombres consécutifs allant de 1 au nombre total de pistes. Ce n'est pas le cas de l'album courant, ce qui peut entraîner de mauvaises affectations lors du collage d'informations. Error Erreur Some files have been modified by an external tool after the last scan. You won't be able to save any changes to those files until you rescan them. Certains fichiers ont été modifiés par un outil externe après le dernier scan. Vous ne pourrez sauvegarder aucun changement sur ces fichers si vous ne les scannez pas à nouveau. Do you want to erase these files?%1 Voulez-vous effacer ces fichiers ? %1 Do you want to erase %1? Voulez-vous effacer %1 ? Confirm Confirmer Erase Effacer Cancel Annuler You cannot erase image files if there are unsaved values. Do you want to save? Impossible de supprimer les fichiers image s'il reste des valeurs non sauvegardées. Voulez-vous sauvegarder ? Save, then erase file Sauvegarder, puis effacer le fichier Erasing image files triggers a full reload, which results in downloaded and pasted data being lost. Erase anyway? L'effacement de fichiers image entraîne un rechargement complet, qui entraîne la perte des données téléchargées et collées. Effacer quand même ? Couldn't erase file "%1" Impossible d'effacer le fichier "%1" Currently pasting multiple file names is not supported, so only the first one is considered. Le collage de noms de fichiers multiples n'est pas supporté actuellement, donc seul le premier sera pris en compte. The pasted value couldn't be assigned to some fields La valeur collée n'a pu être assignée à certains champs The pasted value couldn't be assigned to any field La valeur collée n'a pu être assignée à aucun champ The number of lines in the clipboard is different from the number of files. Paste anyway? Le nombre de lignes dans le presse-papiers diffère du nombre de fichiers. Coller quand même ? Paste Coller The track numbers aren't consecutive numbers starting at 1, so the pasted track information might not match the tracks. Paste anyway? Les numéros de pistes ne sont pas des nombres consécutifs commençant à 1, donc les informations de piste collées peuvent ne pas correspondre. Coller quand même ? Unrecognized clipboard content Contenu du presse-papiers non reconnu ThreadRunnerDlg Thread Runner Lanceur de threads TextLabel TextLabel &Pause &Pause &Abort &Interrompre ThreadRunnerDlgImpl Completed Terminé &Resume &Reprendre &Pause &Pause Total time: %1 Running time: %2 Durée totale : %1 Durée d'exécution : %2 Time: %1 Durée : %1 TrackTextParser "%1" is not a valid pattern. Error in column %2. "%1" n'est pas un motif valide. Erreur colonne %2. TransfConfig Error Erreur Invalid value found for file settings. Reverting to default settings. Valeur non valide trouvée dans les options du fichier. Retour aux options par défaut. Transformation Convert non-ASCII ID3V2 text frames to Unicode assuming codepage %1 Convertir les trames de texte ID3V2 non ASCII en Unicode en utilisant la page de code %1 Change case for ID3V2 text frames: Artists - %1; Others - %2 Changer la casse des trames de texte ID3V2 : Artistes - %1 ; Autres - %2 Copy missing ID3V2 frames from ID3V1 assuming codepage %1 Copier les trames ID3V2 manquantes depuis le tag ID3V1 en utilisant la page de code %1 Removes all ID3V2 frames that aren't used by MP3 Diags. You normally don't want to do such a thing, but it may help if some other program misbehaves because of invalid or unknown frames in an ID3V2 tag. Supprime toutes les trames ID3V2 inutilisées par MP3Diags. En temps normal, il est rare de devoir le faire, mais cela peut être utile si d'autres programmes ont un comportement anormal en raison de trames non valides ou inconnues dans un tag ID3V2. Remove non-basic ID3V2 frames Supprimer les trames ID3V2 non basiques Copies only ID3V2 frames that seem valid or can be made valid, discarding those that are invalid and can't be fixed (e.g. an APIC frame claiming to hold a picture although it doesn't.) Handles both loadable and broken ID3V2 tags, in the latter case copying being stopped when a fatal error occurs. Ne copie que les trames ID3V2 qui semblent valides ou validables, en ignorant celles qui ne sont pas valides et ne sont pas fréparables (p. ex. une trame APIC déclarant contenir une image alors que ce n'est pas le cas). Prend en charge les tags ID3V2 chargeables et endommagés, dans le second cas la copie est interrompue lorsqu'une erreur fatale survient. Discard invalid ID3V2 data Ignorer les données ID3V2 non valides Transforms text frames in ID3V2 encoded as Latin1 to Unicode (UTF16.) The reason to do this is that sometimes non-conforming software treats these frames as they are encoded in a different code page, causing other programs to display unexpected data. Transforme les trames de texte ID3V2 encodées en Latin1 en Unicode (UTF16). La raison en est que parfois certains logiciels irrespectueux des standards traitent ces trames comme si elles étaient encodées avec une page de code différente, ce qui provoque l'affichage de données inattendues dans d'autres programmes. Convert non-ASCII ID3V2 text frames to Unicode Convertir les trames de texte ID3V2 non ASCII en Unicode Transforms the case of text frames in ID3V2 tags, according to global settings. Modifier la casse des trames de texte des tags ID3V2, conformément aux réglages globaux. Change case for ID3V2 text frames Changer la casse des trames de texte ID3V2 Copies frames from ID3V1 to ID3V2 if those frames don't exist in the destination or if the destination doesn't exist at all. Copie les trames depuis le tag ID3V1 vers l'ID3V2 si ces trames n'existent pas dans le tag destination ou si ce dernier n'existe pas. Copy missing ID3V2 frames from ID3V1 Copier les trames ID3V2 manquantes depuis le tag ID3V1 Adds the value of the composer field to the beginning of the artist field in ID3V2 frames. Useful for players that don't use the composer field. Ajoute la valeur du champ compositeur au début du champ artiste dans les trames ID3V2. Utile pour les lecteurs qui n'utilisent pas le champ compositeur. Add composer field to the artist field in ID3V2 frames Ajouter le champ compositeur à l'artiste dans les trames ID3V2 "Undo" for "adding composer field." Removes the value of the composer field from the beginning of the artist field in ID3V2 frames, if it was previously added. "Annuler" pour l'opération "ajouter le champ compositeur". Supprime la valeur du champ compositeur du début du champ artiste dans les trames ID3V2 s'il avait été ajouté précédemment. Remove composer field from the artist field in ID3V2 frames Supprimer le champ compositeur du champ artiste dans les trames ID3V2 Copies to the "Composer" field the beginning of an "Artist" field that is formatted as "Composer [Artist]". Does nothing if the "Artist" field doesn't have this format. Copie dans le champ "Compositeur" le début d'un champ "Artiste" formaté comme ceci : "Compositeur [Artiste]". Ne fait rien si le champ "Artiste" n'est pas à ce format. Fill in composer field based on artist in ID3V2 frames Remplir le champ compositeur à partir de l'artiste dans les trames ID3V2 Keeps only the biggest (and supposedly the best) image in a file. The image type is set to Front Cover. (This may result in the replacement of the Front Cover image.) Ne garde que la plus grande (théoriquement la meilleure) image dans un fichier. Le type de cette image devient Pochette avant. (Ceci peut entraîner le remplacement de l'image de la Pochette avant.) Make the largest image "Front Cover" and remove the rest Utiliser la plus grande image comme "Pochette avant" et supprimer le reste Adds extra spacing to the ID3V2 tag. This allows subsequent saving from the tag editor to complete quicker. Ajoute de l'espace supplémentaire au tag ID3V2. Cette opération permet d'accélérer de prochaines sauvegardes de l'éditeur de tags. Reserve space in ID3V2 for fast tag editing Réserver de la place dans le tag ID3V2 pour l'édition rapide de tag Removes large unused blocks from ID3V2 tags. (Usually these have been reserved for fast tag editing, in which case they should be removed only after the ID3V2 tag has all the right values.) Supprime des blocs inutilsés de grande taille des tags ID3V2. (Ceux-ci ont généralement été réservés pour une édition de tag rapide, dans ce cas ils ne devraient être supprimés qu'une fois que toutes les valeurs du tag ID3V2 sont correctes.) Remove extra space from ID3V2 Enlever l'espace supplémentaire du tag ID3V2 Removes selected streams. Supprime les flux sélectionnés. Remove selected stream(s) Supprimer les flux sélectionnés Removes specified stream. Supprime le flux spécifié. Remove specified stream Supprimer le flux spécifié Sometimes a bit gets flipped in a file. This tries to identify places having this issue in audio frame headers. If found, it fixes the problem. It is most likely to apply to files that have 2 audio streams. Parfois un bit s'inverse dans un fichier. Ceci essaie d'identifier les emplacements concernés par ce problème dans les en-têtes de trames audio. Si de tels emplacements sont rencontrés, le problème est corrigé. Ceci s'applique davantage à des fichiers contenant 2 flux audio. Restore flipped bit in audio Restaurer les bits inversés dans l'audio Removes all non-audio data that is found between audio streams. In this context, VBRI streams are considered audio streams (while Xing streams are not.) Supprime toutes les données non-audio qui se trouvent entre les flux audio. Dans ce contexte, les flux VBRI sont considérés comme flux audio (contrairement aux flux Xing.) Remove inner non-audio Supprimer le non-audio interne Removes all unknown streams. Supprime tous les flux inconnus. Remove unknown streams Supprimer les flux inconnus Removes all broken streams. Supprime tous les flux endommagés. Remove broken streams Supprimer les flux endommagés Removes all unsupported streams. Supprime tous les flux non supportés. Remove unsupported streams Supprimer les flux non supportés Removes all truncated audio streams. Supprime tous les flux audio tronqués. Remove truncated audio streams Supprimer les flux audio tronqués Removes all null streams. Supprime tous les flux nuls. Remove null streams Supprimer les flux nuls Removes all streams that are broken, truncated, unsupported or have some errors making them unusable. Supprime tous les flux endommagés, tronqués, non supportés ou rendus inutilisables par des erreurs. Removes broken ID3V2 streams. Supprime les flux ID3V2 endommagés. Remove broken ID3V2 streams Supprimer les flux ID3V2 endommagés Removes unsupported ID3V2 streams. Supprime les flux ID3V2 non supportés. Remove unsupported ID3V2 streams Supprimer les flux ID3V2 non supportés If a file has multiple ID3 streams it keeps only the last ID3V1 and the first ID3V2 stream. Si un fichier a plusieurs flux ID3, seuls le premier ID3V2 et le dernier ID3V1 sont conservés. Remove multiple ID3 streams Supprimer les flux ID3 multiples Sometimes the number of frames in an audio stream is different from the number of frames in a preceding Xing (or Lame) header, usually because the audio stream was damaged. It's probably best for the Xing header to be removed in this case. If the audio is VBR, you may want to try "Repair VBR data" first. Il arrive que le nombre de trames dans un flux audio diffère de celui de l'en-tête Xing (ou Lame) qui le précède, souvent parce que le flux audio a été endommagé. Il est probablement souhaitable dans ce cas de supprimer l'en-tête Xing. Si l'audio est en VBR, vous souhaiterez peut-être essayer d'abord "Réparer les données VBR". Remove mismatched Xing headers Supprimer les en-têtes Xing inadéquats Removes all ID3V1 streams. Supprime tous les flux ID3V1. Remove all ID3V1 streams Supprimer tous les flux ID3V1 Removes all APE streams. Supprime tous les flux APE. Remove all APE streams Supprimer tous les flux APE Removes all non-audio streams. Supprime tous les flux non audio. Remove all non-audio streams Supprimer tous les flux non audio Pads truncated audio frames with 0 to the right. Its usefulness hasn't been determined yet (it might be quite low.) Bourre les trames audio tronquées avec des 0 vers la droite. Son utilité n'a pas encore été déterminée (elle pourrait s'avérer faible.) Pad truncated audio Bourrer l'audio tronqué If a file contains VBR audio but doesn't have a Xing header, one such header is added. If a VBRI header exists, it is removed. If a Xing header exists but is determined to be incorrect, it is corrected or replaced. Only the first audio stream is considered; if a file contains more than one audio stream, this should be fixed first. Si un fichier contient de l'audio VBR mais pas d'en-tête Xing, un tel en-tête est alors ajouté. Si un en-tête VBRI existe, il est supprimé. Si un en-tête Xing existe mais est considéré incorrect, il est corrigé ou remplacé. Seul le premier flux audio est pris en compte ; si un fichier en contient davantage, ceci doit être corrigé en premier lieu. Repair VBR data Réparer les données VBR If a file contains VBR audio, any existing VBRI or Xing headers are removed and a new Xing header is created. Only the first audio stream is considered; if a file contains more than one audio stream, this should be fixed first. Si un fichier contient de l'audio VBR, les éventuels en-têtes Xing ou VBRI existants sont supprimés et un nouvel en-tête Xing est créé. Seul le premier flux audio est pris en compte ; si un fichier en contient davantage, ceci doit être corrigé en premier lieu. Rebuild VBR data Reconstruire les données VBR Saves user-edited ID3V2.3.0 tags. Sauvegarde les tags ID3V2.3.0 édités par l'utilisateur. Save ID3V2.3.0 tags Sauvegarder les tags ID3V2.3.0 Doesn't actually change the file, but it creates a temporary copy and it reports that it does change it. This is not as meaningless as it might first seem: if the configuration settings indicate some action (i.e. rename, move or erase) to be taken for processed files, then that action will be performed for these files. While the same can be achieved by changing the settings for unprocessed files, this is easier to use when it is executed on a subset of all the files (filtered or selected). Ne change pas réellement le fichier, mais en crée une copie temporaire et rapporte les changements. Ceci n'est pas aussi insensé qu'il n'y paraît : si les réglages indiquent une action (renommer, déplacer ou effacer) à effectuer sur les fichiers traités, alors cette action sera effectuée pour ces fichiers. Mais alors que le même résultat peut être obtenu en modifiant les réglages pour les fichiers non traités, ceci est d'un usage plus simple lorsqu'il est exécuté sur une partie des fichiers (filtrés ou sélectionnés). No change Aucun changement UniqueNotesModel L L Note Note VisibleTransfPainter Action Action Description Description Add selected transformation(s) Ajouter la(les) transformation(s) sélectionné(es) Remove selected transformation(s) Enlever la(les) transformation(s) sélectionné(es) Restore current list to its default value Restaurer la liste courante à sa valeur par défaut Restore current list to the configuration it had when the window was open Restaurer la liste courante à sa configuration à l'ouverture de la fenêtre WebDwnldModel Pos Pos Title Titre Artist Artiste Composer Compositeur MP3Diags-1.2.02/src/MpegFrame.cpp0000644000175000001440000002353211724437411015316 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include "MpegFrame.h" #include "DataStream.h" #include "Helpers.h" #include "Notes.h" #include "Widgets.h" // for GlobalTranslHlp using namespace std; //using namespace pearl; // based on http://mpgedit.org/mpgedit/mpeg_format/mpeghdr.htm MpegFrameBase::MpegFrameBase(NoteColl& notes, istream& in) { m_pos = in.tellg(); const int BFR_SIZE (4); char bfr [BFR_SIZE]; MP3_CHECK_T (BFR_SIZE == read(in, bfr, BFR_SIZE), m_pos, "Not an MPEG frame. File too short.", NotMpegFrame()); init(notes, bfr); } MpegFrameBase::MpegFrameBase(NoteColl& notes, streampos pos, const char* bfr) { m_pos = pos; init(notes, bfr); } #ifdef GENERATE_TOC MpegFrameBase MpegFrameBase::getBigBps() const // returns a "similar" frame to "this" but with a bigger bps, so it can hold a Xing TOC { //char bfr [1000]; string s (&m_header[0], 4); //s[2] = (s[2] & 0x0f) | 0xe0; //ttt2 maybe use this; it provides maximum space; s[2] = (s[2] & 0x0f) | 0x90; s.resize(2000); NoteColl notes; istringstream in (s); MpegFrameBase res (notes, in); return res; } #endif void MpegFrameBase::init(NoteColl& notes, const char* bfr) //ttt2 should have some means to enforce bfr being large enough (perhaps switch to a vector) { memcpy(m_header, bfr, 4); //inspect(bfr, BFR_SIZE); //ttt2 check the CRC right after the header, if present (note that the size of a frame doesn't change as a result of using the CRC, as it can be seen in several files, e.g. "05 Are You Gonna Go My Way.mp3") const unsigned char* pHeader (reinterpret_cast(bfr)); MP3_CHECK_T (0xff == *pHeader && 0xe0 == (0xe0 & *(pHeader + 1)), m_pos, "Not an MPEG frame. Synch missing.", NotMpegFrame()/*"missing synch"*/); ++pHeader; { int nVer ((*pHeader & 0x18) >> 3); switch (nVer) {//TRACE case 0x00: MP3_THROW_T (m_pos, "Not an MPEG frame. Unsupported version (2.5).", NotMpegFrame()); //ttt2 see about supporting this: search for MPEG1 to find other places // ttt2 in a way it would make more sense to warn that it's not supported, with "MP3_THROW(SUPPORT, ...)", but before warn, make sure it's a valid 2.5 frame, followed by another frame ... case 0x02: m_eVersion = MPEG2; break; case 0x03: m_eVersion = MPEG1; break; default: MP3_THROW_T (m_pos, "Not an MPEG frame. Invalid version.", NotMpegFrame()); } } { int nLayer ((*pHeader & 0x06) >> 1); switch (nLayer) { case 0x01: m_eLayer = LAYER3; break; case 0x02: m_eLayer = LAYER2; break; case 0x03: m_eLayer = LAYER1; break; default: MP3_THROW_T (m_pos, "Not an MPEG frame. Invalid layer.", NotMpegFrame()); } } { m_bCrc = !(*pHeader & 0x01); } ++pHeader; { static int s_bitrates [14][5] = { { 32, 32, 32, 32, 8 }, { 64, 48, 40, 48, 16 }, { 96, 56, 48, 56, 24 }, { 128, 64, 56, 64, 32 }, { 160, 80, 64, 80, 40 }, { 192, 96, 80, 96, 48 }, { 224, 112, 96, 112, 56 }, { 256, 128, 112, 128, 64 }, { 288, 160, 128, 144, 80 }, { 320, 192, 160, 160, 96 }, { 352, 224, 192, 176, 112 }, { 384, 256, 224, 192, 128 }, { 416, 320, 256, 224, 144 }, { 448, 384, 320, 256, 160 } }; int nRateIndex ((*pHeader & 0xf0) >> 4); MP3_CHECK_T (nRateIndex >= 1 && nRateIndex <= 14, m_pos, "Not an MPEG frame. Invalid bitrate.", NotMpegFrame()/*"invalid bitrate"*/); //ttt3 add tests for invalid combinations of channel mode and bitrate (MPEG 1 Layer II only) int nTypeIndex (m_eVersion*3 + m_eLayer); if (nTypeIndex == 5) { nTypeIndex = 4; } m_nBitrate = s_bitrates[nRateIndex - 1][nTypeIndex]*1000; } { int nSmpl ((*pHeader & 0x0c) >> 2); switch (m_eVersion) { case MPEG1: switch (nSmpl) { case 0x00: m_nFrequency = 44100; break; case 0x01: m_nFrequency = 48000; break; case 0x02: m_nFrequency = 32000; break; default: MP3_THROW_T (m_pos, "Not an MPEG frame. Invalid frequency for MPEG1.", NotMpegFrame()); } break; case MPEG2: switch (nSmpl) { case 0x00: m_nFrequency = 22050; break; case 0x01: m_nFrequency = 24000; break; case 0x02: m_nFrequency = 16000; break; default: MP3_THROW_T (m_pos, "Not an MPEG frame. Invalid frequency for MPEG2.", NotMpegFrame()); } break; default: CB_ASSERT(false); // it should have thrown before getting here } } { m_nPadding = (0x02 & *pHeader) >> 1; } ++pHeader; { int nChMode ((*pHeader & 0xc0) >> 6); m_eChannelMode = (ChannelMode)nChMode; } switch (m_eLayer) { case LAYER1: m_nSize = (12*m_nBitrate/m_nFrequency + m_nPadding)*4; break; case LAYER2: m_nSize = 144*m_nBitrate/m_nFrequency + m_nPadding; break; case LAYER3: //cout << "m_nBitrate: " << m_nBitrate << endl; //cout << "m_nFrequency: " << m_nFrequency << endl; //cout << "m_nPadding: " << m_nPadding << endl; m_nSize = (MPEG1 == m_eVersion ? 144*m_nBitrate/m_nFrequency + m_nPadding : 72*m_nBitrate/m_nFrequency + m_nPadding); //MP3_CHECK (MPEG1 == m_eVersion, m_pos, "Temporary test for MPEG2. Remove after making sure the code works.", NotMpegFrame()/*"temporary"*/); // see http://www.codeproject.com/KB/audio-video/mpegaudioinfo.aspx break; default: CB_ASSERT(false); // it should have thrown before getting here } } MpegFrameBase::MpegFrameBase() : m_eVersion(MPEG1), m_eLayer(LAYER1), m_nBitrate(-1), m_nFrequency(-1), m_nPadding(-1), m_eChannelMode(SINGLE_CHANNEL), m_nSize(-1), m_bCrc(false) {} const char* MpegFrameBase::getSzVersion() const { static const char* s_versionName[] = { QT_TRANSLATE_NOOP("DataStream", "MPEG-1"), QT_TRANSLATE_NOOP("DataStream", "MPEG-2") }; return s_versionName[m_eVersion]; } const char* MpegFrameBase::getSzLayer() const { static const char* s_layerName[] = { QT_TRANSLATE_NOOP("DataStream", "Layer I"), QT_TRANSLATE_NOOP("DataStream", "Layer II"), QT_TRANSLATE_NOOP("DataStream", "Layer III") }; return s_layerName[m_eLayer]; } const char* MpegFrameBase::getSzChannelMode() const { static const char* s_channelModeName[] = { QT_TRANSLATE_NOOP("DataStream", "Stereo"), QT_TRANSLATE_NOOP("DataStream", "Joint stereo"), QT_TRANSLATE_NOOP("DataStream", "Dual channel"), QT_TRANSLATE_NOOP("DataStream", "Single channel") }; return s_channelModeName[m_eChannelMode]; } ostream& MpegFrameBase::write(ostream& out) const { out << convStr(DataStream::tr(getSzVersion())) << " " << convStr(DataStream::tr(getSzLayer())) << ", " << convStr(DataStream::tr(getSzChannelMode())) << ", " << m_nFrequency << "Hz, " << m_nBitrate << "bps, CRC=" << convStr(GlobalTranslHlp::tr(boolAsYesNo(m_bCrc))) << ", " << convStr(DataStream::tr("length=")) << m_nSize << " (0x" << hex << m_nSize << dec << "), " << convStr(DataStream::tr("padding=")) << convStr(GlobalTranslHlp::tr(boolAsYesNo(m_nPadding))); return out; } ostream& operator<<(ostream& out, const MpegFrameBase& frm) { return frm.write(out); } int MpegFrameBase::getSideInfoSize() const // needed by Xing { return MpegFrame::MPEG1 == getVersion() ? (MpegFrame::SINGLE_CHANNEL == getChannelMode() ? 17 : 32) : (MpegFrame::SINGLE_CHANNEL == getChannelMode() ? 9 : 17); } MpegFrame::MpegFrame(NoteColl& notes, std::istream& in) : MpegFrameBase(notes, in) { streampos pos (m_pos); StreamStateRestorer rst (in); pos += m_nSize - 1; in.seekg(pos); char bfr [1]; if (1 != read(in, bfr, 1)) { ostringstream out; write(out); MP3_THROW_T (pos, "Not an MPEG frame. File too short.", PrematurelyEndedMpegFrame(out.str())); } rst.setOk(); } MP3Diags-1.2.02/src/Mp3TransformThread.h0000644000175000001440000000733111720007534016575 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef Mp3TransformThreadH #define Mp3TransformThreadH #include #include #include #include #include #include // for translation class Mp3Handler; class QWidget; class Transformation; class CommonData; class TransfConfig; typedef QList StrList; class Mp3Transformer { Q_DECLARE_TR_FUNCTIONS(Mp3Transformer) public: CommonData* m_pCommonData; const TransfConfig& m_transfConfig; //bool m_bAll; // if to use all handlers or only the selected ones const std::deque& m_vpHndlr; std::vector& m_vpDel; std::vector& m_vpAdd; // for proc files that are in the same directory as the source and have a different name std::vector& m_vpTransf; std::string m_strErrorFile; // normally this is empty; if it's not, writing to the specified file failed std::string m_strErrorDir; // normally this is empty; if it's not, creating the specified backup file failed bool m_bWriteError; bool m_bFileChanged; std::ostream* m_pLog; virtual bool isAborted() = 0; virtual void checkPause() = 0; virtual void emitStepChanged(const StrList& v, int nStep) = 0; public: Mp3Transformer( CommonData* pCommonData, const TransfConfig& transfConfig, const std::deque& vpHndlr, std::vector& vpDel, std::vector& vpAdd, std::vector& vpTransf, std::ostream* pLog) : m_pCommonData(pCommonData), m_transfConfig(transfConfig), m_vpHndlr(vpHndlr), m_vpDel(vpDel), m_vpAdd(vpAdd), m_vpTransf(vpTransf), m_bWriteError(true), m_bFileChanged(false), m_pLog(pLog) { } bool transform(); // returns on the first error std::string getError() const; }; // if there are errors the user is notified; // returns true iff there were no errors; bool transform(const std::deque& vpHndlr, std::vector& vpTransf, const std::string& strTitle, QWidget* pParent, CommonData* pCommonData, TransfConfig& transfConfig); #endif MP3Diags-1.2.02/src/LogModel.cpp0000644000175000001440000001065711724340425015157 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include "LogModel.h" #include "CommonData.h" using namespace std; LogModel::LogModel(CommonData* pCommonData, QTableView* pLogG) : QAbstractTableModel(pLogG), m_pCommonData(pCommonData), m_pLogG(pLogG) { } /*override*/ int LogModel::rowCount(const QModelIndex&) const { //qDebug("size %d", cSize(m_pCommonData->getLog())); return cSize(m_pCommonData->getLog()); } /*override*/ int LogModel::columnCount(const QModelIndex&) const { return 1; } /*override*/ QVariant LogModel::data(const QModelIndex& index, int nRole) const { LAST_STEP("LogModel::data()"); if (!index.isValid()) { return QVariant(); } if (nRole == Qt::ToolTipRole) { QString s (convStr(m_pCommonData->getLog()[index.row()])); //QFontMetrics fm (QApplication::fontMetrics()); QFontMetrics fm (m_pLogG->fontMetrics()); // !!! no need to use "QApplication::fontMetrics()" int nWidth (fm.width(s)); if (nWidth + 10 < m_pLogG->horizontalHeader()->sectionSize(0)) // ttt2 "10" is hard-coded { //return QVariant(); return ""; // !!! with "return QVariant()" the previous tooltip remains until the cursor moves over another cell that has a tooltip }//*/ return s; } if (nRole != Qt::DisplayRole) { return QVariant(); } return convStr(m_pCommonData->getLog()[index.row()]); } /*override*/ QVariant LogModel::headerData(int nSection, Qt::Orientation eOrientation, int nRole /* = Qt::DisplayRole*/) const { LAST_STEP("LogModel::headerData"); #if 0 if (nRole == Qt::SizeHintRole) { /*QVariant v (QAbstractTableModel::headerData(nSection, eOrientation, nRole)); // !!! doesn't work because QAbstractTableModel::headerData always returns an invalid value [...] */ if (eOrientation == Qt::Vertical) { /*QFontMetrics fm (m_pLogG->fontMetrics()); // ttt2 duplicate of FilesModel; move double d (1.01 + log10(double(m_pCommonData->getLog().size()))); int n (d); QString s (n, QChar('9')); //QSize size (fm.boundingRect(r, Qt::AlignTop | Qt::TextWordWrap, s).size()); int nWidth (fm.width(s)); return QSize(nWidth + 10, CELL_HEIGHT); //ttt2 replace CELL_HEIGHT; ttt2 "10" hard-coded*/ getNumVertHdrSize ... } return QVariant(); } #endif if (nRole != Qt::DisplayRole) { return QVariant(); } if (Qt::Horizontal == eOrientation) { switch (nSection) { case 0: return tr("Message"); default: CB_ASSERT (false); } } //return ""; return nSection + 1; } /* void LogModel::selectTopLeft() { QItemSelectionModel* pSelModel (m_pLogG->selectionModel()); pSelModel->clear(); emit layoutChanged(); if (!m_pCommonData->getLog().empty()) { m_pLogG->setCurrentIndex(index(0, 0)); } } */ MP3Diags-1.2.02/src/AboutDlgImpl.h0000644000175000001440000000361111230111215015416 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef AboutDlgImplH #define AboutDlgImplH #include #include "ui_About.h" class AboutDlgImpl : public QDialog, private Ui::AboutDlg { Q_OBJECT void initText(QTextBrowser* p, const char* szFileName); public: AboutDlgImpl(QWidget* pParent = 0); ~AboutDlgImpl(); protected slots: void on_m_pOkB_clicked() { accept(); } void onHelp(); }; #endif MP3Diags-1.2.02/src/AlbumInfoDownloaderDlgImpl.h0000644000175000001440000002044211717266604020267 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef AlbumInfoDownloaderDlgImplH #define AlbumInfoDownloaderDlgImplH #include #include #include #include "ui_AlbumInfoDownloader.h" #include "CommonTypes.h" class QHttp; class QPixmap; class SessionSettings; struct WebAlbumInfoBase { WebAlbumInfoBase() : m_eVarArtists(AlbumInfo::VA_NOT_SUPP) {} virtual ~WebAlbumInfoBase() {} std::string m_strTitle; std::string m_strArtist; std::string m_strReleased; std::vector m_vTracks; // make sure they come in proper order std::vector m_vstrImageNames; // for Discogs - IDs; for MusicBrainz - full URLs std::string m_strFormat; // CD, tape, ... std::vector m_vpImages; // doesn't own the pointers; initially it contains 0s; this isn't used by MainFormDlgImpl, only internally by AlbumInfoDownloaderDlgImpl, which passes to MainFormDlgImpl at most an image, in a separate parameter std::vector m_vstrImageInfo; // size in pixels and bytes AlbumInfo::VarArtists m_eVarArtists; virtual void copyTo(AlbumInfo& dest) = 0; }; class QXmlDefaultHandler; class WebDwnldModel; // at any time there's at most 1 HTTP request pending // //class AlbumInfoDownloaderDlgImpl : public QDialog, private Ui::AlbumInfoDownloaderDlg class AlbumInfoDownloaderDlgImpl : public QDialog, protected Ui::AlbumInfoDownloaderDlg { Q_OBJECT void next(); // meant to be called only by retryNavigation() void previous(); bool m_bSaveResults; int m_nLastCount; time_t m_nLastTime; std::string getTempName(); // time-based, with no extension; doesn't check for existing names, but uses a counter, so files shouldn't get removed (except during daylight saving time changes) void saveDownloadedData(const char*, int nSize, const char* szExt); virtual char getReplacementChar() const = 0; protected: bool m_bSaveImageOnly; // for Discogs: pages have 20 entries (except for the last page), but among the entries there are artists, which should be ignored; // for MusicBrainz there's only 1 page; int m_nTotalPages, m_nLastLoadedPage; std::vector m_vpImages; // owns the pointers int m_nLoadingAlbum, m_nLoadingImage; ImageInfo::Compr m_eLoadingImageCompr; // for Discogs: e.g. http://www.discogs.com/search?type=all&q=beatles&f=xml, without page number; to be used by loadNextPage(); // for MusicBrainz it's a fixed, full, query; std::string m_strQuery; int m_nCrtAlbum, m_nCrtImage; //std::string m_strArtist; //std::string m_strAlbum; enum NavigDir { NONE, NEXT, PREV }; // navigation direction enum Waiting { NOTHING, IMAGE, SEARCH, ALBUM }; NavigDir m_eNavigDir; Waiting m_eWaiting; void setWaiting(Waiting eWaiting); bool m_bNavigateByAlbum; virtual std::string createQuery() = 0; void search(); virtual void loadNextPage() = 0; virtual void requestAlbum(int nAlbum) = 0; virtual void requestImage(int nAlbum, int nImage) = 0; virtual void reloadGui(); void onSearchLoaded(const QString& qstrXml); void onAlbumLoaded(const QString& qstrXml); void onImageLoaded(const QByteArray& comprImg, int nWidth, int nHeight, const QString& qstrInfo); // pPixmap may not be 0 void retryNavigation(); virtual bool initSearch(const std::string& strArtist, const std::string& strAlbum) = 0; static std::string removeParentheses(const std::string& s); void addNote(const QString& qstrNote); void setImageType(const std::string& strName); virtual QHttp* getWaitingHttp() = 0; // the one QHttp object waiting for a reply; virtual void resetNavigation(); // clears pending HTTP requests, m_eNavigDir and m_eWaiting; restores the cursor if needed; virtual WebAlbumInfoBase& album(int i) = 0; virtual int getAlbumCount() const = 0; virtual QXmlDefaultHandler* getSearchXmlHandler() = 0; virtual QXmlDefaultHandler* getAlbumXmlHandler(int nAlbum) = 0; void updateTrackList(); virtual void saveSize() = 0; std::string replaceSymbols(std::string); // replaces everything besides letters and digits with getReplacementChar() WebDwnldModel* m_pModel; SessionSettings& m_settings; protected: QHttp* m_pQHttp; int m_nExpectedTracks; AlbumInfoDownloaderDlgImpl(QWidget* pParent, SessionSettings& settings, bool bSaveResults); static const char* NOT_FOUND_AT_AMAZON; public: ~AlbumInfoDownloaderDlgImpl(); /*$PUBLIC_FUNCTIONS$*/ // pAlbumInfo is non-0 upon exit iff the return is true and the user closed with "Save All"; // pQPixmap is non-0 upon exit iff the return is true and there is a picture; // the pointers must be deallocated by the caller; // pAlbumInfo->m_imageInfo is not set; (the caller manually links the result returned in pImageInfo) // for Discogs: the other fields are set; // for MusicBrainz: pAlbumInfo->m_strComposer and pAlbumInfo->m_strNotes are not set; (they are not available) bool getInfo(const std::string& strArtist, const std::string& strAlbum, int nTrackCount, AlbumInfo*& pAlbumInfo, ImageInfo*& pImageInfo); virtual const WebAlbumInfoBase* getCrtAlbum() const = 0; // returns 0 if there's no album virtual int getColumnCount() const = 0; // really just for composer; 4 if the field exists and 4 if it doesn't; public slots: /*$PUBLIC_SLOTS$*/ protected: /*$PROTECTED_FUNCTIONS$*/ protected slots: /*$PROTECTED_SLOTS$*/ void on_m_pPrevB_clicked(); void on_m_pNextB_clicked(); void on_m_pPrevAlbumB_clicked(); void on_m_pNextAlbumB_clicked(); void on_m_pSaveAllB_clicked(); void on_m_pSaveImageB_clicked(); void on_m_pCancelB_clicked(); //void onDone(bool bError); void onRequestFinished(int nId, bool bError); void onHelp(); private: }; void addIfMissing(std::string& strDest, const std::string& strSrc); // splits a string based on a separator, putting the components in a vector; trims the substrings; discards empty components; void split(const std::string& s, const std::string& sep, std::vector& v); //ttt2 move to Helpers, improve interface ... void addList(std::string& strDest, const std::string& strSrc); class WebDwnldModel : public QAbstractTableModel { Q_OBJECT AlbumInfoDownloaderDlgImpl& m_dwnld; QTableView& m_grid; //typedef Discogs::DiscogsAlbumInfo AlbumInfo; public: WebDwnldModel(AlbumInfoDownloaderDlgImpl&, QTableView&); /*override*/ int rowCount(const QModelIndex&) const; /*override*/ int columnCount(const QModelIndex&) const; /*override*/ QVariant data(const QModelIndex&, int nRole) const; /*override*/ QVariant headerData(int nSection, Qt::Orientation eOrientation, int nRole = Qt::DisplayRole) const; void emitLayoutChanged() { emit layoutChanged(); } }; #endif // #ifndef AlbumInfoDownloaderDlgImplH MP3Diags-1.2.02/src/ExternalToolDlgImpl.h0000644000175000001440000000614611705372311017007 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef ExternalToolDlgImplH #define ExternalToolDlgImplH #include #include #include "ui_ExternalTool.h" class QProcess; class SessionSettings; class CommonData; class ExternalToolDlgImpl : public QDialog, private Ui::ExternalToolDlg { Q_OBJECT QProcess* m_pProc; bool m_bFinished; void addText(QString); int m_nLastKey; //QString m_qstrLastLine; QString m_qstrText; SessionSettings& m_settings; const CommonData* m_pCommonData; // for logging const std::string m_strCommandName; const char* m_szHelpFile; /*override*/ void closeEvent(QCloseEvent* pEvent); // /*override*/ void keyPressEvent(QKeyEvent* pEvent); // /*override*/ void keyReleaseEvent(QKeyEvent* pEvent); public: ExternalToolDlgImpl(QWidget* pParent, bool bKeepOpen, SessionSettings& settings, const CommonData* pCommonData, const std::string& strCommandName, const char* szHelpFile); ~ExternalToolDlgImpl(); void run(const QString& qstrProg, const QStringList& lFiles); // it would make sense to return true if something was changed (or even exactly what changed), but that's not easy to do, so the caller should just assume that something changed static void prepareArgs(const QString& qstrCommand, const QStringList& lFiles, QString& qstrProg, QStringList& args); public slots: /*$PUBLIC_SLOTS$*/ protected: /*$PROTECTED_FUNCTIONS$*/ protected slots: /*$PROTECTED_SLOTS$*/ void onFinished(); void onOutputTxt(); void onErrorTxt(); void on_m_pCloseB_clicked(); void on_m_pAbortB_clicked(); void onHelp(); }; #endif MP3Diags-1.2.02/src/Helpers.h0000644000175000001440000003366112265760162014531 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef HelpersH #define HelpersH #include #include #include #include #include // for exit() #include #include //#include #include // ttt2 what we really want is QString; however, by including QString directly, lots of warnings get displayed; perhaps some defines are needed but don't know which; so we just include QStringList to avoid the warnings void logToGlobalFile(const std::string& s); #define CB_CHECK1(COND, EXCP) { if (!(COND)) { ::trace(#EXCP); throw EXCP; } } //#define CB_THROW(MSG) { throw std::runtime_error(MSG); } #define CB_THROW1(EXCP) { ::trace(#EXCP); throw EXCP; } //#define CB_ASSERT(COND) { if (!(COND)) { ::trace("assert"); throw std::runtime_error("assertion failure"); } } #define CB_ASSERT(COND) { if (!(COND)) { assertBreakpoint(); ::trace("assert"); logAssert(__FILE__, __LINE__, #COND); ::exit(1); } } #define CB_ASSERT1(COND, MSG) { if (!(COND)) { assertBreakpoint(); ::trace("assert"); logAssert(__FILE__, __LINE__, #COND, MSG); ::exit(1); } } ////#include #define CB_LIB_CALL void assertBreakpoint(); /* Protocol for reading from files: - the file is always left in good state, no eof or fail - initializing objects with from a file: - the starting position is not passed, but it is the current position - if an error occurs and the constructor fails, the read pointer is restored (and the error flags are cleared); StreamStateRestorer can be used to do this - on success, the read pointer is left after the last byte that was used; (that may be EOF, but the eof flag should still be clear, because no attempt is made to actually read it after moving the pointer) - relying on a stream's flags should be avoided; (the flags are cleared most of the time anyway); the test should normally be to check if a desired number of bytes could be read; */ // !!! readsome seems to do the job but it only returns whatever is in buffer, without waiting for new data to be brought in. Therefore it is not usable in this case. // !!! doesn't leave the file with fail or eof set; the caller should check the returned value; inline std::streamsize read(std::istream& in, char* bfr, std::streamsize nCount) { in.read(bfr, nCount); std::streamsize nRes (in.gcount()); in.clear(); return nRes; } /* Usage: declare a StreamStateRestorer. Before it goes out of scope, call setOk() if everything went OK. On the destructor it checks if setOk() was called. If it wasn't, the read position is set to whatever was on the constructor. All flags of the stream are cleared in either case. */ class StreamStateRestorer { std::istream& m_in; std::streampos m_pos; bool m_bOk; public: StreamStateRestorer(std::istream& in); ~StreamStateRestorer(); void setOk() { m_bOk = true; } }; // on its destructor restores the value of the variable passed on the constructor unless setOk() gets called template class ValueRestorer { T m_val; T& m_ref; bool m_bRestore; public: ValueRestorer(T& ref) : m_val(ref), m_ref(ref), m_bRestore(true) {} ~ValueRestorer() { if (m_bRestore) { m_ref = m_val; } } void setOk(bool b = true) { m_bRestore = !b; } }; template void CB_LIB_CALL releasePtr(T*& p) { delete p; p = 0; } #define TRACE(A) { std::ostringstream sTrM; sTrM << A; ::trace(sTrM.str()); } void trace(const std::string& s); void logAssert(const char* szFile, int nLine, const char* szCond); void logAssert(const char* szFile, int nLine, const char* szCond, const std::string& strAddtlInfo); namespace pearl { template void CB_LIB_CALL clearPtrContainer(T& c) // calls delete on all the elements of a container of pointers, then sets its size to 0; shouldn't be used on sets { for (typename T::iterator it = c.begin(), end = c.end(); it != end; ++it) { delete *it; } c.clear(); } template void CB_LIB_CALL clearPtrContainer(std::set& c) // specialization for sets { while (!c.empty()) { T* p; p = *c.begin(); //ddd see if it's better to remove from end / middle instead of front c.erase(c.begin()); delete p; } } // to make sure that an array is deallocated even when exceptions are thrown; similar to auto_ptr template class ArrayPtrRelease { T* m_pArray; ArrayPtrRelease(); ArrayPtrRelease(const ArrayPtrRelease&); ArrayPtrRelease& operator=(const ArrayPtrRelease&); public: ArrayPtrRelease(T* pArray) : m_pArray(pArray) {} ~ArrayPtrRelease() { delete[] m_pArray; } T* get() { return m_pArray; } const T* get() const { return m_pArray; } }; } // namespace pearl template int CB_LIB_CALL cSize(const T& c) // returns the size of a container as an int { return (int)c.size(); } struct EndOfFile {}; struct WriteError {}; // throws WriteError or EndOfFile void appendFilePart(std::istream& in, std::ostream& out, std::streampos pos, std::streamoff nSize); // prints to cout the content of a memory location, as ASCII and hex; // GDB has a tendency to not see char arrays and other local variables; actually GCC seems to be the culprit (bug 34767); void inspect(const void* p, int nSize); template void inspect(const T& x) { if (sizeof(x) == 1001) { return; } } // prints the elements of a container template void printContainer(const T& s, std::ostream& out, const std::string& strSep = " ") { for (typename T::const_iterator it = s.begin(); it != s.end(); ++it) { out << *it << strSep; } out << std::endl; } std::string asHex(const char* p, int nSize); inline bool CB_LIB_CALL beginsWith(const std::string& strMain, const std::string& strSubstr) { if (strSubstr.size() > strMain.size()) return false; return strMain.substr(0, strSubstr.size()) == strSubstr; } inline bool CB_LIB_CALL endsWith(const std::string& strMain, const std::string& strSubstr) { if (strSubstr.size() > strMain.size()) return false; // !!! otherwise the next line might incorrectly return true if // strMain.size() = strSubstr.size() - 1 return strMain.rfind(strSubstr) == strMain.size() - strSubstr.size(); } bool CB_LIB_CALL rtrim(std::string& s); // removes whitespaces at the end of the string bool CB_LIB_CALL ltrim(std::string& s); // removes whitespaces at the beginning of the string bool CB_LIB_CALL trim(std::string& s); int get32BitBigEndian(const char*); void put32BitBigEndian(int n, char*); // The reference that is passed in the constructor (the "guard") should be accessible to all parties interested; it should be initialized to "false" first. When a piece of code wants access to resources protected by the guard, it should declare an NonblockingGuard variable, initialize it with the guard and then check if it went OK, by calling "operator()"; if "operator()" returns true, it means the guard was acquired, so it can proceed. // The major point is that it's a non-blocking call. // The intended use is with single-threaded applications (or rather to not share a guard among threads). This is useful to handle the situation where function f() may call g() and g() may call f(), but with the restriction that f() may call g() only if f() wasn't already called by g(). As this isn't designed for multi-threading, it doesn't need any system-specific code or porting. class NonblockingGuard { bool& m_bLock; bool m_bInitialState; public: CB_LIB_CALL NonblockingGuard(bool& bLock) : m_bLock(bLock), m_bInitialState(m_bLock) { m_bLock = true; // doesn't matter if it was true } CB_LIB_CALL ~NonblockingGuard() { m_bLock = m_bInitialState; } operator bool () const { return !m_bInitialState; } }; // takes a Latin1 string and converts it to UTF8 std::string utf8FromLatin1(const std::string&); // the total memory currently used by the current process, in kB long getMemUsage(); std::string decodeMpegFrame(unsigned int n, const char* szSep, bool* pbIsValid = 0); // on error doesn't throw, but returns an error string; szSep is used as separator for the output string std::string decodeMpegFrame(const char* bfr, const char* szSep, bool* pbIsValid = 0); std::string decodeMpegFrameAsXml(const char* bfr, bool* pbIsValid = 0); inline const char* boolAsYesNo(bool b) { return b ? QT_TRANSLATE_NOOP("GlobalTranslHlp", "yes") : QT_TRANSLATE_NOOP("GlobalTranslHlp", "no"); } char getPathSep(); const std::string& getPathSepAsStr(); std::streampos getSize(std::istream& in); // throws WriteError or EndOfFile void writeZeros(std::ostream& out, int nCnt); // needed because gdb has trouble setting breakpoints in template code inline void templateBreakpoint() { qDebug("breakpoint"); } // by including this as a member the compiler generated constructor / copy op. are disabled class NoDefaults { CB_LIB_CALL NoDefaults(); CB_LIB_CALL NoDefaults(const NoDefaults&); NoDefaults& CB_LIB_CALL operator=(NoDefaults&); //ttt2 see if there are other generated operations and include them all public: CB_LIB_CALL NoDefaults(int) {} }; class QWidget; void listWidget(QWidget* p, int nIndent = 0); //ttt2 move this elsewhere std::string escapeHttp(const std::string& s); // replaces invalid HTTP characters like ' ' or '"' with their hex code (%20 or %22) inline QString convStr(const std::string& s) { return QString::fromUtf8(s.c_str(), cSize(s)); } //ttt2 perhaps move inline std::string convStr(const QString& qs) { const QByteArray& b (qs.toUtf8()); return std::string(b.constData(), b.size()); } //ttt2 perhaps move std::vector convStr(const std::vector&); std::vector convStr(const std::vector&); Qt::WindowFlags getMainWndFlags(); // minimize, maximize, no "what's this" Qt::WindowFlags getDialogWndFlags(); // maximize, no "what's this" Qt::WindowFlags getNoResizeWndFlags(); // no "what's this"; the window may be resizable, but the min/max icons aren't shown bool getDefaultForShowCustomCloseButtons(); QString getSystemInfo(); class QGradient; class QColor; // sets colors at various points to emulate a non-linear gradient that better suits our needs; // dStart and dEnd should be between 0 and 1, with dStart < dEnd; they may also be both -1, in which case the gradient will have a solid color void configureGradient(QGradient& grad, const QColor& col, double dStart, double dEnd); // opens a web page from the documentation in the default browser; // first looks in several places on the local computer; if the file can't be found there, it goes to SourceForge void openHelp(const std::string& strFileName); // meant for displaying tooltips; converts some spaces to \n, so the tooltips have several short lines instead of a single wide line QString makeMultiline(const QString& qstrDescr); QString toNativeSeparators(const QString&); QString fromNativeSeparators(const QString&); inline std::string toNativeSeparators(const std::string& s) { return convStr(toNativeSeparators(convStr(s))); } inline std::string fromNativeSeparators(const std::string& s) { return convStr(fromNativeSeparators(convStr(s))); } QString getTempDir(); struct ShellIntegration { static bool isShellIntegrationEditable(); static std::string getShellIntegrationError(); static void enableTempSession(bool); static bool isTempSessionEnabled(); static void enableVisibleSession(bool); static bool isVisibleSessionEnabled(); static void enableHiddenSession(bool); static bool isHiddenSessionEnabled(); }; //====================================================================================================== //====================================================================================================== //====================================================================================================== void traceToFile(const std::string& s, int nLevelChange); struct Tracer { const std::string m_s; Tracer(const std::string& s); ~Tracer(); }; #define TRACER(X) Tracer FiLeTrAcEr (X); #define TRACER1(X, N) Tracer FiLeTrAcEr##N (X); #define TRACER1A(X, N) Tracer FiLeTrAcEr##N (X#N); void traceLastStep(const std::string& s, int nLevelChange); struct LastStepTracer { const std::string m_s; LastStepTracer(const std::string& s); ~LastStepTracer(); }; #define LAST_STEP(X) LastStepTracer LaStStEp (X); #define LAST_STEP1(X, N) LastStepTracer LaStStEp##N (X); #endif // ifndef HelpersH MP3Diags-1.2.02/src/ImageInfoPanelWdgImpl.h0000644000175000001440000000466011232274740017221 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef ImageInfoPanelWdgImplH #define ImageInfoPanelWdgImplH #include #include "ui_ImageInfoPanel.h" #include "TagWriter.h" #include "CommonTypes.h" class ImageInfoPanelWdgImpl : public QFrame, private Ui::ImageInfoPanelWdg { Q_OBJECT const TagWrtImageInfo m_tagWrtImageInfo; int m_nPos; public: ImageInfoPanelWdgImpl(QWidget* pParent, const TagWrtImageInfo& tagWrtImageInfo, int nPos); ~ImageInfoPanelWdgImpl(); /*$PUBLIC_FUNCTIONS$*/ void setNormalBackground(); void setHighlightBackground(); public slots: /*$PUBLIC_SLOTS$*/ protected: /*$PROTECTED_FUNCTIONS$*/ protected slots: /*$PROTECTED_SLOTS$*/ void on_m_pFullB_clicked(); void on_m_pAssignB_clicked() { emit assignImage(m_nPos); } void on_m_pEraseB_clicked() { emit eraseFile(m_nPos); } signals: void assignImage(int); void eraseFile(int); }; #endif // #ifndef ImageInfoPanelWdgImplH MP3Diags-1.2.02/src/images/0000755000175000001440000000000011713555516014214 5ustar ciobiusersMP3Diags-1.2.02/src/images/remove_file.png0000644000175000001440000000221210704447121017202 0ustar ciobiusers‰PNG  IHDRÄ´l;sBIT|dˆ pHYsaaÁ0UútEXtSoftwarewww.inkscape.org›î<IDAT8µ•moUÇçÞ™-KÛ](´Àv·­ ˜ 6Rí • hh´ ‰~>ÐZ­€¢F}Ù¨$äAjŠ Ñ^”@LxØÒÚ.ívv»»s}±í¶X ‰‰ÿäÎMî=÷7çÌ9÷Œcø?d=Ë ±JZÄ" "ˆÊø–ÕäÝyÚ9YÉã‘ì3èUUÕ¬kk±jc€¢p„üè~29¨D÷mšp{1&ÿLð½lDÛ§ô¦hÓª¶ýXÏmâCæ'!ç6Þ…³ÆÇ‚óaÕÇ©Áë¤|6ï\)Ûýv#[žÇŸ›£lã¦l)8;9…Ò sû™ß~½fºµöæðØK-¾B,OVŸ 6¿Ö˜_³–©ÄÙpSƒƒø)ßu‹sÊ%5twbŠÉÛ òaœí;Ö©¥—À£ÕáƒV¼~o¡&Šë¦ÙòÍ·ÄÚÛQ/½Bò¯kø©&•bææßxÚ¦áx/›¿úw2Ia]5:oyù…}ËÀƶ?¶êðÝiʶ¾ˆ ˆiGoßÁ£¡ëÌܺE&TÎæŸ#Z£‡àÖ­¦]¬Xp:'êjê%iBÇ&wé"÷Ž-…ë<еóU²kÖ±ù‹/Aëb¢»Ž‘ÿíœP(•Íc-;[`¾Ž-'°_´ÆŸžÛbu¤÷ì…ñ®î"¼«|¿†;;˜»ô3±(~: ¾h±T ¸Z¼ –4ùù±Q$´  Q©Ä=ÓÏÝBºO{æã+Bï>LnàÑ ˜ÙYL6ƒÉfA9HÉc,¥@ärønªÉåÈ?x…B)übB &›)Ú-”¢e(Œ¨E°œdÑ@”bv:…~s7 Ç{Kž.¨®§‡„V¤~ image/svg+xml image/svg+xml MP3Diags-1.2.02/src/images/edit-undo.svg0000644000175000001440000010607310737543470016635 0ustar ciobiusers image/svg+xml MP3Diags-1.2.02/src/images/rename.svg0000644000175000001440000020122710737543475016216 0ustar ciobiusers MP3Diags-1.2.02/src/images/arrow-right-double.svg0000644000175000001440000012346610737543467020475 0ustar ciobiusers image/svg+xml MP3Diags-1.2.02/src/images/mode_file.svg0000644000175000001440000011414510737543502016663 0ustar ciobiusers image/svg+xml MP3Diags-1.2.02/src/images/arrow-right.svg0000644000175000001440000010505610737543470017212 0ustar ciobiusers image/svg+xml MP3Diags-1.2.02/src/images/assgn-all.svg0000644000175000001440000000677711163114125016621 0ustar ciobiusers image/svg+xml MP3Diags-1.2.02/src/images/patterns.svg0000644000175000001440000002046511207732740016576 0ustar ciobiusers image/svg+xml MP3Diags-1.2.02/src/images/about.svg0000644000175000001440000003731610737543470016062 0ustar ciobiusers image/svg+xml MP3Diags-1.2.02/src/images/go-previous.svg0000644000175000001440000010673410737543467017236 0ustar ciobiusers image/svg+xml MP3Diags-1.2.02/src/images/save_notes.svg0000644000175000001440000012547611102272707017110 0ustar ciobiusers image/svg+xml MP3Diags-1.2.02/src/images/mode_album.svg0000644000175000001440000027177111074307216017050 0ustar ciobiusers image/svg+xml MP3Diags-1.2.02/src/images/file-rename.svg0000644000175000001440000021047111165071421017113 0ustar ciobiusers image/svg+xml Oxygen team image/svg+xml MP3Diags-1.2.02/src/images/test.svg0000644000175000001440000003600511074465356015722 0ustar ciobiusers image/svg+xml image/svg+xml Thin Thllk Now MP3Diags-1.2.02/src/images/debug.svg0000644000175000001440000037016711162224611016024 0ustar ciobiusers image/svg+xml MP3Diags-1.2.02/src/images/musicbrainz_logo1.svg0000644000175000001440000011377511163125541020370 0ustar ciobiusers image/svg+xml MP3Diags-1.2.02/src/images/transform3.svg0000644000175000001440000021044311207732764017037 0ustar ciobiusers image/svg+xml image/svg+xml MP3Diags-1.2.02/src/images/reset_settings.svg0000644000175000001440000011000211075400475017763 0ustar ciobiusers image/svg+xml MP3Diags-1.2.02/src/images/edit-copy.svg0000644000175000001440000006741211151311360016623 0ustar ciobiusers image/svg+xml MP3Diags-1.2.02/src/images/dialog-close.svg0000644000175000001440000001441311165066534017300 0ustar ciobiusers image/svg+xml MP3Diags-1.2.02/src/images/save_log.svg0000644000175000001440000075001511102347372016533 0ustar ciobiusers MP3Diags-1.2.02/src/images/filter-folder.svg0000644000175000001440000013663311074311505017473 0ustar ciobiusers image/svg+xml Oxygen team MP3Diags-1.2.02/src/images/export.svg0000644000175000001440000001621511260104701016242 0ustar ciobiusers image/svg+xml MP3Diags-1.2.02/src/images/session.svg0000644000175000001440000006522511170306677016431 0ustar ciobiusers MP3Diags-1.2.02/src/images/edit-paste.svg0000644000175000001440000025704111151311737016774 0ustar ciobiusers image/svg+xml MP3Diags-1.2.02/src/images/go-next.svg0000644000175000001440000010154610737543470016326 0ustar ciobiusers image/svg+xml MP3Diags-1.2.02/src/images/palette.svg0000644000175000001440000005627411206455433016403 0ustar ciobiusers image/svg+xml MP3Diags-1.2.02/src/images/decode.svg0000644000175000001440000001367011074457531016165 0ustar ciobiusers image/svg+xml image/svg+xml 00011 10101 MP3Diags-1.2.02/src/images/sort_asc.svg0000644000175000001440000000431311163115364016543 0ustar ciobiusers image/svg+xml MP3Diags-1.2.02/src/images/tag_editor.svg0000644000175000001440000006644011162224767017067 0ustar ciobiusers image/svg+xml Oxygen team MP3Diags-1.2.02/src/images/normalize.svg0000644000175000001440000000702511162374370016734 0ustar ciobiusers image/svg+xml MP3Diags-1.2.02/src/images/configure.svg0000644000175000001440000020426511074212714016715 0ustar ciobiusers image/svg+xml MP3Diags-1.2.02/src/images/discogs.png0000644000175000001440000000633111163131036016342 0ustar ciobiusers‰PNG  IHDR‡<LꩼbKGDÿÿÿ ½§“ pHYs  šœtIMEÙ 2äÄÂ)tEXtCommentCreated with The GIMPïd%n =IDATxÚí\AhWþ¦äà@MäÛÌ% #ÃZ†Ò‘íÍ!uÙ Uðb+lÙÆØèì@j»¥X”2¶¥>ìƒpqº´Dk¢º9d™¤‡ŒJÀRÀ‚\f`JðÁ¾Íä7™Í{óF–g; ‘£Ñ̼÷¾÷ÿßÿ¿÷!B„"Dˆ!B„"Dˆ!B„"Dˆ¨ê’Åûƒ‰‰ÓˆÇ%H’$ðœ¯iºÕjéÿ¿drXˆ†àˆ“#‘ø“Õhì†úa"1ˆ¹¹3Èf§˜ƒL»v:=†J¥‘ããnØhì¢P¸…dò<Òéœe†¯õ9õòAÛCüÑ>ÚcÖþL¾K§sÖ¡ÉáD¥²…S§æP«íXÑ] yxp`­/¤Á¡P¸àP©lÙçãqdöoo?Íý4»Èç¯F#ð{r+##'Q©…J¥(ll\G:=Æ´ aÄm„£caNN&‡Ã0¬S§æ¨DÓê0 Ãâf^GxõÕë×ÖBçgï¿ÑŠ_T¡ªKVÛGÑÂ×(JJ€r¹héúsߘœsÔÓ0 ko¯†z½‰å凾ç(Ê(ΟÇ'ñÿ21º"‡¦é–¢,0Tñ¹ñÓÝ@êzºþšVwu~˜‹aV³i`só4­Žíí§®èéòåw‘Je˜×"ϲ¸¸‚jµÊõü²,czzçÏ¿áá ¡›ö:Ûªëeßß ¥|'—³ÝÄ’“k¦R'Bå—º"‡aÖ… 8•-í7étÎ"ãÄÜÜ ðŠÅ°ºz¬œK"1ˆo¿ý+2™µµÚŽ•Ï_¥>ϵÊå¢å%…(ŠÌëI’äú{zz¹ÜGT–ËEëÊ•Ÿ¨íM$™}á´ÐdÒªêµÀv«ê9æsu¥9œfÙ{±½ýÔÖ´Çi€E6oT”É|wàÚ:­“¦éÖää%ð$õȵÊå¢EBˆŸÉ䘤ð¡}Þ¸m•`qqëëk–Ö=ËÛ÷ü.ðùXÄöºz–5w‡´·¸´a×yEe6ªÕÚãN‚©ê’ÅC o½‘Q­¶cÍÎ~†°ÙÞ+W~òXÄ"ûë †¢Œ¢P˜G*•®?‡(ŽÃ0 LOÿårÑr>g1 \.25 -²¼p¡p¸P¶[<{öŒë¼Zm‡Ú°DbÐ>h‘‘ùüU°Ü£_N®].©Ä0M3 ¦ùšVG,ƒ$IB*u¶0¦G&“³ ”bµÙ‰X,T‘t­Je‹™¸ì9üÛüP*Ýfêû÷W17w†Ú8Òᚦûj§ªTŠ‚AææÎ`qq…i1¼‘$ ’$AÇ!Šã˜žž¢Œ¢ÙüªºäŠÐdypñâ"vv6-?·àÔ×±±qªzŽÙwCCÇ¡i:Õêªê9Ü¿¿Šï¿ÿõ~´þï+9üÂWžÙï°L&'H’$ y–|#÷ÙܼGµ…¼ý™è2@##'¡iõÀhÄ«;ˆÕ0ÍGŽûä…T*M«c}}Í¥CdY†išÈf?¦ºätz …B^H&‡…drXÈå>êz’kI’$(JJ M0Zÿ÷•½ÆåËïB–÷]®&‘1߬FóKB@¯vªVïÞß4Mª{qŠáfÓ°ŸÇIjµ M«cdädÇlö’¦Ù4zÖÙìÒ鱎þëy´À”~Á;Ø™LNð[ù•$IÐ4ÝšýŒKÕ“YECÓêƒî&Iü`À›0 o¾ù¦§G‘ÍN¡Tºm?·(ŽÃ4¹,‹aEÕê,,|šô#uX8#FgvÛ?ú,ö–ýÆööÓŽv±\Jg”•ruÒÊŠê¹D±‰læ…ØÖôÿ„«u—Å Z¤Z½Óöš¦i[àE¨ßË ×hì¢Xü‡É ¿r·Bó¿Æ.fg?ëÉ6€¡¡ã$I’ ëåPVcáânobæÃ}û¸öU×¾ªÂ4¡PÈ»¢"\ Ãè}ƒÒümK1Ã}g¯œ§™ìÏ範¥7XyVèGŒ¯¿Ø§~ÿŸU±pqÀNä†ÁÔ2~ß±Ú_ŒîAQRK;\¹òS×9–ƒÝ¸Je ““—ú²‘È©Xe_±ÇOÚ¿õ‡Îãñàë/ö±p±Šju€š,󆯽Ø)Ǻ†3£üÚ‘ƒ„­Aþsrò4Mï)Ah×AàTÝE ?f>܇(6C‰n29 P’h,æ#¿ŒòkAVëAzE’Œ  "f>Üï Æã'° ãýŽdE½a0O8Ü­õ š`ÝäÈä9nÜPÙßk‚?ÿº„Ÿ]rÄ}#C·,,AúFްa™$IÂÆÆõ׆ ¼“Çïä™næeƒ¶<Ð-Aº&GPz<¬ò&‰š }ªN‚Ð’:< mÄEÑuÀÚ¶+qº1xµL¿´&C4ÁŽ\ú<™nÜP¹âMò„…39å$ƒ¥rÌ&Í<~Ò&ËÚÜ®„ä9HŒw%›ÇÅð„–Uî;9“ú•$‰‹ ««w•L"k^RW'>ýrÀEïAÂÙR9Æyïö=ƒRèý$H£±Ë´}!G"1Ø‘•ì† A>”4Ž¥oXï\¡õ#!†,ŸE©Ã{¡§Ú?ýrS³"·Õå³>I-ý¥DU¯õÞre%{µ;ˆ ››÷õ Ù•æE&“œVƒX ¢ˆe€……O Šã˜š±öã€ë˜ÿ»Œ•ïöCG&¬>|™¡¡/ oŠ2ŠJ…ï\¿Ô®wgõÒÒçØÞ¦ï mgYù‹ÁIå<þ>Ú‘‹¶™oâ‘kñŒD_óPU T¾ãÈaÄ»pg"e´c3µWäiX,†ZmÇ*•nwD‡ÎMÓí07ç»)ˆeY»")l¢abâ4 ¾k‘ý›nËó°C¤¦Ó9«ÑØbZ+¿ïý£ª×°½ý##ösˆ.…,·OOÏ ›Â³gÏlì¶.Í’ð÷Ÿ,Ÿå²<º­TºÕÕ»X]íœÎ Öž°áöévåVt½ÌÜ«K¡®×h캲—áA¢Ô»Žìˆoo ¨C–ÏÚ.Á¹AGÇ¡iu G<.AQF©ë1Ae /ˆ!CQF}뎉ÀæÑuÞþk4víº• ÆZ— m9H­kÂè ¿ßhìâÂ…‚ír–—RYOÌi6;E­yY]½ U]²R©®kµÏ}й¹30Íö.-Y–]dK¥NØ!³¦Õ:äQ蜆(Š(•¾ÁÂÂ?©“$ÎYäÞ““—ºví¤Í§±¹yJ<–,j"/i!ÌcUÚû@Ñ|9—UZI²¥¼÷ *Ó¤ ¥ÓcXYù²ÙQ­V‘Nç|õ<%%”ËEkyùaÇ®./‚i¹\D&“‚ž“7"¼­ÖXu:A…QµÚMjÜ<&¿P¸…Je •Êó!––>ïJMÓRæÄDò†¢Aë tA;Šáá ¡Túæ`Ÿç(Ê(Z-ÝÎ(ʨK ú5‰T‹AˆÁóœ¼hµöL AKöô0ö³4²'yŽtz ×»~ÇÏšŠ_ÃüÊy’gÞëʹ9‹õõ5èúslnÞÃòòC—ÏfÄOc¬¯ÿ»ãYyž“Vkãf¶™\;¨žùX»‚nTÌ“L&‡ÞúÖv£æ©ú’$ I$}­µ·Êþ0ý½Í“d{{5ܼù›=Ãâ”ÔɦR'_ëÐM>ÉÏAÚÏw‹†>Þ ÕÏôú¾¬|R/—("rô‘,Ýéµö+,._~·ÃÅý”Oâ]¢è‘[yð¾É/Rc‰Ê—õ‚ßÈr¼xÅb˜h1‘Ä*µ„±—x#ªWa9º[ªO§Çú.B#·òŠA–ÙƒrA9™ˆ¿ƒ0¹Ù4Ðjé¹°y‰^þ\®KDÇ(IEND®B`‚MP3Diags-1.2.02/src/images/transform.svg0000644000175000001440000024003011106025171016730 0ustar ciobiusers image/svg+xml image/svg+xml MP3Diags-1.2.02/src/images/COPYING-GPL.V3.txt0000644000175000001440000010360611107243727016775 0ustar ciobiusersGNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright © 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. 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 them 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 prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. 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. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. “This License†refers to version 3 of the GNU General Public License. “Copyright†also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. “The Program†refers to any copyrightable work licensed under this License. Each licensee is addressed as “youâ€. “Licensees†and “recipients†may be individuals or organizations. To “modify†a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a “modified version†of the earlier work or a work “based on†the earlier work. A “covered work†means either the unmodified Program or a work based on the Program. To “propagate†a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To “convey†a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays “Appropriate Legal Notices†to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The “source code†for a work means the preferred form of the work for making modifications to it. “Object code†means any non-source form of a work. A “Standard Interface†means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The “System Libraries†of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A “Major Componentâ€, in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The “Corresponding Source†for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey 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; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: * a) The work must carry prominent notices stating that you modified it, and giving a relevant date. * b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to “keep intact all noticesâ€. * c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. * d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an “aggregate†if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: * a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. * b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. * c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. * d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. * e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A “User Product†is either (1) a “consumer productâ€, which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, “normally used†refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. “Installation Information†for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. “Additional permissions†are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: * a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or * b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or * c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or * d) Limiting the use for publicity purposes of names of licensors or authors of the material; or * e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or * f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered “further restrictions†within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An “entity transaction†is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A “contributor†is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's “contributor versionâ€. A contributor's “essential patent claims†are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, “control†includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a “patent license†is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To “grant†such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. “Knowingly relying†means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is “discriminatory†if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If 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 convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU 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 that a certain numbered version of the GNU General Public License “or any later version†applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. 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. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 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. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. 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 state the exclusion of warranty; and each file should have at least the “copyright†line and a pointer to where the full notice is found. Copyright (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 3 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 . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program 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, your program's commands might be different; for a GUI interface, you would use an “about boxâ€. You should also get your employer (if you work as a programmer) or school, if any, to sign a “copyright disclaimer†for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU 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. But first, please read .MP3Diags-1.2.02/src/images/preferences-desktop-locale.svg0000644000175000001440000054346211713555516022160 0ustar ciobiusers MP3Diags-1.2.02/src/images/arrow-left-double.svg0000644000175000001440000011352610737543470020300 0ustar ciobiusers image/svg+xml MP3Diags-1.2.02/src/images/zoom-in.png0000644000175000001440000000216710704447121016307 0ustar ciobiusers‰PNG  IHDRÄ´l;sBIT|dˆ pHYsvv}Õ‚ÌtEXtSoftwarewww.inkscape.org›î<ôIDAT8­”mL[UÇŸçöÞö¶·ôèI°eÄöIYÐP]ÆHˆÉbldÃ@‡š(¾.Nqˆq.!Fctì A³%ĘÌ6`:ˆNÒ8êXp£\(½í½=çñàËBñÿÉ“œœsòËÿ<çœ?¤«ööW3?îÜjOºB"‚?«««û‰=eeÝ'ß‘áð-«Ë¿.-Eš˜˜x¹«ë«ðô;Mg>8;TTTä'Nªd‘uY6'‰×õ¤)®%,V«ì¾1=Ýlmi%"¾XÜœzû‘œœšDéî¾½û~pdØíˆà%ˆæ£Q M—<ÛÓó®žÛ ,Oœ8êv¹j]n着ߨÅ å“Kó…§G~.%@»ÃéNTU¸,[msíÜùLkkðàß‚óówÎòù¢EÅ¥—SŒRºÁï' ~ëÓKóúסûFÒà‘x’ÝÖS >\Z>a³)Ñ’’’÷¶#¢ˆ<ÞLß²dUbàâvȶHˆV3""ä™Ü…y¢Ù¶âtº–,K"Ji{¼ånÃÐE—Ç«šÌ?|êÚA«„Üf@–‹Âóg®?’Ð ´ ¿P1êôxS€ Ø%noê8_•DQ’‘Œ€   J¢Í‚hЄŒHE‰€€8c–´ŽgfffM¢hÄWc^‹Æç_ª˜°H‚ãžPq" ÐÓTv+iðe€˜ÆÉDÜÎ9_M$ói{LD÷c‹+jÔ–b)—ª±¤ª±d\gF X58ÄuflÌsføVÔ%k,û…ˆbiÁãcc¯ÿ8sCA]-⌔µYÓ¹ é\à äµ9žI5;™s]¸xáµtP€õ2<<üaYYy£ï»ÉÇöVVíÖ@¹§j)½÷Øž€ª¥H2Ç q{èæ÷nY–õ#õõY["â'O¾yt|üÊÅo'Ǭ»“é¹fóš–é2ëY)5â¸~íªcrrâ¦Ó‘!×ÔÔœííí}+ø/!„ˆ¦ÚÚÚ§Ÿ|êP{v¶o‡ÓåÊdŒñÅÅèÂÂÂÝȹÏ?ë¾2>þ…ßïÿ²¸¸øñ¶¶6}¿¥¥¥L³Í"oý$>I’*$I*€¬ ëëæºººóMMM<S__ßG°öþ`ü—¬]‡K C4;;KýýýC•••Ò¶ÁëpS  < ÑÀÀÀHuuµuÓ ÿ·BD! žs»Ýõ~¿ÂáðWƒƒƒG¶ Þ€wtt åææ.,,¤©©©W¶ÕŠ/¼³³ó|sssàÿâx3ýº »2€IEND®B`‚MP3Diags-1.2.02/src/images/assgn-none.svg0000644000175000001440000000620011163114147016771 0ustar ciobiusers image/svg+xml MP3Diags-1.2.02/src/images/change-case.svg0000644000175000001440000001063311265372005017066 0ustar ciobiusers image/svg+xml MP3Diags-1.2.02/src/images/transform2.svg0000644000175000001440000020745511207732512017036 0ustar ciobiusers image/svg+xml image/svg+xml MP3Diags-1.2.02/src/images/COPYING-LGPL.V3.txt0000644000175000001440000001645411107243736017115 0ustar ciobiusersGNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, “this License†refers to version 3 of the GNU Lesser General Public License, and the “GNU GPL†refers to version 3 of the GNU General Public License. “The Library†refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An “Application†is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A “Combined Work†is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the “Linked Versionâ€. The “Minimal Corresponding Source†for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The “Corresponding Application Code†for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: * a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or * b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: * a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. * b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: * a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. * b) Accompany the Combined Work with a copy of the GNU GPL and this license document. * c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. * d) Do one of the following: o 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. o 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. * e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: * a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. * b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License “or any later version†applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library.MP3Diags-1.2.02/src/images/duplicate_first.svg0000644000175000001440000001315311170303244020103 0ustar ciobiusers image/svg+xml MP3Diags-1.2.02/src/images/scan.svg0000644000175000001440000024603111074212755015662 0ustar ciobiusers image/svg+xml MP3Diags-1.2.02/src/images/assign.png0000644000175000001440000000112510704447121016174 0ustar ciobiusers‰PNG  IHDRÄ´l;sBIT|dˆ pHYsaaÁ0UútEXtSoftwarewww.inkscape.org›î<ÒIDAT8Í•=kTA†ŸwïY7)ì„(A,ü †D‚)V,…TAQ‘X µÐBˆ(ŒK‚"La³‚… þ±³ÐR‘Ý“×"w×»w?1.80xïœç¼sî #Ûtc¤ºBýïÁBÒ¬vüS°˜a–uÞjF{ªúfžnkª|Æd}Ê7åXwt“€q R p­Ð]mï:§ë„LT¡ó'˜¬kN»ÙÂ;Ý×­¶Ðy]!à\:Ï'NšÞÊ6Êkk¼£Ü>â³ ¡t 1U#š<½uÎk©âø4ƒ±êgôXWë y'dªÆiÈvr,ýþÀER½&`ÄÃþÑ Ú  ‚Ò¬°„ØIo(3ìQom Ð’2dxéà yÈÅN mÁ‘ó~Bz¼×_;…vþÛѵ$i È[£ØÅtdb(ÅfX‰ÅRWÍ®ÆC7±IEND®B`‚MP3Diags-1.2.02/src/images/logo.svg0000644000175000001440000002343511206457071015676 0ustar ciobiusers image/svg+xml MP3Diags-1.2.02/src/images/filter-note.svg0000644000175000001440000011524211074310715017160 0ustar ciobiusers image/svg+xml Oxygen team MP3Diags-1.2.02/src/images/undo_settings.svg0000644000175000001440000010607310737543470017632 0ustar ciobiusers image/svg+xml MP3Diags-1.2.02/src/images/mode_all.svg0000644000175000001440000063401511074211623016506 0ustar ciobiusers image/svg+xml ¡ ! " £ $ % ^ & * ( ) - + ctrl Q ctrl W E R T Y U I O P { } A S D F G H J K L : @ ~ | Z X C V B N M < > ? ^ fn ctrl MP3Diags-1.2.02/src/images/arrow-left.svg0000644000175000001440000010505510737543470017026 0ustar ciobiusers image/svg+xml MP3Diags-1.2.02/src/images/va_va.svg0000644000175000001440000001557711257112456016043 0ustar ciobiusers image/svg+xml MP3Diags-1.2.02/src/images/va_sa.svg0000644000175000001440000001014711257112602016015 0ustar ciobiusers image/svg+xml MP3Diags-1.2.02/src/images/document-save.svg0000644000175000001440000003066211102267310017476 0ustar ciobiusers image/svg+xml Oxygen team MP3Diags-1.2.02/src/images/transform1.svg0000644000175000001440000020602711207732453017033 0ustar ciobiusers image/svg+xml image/svg+xml MP3Diags-1.2.02/src/images/assgn-some.svg0000644000175000001440000000700011163113660016773 0ustar ciobiusers image/svg+xml MP3Diags-1.2.02/src/LyricsStream.cpp0000644000175000001440000003126011724456473016102 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include "LyricsStream.h" #include "Helpers.h" #include "Widgets.h" using namespace std; namespace { const char* LYR_BEGIN ("LYRICSBEGIN"); const int LYR_BEGIN_SIZE (strlen(LYR_BEGIN)); const char* LYR_END ("LYRICS200"); const int LYR_END_SIZE (strlen(LYR_END)); } LyricsStream::LyricsStream() { m_bHasTitle = m_bHasArtist = m_bHasGenre = m_bHasImage = m_bHasAlbum = false; } LyricsStream::LyricsStream(int nIndex, NoteColl& notes, std::istream& in, const std::string& strFileName) : DataStream(nIndex), m_pos(in.tellg()), m_strFileName(strFileName) { StreamStateRestorer rst (in); m_bHasTitle = m_bHasArtist = m_bHasGenre = m_bHasImage = m_bHasAlbum = false; //const int BFR_SIZE (256); //char bfr [BFR_SIZE]; vector vcBfr (256); streamoff nRead (read(in, &vcBfr[0], LYR_BEGIN_SIZE + LYR_END_SIZE)); MP3_CHECK_T (nRead >= LYR_BEGIN_SIZE + LYR_END_SIZE && 0 == strncmp(LYR_BEGIN, &vcBfr[0], LYR_BEGIN_SIZE), m_pos, "Invalid Lyrics stream tag. Header not found.", NotLyricsStream()); streampos pos (m_pos); int nTotalSize (LYR_BEGIN_SIZE); pos += LYR_BEGIN_SIZE; for (;;) { in.seekg(pos); vcBfr.resize(8 + 1); streamoff nRead (read(in, &vcBfr[0], 8)); MP3_CHECK (8 == nRead, pos, lyrTooShort, NotLyricsStream()); char* pLast; int nSize; if (isdigit(vcBfr[0])) { // the size, folowed by the end vcBfr.resize(6 + LYR_END_SIZE); nRead = read(in, &vcBfr[8], 6 + LYR_END_SIZE - 8); MP3_CHECK (6 + LYR_END_SIZE - 8 == nRead, pos, lyrTooShort, NotLyricsStream()); char c (vcBfr[6]); vcBfr[6] = 0; nSize = int(strtol(&vcBfr[0], &pLast, 10)); vcBfr[6] = c; MP3_CHECK (pLast == &vcBfr[6], pos, invalidLyr, NotLyricsStream()); MP3_CHECK (nTotalSize == nSize, pos, invalidLyr, NotLyricsStream()); MP3_CHECK (0 == strncmp(LYR_END, &vcBfr[6], LYR_END_SIZE), pos, invalidLyr, NotLyricsStream()); pos += 6 + LYR_END_SIZE; break; } MP3_CHECK (isalnum(vcBfr[0]) && isalnum(vcBfr[1]) && isalnum(vcBfr[2]), pos, invalidLyr, NotLyricsStream()); string strField (&vcBfr[0], &vcBfr[0] + 3); vcBfr[8] = 0; nSize = int(strtol(&vcBfr[3], &pLast, 10)); MP3_CHECK (pLast == &vcBfr[8], pos, invalidLyr, NotLyricsStream()); MP3_CHECK (nSize >= 0 && nSize < 15000000, pos, invalidLyr, NotLyricsStream()); vcBfr.resize(nSize); nRead = read(in, &vcBfr[0], nSize); MP3_CHECK (nSize == nRead, pos, lyrTooShort, NotLyricsStream()); vcBfr.push_back(0); string strVal (convStr(QString::fromLatin1(&vcBfr[0]))); //qDebug("field %s, size %d, pos %x, val %s", strField.c_str(), nSize, int(pos), strVal.c_str()); if ("LYR" == strField) { if (m_strLyrics.empty()) { m_strLyrics = strVal; //QQQ = true; } else { MP3_NOTE (m_pos, duplicateFields); //ttt2 perhaps differentiate between various dup fields m_strOther += convStr(TagReader::tr("Additional %1 field: %2").arg("LYR").arg(convStr(strVal))) + "\n\n"; } } else if ("INF" == strField) { MP3_NOTE (m_pos, infInLyrics); m_strOther += convStr(TagReader::tr("%1 field: %2").arg("INF").arg(convStr(strVal))) + "\n\n"; } else if ("AUT" == strField) { if (m_strAuthor.empty()) { m_strAuthor = strVal; //QQQ = true; } else { MP3_NOTE (m_pos, duplicateFields); m_strOther += convStr(TagReader::tr("Additional %1 field: %2").arg("AUT").arg(convStr(strVal))) + "\n\n"; } } else if ("EAL" == strField) { if (m_strAlbum.empty()) { m_strAlbum = strVal; m_bHasAlbum = true; } else { MP3_NOTE (m_pos, duplicateFields); m_strOther += convStr(TagReader::tr("Additional %1 field: %2").arg("EAL").arg(convStr(strVal))) + "\n\n"; } } else if ("EAR" == strField) { if (m_strArtist.empty()) { m_strArtist = strVal; m_bHasArtist = true; } else { MP3_NOTE (m_pos, duplicateFields); m_strOther += convStr(TagReader::tr("Additional %1 field: %2").arg("EAR").arg(convStr(strVal))) + "\n\n"; } } else if ("ETT" == strField) { if (m_strTitle.empty()) { m_strTitle = strVal; m_bHasTitle = true; } else { MP3_NOTE (m_pos, duplicateFields); m_strOther += convStr(TagReader::tr("Additional %1 field: %2").arg("ETT").arg(convStr(strVal))) + "\n\n"; } } else if ("IMG" == strField) { if (m_strImageFiles.empty()) { m_strImageFiles = strVal; m_bHasImage = true; //MP3_NOTE (m_pos, imgInLyrics); } else { MP3_NOTE (m_pos, duplicateFields); m_strOther += convStr(TagReader::tr("Additional %1 field: %2").arg("IMG").arg(convStr(strVal))) + "\n\n"; } } else if ("GRE" == strField) { if (m_strGenre.empty()) { m_strGenre = strVal; m_bHasGenre = true; } else { MP3_NOTE (m_pos, duplicateFields); m_strOther += convStr(TagReader::tr("Additional %1 field: %2").arg("GRE").arg(convStr(strVal))) + "\n\n"; } } else if ("IND" == strField) { if (m_strInd.empty()) { m_strInd = strVal; } else { MP3_NOTE (m_pos, duplicateFields); m_strOther += convStr(TagReader::tr("Additional %1 field: %2").arg("IND").arg(convStr(strVal))) + "\n\n"; } } else { m_strOther += convStr(TagReader::tr("%1 field: %2").arg(convStr(strField)).arg(convStr(strVal))) + "\n\n"; } nTotalSize += 8 + nSize; pos += 8 + nSize; } in.seekg(pos); m_nSize = pos - m_pos; //m_strOther=string(512, 'a'); m_strOther[391] = 10; // err //m_strOther=string(512, 'a'); m_strOther[303] = 10; // err //m_strOther=string(512, 'a'); m_strOther[302] = 10; // ok //m_strOther=string(511, 'a'); m_strOther[303] = 10; // ok MP3_TRACE (m_pos, "LyricsStream built."); rst.setOk(); } /*override*/ void LyricsStream::copy(std::istream& in, std::ostream& out) { appendFilePart(in, out, m_pos, m_nSize); } /*override*/ std::string LyricsStream::getInfo() const { return ""; } /*override*/ std::string LyricsStream::getTitle(bool* pbFrameExists /* = 0*/) const { if (0 != pbFrameExists) { *pbFrameExists = m_bHasTitle; } return m_strTitle; } /*override*/ std::string LyricsStream::getArtist(bool* pbFrameExists /* = 0*/) const { if (0 != pbFrameExists) { *pbFrameExists = m_bHasArtist; } return m_strArtist; } /*override*/ std::string LyricsStream::getGenre(bool* pbFrameExists /* = 0*/) const { if (0 != pbFrameExists) { *pbFrameExists = m_bHasGenre; } return m_strGenre; } ImageInfo LyricsStream::readImage(const QString& strRelName) const //ttt2 perhaps move to Helpers and use for ID3V2 links as well; keep in mind that in many places link is considered invalid, so they would need updating; { QString qs (QFileInfo(convStr(m_strFileName)).dir().filePath(strRelName)); //qs = convStr(m_strFileName) + getPathSep() + qs; QFile f (qs); if (f.open(QIODevice::ReadOnly)) { CursorOverrider crsOv; int nSize ((int)f.size()); QByteArray comprImg (f.read(nSize)); QImage pic; //ttt1 rename QImage variables as "img" if (pic.loadFromData(comprImg)) { ImageInfo::Compr eCompr; int nWidth, nHeight; if (nSize <= ImageInfo::MAX_IMAGE_SIZE) { nWidth = pic.width(); nHeight = pic.height(); eCompr = qs.endsWith(".png", Qt::CaseInsensitive) ? ImageInfo::PNG : (qs.endsWith(".jpg", Qt::CaseInsensitive) || qs.endsWith(".jpeg", Qt::CaseInsensitive) ? ImageInfo::JPG : ImageInfo::INVALID); //ttt2 add more cases if supporting more image types } else { QImage scaledImg; ImageInfo::compress(pic, scaledImg, comprImg); nWidth = scaledImg.width(); nHeight = scaledImg.height(); eCompr = ImageInfo::JPG; } ImageInfo img (-1, ImageInfo::OK, eCompr, comprImg, nWidth, nHeight); return img; } } return ImageInfo(-1, ImageInfo::ERROR_LOADING); } /*override*/ ImageInfo LyricsStream::getImage(bool* pbFrameExists /* = 0*/) const { if (0 != pbFrameExists) { *pbFrameExists = m_bHasImage; } if (m_bHasImage) { // http://www.id3.org/Lyrics3v2 // http://www.mpx.cz/mp3manager/tags.htm QString qs (convStr(m_strImageFiles)); { int k (qs.indexOf("\r\n")); if (-1 != k) { qs.remove(k, qs.size()); } } return readImage(qs); } return ImageInfo(); } /*override*/ std::vector LyricsStream::getImages() const { vector v; if (m_strImageFiles.empty()) { return v; } QString qs (convStr(m_strImageFiles) + "\r\n"); for (;;) { int k (qs.indexOf("\r\n")); if (-1 == k) { return v; } v.push_back(readImage(qs.left(k))); qs.remove(0, k + 2); } } /*override*/ std::string LyricsStream::getAlbumName(bool* pbFrameExists /* = 0*/) const { if (0 != pbFrameExists) { *pbFrameExists = m_bHasAlbum; } return m_strAlbum; } /*override*/ std::string LyricsStream::getOtherInfo() const { QString s; if (!m_strInd.empty()) { s += TagReader::tr("%1 field: %2").arg("IND").arg(convStr(m_strInd)) + "\n\n"; } if (!m_strImageFiles.empty()) { s += TagReader::tr("%1 field: %2").arg("IMG").arg(convStr(m_strImageFiles)) + "\n\n"; } if (!m_strAuthor.empty()) { s += TagReader::tr("%1 field: %2").arg("AUT").arg(convStr(m_strAuthor)) + "\n\n"; } if (!m_strLyrics.empty()) { s += TagReader::tr("%1 field: %2").arg("LYR").arg(convStr(m_strLyrics)) + "\n\n"; } if (!m_strOther.empty()) { s += convStr(m_strOther) + "\n\n"; } return convStr(s); } #if 1 //... /*override*/ TagReader::SuportLevel LyricsStream::getSupport(Feature eFeature) const { switch (eFeature) { case TITLE: case ARTIST: case GENRE: case IMAGE: case ALBUM: return READ_ONLY; default: return NOT_SUPPORTED; } } #endif //ttt2 perhaps remove \r MP3Diags-1.2.02/src/CheckedDir.h0000644000175000001440000000610411717301643015100 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef CheckedDirH #define CheckedDirH #include #include #include class QTreeView; class CheckedDirModel : public QDirModel { /*override*/ int columnCount(const QModelIndex&) const { return 1; } /*override*/ Qt::ItemFlags flags(const QModelIndex& ndx) const { return QDirModel::flags(ndx) | (m_bUserCheckable ? Qt::ItemIsUserCheckable | Qt::ItemIsTristate : Qt::ItemIsTristate); } /*override*/ QVariant data(const QModelIndex& index, int nRole = Qt::DisplayRole) const; bool setData(const QModelIndex& index, const QVariant& value, int nRole /* = Qt::EditRole*/); // non-reflexive (isDescendant(s, s) is false) // order: "" is above "/"; "/" is above the rest; bool isDescendant(const QString& s1, const QString& s2) const; bool m_bUserCheckable; std::vector m_vCheckedDirs; // strings are not terminated with the path separator, except for the root std::vector m_vUncheckedDirs; void expandNode(const QString& s, QTreeView* pTreeView); public: enum { NOT_USER_CHECKABLE, USER_CHECKABLE }; CheckedDirModel(QObject* pParent, bool bUserCheckable) : QDirModel(pParent), m_bUserCheckable(bUserCheckable) {} void setDirs(const std::vector& vstrCheckedDirs, const std::vector& vstrUncheckedDirs, QTreeView* pTreeView); // besides assigning the vectors, also expands the tree to show them std::vector getCheckedDirs() const; std::vector getUncheckedDirs() const; void expandNodes(QTreeView* pTreeView); }; #endif MP3Diags-1.2.02/src/DirFilterDlgImpl.h0000644000175000001440000000624411720130542016245 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef DirFilterDlgImplH #define DirFilterDlgImplH #include "ui_DirFilter.h" #include "DoubleList.h" #include "Helpers.h" class CommonData; //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== class DirFilterDlgImpl : public QDialog, private Ui::DirFilterDlg, public ListPainter { Q_OBJECT CommonData* m_pCommonData; void logState(const char* szPlace) const; void populateLists(); /*override*/ int getColCount() const { return 1; } /*override*/ std::string getColTitle(int /*nCol*/) const { return convStr(tr("Folder")); } /*override*/ void getColor(int /*nIndex*/, int /*nColumn*/, bool /*bSubList*/, QColor& /*bckgColor*/, QColor& /*penColor*/, double& /*dGradStart*/, double& /*dGradEnd*/) const { } /*override*/ int getColWidth(int /*nCol*/) const { return -1; } // positive values are used for fixed widths, while negative ones are for "stretched" /*override*/ int getHdrHeight() const; /*override*/ std::string getTooltip(TooltipKey eTooltipKey) const; /*override*/ Qt::Alignment getAlignment(int nCol) const; /*override*/ void reset(); public: DirFilterDlgImpl(CommonData* pCommonData, QWidget *pParent = 0); ~DirFilterDlgImpl(); private slots: void on_m_pOkB_clicked(); void on_m_pCancelB_clicked(); void onAvlDoubleClicked(int nRow); void onHelp(); }; #endif // #ifndef DirFilterDlgImplH MP3Diags-1.2.02/src/ColumnResizer.cpp0000644000175000001440000005667111724340044016261 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include #include #include #include "ColumnResizer.h" //#define PRINT_SIZES #ifdef PRINT_SIZES #include "fstream_unicode.h" #endif using namespace std; //====================================================================================================================== //====================================================================================================================== //====================================================================================================================== SimpleQTableViewWidthInterface::SimpleQTableViewWidthInterface(QTableView& tbl) : TableWidthInterface(tbl.model()->columnCount()), m_tbl(tbl), m_pModel(tbl.model()) { int nCols (m_pModel->columnCount()); m_vbBold.resize(nCols); QFont font (m_tbl.font()); font.setBold(true); QFontMetrics fontMetrics (font); QHeaderView* pHdr (tbl.horizontalHeader()); for (int j = 0; j < nCols; ++j) { if (pHdr->isSectionHidden(j)) { setFixedWidth(j, 0); } else { m_vnHdrWidth[j] = fontMetrics.width(m_pModel->headerData(j, Qt::Horizontal).toString()) + 8/*2*QApplication::style()->pixelMetric(QStyle::PM_DefaultFrameWidth)*/; // PM_DefaultFrameWidth is not THE thing to use, but it's one way to get some spacing for the header; (well it turned up to be too small, so it got replaced by 8; probably look at the source to see what should be used) } } } /*override*/ int SimpleQTableViewWidthInterface::getRequestedWidth(int nRow, int nCol) const { if (hasFixedWidth(nCol)) { return m_vnMinWidth[nCol]; } int nMargin (QApplication::style()->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1); // this "1" should probably be another "metric" constant QFont font (m_tbl.font()); font.setBold(m_vbBold.at(nCol)); QFontMetrics fontMetrics (font); QString qstrVal (m_pModel->data(m_pModel->index(nRow, nCol)).toString()); int nWidth (fontMetrics.width(qstrVal)); return nWidth + 2*nMargin + 1; // the "+1" is for the width of the line separating columns } /*override*/ int SimpleQTableViewWidthInterface::getTableWidth() const // space available for data columns { int nRes (m_tbl.width() - m_tbl.verticalHeader()->sizeHint().width() - 2*QApplication::style()->pixelMetric(QStyle::PM_DefaultFrameWidth)); // ttt2 not sure if PM_DefaultFrameWidth is the right param; perhaps looking at the sources might clarify this if (m_tbl.verticalScrollBar()->isVisible()) { nRes -= QApplication::style()->pixelMetric(QStyle::PM_ScrollBarExtent); // nothing; // Plastique // nRes -= 6; ??? to test //Oxygen // nRes -= 2; // CDE // nRes -= 4; // Motif if (0 != QApplication::style()->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents)) { nRes -= 2*QApplication::style()->pixelMetric(QStyle::PM_DefaultFrameWidth, 0, &m_tbl); //ttt2 Qt 4.4 (and below) - specific; in 4.5 there's a QStyle::PM_ScrollView_ScrollBarSpacing } } return nRes; } /*override*/ bool SimpleQTableViewWidthInterface::isHidden(int nCol) const { return m_tbl.horizontalHeader()->isSectionHidden(nCol); } /*override*/ void SimpleQTableViewWidthInterface::setWidth(int nCol, int nWidth) { m_tbl.horizontalHeader()->resizeSection(nCol, nWidth); } /*override*/ int SimpleQTableViewWidthInterface::getRowCount() const { return m_pModel->rowCount(); } /*override*/ int SimpleQTableViewWidthInterface::getColumnCount() const { return m_pModel->columnCount(); } //====================================================================================================================== //====================================================================================================================== //====================================================================================================================== SimpleQTreeWidgetWidthInterface::SimpleQTreeWidgetWidthInterface(QTreeWidget& tbl) : TableWidthInterface(tbl.columnCount()), m_tbl(tbl) { int nCols (m_tbl.columnCount()); m_vbBold.resize(nCols); QFont font (m_tbl.font()); font.setBold(true); QFontMetrics fontMetrics (font); QHeaderView* pHdr (tbl.header()); for (int j = 0; j < nCols; ++j) { if (pHdr->isSectionHidden(j)) { setFixedWidth(j, 0); } else { m_vnHdrWidth[j] = fontMetrics.width(m_tbl.headerItem()->text(j)) + 8/*2*QApplication::style()->pixelMetric(QStyle::PM_DefaultFrameWidth)*/; // PM_DefaultFrameWidth is not THE thing to use, but it's one way to get some spacing for the header; (well it turned up to be too small, so it got replaced by 8; probably look at the source to see what should be used) } } } /*override*/ int SimpleQTreeWidgetWidthInterface::getRequestedWidth(int nRow, int nCol) const { if (hasFixedWidth(nCol)) { return m_vnMinWidth[nCol]; } int nMargin (QApplication::style()->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1); // this "1" should probably be another "metric" constant QFont font (m_tbl.font()); font.setBold(m_vbBold.at(nCol)); QFontMetrics fontMetrics (font); QString qstrVal (m_tbl.topLevelItem(nRow)->text(nCol)); int nWidth (fontMetrics.width(qstrVal)); return nWidth + 2*nMargin; } /*override*/ int SimpleQTreeWidgetWidthInterface::getTableWidth() const // space available for data columns { int nRes (m_tbl.width() //- m_tbl.verticalHeader()->sizeHint().width() - 2*QApplication::style()->pixelMetric(QStyle::PM_DefaultFrameWidth)); // ttt2 not sure if PM_DefaultFrameWidth is the right param; perhaps looking at the sources might clarify this /*if (m_tbl.verticalScrollBar()->isVisible()) { nRes -= QApplication::style()->pixelMetric(QStyle::PM_ScrollBarExtent); }*/ nRes -= QApplication::style()->pixelMetric(QStyle::PM_ScrollBarExtent); // just assume that the scrollbar is visible //ttt2 detect somehow when the scrollbar is visible return nRes; } /*override*/ bool SimpleQTreeWidgetWidthInterface::isHidden(int nCol) const { return m_tbl.header()->isSectionHidden(nCol); } /*override*/ void SimpleQTreeWidgetWidthInterface::setWidth(int nCol, int nWidth) { m_tbl.header()->resizeSection(nCol, nWidth); } //====================================================================================================================== //====================================================================================================================== //====================================================================================================================== //ttt2 maybe separate text width calculations from resizing, to avoid unneeded calls to QFontMetrics (useful only when resizing a window, so perhaps can be dropped) ColumnResizer::ColumnResizer(TableWidthInterface& intf, int nMaxRows /* = 80*/, bool bFill /* = false*/, bool bConsistentResults /* = false*/) : m_intf(intf), m_nMaxRows(nMaxRows), m_bConsistentResults(bConsistentResults) // if bFill is true, colums are resized wider than needed, so they fill the whole area of the table { autoSize(bFill); } // how many rows from a column can be shown completely for a given size int ColumnResizer::getFitCount(int nCol, int nWidth) const { const vector& v (m_vvAllWidths[nCol]); vector::const_iterator it (lower_bound(v.begin(), v.end(), nWidth)); return it - v.begin(); } double ColumnResizer::SequenceGen::rnd() const { if (RAND_MAX == 32767) { return (0.0 + rand() + rand()*32768)/32768/32768; } return rand()*1.0/RAND_MAX; } ColumnResizer::SequenceGen::SequenceGen(int nTotalRows, int nTargetRows) : m_nCrt(0) { if (nTargetRows > nTotalRows || nTotalRows <= 0) { throw invalid_argument("Invalid params for SequenceGen"); } if (nTotalRows == nTargetRows) { m_vn.resize(nTargetRows); for (int i = 0; i < nTotalRows; ++i) { m_vn[i] = i; } return; } m_vn.reserve(nTargetRows); if (nTotalRows > nTargetRows*2) { set s; while ((int)s.size() < nTargetRows) { int n (int(rnd()*nTotalRows)); if (0 == s.count(n)) { s.insert(n); } } m_vn.insert(m_vn.end(), s.begin(), s.end()); return; } // total not much bigger than target; proceed by elimination; for (int i = 0; i < nTotalRows; ++i) { m_vn.push_back(i); } set s (m_vn.begin(), m_vn.end()); while ((int)s.size() > nTargetRows) { int n (int(rnd()*nTotalRows)); if (0 != s.count(n)) { s.erase(int(rnd()*nTotalRows)); } } m_vn.clear(); m_vn.insert(m_vn.end(), s.begin(), s.end()); } /*struct QQQ { QQQ() { { int tot (20); int tgt (20); ColumnResizer::SequenceGen seq (tot, tgt); for (int i = 0; i < tgt; ++i) cout << seq.getNext() << " "; cout << endl; } { int tot (30); int tgt (20); ColumnResizer::SequenceGen seq (tot, tgt); for (int i = 0; i < tgt; ++i) cout << seq.getNext() << " "; cout << endl; } { int tot (100); int tgt (20); ColumnResizer::SequenceGen seq (tot, tgt); for (int i = 0; i < tgt; ++i) cout << seq.getNext() << " "; cout << endl; } } }; QQQ qqq;//*/ #ifdef PRINT_SIZES void ColumnResizer::printWidths(const char* szLabel) { ofstream_utf8 out ("/home/ciobi/yast.txt", ios_base::app); // !!! has to log to a file for when it's running in a library out << szLabel << ": " << m_nTableWidth << " / "; int qq (0); for (int j = 0; j < m_nCols; ++j) { if (m_intf.isHidden(j)) { out << "[" << m_vColInfo[j].m_nWidth << "] "; } else { out << m_vColInfo[j].m_nWidth << " "; } qq += m_vColInfo[j].m_nWidth; } out << " / " << m_nTotalWidth << "=" << qq << endl; } void ColumnResizer::printCellInfo(const char* szLabel) { ofstream_utf8 out ("/home/ciobi/yast.txt", ios_base::app); // !!! has to log to a file for when it's running in a library out << szLabel << ": " << m_nTableWidth << endl; for (int j = 0; j < m_nCols; ++j) { ColInfo& inf (m_vColInfo[j]); if (m_intf.isHidden(j)) { out << "[" << m_vColInfo[j].m_nWidth << "] "; } else { out << m_vColInfo[j].m_nWidth << " "; } out << ", " << inf.m_nMinWidth << ", " << inf.m_nLargeAvg << ",, " << inf.m_nAskSmall << ", " << inf.m_nAskLarge << ",, " << inf.m_dLargeDev << ",, **" << inf.m_dPrioSmall << "**, " << inf.m_dPrioLarge << endl; } } #else //inline void ColumnResizer::printWidths(const char*) {} //inline void ColumnResizer::printCellInfo(const char*) {} #define printWidths(X) #define printCellInfo(X) #endif void ColumnResizer::readAllWidths() { for (int j = 0; j < m_nCols; ++j) { vector& v (m_vvAllWidths[j]); v.resize(m_nUsedRows); SequenceGen seq (m_nTotalRows, m_nUsedRows); for (int i = 0; i < m_nUsedRows; ++i) { v[i] = m_intf.isHidden(j) ? 0 : m_intf.getRequestedWidth(seq.getNext(), j); // !!! getRequestedWidth() returns the fixed width for "fixed width" columns, so no special test is needed } sort(v.begin(), v.end()); if (!m_intf.hasFixedWidth(j) && !m_intf.isHidden(j) && m_intf.getMinWidthDataHdr(j) > m_vvAllWidths[j][m_nUsedRows - 1]) { m_vvAllWidths[j][m_nUsedRows - 1] = m_intf.getMinWidthDataHdr(j); // this way we avoid doing a lot of "max(m_intf.getMinWidthDataHdr(j), m_vvAllWidths[j][nRows - 1])"; it doesn't matter that the value is destroyed; } } m_nTotalMax = 0; for (int j = 0; j < m_nCols; ++j) { m_nTotalMax += m_vvAllWidths[j][m_nUsedRows - 1]; } } void ColumnResizer::computeColInfo() { int nBeg (m_nUsedRows / 2); int nLargeElemsCnt (m_nUsedRows - nBeg); if (nLargeElemsCnt > 34) { nLargeElemsCnt = nLargeElemsCnt*90/100; // cut 10% } else if (nLargeElemsCnt > 20) { nLargeElemsCnt -= 3; } else if (nLargeElemsCnt > 10) { nLargeElemsCnt -= 2; } else if (nLargeElemsCnt > 3) { nLargeElemsCnt -= 1; } m_nTotalWidth = 0; for (int j = 0; j < m_nCols; ++j) { // assign LargeAvg for width vector& v (m_vvAllWidths[j]); // this contains only zeroes for hidden columns int nSum (0); int nDev (0); for (int i = nBeg; i < nBeg + nLargeElemsCnt; ++i) { nSum += v[i]; int x (v[i] - v[nBeg]); //nDev += x*x; nDev += x; } ColInfo& inf (m_vColInfo[j]); inf.m_nLargeAvg = int(nSum*1.0/nLargeElemsCnt); inf.m_dLargeDev = nDev*1.0/nSum; inf.m_nMinWidth = m_intf.getMinWidthDataHdr(j); if (inf.m_nLargeAvg < inf.m_nMinWidth) { inf.m_nLargeAvg = inf.m_nMinWidth; } if (m_intf.hasFixedWidth(j)) { inf.m_dPrioSmall = inf.m_dPrioLarge = 0; } else { inf.m_nAskSmall = int(inf.m_nLargeAvg*(1 + inf.m_dLargeDev/2)); inf.m_dPrioSmall = 1 - inf.m_dLargeDev*0.8; //inf.m_dPrioSmall *= log(5) - log(inf.m_nLargeAvg*1.0 / m_nTableWidth + 0.02) + 0.5; // narrow colums get increased priority inf.m_nAskLarge = int(inf.m_nLargeAvg*(1 + inf.m_dLargeDev*2)); inf.m_dPrioLarge = 1 - inf.m_dLargeDev*0.8; } } printCellInfo("computeColInfo"); } void ColumnResizer::setUpMaxWidths() { m_nTotalWidth = 0; for (int j = 0; j < m_nCols; ++j) { ColInfo& inf (m_vColInfo[j]); inf.m_nWidth = m_vvAllWidths[j][m_nUsedRows - 1]; m_nTotalWidth += inf.m_nWidth; } printWidths("after setUpMaxWidths"); } void ColumnResizer::setUpAvgWidths() { m_nTotalWidth = 0; for (int j = 0; j < m_nCols; ++j) { ColInfo& inf (m_vColInfo[j]); inf.m_nWidth = inf.m_nLargeAvg; m_nTotalWidth += inf.m_nWidth; } printWidths("after setUpAvgWidths"); } // if MinWidth wasn't specified, LargeAvg is used instead void ColumnResizer::setUpMinWidths() { m_nTotalWidth = 0; for (int j = 0; j < m_nCols; ++j) { ColInfo& inf (m_vColInfo[j]); if (!m_intf.hasFixedWidth(j) && m_intf.getMinWidth(j) > 0) // !!! "m_intf.getMinWidth(inf.m_nCol) > 0" means that a minWidth was explicitely specified (inf.m_nMinWidth also includes the header) { inf.m_nWidth = inf.m_nMinWidth; } else { inf.m_nWidth = inf.m_nLargeAvg; } m_nTotalWidth += inf.m_nWidth; } printWidths("after setUpMinWidths"); } void ColumnResizer::distributeAskSmall() { if (m_nTotalWidth >= m_nTableWidth) { return; } // distribute AskSmall according to prioriries int nAskSmallDiffTotal (0); double dPrioSum (0); for (int j = 0; j < m_nCols; ++j) { if (!m_intf.hasFixedWidth(j)) { ColInfo& inf (m_vColInfo[j]); int nDiff (inf.m_nAskSmall - inf.m_nWidth); nAskSmallDiffTotal += nDiff; // !!! inf.m_nAskSmall >= inf.m_nWidth always dPrioSum += inf.m_dPrioSmall*nDiff; } } if (nAskSmallDiffTotal + m_nTotalWidth <= m_nTableWidth) { // assign all for (int j = 0; j < m_nCols; ++j) { if (!m_intf.hasFixedWidth(j)) { ColInfo& inf (m_vColInfo[j]); inf.m_nWidth = inf.m_nAskSmall; } } m_nTotalWidth += nAskSmallDiffTotal; } else { // use priorities to assign all remaining space to AskSmall for (int j = 0; j < m_nCols; ++j) { if (!m_intf.hasFixedWidth(j)) { ColInfo& inf (m_vColInfo[j]); int nDiff (inf.m_nAskSmall - inf.m_nWidth); //int nAdd (int(nDiff*inf.m_dPrioSmall/dPrioSum)); int nAdd (int(nDiff*inf.m_dPrioSmall*(m_nTableWidth - m_nTotalWidth)/dPrioSum)); inf.m_nWidth += nAdd; m_nTotalWidth += nAdd; } } } printWidths("after distributeAskSmall"); } void ColumnResizer::distributeAskLarge() { if (m_nTotalWidth >= m_nTableWidth) { return; } // distribute AskLarge according to prioriries int nAskLargeDiffTotal (0); double dPrioSum (0); for (int j = 0; j < m_nCols; ++j) { if (!m_intf.hasFixedWidth(j)) { ColInfo& inf (m_vColInfo[j]); int nDiff (inf.m_nAskLarge - inf.m_nWidth); if (nDiff > 0) // !!! while initially we had "inf.m_nAskLarge >= inf.m_nWidth", inf.m_nWidth might have been increased in the meantime { nAskLargeDiffTotal += nDiff; dPrioSum += inf.m_dPrioLarge*nDiff; } } } if (nAskLargeDiffTotal + m_nTotalWidth <= m_nTableWidth) { // assign all for (int j = 0; j < m_nCols; ++j) { if (!m_intf.hasFixedWidth(j)) { ColInfo& inf (m_vColInfo[j]); inf.m_nWidth = inf.m_nAskLarge; } } m_nTotalWidth += nAskLargeDiffTotal; } else { // use priorities to assign all remaining space to AskLarge for (int j = 0; j < m_nCols; ++j) { if (!m_intf.hasFixedWidth(j)) { ColInfo& inf (m_vColInfo[j]); int nDiff (inf.m_nAskLarge - inf.m_nWidth); if (nDiff > 0) { //int nAdd (int(nDiff*inf.m_dPrioLarge/dPrioSum)); //int nAdd (int(nDistr*inf.m_dPrioLarge/dPrioSum*nDiff/nAskLargeDiffTotal)); int nAdd (int(nDiff*inf.m_dPrioLarge*(m_nTableWidth - m_nTotalWidth)/dPrioSum)); inf.m_nWidth += nAdd; m_nTotalWidth += nAdd; } } } } printWidths("after distributeAskLarge"); } void ColumnResizer::distributeRemaining() // several pixels might remain undistributed after distributeAskSmall() and distributeAskLarge() because of rounding errors; they are dealt with here; { printWidths("before distributeRemaining"); if (m_nTotalWidth >= m_nTableWidth) { return; } // distribute what's left int nLeft (m_nTableWidth - m_nTotalWidth); int nVarCols (m_nCols - m_nFixedWidthCount); int nStep (nLeft/nVarCols); for (int j = 0; j < m_nCols; ++j) { if (!m_intf.hasFixedWidth(j)) { m_vColInfo[j].m_nWidth += nStep; } } nLeft -= nVarCols*nStep; for (int j = 0; nLeft > 0; ++j) { if (!m_intf.hasFixedWidth(j)) { ++m_vColInfo[j].m_nWidth; --nLeft; } } m_nTotalWidth = m_nTableWidth; printWidths("after distributeRemaining"); } void ColumnResizer::setWidths() { for (int j = 0; j < m_nCols; ++j) { if (!m_intf.isHidden(j)) { m_intf.setWidth(j, m_vColInfo[j].m_nWidth); } } printWidths(" setWidths"); } void ColumnResizer::autoSize(bool bFill) { m_nTotalRows = m_intf.getRowCount(); m_nUsedRows = min(m_nTotalRows, m_nMaxRows); m_nCols = m_intf.getColumnCount(); if (0 == m_nTotalRows || 0 == m_nCols) { return; } // not sure if m_nCols can be 0; ... whatever m_vvAllWidths.resize(m_nCols); m_nFixedWidthCount = 0; for (int j = 0; j < m_nCols; ++j) { if (m_intf.hasFixedWidth(j)) { ++m_nFixedWidthCount; } } m_vColInfo.resize(m_nCols); if (m_nFixedWidthCount == m_nCols) { // all columns have fixed size for (int j = 0; j < m_nCols; ++j) { m_vColInfo[j].m_nWidth = m_intf.getMinWidth(j); } } else { if (!m_bConsistentResults && m_nTotalRows != m_nUsedRows) { srand(11); //ttt2 not such a good idea, as it interferes with other uses of rand(); OTOH, it's better to use it, because then we always get the same results for the same data, even if the selection process is random; seems better to use a separate generator; } readAllWidths(); m_nTableWidth = m_intf.getTableWidth(); computeColInfo(); if (m_nTotalMax <= m_nTableWidth) { setUpMaxWidths(); if (!bFill) { m_nTotalWidth = m_nTableWidth; // so columns won't get expanded } } else { setUpAvgWidths(); if (m_nTotalWidth > m_nTableWidth) { // if still too wide, cut to minWidth, if one was set (header width doesn't count) setUpMinWidths(); } } if (m_nTotalWidth < m_nTableWidth) { distributeAskSmall(); if (m_nTotalWidth < m_nTableWidth) { distributeAskLarge(); if (m_nTotalWidth < m_nTableWidth) { distributeRemaining(); } } } } setWidths(); } //====================================================================================================================== //====================================================================================================================== //====================================================================================================================== /*override*/ int SimpleQTreeWidgetWidthInterface::getRowCount() const { return m_tbl.topLevelItemCount(); } /*override*/ int SimpleQTreeWidgetWidthInterface::getColumnCount() const { return m_tbl.columnCount(); } MP3Diags-1.2.02/src/ImageInfoPanelWdgImpl.cpp0000644000175000001440000000714011717717700017556 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include "ImageInfoPanelWdgImpl.h" #include "Helpers.h" //#include "Profiler.h" using namespace std; //using namespace pearl; static const int IMG_SIZE (110); ImageInfoPanelWdgImpl::ImageInfoPanelWdgImpl(QWidget* pParent, const TagWrtImageInfo& tagWrtImageInfo, int nPos) : QFrame(pParent, 0), Ui::ImageInfoPanelWdg(), m_tagWrtImageInfo(tagWrtImageInfo), m_nPos(nPos) { //PROF("ImageInfoPanelWdgImpl::ImageInfoPanelWdgImpl"); CB_ASSERT (nPos >= 0 /*&& nPos < cSize(vImageInfo)*/); setupUi(this); m_pPosL->setText(QString("# %1").arg(nPos + 1)); m_pSizeL->setText(QString("%1kB").arg(tagWrtImageInfo.m_imageInfo.getSize()/1024)); m_pDimL->setText(QString("%1x%2").arg(tagWrtImageInfo.m_imageInfo.getWidth()).arg(tagWrtImageInfo.m_imageInfo.getHeight())); //PROFD(4); m_pThumbL->setPixmap(QPixmap::fromImage(tagWrtImageInfo.m_imageInfo.getImage(IMG_SIZE))); //ttt2p performance issue; doesn't look like much can be done, though //PROFD(5); if (tagWrtImageInfo.m_sstrFiles.empty()) { m_pEraseB->hide(); } else { QString s; if (tagWrtImageInfo.m_sstrFiles.size() > 1) { s = tr("Erase these files:"); for (set::const_iterator it = tagWrtImageInfo.m_sstrFiles.begin(); it != tagWrtImageInfo.m_sstrFiles.end(); ++it) { s += "\n" + convStr(*it); } } else { s = tr("Erase file %1").arg(convStr(*tagWrtImageInfo.m_sstrFiles.begin())); } m_pEraseB->setToolTip(s); } } ImageInfoPanelWdgImpl::~ImageInfoPanelWdgImpl() { } void ImageInfoPanelWdgImpl::on_m_pFullB_clicked() { m_tagWrtImageInfo.m_imageInfo.showFull(this); } void ImageInfoPanelWdgImpl::setNormalBackground() { QPalette pal; setPalette(pal); } void ImageInfoPanelWdgImpl::setHighlightBackground() { QPalette pal; //QPalette pal (palette()); //QPalette pal (QColor(255, 220, 190)); pal.setColor(QPalette::Window, pal.color(QPalette::Disabled, QPalette::Dark)); setPalette(pal); } MP3Diags-1.2.02/src/RenamerPatternsDlgImpl.cpp0000644000175000001440000001354211723776756020057 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include "RenamerPatternsDlgImpl.h" #include "Helpers.h" #include "FileRenamerDlgImpl.h" #include "StoredSettings.h" #include "Widgets.h" using namespace std; RenamerPatternsDlgImpl::RenamerPatternsDlgImpl(QWidget* pParent, SessionSettings& settings) : QDialog(pParent, getDialogWndFlags()), Ui::PatternsDlg(), m_settings(settings), m_nCrtLine(-1), m_nCrtCol(-1) { setupUi(this); m_pAddPredefB->hide(); m_pSpacerW->hide(); QPalette grayPalette (m_infoM->palette()); grayPalette.setColor(QPalette::Base, grayPalette.color(QPalette::Disabled, QPalette::Window)); m_infoM->setPalette(grayPalette); m_infoM->setTabStopWidth(fontMetrics().width("%ww")); #ifndef WIN32 m_infoM->setText(tr("%n\ttrack number\n%a\tartist\n%t\ttitle\n%b\talbum\n%y\tyear\n%g\tgenre\n%r\trating (a lowercase letter)\n%c\tcomposer" "\n\nTo include the special characters \"%\", \"[\" and \"]\", precede them by a \"%\": \"%%\", \"%[\" and \"%]\"\n\nThe path should be either a full path, starting with a " "\"%1\", or it should contain no \"%1\", if what is wanted is for the renamed files to remain in their original directories").arg(getPathSep())); #else m_infoM->setText(tr("%n\ttrack number\n%a\tartist\n%t\ttitle\n%b\talbum\n%y\tyear\n%g\tgenre\n%r\trating (a lowercase letter)\n%c\tcomposer" "\n\nTo include the special characters \"%\", \"[\" and \"]\", precede them by a \"%\": \"%%\", \"%[\" and \"%]\"\n\nThe path should be either a full path, starting with a " "drive letter followed by \":\\\", or it should contain no \"\\\", if what is wanted is for the renamed files to remain in their original directories")); #endif int nWidth, nHeight; m_settings.loadRenamerPatternsSettings(nWidth, nHeight); if (nWidth > 400 && nHeight > 300) { resize(nWidth, nHeight); } connect(m_pTextM, SIGNAL(cursorPositionChanged()), this, SLOT(onCrtPosChanged())); { QAction* p (new QAction(this)); p->setShortcut(QKeySequence("F1")); connect(p, SIGNAL(triggered()), this, SLOT(onHelp())); addAction(p); } } RenamerPatternsDlgImpl::~RenamerPatternsDlgImpl() { } /*$SPECIALIZATION$*/ void RenamerPatternsDlgImpl::on_m_pCancelB_clicked() { reject(); } void RenamerPatternsDlgImpl::on_m_pOkB_clicked() { m_vstrPatterns.clear(); string s (convStr(m_pTextM->toPlainText())); const char* p (s.c_str()); if (0 == *p) { accept(); return; } const char* q (p); for (;;) { if ('\n' == *p || 0 == *p) { string s1 (q, p - q); s1 = fromNativeSeparators(s1); string strErr; try { Renamer r (s1, 0, false); } catch (const Renamer::InvalidPattern& ex) { strErr = ex.m_strErr; } if (!strErr.empty()) { showCritical(this, tr("Error"), convStr(strErr)); return; } m_vstrPatterns.push_back(s1); for (; '\n' == *p; ++p) {} if (0 == *p) { break; } q = p; } ++p; } m_settings.saveRenamerPatternsSettings(width(), height()); accept(); } bool RenamerPatternsDlgImpl::run(vector& v) { string s; for (int i = 0, n = cSize(v); i < n; ++i) { if (!s.empty()) { s += "\n"; } s += toNativeSeparators(v[i]); } m_pTextM->setText(convStr(s)); if (QDialog::Accepted != exec()) { return false; } v = m_vstrPatterns; /* set sPos; vector > v1; for (int i = 0, n = cSize(m_vPatterns); i < n; ++i) { int j (0); int m (cSize(v)); for (; j < m; ++j) { if (m_vPatterns[i] == v[j] && sPos.end() == sPos.find(j)) { sPos.insert(j); break; } } if (m == j) { j = -1; } v1.push_back(make_pair(m_vPatterns[i], j)); } //v.clear(); v.swap(v1);*/ return true; } void RenamerPatternsDlgImpl::onHelp() { openHelp("240_file_renamer.html"); } void RenamerPatternsDlgImpl::onCrtPosChanged() { QTextCursor crs (m_pTextM->textCursor()); m_nCrtLine = crs.blockNumber(); m_nCrtCol = crs.columnNumber(); m_pCrtPosL->setText(tr("Line %1, Col %2").arg(m_nCrtLine + 1).arg(m_nCrtCol + 1)); } MP3Diags-1.2.02/src/Id3Transf.h0000644000175000001440000002615311724425140014713 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef Id3TransfH #define Id3TransfH #include "fstream_unicode.h" #include "Transformation.h" class CommonData; class Id3V2StreamBase; class Id3V2Cleaner : public Transformation { bool processId3V2Stream(Id3V2StreamBase& frm, ofstream_utf8& out); CommonData* m_pCommonData; public: Id3V2Cleaner(CommonData* pCommonData) : m_pCommonData(pCommonData) {} /*override*/ Transformation::Result apply(const Mp3Handler&, const TransfConfig&, const std::string& strOrigSrcName, std::string& strTempName); /*override*/ const char* getActionName() const { return getClassName(); } /*override*/ const char* getDescription() const { return QT_TRANSLATE_NOOP("Transformation", "Removes all ID3V2 frames that aren't used by MP3 Diags. You normally don't want to do such a thing, but it may help if some other program misbehaves because of invalid or unknown frames in an ID3V2 tag."); } static const char* getClassName() { return QT_TRANSLATE_NOOP("Transformation", "Remove non-basic ID3V2 frames"); } }; class Id3V2Rescuer : public Transformation { bool processId3V2Stream(Id3V2StreamBase& frm, ofstream_utf8* pOut); // nothing gets written if pOut is 0 CommonData* m_pCommonData; public: Id3V2Rescuer(CommonData* pCommonData) : m_pCommonData(pCommonData) {} /*override*/ Transformation::Result apply(const Mp3Handler&, const TransfConfig&, const std::string& strOrigSrcName, std::string& strTempName); /*override*/ const char* getActionName() const { return getClassName(); } /*override*/ const char* getDescription() const { return QT_TRANSLATE_NOOP("Transformation", "Copies only ID3V2 frames that seem valid or can be made valid, discarding those that are invalid and can't be fixed (e.g. an APIC frame claiming to hold a picture although it doesn't.) Handles both loadable and broken ID3V2 tags, in the latter case copying being stopped when a fatal error occurs."); } static const char* getClassName() { return QT_TRANSLATE_NOOP("Transformation", "Discard invalid ID3V2 data"); } }; class Id3V2UnicodeTransformer : public Transformation { void processId3V2Stream(Id3V2StreamBase& frm, ofstream_utf8& out); CommonData* m_pCommonData; public: Id3V2UnicodeTransformer(CommonData* pCommonData) : m_pCommonData(pCommonData) {} /*override*/ Transformation::Result apply(const Mp3Handler&, const TransfConfig&, const std::string& strOrigSrcName, std::string& strTempName); /*override*/ const char* getActionName() const { return getClassName(); } /*override*/ QString getVisibleActionName() const; /*override*/ const char* getDescription() const { return QT_TRANSLATE_NOOP("Transformation", "Transforms text frames in ID3V2 encoded as Latin1 to Unicode (UTF16.) The reason to do this is that sometimes non-conforming software treats these frames as they are encoded in a different code page, causing other programs to display unexpected data."); } static const char* getClassName() { return QT_TRANSLATE_NOOP("Transformation", "Convert non-ASCII ID3V2 text frames to Unicode"); } }; class Id3V2CaseTransformer : public Transformation { bool processId3V2Stream(Id3V2StreamBase& frm, ofstream_utf8& out); CommonData* m_pCommonData; public: Id3V2CaseTransformer(CommonData* pCommonData) : m_pCommonData(pCommonData) {} /*override*/ Transformation::Result apply(const Mp3Handler&, const TransfConfig&, const std::string& strOrigSrcName, std::string& strTempName); /*override*/ const char* getActionName() const { return getClassName(); } /*override*/ QString getVisibleActionName() const; /*override*/ const char* getDescription() const { return QT_TRANSLATE_NOOP("Transformation", "Transforms the case of text frames in ID3V2 tags, according to global settings."); } static const char* getClassName() { return QT_TRANSLATE_NOOP("Transformation", "Change case for ID3V2 text frames"); } }; class Id3V1Stream; class Id3V1ToId3V2Copier : public Transformation { bool processId3V2Stream(Id3V2StreamBase& frm, ofstream_utf8& out, Id3V1Stream* pId3V1Stream); std::string convert(const std::string& s); CommonData* m_pCommonData; public: Id3V1ToId3V2Copier(CommonData* pCommonData) : m_pCommonData(pCommonData) {} /*override*/ Transformation::Result apply(const Mp3Handler&, const TransfConfig&, const std::string& strOrigSrcName, std::string& strTempName); /*override*/ const char* getActionName() const { return getClassName(); } /*override*/ QString getVisibleActionName() const; /*override*/ const char* getDescription() const { return QT_TRANSLATE_NOOP("Transformation", "Copies frames from ID3V1 to ID3V2 if those frames don't exist in the destination or if the destination doesn't exist at all."); } static const char* getClassName() { return QT_TRANSLATE_NOOP("Transformation", "Copy missing ID3V2 frames from ID3V1"); } }; class Id3V2ComposerAdder : public Transformation { CommonData* m_pCommonData; public: Id3V2ComposerAdder(CommonData* pCommonData) : m_pCommonData(pCommonData) {} /*override*/ Transformation::Result apply(const Mp3Handler&, const TransfConfig&, const std::string& strOrigSrcName, std::string& strTempName); /*override*/ const char* getActionName() const { return getClassName(); } /*override*/ const char* getDescription() const { return QT_TRANSLATE_NOOP("Transformation", "Adds the value of the composer field to the beginning of the artist field in ID3V2 frames. Useful for players that don't use the composer field."); } static const char* getClassName() { return QT_TRANSLATE_NOOP("Transformation", "Add composer field to the artist field in ID3V2 frames"); } }; class Id3V2ComposerRemover : public Transformation { CommonData* m_pCommonData; public: Id3V2ComposerRemover(CommonData* pCommonData) : m_pCommonData(pCommonData) {} /*override*/ Transformation::Result apply(const Mp3Handler&, const TransfConfig&, const std::string& strOrigSrcName, std::string& strTempName); /*override*/ const char* getActionName() const { return getClassName(); } /*override*/ const char* getDescription() const { return QT_TRANSLATE_NOOP("Transformation", "\"Undo\" for \"adding composer field.\" Removes the value of the composer field from the beginning of the artist field in ID3V2 frames, if it was previously added."); } static const char* getClassName() { return QT_TRANSLATE_NOOP("Transformation", "Remove composer field from the artist field in ID3V2 frames"); } }; class Id3V2ComposerCopier : public Transformation { CommonData* m_pCommonData; public: Id3V2ComposerCopier(CommonData* pCommonData) : m_pCommonData(pCommonData) {} /*override*/ Transformation::Result apply(const Mp3Handler&, const TransfConfig&, const std::string& strOrigSrcName, std::string& strTempName); /*override*/ const char* getActionName() const { return getClassName(); } /*override*/ const char* getDescription() const { return QT_TRANSLATE_NOOP("Transformation", "Copies to the \"Composer\" field the beginning of an \"Artist\" field that is formatted as \"Composer [Artist]\". Does nothing if the \"Artist\" field doesn't have this format."); } static const char* getClassName() { return QT_TRANSLATE_NOOP("Transformation", "Fill in composer field based on artist in ID3V2 frames"); } }; class SmallerImageRemover : public Transformation { CommonData* m_pCommonData; public: SmallerImageRemover(CommonData* pCommonData) : m_pCommonData(pCommonData) {} /*override*/ Transformation::Result apply(const Mp3Handler&, const TransfConfig&, const std::string& strOrigSrcName, std::string& strTempName); /*override*/ const char* getActionName() const { return getClassName(); } /*override*/ const char* getDescription() const { return QT_TRANSLATE_NOOP("Transformation", "Keeps only the biggest (and supposedly the best) image in a file. The image type is set to Front Cover. (This may result in the replacement of the Front Cover image.)"); } static const char* getClassName() { return QT_TRANSLATE_NOOP("Transformation", "Make the largest image \"Front Cover\" and remove the rest"); } }; class Id3V2Expander : public Transformation { CommonData* m_pCommonData; public: Id3V2Expander(CommonData* pCommonData) : m_pCommonData(pCommonData) {} /*override*/ Transformation::Result apply(const Mp3Handler&, const TransfConfig&, const std::string& strOrigSrcName, std::string& strTempName); /*override*/ const char* getActionName() const { return getClassName(); } /*override*/ const char* getDescription() const { return QT_TRANSLATE_NOOP("Transformation", "Adds extra spacing to the ID3V2 tag. This allows subsequent saving from the tag editor to complete quicker."); } static const char* getClassName() { return QT_TRANSLATE_NOOP("Transformation", "Reserve space in ID3V2 for fast tag editing"); } static const int EXTRA_SPACE; // this gets added to whatever the current frames already occupy; }; class Id3V2Compactor : public Transformation { CommonData* m_pCommonData; public: Id3V2Compactor(CommonData* pCommonData) : m_pCommonData(pCommonData) {} /*override*/ Transformation::Result apply(const Mp3Handler&, const TransfConfig&, const std::string& strOrigSrcName, std::string& strTempName); /*override*/ const char* getActionName() const { return getClassName(); } /*override*/ const char* getDescription() const { return QT_TRANSLATE_NOOP("Transformation", "Removes large unused blocks from ID3V2 tags. (Usually these have been reserved for fast tag editing, in which case they should be removed only after the ID3V2 tag has all the right values.)"); } static const char* getClassName() { return QT_TRANSLATE_NOOP("Transformation", "Remove extra space from ID3V2"); } }; #endif // ifndef Id3TransfH MP3Diags-1.2.02/src/AlbumInfoDownloaderDlgImpl.cpp0000644000175000001440000010732711724442275020630 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #ifndef WIN32 #include #else #include #endif #include #include #include #include #include #include #include #include #include "AlbumInfoDownloaderDlgImpl.h" #include "Helpers.h" #include "ColumnResizer.h" #include "Widgets.h" #include "fstream_unicode.h" using namespace std; using namespace pearl; // /*extern*/ int CELL_WIDTH (22); extern int CELL_HEIGHT; AlbumInfoDownloaderDlgImpl::AlbumInfoDownloaderDlgImpl(QWidget* pParent, SessionSettings& settings, bool bSaveResults) : QDialog(pParent, getDialogWndFlags()), Ui::AlbumInfoDownloaderDlg(), m_bSaveResults(bSaveResults), m_nLastCount(0), m_nLastTime(0), m_settings(settings) { setupUi(this); m_pQHttp = new QHttp (this); connect(m_pQHttp, SIGNAL(requestFinished(int, bool)), this, SLOT(onRequestFinished(int, bool))); m_pTrackListG->verticalHeader()->setMinimumSectionSize(CELL_HEIGHT); m_pTrackListG->verticalHeader()->setDefaultSectionSize(CELL_HEIGHT); { QAction* p (new QAction(this)); p->setShortcut(QKeySequence("F1")); connect(p, SIGNAL(triggered()), this, SLOT(onHelp())); addAction(p); } } AlbumInfoDownloaderDlgImpl::~AlbumInfoDownloaderDlgImpl() { } bool AlbumInfoDownloaderDlgImpl::getInfo(const std::string& strArtist, const std::string& strAlbum, int nTrackCount, AlbumInfo*& pAlbumInfo, ImageInfo*& pImageInfo) { LAST_STEP("AlbumInfoDownloaderDlgImpl::getInfo"); m_nExpectedTracks = nTrackCount; pAlbumInfo = 0; pImageInfo = 0; if (initSearch(strArtist, strAlbum)) { search(); } bool bRes (QDialog::Accepted == exec()); if (bRes) { saveSize(); CB_ASSERT (0 != getAlbumCount()); if (!m_bSaveImageOnly) { pAlbumInfo = new AlbumInfo(); album(m_nCrtAlbum).copyTo(*pAlbumInfo); //pAlbumInfo->m_strReleased = convStr(m_pRealeasedE->text()); //ttt2 maybe allow users to overwrite fields in edit boxes, esp. genre; however, most of the cases it's almost as easy to make any changes in the tag editor (well, there's an F2 and then "copy form first"); there are several issues in implementing this: 1) going to prev/next album; 2) artist name gets copied to each track; 3) consistency: if the edit boxes are editable why not the table? so, better without if (m_pVolumeCbB->isEnabled()) { if (m_pVolumeCbB->currentIndex() == m_pVolumeCbB->count() - 1) { // just give sequential numbers when "" in a multivolume is used - see https://sourceforge.net/projects/mp3diags/forums/forum/947206/topic/4503061/index/page/1 - perhaps can be improved char a [10]; for (int i = 0; i < cSize(pAlbumInfo->m_vTracks); ++i) { sprintf(a, "%02d", i + 1); pAlbumInfo->m_vTracks[i].m_strPos = a; } } else { vector vTracks; string s (convStr(m_pVolumeCbB->itemText(m_pVolumeCbB->currentIndex()))); int k (cSize(s)); for (int i = 0, n = cSize(pAlbumInfo->m_vTracks); i < n; ++i) { if (beginsWith(pAlbumInfo->m_vTracks[i].m_strPos, s)) { vTracks.push_back(pAlbumInfo->m_vTracks[i]); vTracks.back().m_strPos.erase(0, k); } } vTracks.swap(pAlbumInfo->m_vTracks); } } } if (m_nCrtImage >= 0) { pImageInfo = new ImageInfo(*album(m_nCrtAlbum).m_vpImages[m_nCrtImage]); } } return bRes; } // clears pending HTTP requests, m_eNavigDir and m_eWaiting; restores the cursor if needed; /*virtual*/ void AlbumInfoDownloaderDlgImpl::resetNavigation() { LAST_STEP("AlbumInfoDownloaderDlgImpl::resetNavigation"); m_pQHttp->clearPendingRequests(); setWaiting(NOTHING); m_eNavigDir = NONE; } string AlbumInfoDownloaderDlgImpl::replaceSymbols(string s) // replaces everything besides letters and digits with getReplacementChar() { LAST_STEP("AlbumInfoDownloaderDlgImpl::replaceSymbols"); char c (getReplacementChar()); for (int i = 0; i < cSize(s); ++i) { if ((unsigned char)(s[i]) < 128 && '\'' != s[i] && !isalnum(s[i])) { s[i] = c; } } return s; } /*static*/ const char* AlbumInfoDownloaderDlgImpl::NOT_FOUND_AT_AMAZON = QT_TRANSLATE_NOOP("AlbumInfoDownloaderDlgImpl", "not found at amazon.com"); void AlbumInfoDownloaderDlgImpl::search() { LAST_STEP("AlbumInfoDownloaderDlgImpl::search"); m_pResArtistE->setText(""); m_pResAlbumE->setText(""); m_pDownloadsM->setText(""); m_pAlbumNotesM->setText(""); m_pFormatE->setText(""); m_pRealeasedE->setText(""); m_pResultNoL->setText(""); updateTrackList(); m_pVolumeCbB->clear(); m_pImageL->setPixmap(0); m_pImageL->setText(""); m_pImgSizeL->setText("\n"); m_pViewAtAmazonL->setText(tr(NOT_FOUND_AT_AMAZON)); m_strQuery = escapeHttp(createQuery()); // e.g. http://www.discogs.com/search?type=all&q=beatles&f=xml&api_key=f51e9c8f6c, without page number; to be used by loadNextPage(); m_nTotalPages = 1; m_nLastLoadedPage = -1; m_nCrtAlbum = -1; m_nCrtImage = -1; resetNavigation(); //m_eNavigDir = NEXT; m_eNavigDir = NEXT; m_bNavigateByAlbum = false; addNote(tr("searching ...")); m_pGenreE->setText(""); //next(); //loadNextPage(); retryNavigation(); } void AlbumInfoDownloaderDlgImpl::on_m_pPrevB_clicked() { LAST_STEP("AlbumInfoDownloaderDlgImpl::on_m_pPrevB_clicked"); if (0 == getAlbumCount() || NOTHING != m_eWaiting) { return; } m_bNavigateByAlbum = false; m_eNavigDir = PREV; retryNavigation(); } void AlbumInfoDownloaderDlgImpl::on_m_pNextB_clicked() { LAST_STEP("AlbumInfoDownloaderDlgImpl::on_m_pNextB_clicked"); if (0 == getAlbumCount() || NOTHING != m_eWaiting) { return; } m_bNavigateByAlbum = false; m_eNavigDir = NEXT; retryNavigation(); } void AlbumInfoDownloaderDlgImpl::on_m_pPrevAlbumB_clicked() { LAST_STEP("AlbumInfoDownloaderDlgImpl::on_m_pPrevAlbumB_clicked"); if (0 == getAlbumCount() || NOTHING != m_eWaiting) { return; } m_bNavigateByAlbum = true; m_eNavigDir = PREV; retryNavigation(); } void AlbumInfoDownloaderDlgImpl::on_m_pNextAlbumB_clicked() { LAST_STEP("AlbumInfoDownloaderDlgImpl::on_m_pNextAlbumB_clicked"); if (0 == getAlbumCount() || NOTHING != m_eWaiting) { return; } m_bNavigateByAlbum = true; m_eNavigDir = NEXT; retryNavigation(); } void AlbumInfoDownloaderDlgImpl::on_m_pSaveAllB_clicked() { LAST_STEP("AlbumInfoDownloaderDlgImpl::on_m_pSaveAllB_clicked"); if (NOTHING != m_eWaiting) { showCritical(this, tr("Error"), tr("You cannot save the results now, because a request is still pending")); return; } if (0 == getAlbumCount()) { showCritical(this, tr("Error"), tr("You cannot save the results now, because no album is loaded")); return; } int nCnt (0); CB_ASSERT (m_nCrtAlbum >= 0 && m_nCrtAlbum < getAlbumCount()); const WebAlbumInfoBase& albumInfo (album(m_nCrtAlbum)); if (m_pVolumeCbB->isEnabled() && m_pVolumeCbB->currentIndex() != m_pVolumeCbB->count() - 1) { string s (convStr(m_pVolumeCbB->itemText(m_pVolumeCbB->currentIndex()))); for (int i = 0, n = cSize(albumInfo.m_vTracks); i < n; ++i) { if (beginsWith(albumInfo.m_vTracks[i].m_strPos, s)) { ++nCnt; } } } else { nCnt = cSize(albumInfo.m_vTracks); } if (nCnt != m_nExpectedTracks) { QString s; const QString& qstrVolMsg ( m_pVolumeCbB->isEnabled() && ( (nCnt > m_nExpectedTracks && m_pVolumeCbB->currentIndex() == m_pVolumeCbB->count() - 1) || (nCnt < m_nExpectedTracks && m_pVolumeCbB->currentIndex() != m_pVolumeCbB->count() - 1) ) ? tr("You may want to use a different volume selection on this multi-volume release.\n\n") : ""); if (nCnt > m_nExpectedTracks) { s = tr("A number of %1 tracks were expected, but your selection contains %2. Additional tracks will be discarded.\n\n%3Save anyway?").arg(m_nExpectedTracks).arg(nCnt).arg(qstrVolMsg); } else { s = tr("A number of %1 tracks were expected, but your selection only contains %2. Remaining tracks will get null values.\n\n%3Save anyway?").arg(m_nExpectedTracks).arg(nCnt).arg(qstrVolMsg); } if (showMessage(this, QMessageBox::Question, 1, 1, tr("Count inconsistency"), s, tr("&Save"), tr("Cancel")) != 0) { return; } } m_bSaveImageOnly = false; accept(); } void AlbumInfoDownloaderDlgImpl::on_m_pSaveImageB_clicked() { LAST_STEP("AlbumInfoDownloaderDlgImpl::on_m_pSaveImageB_clicked"); if (NOTHING != m_eWaiting) { showCritical(this, tr("Error"), tr("You cannot save the results now, because a request is still pending")); return; } if (0 == getAlbumCount() || -1 == m_nCrtImage) { showCritical(this, tr("Error"), tr("You cannot save any image now, because there is no image loaded")); return; } // ttt2 perhaps shouldn't save an "error" image m_bSaveImageOnly = true; accept(); //ttt2 perhaps allow multiple images to be saved, by adding a button to "add to save list"; see https://sourceforge.net/projects/mp3diags/forums/forum/947207/topic/4006484 } void AlbumInfoDownloaderDlgImpl::on_m_pCancelB_clicked() { LAST_STEP("AlbumInfoDownloaderDlgImpl::on_m_pCancelB_clicked"); reject(); } //========================================================================================================================== //========================================================================================================================== //========================================================================================================================== // what may happen: // 1) there is a "next" element and it is in memory; then the GUI is updated and that is all (this calls resetNavigation() ); // 2) there is a "next" element but it is not loaded; album info or picture are missing, or there's another page with search results that wasn't loaded yet; then a request is made for what's missing to be downloaded, and the state is updated to reflect that; when the request completes, retryNavigation() will get called, which will call again next() // 3) there is no "next"; nothing happens in this case; // // if there's nothing to wait for, retryNavigation() calls resetNavigation() after next() exits; (retryNavigation() is the only function that is supposed to call next() ); // // if no new HTTP request is sent, either setWaiting(NOTHING) or reloadGui() should be called, to leave m_eWaiting in a consistent state; // void AlbumInfoDownloaderDlgImpl::next() { LAST_STEP("AlbumInfoDownloaderDlgImpl::next"); //if (NONE != m_eNavigDir) { return; } CB_ASSERT (NEXT == m_eNavigDir); int nNextAlbum (m_nCrtAlbum); int nNextImage (m_nCrtImage); for (;;) { if (nNextAlbum >= 0 && nNextImage < cSize(album(nNextAlbum).m_vstrImageNames) - 1 && (-1 == nNextImage || !m_bNavigateByAlbum)) { ++nNextImage; } else if (nNextAlbum + 1 < getAlbumCount()) { // m_vAlbums contains a "next" album, which may or may not be loaded if (album(nNextAlbum + 1).m_strTitle.empty()) { requestAlbum(nNextAlbum + 1); return; } else { ++nNextAlbum; nNextImage = album(nNextAlbum).m_vstrImageNames.empty() ? -1 : 0; } } else { // nothing left in m_vAlbums if (m_nLastLoadedPage == m_nTotalPages - 1) { // !!! there's no "next" at all; just exit; setWaiting(NOTHING); } else { loadNextPage(); } return; } // if it got here, we have a loaded album (pictures might be missing, though) if ((m_pImageFltCkB->isChecked() && album(nNextAlbum).m_vpImages.empty()) || (m_pCdFltCkB->isChecked() && string::npos == album(nNextAlbum).m_strFormat.find("CD")) || (m_pTrackCntFltCkB->isChecked() && m_nExpectedTracks != cSize(album(nNextAlbum).m_vTracks))) // ttt2 MusicBrainz could perform better here, because it knows how many tracks are in an album without actually loading it; ttt2 redo the whole next() / prev() thing in a more logical way; perhaps separate next() from nextAlbum(); { continue; } if (-1 != nNextImage && 0 == album(nNextAlbum).m_vpImages[nNextImage]) { requestImage(nNextAlbum, nNextImage); return; } m_nCrtAlbum = nNextAlbum; m_nCrtImage = nNextImage; reloadGui(); return; } } //ttt2 perhaps filter by durations (but see first how it works without it) // like next(), but here there's no need to load result pages; another difference is that when m_bNavigateByAlbum is set and we are at the first album and some other picture than the first, it goes to the first picture; in the similar case, next doesn't do anything (going to the last picture doesn't feel right) void AlbumInfoDownloaderDlgImpl::previous() { LAST_STEP("AlbumInfoDownloaderDlgImpl::previous"); CB_ASSERT (PREV == m_eNavigDir); //if (NONE != m_eNavigDir) { return; } int nPrevAlbum (m_nCrtAlbum); int nPrevImage (m_nCrtImage); for (;;) { if (nPrevImage > 0 && !m_bNavigateByAlbum) { --nPrevImage; } else if (nPrevAlbum > 0) { // m_vAlbums contains a "prev" album, which is loaded CB_ASSERT (!album(nPrevAlbum - 1).m_strTitle.empty()); --nPrevAlbum; int nImgCnt (cSize(album(nPrevAlbum).m_vstrImageNames)); nPrevImage = (m_bNavigateByAlbum && nImgCnt > 0) ? 0 : nImgCnt - 1; // !!! it's OK if images don't exist for that album } else { // nothing left in m_vAlbums if (m_bNavigateByAlbum && nPrevImage > 0) { m_nCrtAlbum = 0; m_nCrtImage = 0; reloadGui(); } else { setWaiting(NOTHING); } return; } // if it got here, we have a loaded album (pictures might be missing, though) if ((m_pImageFltCkB->isChecked() && album(nPrevAlbum).m_vpImages.empty()) || (m_pCdFltCkB->isChecked() && string::npos == album(nPrevAlbum).m_strFormat.find("CD")) || // Discogs has one format, but MusicBrainz may have several, so find() is used instead of "==" (m_pTrackCntFltCkB->isChecked() && m_nExpectedTracks != cSize(album(nPrevAlbum).m_vTracks))) { continue; } if (-1 != nPrevImage && 0 == album(nPrevAlbum).m_vpImages[nPrevImage]) { requestImage(nPrevAlbum, nPrevImage); return; } m_nCrtAlbum = nPrevAlbum; m_nCrtImage = nPrevImage; reloadGui(); return; } } void AlbumInfoDownloaderDlgImpl::setImageType(const string& strName) { LAST_STEP("AlbumInfoDownloaderDlgImpl::setImageType"); m_eLoadingImageCompr = ImageInfo::INVALID; string::size_type m (strName.rfind('.')); if (string::npos != m) { string s; ++m; for (; m < strName.size(); ++m) { s += tolower(strName[m]); } if (s == "jpg" || s == "jpeg") { m_eLoadingImageCompr = ImageInfo::JPG; } else if (s == "png") { m_eLoadingImageCompr = ImageInfo::PNG; } } } void AlbumInfoDownloaderDlgImpl::onRequestFinished(int /*nId*/, bool bError) { LAST_STEP("AlbumInfoDownloaderDlgImpl::onRequestFinished"); //cout << "received ID = " << nId << endl; //if (1 == nId) { return; } // some automatically generated request, which should be ignored if (bError) { addNote(tr("request error")); resetNavigation(); return; } QHttp* pQHttp (getWaitingHttp()); qint64 nAv (pQHttp->bytesAvailable()); if (0 == nAv) { //addNote("received empty response"); //cout << "empty request returned\n"; //m_eState = NORMAL; // !!! DON'T set m_eWaiting to NOTHING; empty responses come for no identifiable requests and they should be just ignored return; } CB_ASSERT (NOTHING != m_eWaiting); { QString qstrMsg (tr("received %1 bytes").arg(nAv)); addNote(qstrMsg); } QString qstrXml; QByteArray b (pQHttp->readAll()); CB_ASSERT (b.size() == nAv); if (nAv < 10) { // too short addNote(tr("received very short response; aborting request ...")); resetNavigation(); return; } if (IMAGE == m_eWaiting) { QString qstrInfo; QImage img; QByteArray comprImg (b); ImageInfo::Compr eOrigCompr (m_eLoadingImageCompr); if (img.loadFromData(b)) //ttt2 not sure what happens for huge images; { qstrInfo = tr("Original: %1kB, %2x%3").arg(nAv/1024).arg(img.width()).arg(img.height()); //cout << "image size " << img.width() << "x" << img.height() << endl; int nWidth (img.width()), nHeight (img.height()); if (nAv > ImageInfo::MAX_IMAGE_SIZE || m_eLoadingImageCompr == ImageInfo::INVALID) { QImage scaledImg; ImageInfo::compress(img, scaledImg, comprImg); nWidth = scaledImg.width(); nHeight = scaledImg.height(); m_eLoadingImageCompr = ImageInfo::JPG; //cout << "scaled image size " << img.width() << "x" << img.height() << endl; qstrInfo += tr("\nRecompressed to: %1kB, %2x%3").arg(comprImg.size()/1024).arg(img.width()).arg(img.height()); } else { qstrInfo += tr("\nNot recompressed"); } onImageLoaded(comprImg, nWidth, nHeight, qstrInfo); } else { showCritical(this, tr("Error"), tr("Failed to load the image")); const int SIZE (150); QImage errImg (SIZE, SIZE, QImage::Format_ARGB32); QPainter pntr (&errImg); pntr.fillRect(0, 0, SIZE, SIZE, QColor(255, 128, 128)); pntr.drawRect(0, 0, SIZE - 1, SIZE - 1); pntr.drawText(QRectF(0, 0, SIZE, SIZE), Qt::AlignCenter, tr("Error")); qstrInfo = tr("Error loading image\n"); comprImg.clear(); QBuffer bfr (&comprImg); errImg.save(&bfr, "png"); m_eLoadingImageCompr = ImageInfo::PNG; onImageLoaded(comprImg, SIZE, SIZE, qstrInfo); } if (m_bSaveResults) { saveDownloadedData(b.constData(), b.size(), (ImageInfo::JPG == eOrigCompr ? "jpg" : (ImageInfo::PNG == eOrigCompr ? "png" : "unkn"))); } return; } if (0x1f == (unsigned char)b[0] && 0x8b == (unsigned char)b[1]) { // gzip z_stream strm; strm.zalloc = Z_NULL; strm.zfree = Z_NULL; strm.opaque = 0; strm.next_in = const_cast(reinterpret_cast(b.constData())); strm.avail_in = nAv; vector v (nAv); //int nRes (inflateInit(&strm)); int nRes (inflateInit2(&strm, 16 + 15)); // !!! see libz.h for details; "32" makes this able to handle both gzip and zlib, by auto-detecting the format; 16 is used to force gzip if (Z_OK != nRes) { addNote(tr("init error")); goto e2; } strm.next_out = reinterpret_cast(&v[0]); strm.avail_out = v.size(); //cout << (void*)strm.next_in << " " << strm.avail_in << " " << (void*)strm.next_out << " " << strm.avail_out << endl; for (;;) { nRes = inflate(&strm, Z_SYNC_FLUSH); // Z_FINISH //cout << (void*)strm.next_in << " " << strm.avail_in << " " << (void*)strm.next_out << " " << strm.avail_out << endl; if (Z_STREAM_END == nRes) { break; } if (Z_OK == nRes) { // extend the buffer v.resize(v.size() + 128); strm.next_out = reinterpret_cast(&v[v.size() - 128]); strm.avail_out = 128; continue; } addNote(tr("unexpected result")); goto e2; } { int nUncomprSize (reinterpret_cast(strm.next_out) - &v[0]); v.resize(nUncomprSize + 1); v[nUncomprSize] = 0; qstrXml = &v[0]; } e2: inflateEnd(&strm); } else { // it's not gzip, so perhaps it is ASCII; //ttt2 check that it's ASCII qstrXml = b; } if (qstrXml.isEmpty()) { addNote(tr("empty string received")); } else { if (m_bSaveResults) { QByteArray b1 (qstrXml.toUtf8()); saveDownloadedData(b1.constData(), b1.size(), "xml"); } } switch (m_eWaiting) { case ALBUM: onAlbumLoaded(qstrXml); break; case SEARCH: onSearchLoaded(qstrXml); break; default: CB_ASSERT (false); } } string AlbumInfoDownloaderDlgImpl::getTempName() // time-based, with no extension; doesn't check for existing names, but uses a counter, so files shouldn't get removed (except during daylight saving time changes) { LAST_STEP("AlbumInfoDownloaderDlgImpl::getTempName"); time_t t (time(0)); if (t == m_nLastTime) { ++m_nLastCount; } else { m_nLastTime = t; m_nLastCount = 0; } char a [50]; #ifndef WIN32 ctime_r(&t, &a[0]); #else strcpy(a, ctime(&t)); //ttt2 try to get rid of ctime #endif string s; const char* p (&a[0]); for (; 0 != *p; ++p) { char c (*p); if ('\n' == c || '\r' == c) { break; } s += ':' == c ? '.' : c; } sprintf(a, ".%03d", m_nLastCount); return s; } void AlbumInfoDownloaderDlgImpl::retryNavigation() { LAST_STEP("AlbumInfoDownloaderDlgImpl::retryNavigation"); if (NEXT == m_eNavigDir) { next(); } else if (PREV == m_eNavigDir) { previous(); } else { CB_ASSERT (false); } if (NOTHING == m_eWaiting) { resetNavigation(); } // ttt2 perhaps add assert that either there are no pending requests and NOTHING==m_eWaiting or there is 1 pending request and NOTHING!=m_eWaiting (keep in mind that when the connection is opened there is a system-generated request); } void AlbumInfoDownloaderDlgImpl::onSearchLoaded(const QString& qstrXml) { LAST_STEP("AlbumInfoDownloaderDlgImpl::onSearchLoaded"); addNote(tr("search results received")); QByteArray b (qstrXml.toLatin1()); QBuffer bfr (&b); //SearchXmlHandler hndl (*this); auto_ptr pHndl (getSearchXmlHandler()); QXmlDefaultHandler& hndl (*pHndl); QXmlSimpleReader rdr; rdr.setContentHandler(&hndl); rdr.setErrorHandler(&hndl); QXmlInputSource src (&bfr); if (!rdr.parse(src)) { showCritical(this, tr("Error"), tr("Couldn't process the search result. (Usually this means that the server is busy, so trying later might work.)")); if (0 == getAlbumCount()) { m_nTotalPages = 0; m_nLastLoadedPage = -1; } resetNavigation(); return; } if (0 == getAlbumCount() && m_nLastLoadedPage == m_nTotalPages - 1) { showCritical(this, tr("Error"), tr("No results found")); } retryNavigation(); } void AlbumInfoDownloaderDlgImpl::onAlbumLoaded(const QString& qstrXml) { LAST_STEP("AlbumInfoDownloaderDlgImpl::onAlbumLoaded"); addNote(tr("album info received")); QByteArray b (qstrXml.toLatin1()); QBuffer bfr (&b); //AlbumXmlHandler hndl (album(m_nLoadingAlbum)); auto_ptr pHndl (getAlbumXmlHandler(m_nLoadingAlbum)); QXmlDefaultHandler& hndl (*pHndl); QXmlSimpleReader rdr; rdr.setContentHandler(&hndl); rdr.setErrorHandler(&hndl); QXmlInputSource src (&bfr); if (!rdr.parse(src)) { //CB_ASSERT (false); showCritical(this, tr("Error"), tr("Couldn't process the album information. (Usually this means that the server is busy, so trying later might work.)")); /*if (0 == getAlbumCount()) { m_nTotalPages = 0; m_nLastLoadedPage = -1; }*/ resetNavigation(); return; } //cout << album(m_nLoadingAlbum); retryNavigation(); } void AlbumInfoDownloaderDlgImpl::onImageLoaded(const QByteArray& comprImg, int nWidth, int nHeight, const QString& qstrInfo) { LAST_STEP("AlbumInfoDownloaderDlgImpl::onImageLoaded"); addNote(tr("image received")); CB_ASSERT (0 == album(m_nLoadingAlbum).m_vpImages[m_nLoadingImage]); CB_ASSERT (ImageInfo::INVALID != m_eLoadingImageCompr); album(m_nLoadingAlbum).m_vpImages[m_nLoadingImage] = new ImageInfo(-1, ImageInfo::OK, m_eLoadingImageCompr, comprImg, nWidth, nHeight); album(m_nLoadingAlbum).m_vstrImageInfo[m_nLoadingImage] = convStr(qstrInfo); m_vpImages.push_back(album(m_nLoadingAlbum).m_vpImages[m_nLoadingImage]); retryNavigation(); } void AlbumInfoDownloaderDlgImpl::addNote(const QString& qstrNote) { LAST_STEP("AlbumInfoDownloaderDlgImpl::addNote"); QString q (m_pDownloadsM->toPlainText()); if (!q.isEmpty()) { q += "\n"; } { QTime t (QTime::currentTime()); char a [15]; sprintf(a, "%02d:%02d:%02d.%03d ", t.hour(), t.minute(), t.second(), t.msec()); q += a; } q += qstrNote; m_pDownloadsM->setText(q); QScrollBar* p (m_pDownloadsM->verticalScrollBar()); if (p->isVisible()) { p->setValue(p->maximum()); } } void AlbumInfoDownloaderDlgImpl::setWaiting(Waiting eWaiting) { LAST_STEP("AlbumInfoDownloaderDlgImpl::setWaiting"); if (NOTHING == m_eWaiting && NOTHING != eWaiting) { QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); } if (NOTHING != m_eWaiting && NOTHING == eWaiting) { QApplication::restoreOverrideCursor(); } m_eWaiting = eWaiting; } void AlbumInfoDownloaderDlgImpl::reloadGui() { LAST_STEP("AlbumInfoDownloaderDlgImpl::reloadGui"); resetNavigation(); if (0 == getAlbumCount()) { return; } CB_ASSERT (m_nCrtAlbum >= 0 && m_nCrtAlbum < getAlbumCount()); //const MusicBrainzAlbumInfo& album (m_vAlbums[m_nCrtAlbum]); const WebAlbumInfoBase& albumInfo (album(m_nCrtAlbum)); m_pFormatE->setText(convStr(albumInfo.m_strFormat)); m_pRealeasedE->setText(convStr(albumInfo.m_strReleased)); //m_pTrackListL->clear(); updateTrackList(); set sstrPrefixes; for (int i = 0, n = cSize(albumInfo.m_vTracks); i < n; ++i) { const TrackInfo& trk (albumInfo.m_vTracks[i]); const string& s1 (trk.m_strPos); string::size_type k (s1.find_last_not_of("0123456789")); if (string::npos != k && s1.size() - 1 != k) { sstrPrefixes.insert(s1.substr(0, k + 1)); } } m_pVolumeCbB->clear(); if (sstrPrefixes.empty() || sstrPrefixes.size() == albumInfo.m_vTracks.size()) { m_pVolumeCbB->setEnabled(false); m_pVolumeL->setEnabled(false); } else { m_pVolumeCbB->setEnabled(true); m_pVolumeL->setEnabled(true); for (set::iterator it = sstrPrefixes.begin(); it != sstrPrefixes.end(); ++it) { m_pVolumeCbB->addItem(convStr(*it)); } m_pVolumeCbB->addItem(tr("")); } QString q1 (m_nTotalPages == m_nLastLoadedPage + 1 ? "" : "+"); QString s (tr("Album %1/%2%3, image %4/%5").arg(m_nCrtAlbum + 1).arg(getAlbumCount()).arg(q1).arg(m_nCrtImage + 1).arg(albumInfo.m_vpImages.size())); m_pResultNoL->setText(s); m_pResArtistE->setText(convStr(albumInfo.m_strArtist)); m_pResAlbumE->setText(convStr(albumInfo.m_strTitle)); if (-1 == m_nCrtImage || 0 == albumInfo.m_vpImages[m_nCrtImage]) { m_pImageL->setPixmap(0); m_pImageL->setText(""); m_pImgSizeL->setText(tr("No image\n")); } else { m_pImageL->setPixmap(QPixmap::fromImage(albumInfo.m_vpImages[m_nCrtImage]->getImage(m_pImageL->width(), m_pImageL->height()))); m_pImgSizeL->setText(convStr(albumInfo.m_vstrImageInfo[m_nCrtImage])); } } /*override*/ void AlbumInfoDownloaderDlgImpl::updateTrackList() { LAST_STEP("AlbumInfoDownloaderDlgImpl::updateTrackList"); m_pModel->emitLayoutChanged(); SimpleQTableViewWidthInterface intf (*m_pTrackListG); ColumnResizer rsz (intf, 100, ColumnResizer::DONT_FILL, ColumnResizer::CONSISTENT_RESULTS); } /*static*/ string AlbumInfoDownloaderDlgImpl::removeParentheses(const string& s) { string r; int k1 (0), k2 (0), k3 (0), k4 (0); for (int i = 0, n = cSize(s); i < n; ++i) { char c (s[i]); if ('(' == c) { ++k1; } if ('[' == c) { ++k2; } if ('{' == c) { ++k3; } if ('<' == c) { ++k3; } if (0 == k1 && 0 == k2 && 0 == k3 && 0 == k4) { r += c; } if (')' == c) { --k1; } if (']' == c) { --k2; } if ('}' == c) { --k3; } if ('>' == c) { --k4; } } trim(r); return r; } void AlbumInfoDownloaderDlgImpl::saveDownloadedData(const char* p, int nSize, const char* szExt) { LAST_STEP("AlbumInfoDownloaderDlgImpl::saveDownloadedData"); string s (getTempName() + "." + szExt); ofstream_utf8 out (s.c_str(), ios::binary); out.write(p, nSize); } void AlbumInfoDownloaderDlgImpl::onHelp() { openHelp("200_discogs_query.html"); } //========================================================================================================================== //========================================================================================================================== //========================================================================================================================== void addIfMissing(string& strDest, const string& strSrc) { if (strDest.empty()) { strDest = strSrc; } else { if (string::npos == strDest.find(strSrc)) { strDest += ", " + strSrc; } } } // splits a string based on a separator, putting the components in a vector; trims the substrings; discards empty components; void split(const string& s, const string& sep, vector& v) { v.clear(); string::size_type j (0), k; int n (cSize(s)); do { k = s.find(sep, j); if (string::npos == k) { k = n; } string s1 (s.substr(j, k - j)); trim(s1); if (!s1.empty()) { v.push_back(s1); } j = k + 1; } while ((int)k < n); } void addList(string& strDest, const string& strSrc) { if (strDest.empty()) { strDest = strSrc; } else { vector v; split (strSrc, ",", v); for (int i = 0, n = cSize(v); i < n; ++i) { addIfMissing(strDest, v[i]); } } } //==================================================================================================================== //==================================================================================================================== //==================================================================================================================== WebDwnldModel::WebDwnldModel(AlbumInfoDownloaderDlgImpl& dwnld, QTableView& grid) : QAbstractTableModel(&dwnld), m_dwnld(dwnld), m_grid(grid) { } /*override*/ int WebDwnldModel::rowCount(const QModelIndex&) const { const WebAlbumInfoBase* p (m_dwnld.getCrtAlbum()); if (0 == p) { return 0; } return cSize(p->m_vTracks); } /*override*/ int WebDwnldModel::columnCount(const QModelIndex&) const { return m_dwnld.getColumnCount(); } /*override*/ QVariant WebDwnldModel::data(const QModelIndex& index, int nRole) const { //LAST_STEP("WebDwnldModel::data()"); if (!index.isValid()) { return QVariant(); } if (nRole != Qt::DisplayRole && nRole != Qt::ToolTipRole) { return QVariant(); } int i (index.row()), j (index.column()); //if (nRole == Qt::CheckStateRole && j == 2) { return Qt::Checked; } const WebAlbumInfoBase* p (m_dwnld.getCrtAlbum()); if (0 == p || i >= cSize(p->m_vTracks)) { if (nRole == Qt::ToolTipRole) { return ""; } return QVariant(); } const TrackInfo& t (p->m_vTracks[i]); string s; switch (j) { case 0: s = t.m_strPos; break; case 1: s = t.m_strTitle; break; case 2: s = t.m_strArtist; break; case 3: s = t.m_strComposer; break; default: CB_ASSERT (false); } QString qs (convStr(s)); if (nRole == Qt::ToolTipRole) { QFontMetrics fm (m_grid.fontMetrics()); // !!! no need to use "QApplication::fontMetrics()" int nWidth (fm.width(qs)); if (nWidth + 10 < m_grid.horizontalHeader()->sectionSize(j)) // ttt2 "10" is hard-coded { //return QVariant(); return ""; // !!! with "return QVariant()" the previous tooltip remains until the cursor moves over another cell that has a tooltip }//*/ return qs; } return qs; } /*override*/ QVariant WebDwnldModel::headerData(int nSection, Qt::Orientation eOrientation, int nRole /* = Qt::DisplayRole*/) const { if (nRole != Qt::DisplayRole) { return QVariant(); } if (Qt::Horizontal == eOrientation) { switch (nSection) { case 0: return tr("Pos"); case 1: return tr("Title"); case 2: return tr("Artist"); case 3: return tr("Composer"); default: CB_ASSERT (false); } } return nSection + 1; } MP3Diags-1.2.02/src/ConfigDlgImpl.h0000644000175000001440000002232011714052726015571 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef ConfigDlgImplH #define ConfigDlgImplH #include #include "ui_Config.h" #include "NoteFilterDlgImpl.h" // for NoteListElem and NoteListPainter //ttt2 perhaps move them in their own file #include "Transformation.h" #include "CommonTypes.h" //--------------------------------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------------------------------- class TransfConfig; class CustomTransfListPainter; class VisibleTransfPainter; class QSettings; class QStackedLayout; class CommonData; class ConfigDlgImpl; class ExternalToolsModel : public QAbstractTableModel { Q_OBJECT const ConfigDlgImpl* m_pConfigDlgImpl; //const CommonData* m_pCommonData; public: ExternalToolsModel(const ConfigDlgImpl* pConfigDlgImpl); /*override*/ int rowCount(const QModelIndex&) const; /*override*/ int columnCount(const QModelIndex&) const; /*override*/ QVariant data(const QModelIndex&, int) const; /*override*/ QVariant headerData(int nSection, Qt::Orientation eOrientation, int nRole = Qt::DisplayRole) const; void emitLayoutChanged() { emit layoutChanged(); } }; //--------------------------------------------------------------------------------------------------------------------- void initDefaultCustomTransf(int k, std::vector >& vv, CommonData* pCommonData); void initDefaultVisibleTransf(std::vector& v, CommonData* pCommonData); class ConfigDlgImpl : public QDialog, private Ui::ConfigDlg, public NoteListPainterBase // ttt2 NoteListPainterBase is used for the ignored notes, while for custom transforms there is a separate CustomTransfListPainter; this is confusing //ttt2 perhaps create IgnoredNotesPainter, but it's not straightforward, because of the use of protected members in NoteListPainterBase { Q_OBJECT TransfConfig& m_transfCfg; CommonData* m_pCommonData; bool m_bFull; void logState(const char* szPlace) const; /*override*/ std::string getTooltip(TooltipKey eTooltipKey) const; /*override*/ void reset(); CustomTransfListPainter* m_pCustomTransfListPainter; DoubleList* m_pCustomTransfDoubleList; std::vector > m_vvnCustomTransf; void selectCustomTransf(int k); // 0 <= k <= CUSTOM_TRANSF_CNT int m_nCurrentTransf; void getTransfData(); std::vector m_vpTransfButtons; std::vector m_vpTransfLabels; void refreshTransfText(int k); // 0 <= k <= CUSTOM_TRANSF_CNT QPalette m_defaultPalette; QPalette m_wndPalette; std::vector > m_vvnDefaultCustomTransf; VisibleTransfPainter* m_pVisibleTransfPainter; DoubleList* m_pVisibleTransfDoubleList; std::vector m_vnVisibleTransf; std::vector m_vnDefaultVisibleTransf; void selectDir(QLineEdit*); QByteArray m_codepageTestText; QFont m_generalFont; QFont m_fixedFont; void setFontLabels(); std::vector m_vpColButtons; std::vector m_vNoteCategColors; void setBtnColor(int n); void onButtonClicked(int n); QStackedLayout* m_pFileSettingsLayout; TransfConfig::Options getOpt(); // has the correct m_bKeepOrigTime TransfConfig::Options getSimpleViewOpt(); // doesn't set m_bKeepOrigTime TransfConfig::Options getFullViewOpt(); // doesn't set m_bKeepOrigTime void setSimpleViewOpt(const TransfConfig::Options& opt); // m_bKeepOrigTime shouldn't be set void setFullViewOpt(const TransfConfig::Options& opt); // m_bKeepOrigTime is ignored ExternalToolsModel* m_pExternalToolsModel; /*override*/ void resizeEvent(QResizeEvent* pEvent); #if 0 /*override*/ void closeEvent(QCloseEvent* pEvent); /*override*/ bool eventFilter(QObject* pObj, QEvent* pEvent); #endif void resizeWidgets(); void tableToEdit(); void editToTable(); bool m_bExtToolChanged; void markExtToolChanged() { m_bExtToolChanged = true; } ExternalToolInfo externalToolInfoFromEdit(); public: enum { SOME_TABS, ALL_TABS }; ConfigDlgImpl(TransfConfig& transfCfg, CommonData* pCommonData, QWidget* pParent, bool bFull); // bFull determines if all the tabs should be visible ~ConfigDlgImpl(); /*$PUBLIC_FUNCTIONS$*/ bool run(); std::vector m_vExternalToolInfos; public slots: /*$PUBLIC_SLOTS$*/ void on_m_pOkB_clicked(); void on_m_pCancelB_clicked(); void on_m_pCustomTransform1B_clicked() { selectCustomTransf(0); } void on_m_pCustomTransform2B_clicked() { selectCustomTransf(1); } void on_m_pCustomTransform3B_clicked() { selectCustomTransf(2); } void on_m_pCustomTransform4B_clicked() { selectCustomTransf(3); } // CUSTOM_TRANSF_CNT void onTransfDataChanged(); void on_m_pSelectSrcDirB_clicked() { selectDir(m_pSrcDirE); } void on_m_pSelectTempDirB_clicked() { selectDir(m_pTempDestE); } void on_m_pSelectCompDirB_clicked() { selectDir(m_pCompDestE); } void on_m_pSelectOrigTransfDestDirB_clicked() { selectDir(m_pPODestE); } void on_m_pSelectOrigTransfDestDir2B_clicked() { selectDir(m_pPODest2E); } void on_m_pSelectOrigNotTransfDestDirB_clicked() { selectDir(m_pUODestE); } void on_m_pSelectTransfDestDirB_clicked() { selectDir(m_pProcDestE); } void on_m_pChangeGenFontB_clicked(); void on_m_pChangeFixedFontB_clicked(); void on_m_pId3LocaleCbB_currentIndexChanged(int); void on_m_pCol0B_clicked() { onButtonClicked(0); } void on_m_pCol1B_clicked() { onButtonClicked(1); } void on_m_pCol2B_clicked() { onButtonClicked(2); } void on_m_pCol3B_clicked() { onButtonClicked(3); } void on_m_pCol4B_clicked() { onButtonClicked(4); } void on_m_pCol5B_clicked() { onButtonClicked(5); } void on_m_pCol6B_clicked() { onButtonClicked(6); } void on_m_pCol7B_clicked() { onButtonClicked(7); } void on_m_pCol8B_clicked() { onButtonClicked(8); } void on_m_pCol9B_clicked() { onButtonClicked(9); } void on_m_pCol10B_clicked() { onButtonClicked(10); } void on_m_pCol11B_clicked() { onButtonClicked(11); } void on_m_pCol12B_clicked() { onButtonClicked(12); } void on_m_pCol13B_clicked() { onButtonClicked(13); } void on_m_pResetColorsB_clicked(); void on_m_pFastSaveCkB_stateChanged(); void on_m_pSimpleViewB_clicked(); void on_m_pFullViewB_clicked(); void on_m_pExtToolAddB_clicked(); void on_m_pExtToolUpdateB_clicked(); void on_m_pExtToolDeleteB_clicked(); void on_m_pExtToolDiscardB_clicked(); void onHelp(); //void on_m_pExternalToolsG_currentChanged(const QModelIndex& /*current*/, const QModelIndex& /*previous*/) { tableToEdit(); } // ttt3 see if this might be made to work (currently cannot even connect automatically, and m_pExternalToolsG->selectionModel() is used instead; there are some comments in MainFormDlgImpl.cpp, around "the next time") void onExternalToolsGCurrentChanged() { tableToEdit(); } protected slots: void onResizeDelayed() { resizeWidgets(); } void on_m_pExtToolNameE_textEdited(const QString &) { markExtToolChanged(); } void on_m_pExtToolCmdE_textEdited(const QString &) { markExtToolChanged(); } void on_m_pExtToolDontWaitRB_clicked() { markExtToolChanged(); } void on_m_pExtToolWaitKeepOpenRB_clicked() { markExtToolChanged(); } void on_m_pExtToolWaitCloseRB_clicked() { markExtToolChanged(); } void on_m_pExtToolConfirmLaunchCkB_clicked() { markExtToolChanged(); } void on_m_pMainTabWidget_currentChanged(int nIndex); }; #endif MP3Diags-1.2.02/src/Id3V230Stream.cpp0000444000175000001440000010424212361700641015611 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include "Id3V230Stream.h" #include "Id3V240Stream.h" #include "Helpers.h" #include "Id3Transf.h" #include "CommonData.h" using namespace std; using namespace pearl; //============================================================================================================ //============================================================================================================ //============================================================================================================ // !!! Note that the exceptions thrown by Id3V230Frame::Id3V230Frame() pass through Id3V230Stream, so they should have a type suited for this situation. // Also note that by the time Id3V230Frame::Id3V230Frame() gets executed, it was already decided that we deal with an Id3V230Stream. Now we should see if it is valid or broken. Id3V230Frame::Id3V230Frame(NoteColl& notes, istream& in, streampos pos, bool bHasUnsynch, streampos posNext, StringWrp* pFileName) : Id3V2Frame(pos, bHasUnsynch, pFileName) { in.seekg(pos); char bfr [ID3_FRAME_HDR_SIZE]; int nHdrBytesSkipped (0); int nRead (readID3V2(bHasUnsynch, in, bfr, ID3_FRAME_HDR_SIZE, posNext, nHdrBytesSkipped)); MP3_CHECK (ID3_FRAME_HDR_SIZE == nRead || (nRead >= 1 && 0 == bfr[0]), pos, id3v2FrameTooShort, StreamIsBroken(Id3V230Stream::getClassDisplayName(), tr("Truncated ID3V2.3.0 tag."))); unsigned char* p (reinterpret_cast (bfr)); if (0 == bfr[0]) { // padding //m_nMemDataSize = -1; // !!! not needed; the constructor makes it -1 //m_nDiskDataSize = -1; // !!! not needed; the constructor makes it -1 m_szName[0] = 0; return; } //inspect(bfr, ID3_FRAME_HDR_SIZE); strncpy(m_szName, bfr, 4); m_szName[4] = 0; m_nMemDataSize = (p[4] << 24) + (p[5] << 16) + (p[6] << 8) + (p[7] << 0); MP3_CHECK (m_nMemDataSize >= 0 && m_nMemDataSize < 5000000, pos, id3v230CantReadFrame, StreamIsBroken(Id3V230Stream::getClassDisplayName(), tr("Broken ID3V2.3.0 tag."))); { char c (m_szName[0]); MP3_CHECK (c >= 'A' && c <= 'Z', pos, id3v2InvalidName, StreamIsBroken(Id3V230Stream::getClassDisplayName(), tr("ID3V2.3.0 tag containing a frame with an invalid name: %1.").arg(convStr(getReadableName())))); for (int i = 1; i < 4; ++i) { char c (m_szName[i]); MP3_CHECK ((c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'), pos, id3v2InvalidName, StreamIsBroken(Id3V230Stream::getClassDisplayName(), tr("ID3V2.3.0 tag containing a frame with an invalid name: %1.").arg(convStr(getReadableName())))); //ttt3 ASCII-specific } } m_cFlag1 = bfr[8]; m_cFlag2 = bfr[9]; //MP3_CHECK (0 == (m_cFlag1 & ~(0x40 | 0x1f)), pos, "Invalid ID3V2.3.0 frame. Flags1 not supported.", Id3V230Stream::NotId3V2()); // !!! 0x1f is for flags that are supposed to be always 0 but aren't; the best way to deal with them seems to be to ignore them // 2008.08.25 - seems best to just ignore these flags ("Tag alter preservation", "File alter preservation" and "Read only") //ttt2 revisit this decision MP3_CHECK (0 == (m_cFlag2 & ~(0x00 | 0x1f)), pos, id3v2UnsuppFlags2, StreamIsUnsupported(Id3V230Stream::getClassDisplayName(), tr("ID3V2.3.0 tag containing a frame with an unsupported flag."))); if (0 != (m_cFlag1 & 0x1f)) { MP3_NOTE (pos, id3v2IncorrectFlg1); } if (0 != (m_cFlag2 & 0x1f)) { MP3_NOTE (pos, id3v2IncorrectFlg2); } m_vcData.resize(m_nMemDataSize); int nContentBytesSkipped (0); nRead = 0; if (m_nMemDataSize > 0) { nRead = readID3V2(bHasUnsynch, in, &m_vcData[0], m_nMemDataSize, posNext, nContentBytesSkipped); } if (m_nMemDataSize != nRead) { vector().swap(m_vcData); MP3_THROW (pos, id3v2FrameTooShort, StreamIsBroken(Id3V230Stream::getClassDisplayName(), tr("Truncated ID3V2.3.0 tag."))); } m_nDiskHdrSize = ID3_FRAME_HDR_SIZE + nHdrBytesSkipped; m_nDiskDataSize = m_nMemDataSize + nContentBytesSkipped; try { try { getUtf8String(); Id3V2FrameDataLoader wrp (*this); const char* pData (wrp.getData()); if ('T' == m_szName[0]) { if (!isTxxx() && 0 == m_nMemDataSize) { // this is really invalid; text frames must have at least a byte; however, by doing these we make sure that an empty (i.e. having a single byte, for text encoding) frame gets copied if the tag is edited; m_vcData.clear(); m_vcData.push_back(0); m_nMemDataSize = cSize(m_vcData); MP3_NOTE_D (pos, id3v2EmptyTextFrame, tr("%1 (Frame: %2)").arg(noteTr(id3v2EmptyTextFrame)).arg(m_szName)); } else if (isTxxx() && m_nMemDataSize <= 1) { // this is really invalid; text frames must have at least a byte; however, by doing these we make sure that an empty (i.e. having a single byte, for text encoding) frame gets copied if the tag is edited; m_vcData.clear(); string strInvalid (convStr(tr("INVALID"))); const char* szInvalid (strInvalid.c_str()); m_vcData.push_back(0); // latin1 m_vcData.insert(m_vcData.end(), szInvalid, szInvalid + strlen(szInvalid)); // description m_vcData.push_back(0); // terminator m_vcData.insert(m_vcData.end(), szInvalid, szInvalid + strlen(szInvalid)); // value m_nMemDataSize = cSize(m_vcData); MP3_NOTE_D (pos, id3v2EmptyTextFrame, tr("%1 (Frame: %2)").arg(noteTr(id3v2EmptyTextFrame)).arg(m_szName)); // ttt2 actually TXXX is not text } else { if (3 == pData[0]) { //MP3_NOTE (pos, id3v230UsesUtf8); MP3_NOTE_D (pos, id3v230UsesUtf8, tr("%1 (Frame: %2)").arg(noteTr(id3v230UsesUtf8)).arg(m_szName)); // perhaps drop the frame name if too many such notes get generated } } //ttt2 add check for embedded 0 for TXXX, to split the value into descr and val } } catch (const NotId3V2Frame&) { MP3_THROW (pos, id3v2TextError, StreamIsBroken(Id3V230Stream::getClassDisplayName(), tr("ID3V2.3.0 tag containing a broken text frame named %1.").arg(convStr(getReadableName())))); } catch (const UnsupportedId3V2Frame&) { MP3_THROW (pos, id3v230UnsuppText, StreamIsUnsupported(Id3V230Stream::getClassDisplayName(), tr("ID3V2.3.0 tag containing a text frame named %1 using unsupported characters.").arg(convStr(getReadableName())))); //ttt2 NotSupported is not specific enough; this might need reviewing in the future } } catch (const std::bad_alloc&) { throw; } catch (...) { vector().swap(m_vcData); throw; } if (m_nMemDataSize > 150) // ttt2 perhaps make configurable { // if the frame needs a lot of space, erase the data from memory; it will be retrieved from the disk when needed; vector().swap(m_vcData); } } // needed by Id3V230StreamWriter::addBinaryFrame(), so objects created with this constructor don't get serialized; destroys vcData by doing a swap for its own representation Id3V230Frame::Id3V230Frame(const std::string& strName, vector& vcData) : Id3V2Frame(0, Id3V2Frame::NO_UNSYNCH, 0) { CB_CHECK1 (4 == cSize(strName), NotId3V2Frame()); strcpy(m_szName, strName.c_str()); m_nMemDataSize = cSize(vcData); m_nDiskDataSize = -1; m_nDiskHdrSize = -1; m_cFlag1 = 0; m_cFlag2 = 0; // m_pos = ...; //m_szFileName = 0; //m_bHasUnsynch = false; vcData.swap(m_vcData); } /*override*/ Id3V230Frame::~Id3V230Frame() { //qDebug("Id3V230Frame destr %p", this); //qDebug(" Id3V230Frame destr %p", m_pData); } // may return multiple null characters; it's the job of getUtf8String() to deal with them; // chars after the first null are considered comments (or after the second null, for TXXX); string Id3V230Frame::getUtf8StringImpl() const { if ('T' != m_szName[0]) { return convStr(TagReader::tr("")); } // have a "pos" to pass here and ability to log; could also be used for large tags, to not store them in memory yet have them available // also use pos below // or perhaps forget these and throw exceptions that have error messages, catch them and log/show dialogs // 2008.07.12 - actually all these proposals don't seem to work well: the callers don't catch the exceptions thrown here and wouldn't know what to do with them; there's no good place to display the errors (the end user isn't supposed to look at logs); it seems better to not throw, but return a string describing the problem; // 2008.07.12 - on a second thought - throw but call this on the constructor, where it can be logged properly; if it worked on the constructor it should work later too CB_CHECK1 (m_nMemDataSize > 0, NotId3V2Frame()); //ttt2 perhaps other excp Id3V2FrameDataLoader wrp (*this); const char* pData (wrp.getData()); string s; if (0 == pData[0]) { // Latin-1 for (int i = 1; i < m_nMemDataSize; ++i) { unsigned char c (pData[i]); if (c < 128) { // !!! 0 is OK s += char(c); } else { m_bHasLatin1NonAscii = true; unsigned char c1 (0xc0 | (c >> 6)); unsigned char c2 (0x80 | (c & 0x3f)); s += char(c1); s += char(c2); } } } else if (1 == pData[0]) { s = utf8FromBomUtf16(pData + 1, m_nMemDataSize - 1); } else if (3 == pData[0]) // not valid, but used by some tools; a note will get generated in this case { s = string(pData + 1, m_nMemDataSize - 1); } else { // pData[0] has an invalid value CB_THROW1 (NotId3V2Frame()); } return s; } /*override*/ bool Id3V230Frame::discardOnChange() const { return 0 != (m_cFlag1 & 0xc0); } //============================================================================================================ //============================================================================================================ //============================================================================================================ Id3V230Stream::Id3V230Stream(int nIndex, NoteColl& notes, istream& in, StringWrp* pFileName, bool bAcceptBroken /* = false*/) : Id3V2StreamBase(nIndex, in, pFileName) { StreamStateRestorer rst (in); streampos pos (m_pos); char bfr [ID3_HDR_SIZE]; MP3_CHECK_T (ID3_HDR_SIZE == read(in, bfr, ID3_HDR_SIZE), pos, "Invalid ID3V2.3.0 tag. File too small.", NotId3V2()); MP3_CHECK_T ('I' == bfr[0] && 'D' == bfr[1] && '3' == bfr[2], pos, "Invalid ID3V2.3.0 tag. Invalid ID3V2 header.", NotId3V2()); MP3_CHECK ((3 == bfr[3] || 4 == bfr[3]) && 0 == bfr[4], pos, id3v2UnsuppVer, StreamIsUnsupported(Id3V2StreamBase::getClassDisplayName(), tr("Unsupported version of ID3V2 tag%1").arg((bfr[3] >= 0 && bfr[3] <=9 && bfr[4] >= 0 && bfr[4] <=9) ? QString(": ID3V2.") + char('0' + bfr[3]) + '.' + char('0' + bfr[4]) : "."))); // !!! tests for both 2.3.0 and 2.4.0 to make sure a SUPPORT message is generated (as opposed to TRACE); this test could be done either here or in ID3V240 (or in a separate function); MP3_CHECK_T (3 == bfr[3] && 0 == bfr[4], pos, "Invalid ID3V2.3.0 tag. Invalid ID3V2.3.0 header.", NotId3V2()); m_nTotalSize = getId3V2Size (bfr); m_cFlags = bfr[5]; MP3_CHECK (0 == (m_cFlags & 0x7f), pos, id3v2UnsuppFlag, StreamIsUnsupported(Id3V230Stream::getClassDisplayName(), tr("ID3V2 tag with unsupported flag."))); //ttt2 review, support streampos posNext (pos); posNext += m_nTotalSize; pos += ID3_HDR_SIZE; bool bHasLatin1NonAscii (false); // if it has a text frame that uses Latin1 encoding and has chars between 128 and 255 try { for (;;) { long long nDiff (pos - posNext); if (nDiff >= 0) { break; } Id3V230Frame* p (new Id3V230Frame(notes, in, pos, hasUnsynch(), posNext, m_pFileName)); bHasLatin1NonAscii = p->m_bHasLatin1NonAscii || bHasLatin1NonAscii; if (-1 == p->m_nMemDataSize) { // it encountered zeroes, which signals the beginning of padding //ttt2 should check that there's no garbage after the first zero m_nPaddingSize = posNext - pos; delete p; break; } m_vpFrames.push_back(p); pos += p->m_nDiskHdrSize + p->m_nDiskDataSize; } } catch (const std::bad_alloc&) { throw; } catch (...) { if (bAcceptBroken) { preparePicture(notes); return; } clearPtrContainer(m_vpFrames); throw; } preparePicture(notes); checkDuplicates(notes); checkFrames(notes); if (bHasLatin1NonAscii) { MP3_NOTE (m_pos, id3v2HasLatin1NonAscii); } switch (m_eImageStatus) { case ImageInfo::NO_PICTURE_FOUND: MP3_NOTE (m_pos, id3v2NoApic); break; case ImageInfo::ERROR_LOADING: MP3_NOTE (m_pos, id3v2CouldntLoadPic); break; //case ImageInfo::USES_LINK: MP3_NOTE (m_pos, id3v2LinkNotSupported); break; // !!! already reported by id3v2LinkInApic, so no need for another note; case ImageInfo::LOADED_NOT_COVER: MP3_NOTE (m_pos, id3v2NotCoverPicture); break; default:; } if (m_nPaddingSize > Id3V230StreamWriter::DEFAULT_EXTRA_SPACE + 4096) //ttt2 hard-coded { MP3_NOTE (m_pos + (getSize() - m_nPaddingSize), id3v2PaddingTooLarge); } if (m_vpFrames.empty()) { MP3_NOTE (m_pos, id3v2EmptyTag); } MP3_TRACE (m_pos, "Id3V230Stream built."); { pos = m_pos; pos += m_nTotalSize - 1; in.seekg(pos); char c; MP3_CHECK (1 == read(in, &c, 1), m_pos, id3v230CantReadFrame, NotId3V2()); } rst.setOk(); } /*override*/ TagReader::SuportLevel Id3V230Stream::getSupport(Feature eFeature) const { switch (eFeature) { case TITLE: case ARTIST: case TRACK_NUMBER: case TIME: case GENRE: case IMAGE: case ALBUM: case RATING: case COMPOSER: case VARIOUS_ARTISTS: return READ_ONLY; default: return NOT_SUPPORTED; } /*{ , , , , , , , }; enum SuportLevel { NOT_SUPPORTED, , READ_WRITE };*/ } /*override*/ TagTimestamp Id3V230Stream::getTime(bool* pbFrameExists /* = 0*/) const { return get230TrackTime(pbFrameExists); } //============================================================================================================ //============================================================================================================ //============================================================================================================ Id3V230StreamWriter::~Id3V230StreamWriter() { /*for (int i = 0; i < cSize(m_vpOwnFrames); ++i) { qDebug("remove %p", m_vpOwnFrames[i]); //qDebug(" remove %p", m_vpOwnFrames[i]->m_pData); }*/ clearPtrContainer(m_vpOwnFrames); } Id3V230StreamWriter::Id3V230StreamWriter(bool bKeepOneValidImg, bool bFastSave, Id3V2StreamBase* p, const std::string& strDebugFileName) : m_bKeepOneValidImg(bKeepOneValidImg), m_bFastSave(bFastSave), m_strDebugFileName(strDebugFileName) { if (0 != p) { if (m_bKeepOneValidImg) { const Id3V2Frame* pPic (0); for (int i = 0; i < cSize(p->getFrames()); ++i) { const Id3V2Frame* q (p->getFrames()[i]); CB_ASSERT1 ((0 == strcmp(q->m_szName, KnownFrames::LBL_IMAGE())) ^ (Id3V2Frame::NO_APIC == q->m_eApicStatus), m_strDebugFileName); // if the frame name is APIC, it should have some status that is not "NO_APIC", i.e. one of ERR, USES_LINK, NON_COVER, COVER if (Id3V2Frame::NO_APIC == q->m_eApicStatus) { bool bCopyFrame (true); if ('T' == q->m_szName[0] && q->m_nMemDataSize > 0 && 0 != dynamic_cast(q)) { // check for UTF-8 encoding Id3V2FrameDataLoader ldr (*q); const char* pData (ldr.getData()); if (3 == pData[0]) { // UTF-8 bCopyFrame = false; addTextFrame(q->m_szName, q->getRawUtf8String()); } } if (bCopyFrame) { m_vpAllFrames.push_back(q); } } else { if ((0 == pPic && q->m_eApicStatus > Id3V2Frame::ERR) || (0 != pPic && pPic->m_eApicStatus < q->m_eApicStatus)) { pPic = q; } } } if (0 != pPic) { m_vpAllFrames.push_back(pPic); } } else { m_vpAllFrames.insert(m_vpAllFrames.end(), p->getFrames().begin(), p->getFrames().end()); } TagTimestamp time; try { time = p->getTime(); } catch (const TagTimestamp::InvalidTime&) { } setRecTime(time); } } void Id3V230StreamWriter::setRecTime(const TagTimestamp& time) { removeFrames(KnownFrames::LBL_TIME_YEAR_230()); removeFrames(KnownFrames::LBL_TIME_DATE_230()); removeFrames(KnownFrames::LBL_TIME_240()); if (0 == time.getYear()[0]) { return; } addTextFrame(KnownFrames::LBL_TIME_YEAR_230(), time.getYear()); if (0 == time.getDayMonth()[0]) { return; } addTextFrame(KnownFrames::LBL_TIME_DATE_230(), time.getDayMonth()); } // this only changes the frames that correspond to the active settings in the configuration; // if WMP handling is disabled, TPE2 is left untouched; if WMP handling is enabled, TPE2 is either removed or set to "Various Artists", based on "b" // if iTunes handling is disabled, TCON is left untouched; if WMP handling is enabled, TCON is either removed or set to "1", based on "b" void Id3V230StreamWriter::setVariousArtists(bool b) { const CommonData* pCommonData (getCommonData()); if (pCommonData->m_bWmpVarArtists) { removeFrames(KnownFrames::LBL_WMP_VAR_ART()); if (b) { addTextFrame(KnownFrames::LBL_WMP_VAR_ART(), "Various Artists"); } } if (pCommonData->m_bItunesVarArtists) { removeFrames(KnownFrames::LBL_ITUNES_VAR_ART()); if (b) { addTextFrame(KnownFrames::LBL_ITUNES_VAR_ART(), "1"); } } } #ifdef MASWDEFWDWDWDW namespace { void view(QString q) { qDebug("str=%s, sz=%d", q.toUtf8().data(), q.size()); { qDebug("utf16()"); const ushort* pUtf16 (q.utf16()); for (int i = 0; 0 != pUtf16[i]; ++i) { qDebug(" %d", int(pUtf16[i])); } } { qDebug("toUcs4()"); QVector v (q.toUcs4()); for (int i = 0; i < v.size(); ++i) { qDebug(" %d", int(v[i])); } } } void testUnicode() //ttt2 retest this; for now it looks like everything is truncated to 16 bits { QString q; q += QChar(49); view(q); q += QChar(int(65536 + 65)); q += QChar(int(65536 + 650)); q += QChar(int(65536 + 6500)); q += QChar(int(65536 + 35000)); view(q); //q += QChar(int(0xd801)); // surrogate //view(q); } } #endif BOOST_STATIC_ASSERT(2 == sizeof(ushort)); // strVal is UTF8; the frame will use ASCII if possible and UTF16 otherwise (so if there's a char with a code above 127, UTF16 gets used, to avoid codepage issues for codes between 128 and 255); nothing is added if strVal is empty; all zeroes are saved and not considered terminators; void Id3V230StreamWriter::addTextFrame(const std::string& strName, const std::string& strVal) { //http://www.id3.org/id3v2.3.0#head-1a37d4a15deafc294208ccfde950f77e47000bca bool bIsAscii (true); int n (cSize(strVal)); for (int i = 0; i < n; ++i) { unsigned char c (strVal[i]); if (c >= 128) { bIsAscii = false; break; } } vector vcData; int nSize (0); if (bIsAscii) { nSize = n + 1; // strings are not null-terminated, but there's the "text encoding" byte vcData.resize(nSize); //qDebug("add %p", p); vcData[0] = 0; copy(strVal.c_str(), strVal.c_str() + nSize - 1, &vcData[0] + 1); } else { QString s (convStr(strVal)); const ushort* pUtf16 (s.utf16()); //int nUtf16Size (0); //while (0 != pUtf16[nUtf16Size++]) {} // there is probably some strlen equivalent, but wcslen isn't that, because it uses wchar_t instead of ushort, which on Unix is usually 4-byte //--nUtf16Size; int nUtf16Size (s.size()); //ttt2 not quite right, but it seems that QString truncates Unicode to 16 bits anyway, in which case it's fine; see testUnicode(), above nSize = 2*nUtf16Size + 3; // "text encoding" byte, BOM, no null terminator vcData.resize(nSize); vcData[0] = 1; #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN int nOrder (1); // x86 #else int nOrder (0); #endif vcData[2 - nOrder] = char(0xff); vcData[1 + nOrder] = char(0xfe); const char* pcUtf16 (reinterpret_cast(pUtf16)); copy(pcUtf16, pcUtf16 + nSize - 3, &vcData[0] + 3); } //inspect(p, nSize); addBinaryFrame(strName, vcData); } // destroys vcData by doing a swap for its own representation; asserts that strName is not APIC void Id3V230StreamWriter::addBinaryFrame(const std::string& strName, vector& vcData) { Id3V230Frame* p (new Id3V230Frame(strName, vcData)); CB_ASSERT1 (0 != strcmp(KnownFrames::LBL_IMAGE(), p->m_szName), m_strDebugFileName); addNonOwnedFrame(p); m_vpOwnFrames.push_back(p); } //ttt2 perhaps something like this: if there's 1 "Other" pic and a "Cover" is added; based on config: 1) delete "Other"; 2) delete "cover"; 3) keep both; //ttt2 the option should be used to mark an "Other" pic as "Cover" in the tag editor, so it doesn't trigger saving. // the image type is ignored; images are always added as cover; // if there is an APIC frame with the same image, it is removed (it doesn't matter if it has different type, description ...); // ttt2 description should be counted too, if used // if cover image already exists it is removed; void Id3V230StreamWriter::addImg(std::vector& vcData) { int n (cSize(vcData)); char* p (&vcData[0]); CB_ASSERT1 (0 == *p || 3 == *p, m_strDebugFileName); // text encoding // this should be kept in synch with Id3V2StreamBase::decodeApic() //ttt1 triggered according to mail; might have been caused by SmallerImageRemover::apply() incorrectly assuming that an invalid APIC frame is a large picture; the test using the frame name was replaced after the assert with a test using m_eApicStatus; will have to wait until some MP3 is received that triggered this to be sure char* q (p + 1); for (; q < p + 90 && 0 != *q; ++q) {} CB_ASSERT1 (0 == *q, m_strDebugFileName); ++q; *q = Id3V2Frame::PT_COVER; ++q; for (; q < p + n && 0 != *q; ++q) {} CB_ASSERT1 (0 == *q, m_strDebugFileName); ++q; // now q points to the beginning of the actual image int nOffs (q - p); int nImgSize (n - nOffs); removeFrames(KnownFrames::LBL_IMAGE(), Id3V2Frame::PT_COVER); { e1: for (int i = 0; i < cSize(m_vpAllFrames); ++i) { const Id3V2Frame* pFrm (m_vpAllFrames[i]); if (0 == strcmp(KnownFrames::LBL_IMAGE(), pFrm->m_szName)) { if (pFrm->m_nImgSize == nImgSize) { Id3V2FrameDataLoader ldr (*pFrm); if (0 == memcmp(&vcData[nOffs], ldr.getData() + pFrm->m_nImgOffset, nImgSize)) // !!! related to ImageInfo::operator==() status is ignored in both places //ttt2 review decision to ignore status { removeFrames(KnownFrames::LBL_IMAGE(), pFrm->m_nPictureType); goto e1; } } } } } { Id3V230Frame* p (new Id3V230Frame(KnownFrames::LBL_IMAGE(), vcData)); p->m_nPictureType = Id3V2Frame::PT_COVER; // probably pointless, because the frame is only used internally and what gets written is vcData, regardless of p->m_nPictureType addNonOwnedFrame(p); m_vpOwnFrames.push_back(p); } } // for UTF8 text frames, a new, owned, frame will be added instead void Id3V230StreamWriter::addNonOwnedFrame(const Id3V2Frame* p) { //qDebug("add %p", p); //qDebug(" add %p", pData); if (0 == strcmp(KnownFrames::LBL_IMAGE(), p->m_szName) || !KnownFrames::canHaveDuplicates(p->m_szName)) { removeFrames(p->m_szName, p->m_nPictureType); // m_vpAllFrames.insert(m_vpAllFrames.begin(), p); //ttt2 putting a front cover image after a back cover might not be the best idea; see if it makes sense to sort the frames; (perhaps have something to sort all frames before saving) } if ('T' == p->m_szName[0] && p->m_nMemDataSize > 0 && 0 != dynamic_cast(p)) { // check for UTF-8 encoding; for UTF8 an owned frame will be added instead Id3V2FrameDataLoader ldr (*p); const char* pData (ldr.getData()); if (3 == pData[0]) { // UTF-8 addTextFrame(p->m_szName, p->getRawUtf8String()); return; } } m_vpAllFrames.push_back(p); } // if multiple frames with the same name exist, they are all removed; asserts that nPictureType is -1 for non-APIC frames; if nPictureType is -1 and strName is APIC, it removes all APIC frames void Id3V230StreamWriter::removeFrames(const std::string& strName, int nPictureType /* = -1*/) { const Id3V2Frame* p; for (int i = cSize(m_vpAllFrames) - 1; i >= 0; --i) { p = m_vpAllFrames[i]; CB_ASSERT1 (-1 == nPictureType || KnownFrames::LBL_IMAGE() == strName, m_strDebugFileName); if (p->m_szName == strName && (p->m_nPictureType == nPictureType || m_bKeepOneValidImg || -1 == nPictureType)) { m_vpAllFrames.erase(m_vpAllFrames.begin() + i); } } for (int i = cSize(m_vpOwnFrames) - 1; i >= 0; --i) { p = m_vpOwnFrames[i]; CB_ASSERT1 (-1 == nPictureType || KnownFrames::LBL_IMAGE() == strName, m_strDebugFileName); if (p->m_szName == strName && (p->m_nPictureType == nPictureType || m_bKeepOneValidImg || -1 == nPictureType)) { delete p; m_vpOwnFrames.erase(m_vpOwnFrames.begin() + i); } } } //ttt2 after rescanning when note text changed, the refound error was not visible once (couldn't replicate) static int getUnsynchVal(int x) { return (x & 0x0000007f) | ((x & 0x00003f80) << 1) | ((x & 0x001fc000) << 2) | ((x & 0x0fe00000) << 3); } /*static*/ const int Id3V230StreamWriter::DEFAULT_EXTRA_SPACE (1024); // throws WriteError if it cannot write, including the case when nTotalSize is too small; // if nTotalSize is >0, the padding will be be whatever is left; // if nTotalSize is <0 and m_bFastSave is true, there will be a padding of around ImageInfo::MAX_IMAGE_SIZE+Id3V2Expander::EXTRA_SPACE; // if (nTotalSize is <0 and m_bFastSave is false) or if nTotalSize is 0 (regardless of m_bFastSave), there will be a padding of between DEFAULT_EXTRA_SPACE and DEFAULT_EXTRA_SPACE + 511; // (0 discards extra padding regardless of m_bFastSave) void Id3V230StreamWriter::write(ostream& out, int nTotalSize /* = -1*/) const { int n (cSize(m_vpAllFrames)); //if (0 == n) { return; } char bfr [Id3V230Stream::ID3_HDR_SIZE] = "ID3\3\0\0"; // no unsynch, no extended header, no experimental int nSize (0); for (int i = 0; i < n; ++i) { const Id3V2Frame* p (m_vpAllFrames[i]); if (!p->discardOnChange()) { nSize += Id3V2Frame::ID3_FRAME_HDR_SIZE + m_vpAllFrames[i]->m_nMemDataSize; } } //if (0 == nSize) { return; } // all frames have discardOnChange int nPaddedSize; if (nTotalSize > 0) { nPaddedSize = nTotalSize; } else if (0 == nTotalSize || !m_bFastSave) { nPaddedSize = ((nSize + (512 - 1) + DEFAULT_EXTRA_SPACE)/512)*512; } else { // <0 and m_bFastSave set nPaddedSize = nSize + ImageInfo::MAX_IMAGE_SIZE + Id3V2Expander::EXTRA_SPACE; } //if (nExtraSpace < 0) { nExtraSpace = ; } //int nPaddedSize (((nSize + (512 - 1) + nExtraSpace)/512)*512); nPaddedSize -= Id3V230Stream::ID3_HDR_SIZE; CB_CHECK1 (nPaddedSize >= nSize, WriteError()); //put32BitBigEndian(1, &bfr[6]); put32BitBigEndian(getUnsynchVal(nPaddedSize), &bfr[6]); out.write(bfr, 10); struct LdrPtrList { LdrPtrList(int n) : m_vpLdr(n) {} ~LdrPtrList() { clearPtrContainer(m_vpLdr); } vector m_vpLdr; }; LdrPtrList ldrList (n); for (int i = 0; i < n; ++i) { const Id3V2Frame* p (m_vpAllFrames[i]); if (!p->discardOnChange()) { ldrList.m_vpLdr[i] = new Id3V2FrameDataLoader (*p); // this loads the data, if it's not already loaded } } for (int i = 0; i < n; ++i) { const Id3V2Frame* p (m_vpAllFrames[i]); if (!p->discardOnChange()) { out.write(p->m_szName, 4); put32BitBigEndian(p->m_nMemDataSize, &bfr[0]); bfr[4] = 0; //bfr[4] = p->m_cFlag1; //ttt2 use flags if any makes sense bfr[5] = 0; //bfr[5] = p->m_cFlag2; out.write(bfr, 6); //Id3V2FrameDataLoader wrp (*p); // this loads the data, if it's not already loaded out.write(ldrList.m_vpLdr[i]->getData(), p->m_nMemDataSize); } } writeZeros(out, nPaddedSize - nSize); CB_CHECK1 (out, WriteError()); } namespace { struct SortFrm { bool operator()(const Id3V2Frame* p1, const Id3V2Frame* p2) { int n (strcmp(p1->m_szName, p2->m_szName)); if (0 != n) { return n < 0; } if (p1->m_nMemDataSize != p2->m_nMemDataSize) { return p1->m_nMemDataSize < p2->m_nMemDataSize; } Id3V2FrameDataLoader wrp1 (*p1); Id3V2FrameDataLoader wrp2 (*p2); n = memcmp(wrp1.getData(), wrp2.getData(), p1->m_nMemDataSize); return n < 0; } }; } // returns true if all of these happen: pId3V2Stream is ID3V2.3.0, no unsynch is used, the frames are identical except for their order; padding is ignored; /*bool Id3V230StreamWriter::equalTo(Id3V2StreamBase* p) const { if (0 == p) { //qDebug("diff ver"); return false; } if (p->hasUnsynch()) { //qDebug("diff unsynch"); return false; } return contentEqualTo(p); }*/ bool Id3V230StreamWriter::contentEqualTo(Id3V2StreamBase* p) const { const vector& v (p->getFrames()); if (cSize(m_vpAllFrames) != cSize(v)) { //qDebug("diff frame cnt"); return false; } vector v1 (m_vpAllFrames); vector v2; v2.insert(v2.end(), v.begin(), v.end()); SortFrm s; sort(v1.begin(), v1.end(), s); sort(v2.begin(), v2.end(), s); /*for (int i = 0, n = cSize(v1); i < n; ++i) { qDebug("frm %s %d %s %d", v1[i]->m_szName, v1[i]->m_nMemDataSize, v2[i]->m_szName, v2[i]->m_nMemDataSize); }//Id3V2Frame p; p.*/ for (int i = 0, n = cSize(v1); i < n; ++i) { if (s(v1[i], v2[i]) || s(v2[i], v1[i])) { //qDebug("diff content %s (%s) / %s (%s)", v1[i]->m_szName, v1[i]->getUtf8String1().c_str(), v2[i]->m_szName, v2[i]->getUtf8String1().c_str()); return false; } } return true; } //============================================================================================================ //============================================================================================================ //============================================================================================================ /* struct KOKO { KOKO() { Id3V230StreamWriter w; w.addTextFrame("TGRF", "abcd"); QString s ("ab\xfeg"); w.addTextFrame("TQQW", convStr(s)); ofstream_utf8 out ("/r/temp/1/tmp2/e/qqq.mp3", ios::binary); w.write(out); } }; KOKO qwerqwr; */ MP3Diags-1.2.02/src/Scan.ui0000644000175000001440000000312211700345717014164 0ustar ciobiusers ScanDlg 0 0 542 579 Scan true Qt::Horizontal Rescan files that seem unchanged Qt::Horizontal 40 20 &Scan Cancel MP3Diags-1.2.02/src/SessionEditor.ui0000644000175000001440000001250111714055552016073 0ustar ciobiusers SessionEditorDlg 0 0 737 566 pppppppppppppp true 0 75 45 :/images/preferences-desktop-locale.svg true Language 0 0 Include directories Backup 0 Don't create backup for modified files Create backup for modified files in true ... Settings file name true ... Scan for new, modified, and deleted files at startup true Open last session at s&tartup true Open the main "Sessions" dialog, to load an existing session Qt::Horizontal 40 20 O&K true Cancel MP3Diags-1.2.02/src/Id3V2Stream.h0000644000175000001440000003362111717734452015132 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef Id3V2StreamH #define Id3V2StreamH #include #include // for translation #include "DataStream.h" #include "SerSupport.h" // Frame of an ID3V2 tag struct Id3V2Frame { Q_DECLARE_TR_FUNCTIONS(Id3V2Frame) public: char m_szName[5]; int m_nMemDataSize; // size in memory, excluding header and unsynch int m_nDiskDataSize; // size including unsynchronization, excluding the header; if unsynch is used, m_nDiskDataSize>=m_nMemDataSize; if unsynch is not used, m_nDiskDataSize==m_nMemDataSize int m_nDiskHdrSize; // normally it's ID3_FRAME_HDR_SIZE but may be larger due to the unsynch scheme unsigned char m_cFlag1; unsigned char m_cFlag2; enum { ID3_FRAME_HDR_SIZE = 10 }; std::streampos m_pos; // position of the frame on the disk, including the header StringWrp* m_pFileName; // needed for serialization (it seems that the serialization cannot save pointers to strings, which makes some sense given that std::string is a "fundamental" type for the Boost serialization) // ttt2 make sure it's true bool m_bHasUnsynch; mutable bool m_bHasLatin1NonAscii; //ttt2 "mutable" doesn't look right virtual ~Id3V2Frame(); enum { SHORT_INFO, FULL_INFO }; void print(std::ostream& out, bool bFullInfo) const; // if bFullInfo is true some frames print more extensive details, e.g. lyrics // for display / export; for frames in KnownFrames::getKnownFrames only the data up to the first 0 is used (effectively removing comments in 2.3.0), while for the others, including TXXX, nulls and all other characters with codes below 32 are replaced with spaces (as a result, both description and value are shown for TXXX), so in either case the return value doesn't contain nulls; // whitespaces at the end of the string are removed; std::string getUtf8String() const; // for internal processing; similar to getUtf8String() but doesn't replace internal null characters with spaces; // removes trailing nulls and whitespaces, as well as 2.3.0 comments; std::string getRawUtf8String() const; bool isTxxx() const; std::string getReadableName() const; // normally returns m_szName, but if it has invalid characters (<=32 or >=127), returns a hex representation void writeUnsynch(std::ostream& out) const; // copies the frame to out, removing the unsynch bytes if they are present std::vector m_vcData; // this is emptied when the constructor exits if it needs to much space; to reliably access a frame's data, Id3V2FrameDataLoader should be used virtual bool discardOnChange() const = 0; // ttt2 should distinguish between audio and ID3V2 change, should be called when audio is changed, ... struct NotId3V2Frame {}; struct UnsupportedId3V2Frame {}; enum ApicStatus { NO_APIC, ERR, USES_LINK, NON_COVER, COVER }; // !!! the reason "ERR" is used (and not "ERROR") is that "ERROR" is a macro in MSVC ApicStatus m_eApicStatus; enum PictureType { PT_COVER = 3 }; int m_nPictureType; // for APIC only (cover, back, ...) int m_nImgOffset; // for APIC only int m_nImgSize; // for APIC only ImageInfo::Compr m_eCompr; // for APIC only short m_nWidth; // for APIC only short m_nHeight; // for APIC only const char* getImageType() const; const char* getImageStatus() const; double getRating() const; // asserts it's POPM virtual int getOffset() const { return 0; } // to accomodate "Data length indicator" in Id3V240Stream private: Id3V2Frame(const Id3V2Frame&); Id3V2Frame& operator=(const Id3V2Frame&); protected: enum { NO_UNSYNCH, HAS_UNSYNCH }; Id3V2Frame(std::streampos pos, bool bHasUnsynch, StringWrp* pFileName); Id3V2Frame() : m_nWidth(-1), m_nHeight(-1) {} // serialization-only constructor static std::string utf8FromBomUtf16(const char* pData, int nSize); // returns a UTF8 string from a buffer containing a UTF16 sting starting with BOM; doesn't stop when finding a 0 terminator, but includes it in the result private: // may return multiple null characters; it's the job of getUtf8String() to deal with them; // chars after the first null are considered comments (or after the second null, for TXXX); virtual std::string getUtf8StringImpl() const = 0; friend class boost::serialization::access; template void serialize(Archive& ar, const unsigned int nVersion) { if (nVersion > 1) { throw std::runtime_error("invalid version of serialized file"); } ar & m_szName; ar & m_nMemDataSize; ar & m_nDiskDataSize; ar & m_nDiskHdrSize; ar & m_cFlag1; ar & m_cFlag2; ar & m_pos; ar & m_pFileName; ar & m_bHasUnsynch; ar & m_bHasLatin1NonAscii; ar & m_vcData; ar & m_eApicStatus; ar & m_nPictureType; ar & m_nImgOffset; ar & m_nImgSize; ar & m_eCompr; if (nVersion > 0) { ar & m_nWidth; ar & m_nHeight; } } }; BOOST_CLASS_VERSION(Id3V2Frame, 1); // Makes that frame data available, loading it from the file when necessary. This is needed for pictures or other large frames that would take too much space if they were stored in the memory, so they get discarded after the constructor completes. class Id3V2FrameDataLoader { Id3V2FrameDataLoader(const Id3V2FrameDataLoader&); Id3V2FrameDataLoader& operator=(const Id3V2FrameDataLoader&); const Id3V2Frame& m_frame; const char* m_pData; std::vector m_vcOwnData; public: Id3V2FrameDataLoader(const Id3V2Frame& frame); ~Id3V2FrameDataLoader(); const char* getData() const { return m_pData; } struct LoadFailure {}; // thrown if the file is deleted / moved (and perhaps changed) after frames are identified }; // reads nCount bytes into pDest; // if bHasUnsynch is true, it actually reads more bytes, applying the unsynchronization algorithm, so pDest gets nCount bytes; // returns the number of bytes it could read; // posNext is the position where the next block begins (might be EOF); nothing starting at that position should be read; needed to take care of an ID3V2 tag whose last frame ends with 0xff and has no padding; // asserts that posNext is not before the current position in the stream std::streamsize readID3V2(bool bHasUnsynch, std::istream& in, char* pDest, std::streamsize nCount, std::streampos posNext, int& nBytesSkipped); // the total size, including the 10-byte header int getId3V2Size(char* pId3Header); //============================================================================================================ //============================================================================================================ //============================================================================================================ class Id3V2StreamBase : public DataStream, public TagReader { Q_DECLARE_TR_FUNCTIONS(Id3V2StreamBase) protected: std::vector m_vpFrames; int m_nTotalSize; // including the header int m_nPaddingSize; unsigned char m_cFlags; std::streampos m_pos; StringWrp* m_pFileName; Id3V2Frame* findFrame(const char* szFrameName); //ttt2 finds the first frame, but doesn't care about duplicates const Id3V2Frame* findFrame(const char* szFrameName) const; //ttt2 finds the first frame, but doesn't care about duplicates void checkDuplicates(NoteColl& notes) const; void checkFrames(NoteColl& notes); // various checks to be called from derived class' constructor ImageInfo::Status m_eImageStatus; Id3V2Frame* m_pPicFrame; static const char* decodeApic(NoteColl& notes, int nDataSize, std::streampos pos, const char* pData, const char*& szMimeType, int& nPictureType, const char*& szDescription); void preparePictureHlp(NoteColl& notes, Id3V2Frame* pFrame, const char* pFrameData, const char* pImgData, const char* szMimeType); void preparePicture(NoteColl& notes); // initializes fields used by the APIC frame TagTimestamp get230TrackTime(bool* pbFrameExists) const; Id3V2StreamBase(int nIndex, std::istream& in, StringWrp* pFileName); Id3V2StreamBase() {} // serialization-only constructor struct NotSupTextEnc {}; struct ErrorDecodingApic {}; public: /*override*/ ~Id3V2StreamBase(); void printFrames(std::ostream& out) const; /*override*/ void copy(std::istream& in, std::ostream& out); /*override*/ std::string getInfo() const; /*override*/ std::streampos getPos() const { return m_pos; } /*override*/ std::streamoff getSize() const { return m_nTotalSize; } static const char* getClassDisplayName(); const std::vector& getFrames() const { return m_vpFrames; } bool hasUnsynch() const; const Id3V2Frame* getFrame(const char* szName) const; // returns a frame with the given name; normally it returns the first such frame, but it may return another if there's a good reason (currently this is done for APIC only); returns 0 if no frame was found; bool hasReplayGain() const; enum { ID3_HDR_SIZE = 10 }; std::vector getKnownFrames() const; // to be used by Id3V2Cleaner; int getPaddingSize() const { return m_nPaddingSize; } struct NotId3V2 {}; // ================================ TagReader ========================================= /*override*/ std::string getTitle(bool* pbFrameExists = 0) const; /*override*/ std::string getArtist(bool* pbFrameExists = 0) const; /*override*/ std::string getTrackNumber(bool* pbFrameExists = 0) const; /*override*/ std::string getGenre(bool* pbFrameExists = 0) const; /*override*/ ImageInfo getImage(bool* pbFrameExists = 0) const; /*override*/ std::string getAlbumName(bool* pbFrameExists = 0) const; /*override*/ double getRating(bool* pbFrameExists = 0) const; /*override*/ std::string getComposer(bool* pbFrameExists = 0) const; /*override*/ int getVariousArtists(bool* pbFrameExists = 0) const; // combination of VariousArtists flags; since several frames might be involved, *pbFrameExists is set to "true" if at least a frame exists /*override*/ std::string getOtherInfo() const; /*override*/ std::vector getImages() const; /*override*/ std::string getImageData(bool* pbFrameExists = 0) const; const std::string& getFileName() const { return m_pFileName->s; } enum { DONT_ACCEPT_BROKEN, ACCEPT_BROKEN }; private: friend class boost::serialization::access; template void serialize(Archive& ar, const unsigned int nVersion) { if (nVersion > 0) { throw std::runtime_error("invalid version of serialized file"); } ar & boost::serialization::base_object(*this); ar & m_vpFrames; ar & m_nTotalSize; ar & m_nPaddingSize; ar & m_cFlags; ar & m_pos; ar & m_pFileName; //StringWrp wrp ("111222"); //ar & wrp; ar & m_eImageStatus; ar & m_pPicFrame; } }; //============================================================================================================ //============================================================================================================ //============================================================================================================ struct KnownFrames { static const char* LBL_TITLE(); static const char* LBL_ARTIST(); static const char* LBL_TRACK_NUMBER(); static const char* LBL_TIME_YEAR_230(); static const char* LBL_TIME_DATE_230(); static const char* LBL_TIME_240(); static const char* LBL_GENRE(); static const char* LBL_IMAGE(); static const char* LBL_ALBUM(); static const char* LBL_RATING(); static const char* LBL_COMPOSER(); static const char* LBL_WMP_VAR_ART(); static const char* LBL_ITUNES_VAR_ART(); static const char* LBL_TXXX(); struct InvalidIndex {}; static const char* getFrameName (int n); // throws InvalidIndex if n is out of bounds static const std::set& getExcludeFromInfoFrames(); // frames that shouldn't be part of "other info"; doesn't include TXXX and "Various Artists" frames static const std::set& getKnownFrames(); // includes "Various Artists" frames; doesn't include TXXX static bool canHaveDuplicates(const char* szName); // to be counted as duplicates, 2 frames must have the same name and picture type, so the value returned here is only part of the test }; #endif // ifndef Id3V2StreamH MP3Diags-1.2.02/src/SongInfoParser.h0000644000175000001440000003067111717741154016025 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef SongInfoParserH #define SongInfoParserH #include #include #include // for translation namespace SongInfoParser { /* Getting song details from file names: The main class is TrackTextParser, but it is mereley an interface to other classes: Reader, BoundReader StaticReader RatingReader TrackNoReader YearReader UnboundReader SequenceReader OptionalReader Reader is the base class in the hierarchy, representing the idea of "something" that can match (or not) a part of a string. I declares 2 main functions: 1) init(const char* pcLeft, const char* pcRight, bool bLeftFirst) - tries to match the string beggining at pcLeft and ending at pcRight and sets the internal state accordingly (m_pcLeft, m_pcRight, m_eLeftBound, m_eRightBound, m_eState). The matching doesn't always have to cover the whole string. A substring is sufficient in many cases. bLeftFirst indicates where the search for a substring should start. 2) set(vector& v) may be called only if init() succeeded; v's size must be "TagReader::LIST_END+1", one element for each supported field and an additional one for ignored strings; each Reader has at most 1 corresponding field; set() changes v's element that corresponds to that field (the order is given by TagReader::Feature); note that some Reader's don't have a field, so they don't change anything. Readers that have a corresponding field are either UnboundReader or something derived from BoundReader. BoundReader is for fields that have clearly identifiable limits (track number, year, rating or some static string), while UnboundReader is for fields without such limits (artist, album, track name). Usage: TagWriter has a list of "patterns". For each pattern it creates a FileTagReader, for which init() gets called, filling in field values, which are then displayed. A FileTagReader has a list of trees of Reader instances, and while it processes its init() it calls init() and set() for those Reader's. To build the tree, a pattern is parsed. First of all it is split at the file separator limit; for each segment a top-level SequenceReader gets created, and the segment is further parsed to determine what will be part of the SequenceReader. When assign() is called for a particular file name, it too is split by file separators and each of the top-level SequenceReader tries a match for its corresponding name segment when init() is called. The match may fail or succeed. If it succeeds, set() is then called, to assign values to fields. A failure in one top-level SequenceReader doesn't affect the others. Patterns are strings containing file name separators, static text and these placeholder: %n track number %a artist %t title %b album %y year %g genre %r rating (a lowercase letter) %c composer %i ignored (there's no predefined field that would take this value) For example "%a/%b/%n.-.%t" is a pattern made up of these elements: %a (artist placeholder) / (file name separator) %b (album) / (file name separator) %n (track number) .-. (static text) %t (title) During matching, corresponding file name separators and static texts are identified, and what's left must match the placeholders and give the name of the corresponding fields. By matching "%a/%b/%n.-.%t" to a file named ".../dir1/artist1/album1.(1995)/02.-.track1.mp3" we get: title = track1 track number = 02 album = album1.(1995) artist = artist1 Matching "%a/%b/%n.-.%t" to a file named ".../dir1/artist1/album1.(1995)/02.track1.mp3" we get: album = album1.(1995) artist = artist1 The last segment caused an error, because "02.track1.mp3" doesn't contain ".-." . Now this pattern would give the same results as in the previous case: "%a/%b/%n.%t" To be able to deal with the different naming conventions that are likely to be used for different albums, 2 options are available: 1) Have more instances of TrackTextParser, to accomodate those conventions. 2) Include optional elements in some of those TrackTextParser instances. Optional elements are delimited by '[' and ']'. For example "%a/%b/%n.[-.]%t" matches correctly ".../dir1/artist1/album1 (1995)/02.-.track1.mp3" as well as ".../dir1/artist1/album1 (1995)/02.track1.mp3". That is, if you consider "album1 (1995)" as a proper album name. Probably makes more sense to use "%a/%b (%y)/%n.[-.]%t" instead, which gives: title = track1 track number = 02 album = album1 artist = artist1 year = 1995 If some albums end with the year enclosed in parantheses and some don't have a year in the directory name, this may work for all the cases: "%a/%b[.(%y)]/%n.[-.]%t" An OptionalReader has a single field, which is a SequenceReader. It's not worth the trouble to have the option to include some other fields, even though in many cases the SequenceReader will only contain an element. Objects created for "%a/%b[.(%y)]/%n.[-.]%t" : TrackTextParser SequenceReader "%a" UnboundReader SequenceReader "%b[.(%y)]" UnboundReader OptionalReader SequenceReader ".(%y)" StaticReader ".(" YearReader StaticReader ")" SequenceReader (%n.[-.]%t) TrackNoReader StaticReader "." OptionalReader SequenceReader "-." StaticReader "-." UnboundReader SequenceReader is a lot more complex than the others, because it has all the different Readers that is has to determine recursively if they match or not. So this is how it basically works: First, it sorts the readers in a way that allows them to be processed sequentially (bounded readers should be matched before unbounded ones, but it's not so simple, because a reader may be bound at none, one or both sides). Then it calls init() on all of them, marking some as failed, if needed. A failure in a non-optional reader makes the whole SequenceReader fail. Any gap in the string to be matched makes the whole SequenceReader fail (but remaining substrings at the margins are OK). While the other readers are either bound or unbound, a SequenceReader may be bound at one side and unbound at the other ("%n %t" corresponds to a SequenceReader that's bound at the left and unbound at the right.) Optional readers may be nested. However, more complex patterns may be harder to understand and to get right. Also, there are limitations, as some backtracking would make sense, but it's not currently implemented. //ttt2 implement Consider the case of "[[.].]%t" : for ".tst1.mp3" the title will be ".tst1", while for "..tst1.mp3" it will be "tst1" . This happens because the inner optional reader first takes up the '.' on the right, so the outer optional doesn't have a "." for itself, which makes the outer reader to fail. What should happen is this: the outer optional reader should discard the inner reader and try again without it, in which case it would succeed. However, this looks time consuming and doesn't provide such great benefits: "[.[.]]%t" achieves what "[[.].]%t" tries to do without backtracking and making the second "." optional is what it takes to do the trick. Sure, this doesn't work in all cases, but it seems to be good enough. Some patterns may look OK but are invalid. Aside from mismatched '[' and ']', which should come in pairs, there's this issue with a succesfully parsed pattern: a SequenceReader isn't allowed to have 2 consecutive readers r1 and r2 if r1 is unbound to the right and r2 is unbound to the left, because there's no way to tell where one ends and the other begins. Optional readers are considered both as missing and present. So these are invalid: "%a%b" "%a[%b]" "%a[-]%b" "[%a][%b]" while these are valid "%a-%b" "%a[-%b]" "[%a-]%b" Since "%", "[" and "]" are used as special characters, they can't appear on their own in patterns. To include them in a pattern, they should be preceded by another "%": "%%", "%[" and "%]" */ struct SequenceReader; struct Reader; // gets track info from a full file name or from a generic string, normally a line from a multi-line track listing // if m_bSplitAtPathSep is true, the string passed to assign is broken into independent pieces, matching the independent pieces from m_strPattern, which may fail individually without triggering the failure of the whole; if it's false, the whole strPattern must match the pattern; the decision to make m_bSplitAtPathSep true or false is based on the pattern containing an unescaped path separator (keeping in mind that to include such a separator in a "table line parser" it has to be escaped) class TrackTextParser { Q_DECLARE_TR_FUNCTIONS(TrackTextParser) std::vector<SequenceReader*> m_vpSeqReaders; std::vector<Reader*> m_vpAllReaders; // all instances of Reader-derived classes, to be easily deleted void construct(const std::string& strPattern); std::string m_strPattern; bool m_bSplitAtPathSep; public: TrackTextParser(const std::string& strPattern); // throws InvalidPattern for invalid pattern ~TrackTextParser(); // parses strFileName, identifying track info details and setting v accordingly; v's size must be TagReader::LIST_END+1, with elements in the order given by TagReader::Feature, while the last one is reserved for "ignored"; v is not erased; if there is no value for a field, the corresponding element doesn't change; void assign(const std::string& s, std::vector<std::string>& v); std::string getPattern() const { return m_strPattern; } bool isFileNameBased() const { return m_bSplitAtPathSep; } struct InvalidPattern { int m_nPos; InvalidPattern(int nPos) : m_nPos(nPos) {} }; }; /* // gets track info from a string, normally a line from a multi-line track listing class TrackTextParser { SequenceReader* m_pSeqReader; std::vector<Reader*> m_vpAllReaders; // all instances of Reader-derived classes, to be easily deleted void construct(const std::string& strPattern); std::string m_strPattern; public: TrackTextParser(const std::string& strPattern); // throws InvalidPattern for invalid pattern ~TrackTextParser(); // parses strTrackInfo, identifying track info details and setting v accordingly; v's size must be TagReader::LIST_END+1, with elements in the order given by TagReader::Feature, while the last one is reserved for "ignored"; v is not erased; if there is no value for a field, the corresponding element doesn't change; void assign(const std::string& strTrackInfo, std::vector<std::string>& v); std::string getPattern() const { return m_strPattern; } struct InvalidPattern { int m_nPos; InvalidPattern(int nPos) : m_nPos(nPos) {} }; }; */ std::string testPattern(const std::string& strPattern); // returns an empty string if the pattern is valid and an error message (including the column) if it's invalid } // namespace SongInfoParser #endif // #ifndef SongInfoParserH �����������������������������������������������������������������������MP3Diags-1.2.02/src/TagWriter.cpp�������������������������������������������������������������������0000644�0001750�0000144�00000222570�11724702256�015370� 0����������������������������������������������������������������������������������������������������ustar �ciobi���������������������������users������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifdef MSVC_QMAKE #pragma warning (disable : 4100) #endif #include <algorithm> #include <QFile> #include <QClipboard> #include <QMimeData> #include "TagWriter.h" #include "Helpers.h" #include "OsFile.h" #include "ImageInfoPanelWdgImpl.h" #include "SongInfoParser.h" #include "Widgets.h" #include "DiscogsDownloader.h" // just for SOURCE_NAME #include "MusicBrainzDownloader.h" // just for SOURCE_NAME #include "Id3V230Stream.h" #include "Id3V240Stream.h" #include "Mp3Manip.h" #include "CommonData.h" #include "MpegStream.h" ////#include <iostream> //ttt remove using namespace std; using namespace pearl; //====================================================================================================================== //====================================================================================================================== //====================================================================================================================== /* // 2012.03.04 - commented out to see if needs translation, noticed it's not used anymore; probably was used for debugging namespace { ostream& operator<<(ostream& out, const TagReaderInfo& inf) { out << "<" << inf.m_strName << ", " << inf.m_nPos << ", " << (inf.m_bAlone ? "true" : "false") << ">"; return out; } void printVec(ostream& out, const vector<TagReaderInfo>& v) { for (int i = 0, n = cSize(v); i < n; ++i) { out << v[i] << " "; } out << endl; } } ostream& operator<<(ostream& out, const TagWriter::OrigValue& val) { out << "song: " << val.m_nSong << ", field: " << val.m_nField << ", status: " << (int)val.m_eStatus << ", value: " << val.m_strVal; return out; } */ //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== ImageColl::ImageColl() : m_nCurrent(-1) { } int ImageColl::addImage(const ImageInfo& img, const string& strFile /* = ""*/) // returns the index of the image; if it already exists it's not added again; if it's invalid returns -1 //ttt1 recompress large images AND mark field as changed { if (img.isNull()) { return -1; } int n (find(img)); if (-1 != n) { if (!strFile.empty()) { m_vTagWrtImageInfo[n].m_sstrFiles.insert(strFile); } return n; } m_vTagWrtImageInfo.push_back(TagWrtImageInfo(img, strFile)); return size() - 1; } void ImageColl::addWidget(ImageInfoPanelWdgImpl* p) // first addImage gets called by TagWriter and after it's done it tells MainFormDlgImpl to create widgets, which calls this; { m_vpWidgets.push_back(p); } void ImageColl::clear() { clearPtrContainer(m_vpWidgets); m_vTagWrtImageInfo.clear(); m_nCurrent = -1; } void ImageColl::select(int n) // -1 deselects all { CB_ASSERT (cSize(m_vpWidgets) == cSize(m_vTagWrtImageInfo)); if (m_nCurrent >= 0) { m_vpWidgets[m_nCurrent]->setNormalBackground(); } m_nCurrent = n; if (n >= 0) { CB_ASSERT (n < size()); m_vpWidgets[m_nCurrent]->setHighlightBackground(); } } int ImageColl::find(const ImageInfo& img) const { vector<TagWrtImageInfo>::const_iterator it (std::find(m_vTagWrtImageInfo.begin(), m_vTagWrtImageInfo.end(), img)); if (m_vTagWrtImageInfo.end() == it) { return -1; } return it - m_vTagWrtImageInfo.begin(); } //====================================================================================================================== //====================================================================================================================== //====================================================================================================================== TrackTextReader::TrackTextReader(SongInfoParser::TrackTextParser* pTrackTextParser, const std::string& s) : m_dRating(-1), m_bHasTitle(false), m_bHasArtist(false), m_bHasTrackNumber(false), m_bHasTimeStamp(false), m_bHasGenre(false), m_bHasAlbumName(false), m_bHasRating(false), m_bHasComposer(false), m_szType(pTrackTextParser->isFileNameBased() ? QT_TRANSLATE_NOOP("TagReader", "(file)") : QT_TRANSLATE_NOOP("TagReader", "(table)")) { vector<string> v ((int)LIST_END + 1, "\1"); // !!! "LIST_END + 1" instead of just "LIST_END", so the last entry can be used for "%i" (i.e. "ignored") pTrackTextParser->assign(s, v); if ("\1" != v[TITLE]) { m_bHasTitle = true; m_strTitle = v[TITLE]; } if ("\1" != v[ARTIST]) { m_bHasArtist = true; m_strArtist = v[ARTIST]; } if ("\1" != v[TRACK_NUMBER]) { m_bHasTrackNumber = true; m_strTrackNumber = v[TRACK_NUMBER]; } if ("\1" != v[TIME]) { try { m_timeStamp = TagTimestamp(v[TIME]); m_bHasTimeStamp = true; } catch (const TagTimestamp::InvalidTime&) { // !!! nothing } } if ("\1" != v[GENRE]) { m_bHasGenre = true; m_strGenre = v[GENRE]; } //if ("\1" != v[IMAGE]) { q = true; m_str = v[IMAGE]; } if ("\1" != v[ALBUM]) { m_bHasAlbumName = true; m_strAlbumName = v[ALBUM]; } if ("\1" != v[RATING]) { const string& s (v[RATING]); if (1 == cSize(s)) { char c (s[0]); // a b c d e f g h i j k l m n o p q r s t u v w x y z static double s_ratingMap [] = { 5.0, 4.5, 4.0, 3.5, 3.0, 2.5, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 1.5, 1.0, 1.0, 1.0, -1, -1, 1.0, 1.0, 1.0, 1.0, 1.0, 0.5, 0.5, 0.0 }; // note that if this changes it should be synchronized with RatingPattern // negative values are ignored if (c >= 'a' && c <= 'z') //ttt3 ASCII-specific { double d (s_ratingMap[c - 'a']); if (d >= 0) { m_bHasRating = true; //m_dRating = ('z' - c)*254/('z' - 'a' /*+ 1*/) + 1; //m_nRating = int(d*254/5 + 1.1); m_dRating = d; } } } } if ("\1" != v[COMPOSER]) { m_bHasComposer = true; m_strComposer = v[COMPOSER]; } } /*override*/ TrackTextReader::~TrackTextReader() { //qDebug("destr TrackTextReader %p", this); } /*override*/ TrackTextReader::SuportLevel TrackTextReader::getSupport(Feature eFeature) const { switch (eFeature) { case TITLE: case ARTIST: case TRACK_NUMBER: case TIME: case GENRE: //case IMAGE: case ALBUM: case RATING: case COMPOSER: //case VARIOUS_ARTISTS: return READ_ONLY; default: return NOT_SUPPORTED; } } //====================================================================================================================== //====================================================================================================================== //====================================================================================================================== WebReader::WebReader(const AlbumInfo& albumInfo, int nTrackNo) : m_strType(albumInfo.m_strSourceName) { //if (nTrackNo >= cSize(albumInfo.m_vTracks)) { return; } CB_ASSERT (0 <= nTrackNo && nTrackNo < cSize(albumInfo.m_vTracks)); const TrackInfo& ti (albumInfo.m_vTracks[nTrackNo]); m_strTitle = ti.m_strTitle; m_strArtist = ti.m_strArtist; //(ti.m_strArtist.empty() ? albumInfo.m_strArtist : ti.m_strArtist); m_strTrackNumber = ti.m_strPos; try { m_timeStamp = TagTimestamp(albumInfo.m_strReleased); } catch (const TagTimestamp::InvalidTime&) { //ttt2 perhaps log something ... } m_strGenre = albumInfo.m_strGenre; m_imageInfo = albumInfo.m_imageInfo; m_strAlbumName = albumInfo.m_strTitle; m_dRating = ti.m_dRating; m_strComposer = ti.m_strComposer; //(ti.m_strComposer.empty() ? albumInfo.m_strComposer : ti.m_strComposer); m_eVarArtists = albumInfo.m_eVarArtists; if (DiscogsDownloader::SOURCE_NAME == albumInfo.m_strSourceName) { m_bSuppGenre = true; m_bSuppComposer = true; m_bSuppVarArtists = false; } else if (MusicBrainzDownloader::SOURCE_NAME == albumInfo.m_strSourceName) { m_bSuppGenre = false; m_bSuppComposer = false; m_bSuppVarArtists = true; } else { CB_ASSERT (false); } } WebReader::~WebReader() { // qDebug("destr %p", this); } /*override*/ WebReader::SuportLevel WebReader::getSupport(Feature eFeature) const { switch (eFeature) { case TITLE: case ARTIST: case TRACK_NUMBER: case TIME: case IMAGE: case ALBUM: return READ_ONLY; case GENRE: return m_bSuppGenre ? READ_ONLY : NOT_SUPPORTED; case COMPOSER: return m_bSuppComposer ? READ_ONLY : NOT_SUPPORTED; case VARIOUS_ARTISTS: return m_bSuppVarArtists ? READ_ONLY : NOT_SUPPORTED; case RATING: default: return NOT_SUPPORTED; } } int WebReader::convVarArtists() const // converts m_eVarArtists to an int is either 0 or contains all VA-enabled values, based on configuration { switch (m_eVarArtists) { case AlbumInfo::VA_NOT_SUPP: case AlbumInfo::VA_SINGLE: return TagReader::VA_NONE; case AlbumInfo::VA_VARIOUS: { const CommonData* p (getCommonData()); int nRes (0); if (p->m_bItunesVarArtists) { nRes += TagReader::VA_ITUNES; } if (p->m_bWmpVarArtists) { nRes += TagReader::VA_WMP; } return nRes; } default: CB_ASSERT (false); } } //====================================================================================================================== //====================================================================================================================== //====================================================================================================================== Mp3HandlerTagData::Mp3HandlerTagData(TagWriter* pTagWriter, const Mp3Handler* pMp3Handler, int nCrtPos, int nOrigPos, const std::string& strPastedVal) : m_pTagWriter(pTagWriter), m_pMp3Handler(pMp3Handler), m_nCrtPos(nCrtPos), m_nOrigPos(nOrigPos), m_vValueInfo(TagReader::LIST_END), m_strPastedVal(strPastedVal) { TRACER("Mp3HandlerTagData constr"); refreshReaders(); setUp(); //qDebug("create %p", this); } Mp3HandlerTagData::~Mp3HandlerTagData() { //qDebug("destr %p", this); TRACER("Mp3HandlerTagData destr"); } static bool isId3V2(const TagReader* p) { return 0 == strcmp(p->getName(), Id3V230Stream::getClassDisplayName()) || 0 == strcmp(p->getName(), Id3V240Stream::getClassDisplayName()); } static const char* g_szImageFmt ("# %d"); extern const int PIC_FMT_HDR (2); void Mp3HandlerTagData::adjustVarArtists(bool b) // if VARIOUS_ARTISTS is not ASSIGNED, sets m_strValue and m_eStatus { ValueInfo& inf (m_vValueInfo[TagReader::VARIOUS_ARTISTS]); string strAll (TagReader::getVarArtistsValue()); if (ASSIGNED != inf.m_eStatus) { inf.m_strValue.clear(); string s (b ? strAll : ""); inf.m_strValue = s; inf.m_eStatus = s.empty() ? EMPTY : NON_ID3V2_VAL; for (int i = 0, n = cSize(m_vpTagReaders); i < n; ++i) { TagReader* p (m_vpTagReaders[i]); if (isId3V2(p)) { string strId3 (p->getValue(TagReader::VARIOUS_ARTISTS)); string s1; // those letters from strId3 that are also in strAll; needed because strId3 contains all known letters, regardless of their being enabled for (int i = 0; i < cSize(strId3); ++i) { if (string::npos != strAll.find(strId3[i])) { s1 += strId3[i]; } } inf.m_eStatus = s1 == s ? ID3V2_VAL : NON_ID3V2_VAL; break; } } } } // to be called initially and each time the priority of tag readers changes; (well, actually a new object will get constructed in the latter case, so only the first matters) void Mp3HandlerTagData::setUp() { //cout << "reload " << this; for (int i = 0, n = cSize(m_vpTagReaders); i < n; ++i) { cout << " " << m_vpTagReaders[i]; } cout << endl; //qDebug("%s", p->getName()); for (int f = 0; f < TagReader::LIST_END; ++f) { if (TagReader::IMAGE != f && TagReader::VARIOUS_ARTISTS != f) // special case needed because TagReader::getValue(TagReader::IMAGE) always returns an empty string; VARIOUS_ARTISTS is handled separately anyway { ValueInfo& inf (m_vValueInfo[f]); if (ASSIGNED != inf.m_eStatus) { inf.m_strValue.clear(); inf.m_eStatus = EMPTY; bool bId3V2Found (false); for (int i = 0, n = cSize(m_vpTagReaders); i < n; ++i) { TagReader* p (m_vpTagReaders[i]); bId3V2Found = bId3V2Found || isId3V2(p); string s (p->getValue((TagReader::Feature)f)); if (!s.empty()) { bool bId3V2Val (true); if ((TagReader::ARTIST == f || TagReader::COMPOSER == f) && TC_NONE != m_pTagWriter->m_eArtistCase) { string s1 (convStr(getCaseConv(convStr(s), m_pTagWriter->m_eArtistCase))); if (s1 != s) { s = s1; bId3V2Val = false; } } if ((TagReader::TITLE == f || TagReader::ALBUM == f) && TC_NONE != m_pTagWriter->m_eTitleCase) //ttt2 perhaps include genre as well { string s1 (convStr(getCaseConv(convStr(s), m_pTagWriter->m_eTitleCase))); if (s1 != s) { s = s1; bId3V2Val = false; } } inf.m_strValue = s; if (bId3V2Val) { bId3V2Val = false; // !!! if (isId3V2(p)) { bId3V2Val = true; } else if (bId3V2Found) { // !!! nothing: an ID3V2 was already found and, since it got here, the value was empty } else { // find the first ID3V2 and see if it has the same value for (; i < n; ++i) { TagReader* p1 (m_vpTagReaders[i]); if (isId3V2(p1)) { string s1 (p1->getValue((TagReader::Feature)f)); bId3V2Val = (s1 == s); break; } } } } inf.m_eStatus = bId3V2Val ? ID3V2_VAL : NON_ID3V2_VAL; break; } } } } } { ValueInfo& inf (m_vValueInfo[TagReader::IMAGE]); if (ASSIGNED != inf.m_eStatus) { inf.m_strValue.clear(); inf.m_eStatus = EMPTY; bool bId3V2Found (false); for (int i = 0, n = cSize(m_vpTagReaders); i < n; ++i) { TagReader* p (m_vpTagReaders[i]); bId3V2Found = bId3V2Found || isId3V2(p); if (TagReader::NOT_SUPPORTED != p->getSupport(TagReader::IMAGE)) { const ImageInfo& imageInfo (p->getImage()); int k (m_pTagWriter->getIndex(imageInfo)); if (-1 != k) { char a [10]; sprintf(a, g_szImageFmt, k + 1); inf.m_strValue = a; // most of the follwing code is pointless as long as the only image-holding tags are ID3V2, but would lead to hard-to-detect bugs if others are added; bool bId3V2Val (false); //if (isId3V2(p) && ImageInfo::OK == imageInfo.getStatus()) // !!! by comparing getStatus() we force the user to save the image with a "correct" type if (isId3V2(p) && (ImageInfo::OK == imageInfo.getStatus() || ImageInfo::LOADED_NOT_COVER == imageInfo.getStatus())) // ttt2 not sure about LOADED_NOT_COVER, but it seems to make more sense; probably most players don't care about the image type { bId3V2Val = true; } else if (bId3V2Found) { // !!! nothing: an ID3V2 was already found and, since it got here, the value was empty } else { // find the first ID3V2 and see if it has the same value for (; i < n; ++i) { TagReader* p1 (m_vpTagReaders[i]); if (isId3V2(p1)) { CB_ASSERT (TagReader::NOT_SUPPORTED != p1->getSupport(TagReader::IMAGE)); const ImageInfo& imageInfo (p1->getImage()); int k1 (m_pTagWriter->getIndex(imageInfo)); bId3V2Val = (k1 == k); break; } } } inf.m_eStatus = bId3V2Val ? ID3V2_VAL : NON_ID3V2_VAL; break; } } } } } /*for (int i = 0; i < TagReader::LIST_END; ++i) { cout << "(" << m_vValueInfo[i].m_strValue << ", " << (int)m_vValueInfo[i].m_eStatus << ") "; } cout << endl;*/ } // may throw InvalidValue void Mp3HandlerTagData::setData(int nField, const std::string& s) { if (s == m_vValueInfo[nField].m_strValue) { return; } if (TagReader::ARTIST == nField) { m_pTagWriter->delayedAdjVarArtists(); } if (TagReader::TIME == nField) { try { TagTimestamp t (s); } catch (const TagTimestamp::InvalidTime&) { throw InvalidValue(); } } if (TagReader::TRACK_NUMBER == nField && !s.empty()) // !!! note that an empty track# gets removed when writing to ID3V2 { if (!isdigit(s[0]) || !isdigit(s[s.size() - 1])) { throw InvalidValue(); } string::size_type n1 (s.find_first_not_of("0123456789")), n2 (s.find_last_not_of("0123456789")); if (string::npos != n1 && (n1 != n2 || s[n1] != '/')) { throw InvalidValue(); } } string s1 (s); if (TagReader::RATING == nField && !s.empty()) { bool bOk (isdigit(s[0])); if (bOk) { double d (atof(s.c_str())); if (d > 5) { bOk = false; } else { char a [15]; sprintf(a, "%0.1f", d); s1 = a; } } if (!bOk) { throw InvalidValue(); } } m_vValueInfo[nField].m_strValue = s1; m_vValueInfo[nField].m_eStatus = ASSIGNED; //ttt2 a check for track numbers would make some sense, but there are many formats for track numbers, e.g. "3", "03", "3/9", "03/09", "A03" (for cassettes), others for vinyl } void Mp3HandlerTagData::setStatus(int nField, Status eStatus) { m_vValueInfo[nField].m_eStatus = eStatus; } //ttt2 ??? ptr deallocated and then reallocated in the same place most of the time? // returns the data corresponding to the k-th element in m_pTagWriter->m_vTagReaderInfo; returns "\1" if it doesn't have a corresponding stream (e.g. 2nd ID3V1 tag), "\2" if the given feature is not supported (e.g. picture in ID3V1) and "\3" if this particular tag doesn't have the requested frame // nField is the "internal" row for which data is retrieved, so TagReader::FEATURE_ON_POS[] has to be used by the UI caller std::string Mp3HandlerTagData::getData(int nField, int k) const { if (k >= cSize(m_vpMatchingTagReaders)) // this may be true in transitory contexts (e.g. going to prev/next album) { return "\1"; } TagReader* p (m_vpMatchingTagReaders.at(k)); if (0 == p) { return "\1"; } if (m_pTagWriter->isFastSaving() && (m_pTagWriter->m_vTagReaderInfo.at(k).m_strName == Id3V230Stream::getClassDisplayName() || m_pTagWriter->m_vTagReaderInfo[k].m_strName == Id3V240Stream::getClassDisplayName())) { return convStr(DataStream::tr("N/A")); } //qDebug("%s", p->getName()); //nField = TagReader::FEATURE_ON_POS[nField]; //qDebug("deref %p", p); bool bFrameExists; switch (nField) { case TagReader::TITLE: { if (TagReader::NOT_SUPPORTED == p->getSupport(TagReader::TITLE)) { return "\2"; } string s (p->getTitle(&bFrameExists)); return bFrameExists ? s : "\3"; } case TagReader::ARTIST: { if (TagReader::NOT_SUPPORTED == p->getSupport(TagReader::ARTIST)) { return "\2"; } string s (p->getArtist(&bFrameExists)); return bFrameExists ? s : "\3"; } case TagReader::TRACK_NUMBER: { if (TagReader::NOT_SUPPORTED == p->getSupport(TagReader::TRACK_NUMBER)) { return "\2"; } string s (p->getTrackNumber(&bFrameExists)); return bFrameExists ? s : "\3"; } case TagReader::TIME: { if (TagReader::NOT_SUPPORTED == p->getSupport(TagReader::TIME)) { return "\2"; } string s (p->getTime(&bFrameExists).asString()); return bFrameExists ? s : "\3"; } case TagReader::GENRE: { if (TagReader::NOT_SUPPORTED == p->getSupport(TagReader::GENRE)) { return "\2"; } string s (p->getGenre(&bFrameExists)); return bFrameExists ? s : "\3"; } case TagReader::IMAGE: { string qq ((m_vstrImgCache[k])); if (qq != "\7") { return qq; } if (m_vstrImgCache[k] != "\7") { return m_vstrImgCache[k]; } if (TagReader::NOT_SUPPORTED == p->getSupport(TagReader::IMAGE)) { m_vstrImgCache[k] = "\2"; return m_vstrImgCache[k]; } ImageInfo imageInfo (p->getImage(&bFrameExists)); if (!bFrameExists) { m_vstrImgCache[k] = "\3"; return m_vstrImgCache[k]; } if (!imageInfo.isNull()) { int j (m_pTagWriter->getIndex(imageInfo)); // CB_ASSERT (j >= 0); // !!! no need to assert; m_pTagWriter already does it char a [10]; sprintf(a, g_szImageFmt, j + 1); //cout << " " << a << endl; m_vstrImgCache[k] = a; return m_vstrImgCache[k]; } m_vstrImgCache[k] = ""; return m_vstrImgCache[k]; } case TagReader::ALBUM: { if (TagReader::NOT_SUPPORTED == p->getSupport(TagReader::ALBUM)) { return "\2"; } string s (p->getAlbumName(&bFrameExists)); return bFrameExists ? s : "\3"; } case TagReader::RATING: { if (TagReader::NOT_SUPPORTED == p->getSupport(TagReader::RATING)) { return "\2"; } char a [15]; double d (p->getRating(&bFrameExists)); if (!bFrameExists) { return "\3"; } //ttt2 perhaps use this: return p->getValue(TagReader::RATING); see why there's no test for "<0" in TagReader::getValue() sprintf(a, "%0.1f", d); if ('-' == a[0]) { a[0] = 0; } return a; } case TagReader::COMPOSER: { if (TagReader::NOT_SUPPORTED == p->getSupport(TagReader::COMPOSER)) { return "\2"; } string s (p->getComposer(&bFrameExists)); return bFrameExists ? s : "\3"; } case TagReader::VARIOUS_ARTISTS: { if (TagReader::NOT_SUPPORTED == p->getSupport(TagReader::VARIOUS_ARTISTS)) { return "\2"; } p->getVariousArtists(&bFrameExists); if (!bFrameExists) { return "\3"; } return p->getValue(TagReader::VARIOUS_ARTISTS); } } CB_ASSERT(false); // all cases have been covered and have "return" } int Mp3HandlerTagData::getImage() const // 0-based; -1 if there-s no image; { string s (getData(TagReader::IMAGE)); int nPic (s.empty() ? -1 : atoi(s.c_str() + PIC_FMT_HDR) - 1); // "-1" because on screen numbering starts from 1, not 0 return nPic; } double Mp3HandlerTagData::getRating() const { string s (getData(TagReader::RATING)); double d (s.empty() ? -1 : atof(s.c_str())); // !!! it's OK to use atof, regardless of the system's locale, because a program's locale is "C" unless changed explicitley ("locale::global(locale(""));" would switch to the system's locale) return d; } // updates m_vpTagReader to reflect m_pTagWriter->m_vTagReaderInfo, then updates unassigned values; // called on constructor void Mp3HandlerTagData::refreshReaders() { m_vTrackTextReaders.clear(); m_vpTagReaders.clear(); vector<TagReaderInfo>& vRdInfo (m_pTagWriter->m_vTagReaderInfo); m_vpMatchingTagReaders.clear(); m_vpMatchingTagReaders.resize(vRdInfo.size()); vector<std::string>(vRdInfo.size(), string("\7")).swap(m_vstrImgCache);//.clear(); m_vstrImgCache.resize(); m_vTrackTextReaders.reserve(m_pTagWriter->getTrackTextParsersCnt()); // !!! without this pointers would get invalidated m_vWebReaders.reserve(m_pTagWriter->getAlbumInfoCnt()); // !!! without this pointers would get invalidated for (int i = 0, n = cSize(vRdInfo); i < n; ++i) { const TagReaderInfo& inf (vRdInfo[i]); if (inf.m_strName == TrackTextReader::getClassDisplayName()) { SongInfoParser::TrackTextParser* pParser (m_pTagWriter->getTrackTextParser(inf.m_nPos)); m_vTrackTextReaders.push_back(TrackTextReader(pParser, pParser->isFileNameBased() ? m_pMp3Handler->getName() : m_strPastedVal)); m_vpTagReaders.push_back(&m_vTrackTextReaders.back()); m_vpMatchingTagReaders[i] = &m_vTrackTextReaders.back(); //qDebug("asgn %p", &m_vTrackTextReaders.back()); } else if (inf.m_strName == WebReader::getClassDisplayName()) { const AlbumInfo& albumInfo (m_pTagWriter->getAlbumInfo(inf.m_nPos)); if (m_nCrtPos < cSize(albumInfo.m_vTracks)) { m_vWebReaders.push_back(WebReader(albumInfo, m_nCrtPos)); m_vpTagReaders.push_back(&m_vWebReaders.back()); m_vpMatchingTagReaders[i] = &m_vWebReaders.back(); } } else { TagReader* pLast (0); int nCount (0); const vector<DataStream*>& vStreams (m_pMp3Handler->getStreams()); for (int j = 0, n = cSize(vStreams); j < n; ++j) { TagReader* pRd (dynamic_cast<TagReader*>(vStreams[j])); if (0 != pRd && pRd->getName() == inf.m_strName) { if (inf.m_nPos == nCount) { m_vpTagReaders.push_back(pRd); m_vpMatchingTagReaders[i] = pRd; goto e1; } ++nCount; pLast = pRd; } } if (0 != pLast) { m_vpTagReaders.push_back(pLast); // wasn't able to find the one on the specified position, so it uses a compatible one; // !!! this seems better than doing nothing: if TagWriter has the order "<ID3V2.3.0, 1>, <ID3V1, 0>, <ID3V2.3.0, 0>" and a file only has an ID3V2.3.0 and an ID3V1, doing nothing for the first (because there's no ID3V2.3.0 with a pos=1) would result in the order for that file being "<ID3V1, 0>, <ID3V2.3.0, 0>", making ID3V1 have priority; with the current implementation, the order is "<ID3V2.3.0, 0>, <ID3V1, 0>, <ID3V2.3.0, 0>", ID3V2 has priority; true, this introduces a needles duplicate but it's no big deal; //ttt3 remove duplicates } // !!! not an exact match, so don't touch m_vpMatchingTagReaders e1:; } } //qDebug("1 %p", this); for (int i = 0, n = cSize(m_vpTagReaders); i < n; ++i) { qDebug("%p", m_vpTagReaders[i]); } //qDebug("%s", p->getName()); //cout << "update " << this; for (int i = 0, n = cSize(m_vpTagReaders); i < n; ++i) { cout << " " << m_vpTagReaders[i]; } cout << endl; } /*void Mp3HandlerTagData::print(std::ostream& out) const // 2012.03.04 - commented out to see if needs translation, noticed it's not used anymore; probably was used for debugging { out << "==========================================\n"; for (int i = 0, n = cSize(m_vValueInfo); i < n; ++i) { out << m_vValueInfo[i].m_strValue << " <" << m_vValueInfo[i].m_eStatus << ">\n"; } }*/ // returns 0 if i is out of range const TagReader* Mp3HandlerTagData::getMatchingReader(int i) const { if (i < 0 || i > cSize(m_vpMatchingTagReaders)) { return 0; } return m_vpMatchingTagReaders[i]; } //====================================================================================================================== //====================================================================================================================== //====================================================================================================================== TagWriter::TagWriter(CommonData* pCommonData, QWidget* pParentWnd, const bool& bIsFastSaving, const TextCaseOptions& eArtistCase, const TextCaseOptions& eTitleCase) : m_bSomeSel(false), m_bNonStandardTrackNo(false), m_pCommonData(pCommonData), m_pParentWnd(pParentWnd), m_nCurrentFile(-1), m_bShowedNonSeqWarn(true), m_bIsFastSaving(bIsFastSaving), m_bShouldShowPatternsNote(false), m_nFileToErase(-1), m_bVariousArtists(false), m_bAutoVarArtists(false), m_bDelayedAdjVarArtists(false), m_bWaitingChangeNotif(false), m_eArtistCase(eArtistCase), m_eTitleCase(eTitleCase) { } TagWriter::~TagWriter() { clearPtrContainer(m_vpTrackTextParsers); clearPtrContainer(m_vpMp3HandlerTagData); } // should be called on startup by MainFormDlgImpl, to get config data; asserts that m_vSortedKnownTagReaders is empty; void TagWriter::addKnownInf(const std::vector<TagReaderInfo>& v) { CB_ASSERT (m_vSortedKnownTagReaders.empty()); for (int i = 0, n = cSize(v); i < n; ++i) { vector<TagReaderInfo>::iterator it (std::find(m_vSortedKnownTagReaders.begin(), m_vSortedKnownTagReaders.end(), v[i])); if (m_vSortedKnownTagReaders.end() == it) { m_vSortedKnownTagReaders.insert(m_vSortedKnownTagReaders.end(), v[i]); } // else nothing (ignore duplicates) } } // sorts m_vTagReaderInfo so that it matches m_vSortedKnownTagReaders void TagWriter::sortTagReaders() { vector<TagReaderInfo> vSorted; //vector<TagReaderInfo> vSorted; for (int i = 0, n = cSize(m_vSortedKnownTagReaders); i < n; ++i) { vector<TagReaderInfo>::iterator it (std::find(m_vTagReaderInfo.begin(), m_vTagReaderInfo.end(), m_vSortedKnownTagReaders[i])); if (m_vTagReaderInfo.end() != it) { vSorted.push_back(*it); m_vTagReaderInfo.erase(it); } } m_vSortedKnownTagReaders.insert(m_vSortedKnownTagReaders.end(), m_vTagReaderInfo.begin(), m_vTagReaderInfo.end()); // by doing this we make sure m_vSortedKnownTagReaders has all the elements from m_vTagReaderInfo (and perhaps more) vSorted.insert(vSorted.end(), m_vTagReaderInfo.begin(), m_vTagReaderInfo.end()); vSorted.swap(m_vTagReaderInfo); } void TagWriter::addAlbumInfo(const AlbumInfo& albumInfo) { int nAlbInfCnt (getAlbumInfoCnt()); // excluding the new albumInfo to be added m_vAlbumInfo.push_back(albumInfo); int nKnownWebCnt (0); int n (cSize(m_vSortedKnownTagReaders)); for (int i = 0; i < n; ++i) { if (WebReader::getClassDisplayName() == m_vSortedKnownTagReaders[i].m_strName) { ++nKnownWebCnt; } } m_vSortedKnownTagReaders.insert(m_vSortedKnownTagReaders.begin(), TagReaderInfo(WebReader::getClassDisplayName(), nAlbInfCnt, TagReaderInfo::ALONE)); // last param doesn't matter for m_vSortedKnownTagReaders if (nAlbInfCnt < nKnownWebCnt) { // we have another TagReaderInfo with the same m_nPos as the one just added; it needs to be removed; for (int i = 1; i < n; ++i) // !!! start from 1 { if (WebReader::getClassDisplayName() == m_vSortedKnownTagReaders[i].m_strName && m_vSortedKnownTagReaders[i].m_nPos == nAlbInfCnt) { m_vSortedKnownTagReaders.erase(m_vSortedKnownTagReaders.begin() + i); break; } } } reloadAll("", DONT_CLEAR_DATA, DONT_CLEAR_ASSGN); } namespace { struct SortByTrack { enum { NO_TRACK = 999999 }; static double getTrack(const string& s) // returns -1 if it can't identify a track number { const char* p (s.c_str()); // requiring all chars to be digits isn't OK, because "5/12" should be handled as "5" for (; ' ' == *p; ++p) {} if (0 == *p || !isdigit(*p)) { return NO_TRACK; } char* pLast; double d (int(strtol(p, &pLast, 10))); for (; 0 != *pLast && !isdigit(*pLast); ++pLast) {} if (0 != *pLast) { d += atoi(pLast)/100000.0; // this sort of takes care of tracks in the format <total>/<track> } return d; } bool operator()(const Mp3HandlerTagData* p1, const Mp3HandlerTagData* p2) const { // !!! this defines a "strict partial order" (irreflexive and transitive) iff the codes for digits are grouped together (as is the case with ASCII) const string& s1 (p1->getData(TagReader::TRACK_NUMBER)); const string& s2 (p2->getData(TagReader::TRACK_NUMBER)); double d1 (getTrack(s1)); double d2 (getTrack(s2)); //if (d1 < NO_TRACK && d2 < NO_TRACK) { return d1 < d2; } if (d1 != d2) { return d1 < d2; } return s1 < s2; } }; } void TagWriter::sortSongs() // sorts by track number; shows a warning if issues are detected (should be exactly one track number, from 1 to the track count) { stable_sort(m_vpMp3HandlerTagData.begin(), m_vpMp3HandlerTagData.end(), SortByTrack()); int n (cSize(m_vpMp3HandlerTagData)); vector<pair<int, int> > v (n); for (int i = 0; i < n; ++i) { v[i] = make_pair(m_vpMp3HandlerTagData[i]->getOrigPos(), i); } std::sort(v.begin(), v.end()); m_vnMovedTo.resize(n); for (int i = 0; i < n; ++i) { m_vnMovedTo[i] = v[i].second; //cout << "## " << m_vnMovedTo[i] << endl; } m_bNonStandardTrackNo = false; for (int i = 0; i < n; ++i) { if (int(SortByTrack::getTrack(m_vpMp3HandlerTagData[i]->getData(TagReader::TRACK_NUMBER))) != i + 1) { m_bNonStandardTrackNo = true; if (m_pCommonData->m_bWarnOnNonSeqTracks && !m_bShowedNonSeqWarn) { m_bShowedNonSeqWarn = true; QTimer::singleShot(1, this, SLOT(onDelayedTrackSeqWarn())); } break; } } } void TagWriter::onDelayedTrackSeqWarn() { //CursorOverrider crs (Qt::ArrowCursor); showWarning(m_pParentWnd, tr("Warning"), tr("Track numbers are supposed to be consecutive numbers from 1 to the total number of tracks. This is not the case with the current album, which may lead to incorrect assignments when pasting information.")); } // returns 0 if there's no current handler const Mp3Handler* TagWriter::getCurrentHndl() const { if (m_nCurrentFile < 0 || m_nCurrentFile >= cSize(m_vpMp3HandlerTagData)) { return 0; } return m_vpMp3HandlerTagData[m_nCurrentFile]->getMp3Handler(); } string TagWriter::getCurrentName() const { const Mp3Handler* p (getCurrentHndl()); return 0 != p ? p->getName() : ""; } const Mp3HandlerTagData* TagWriter::getCrtMp3HandlerTagData() const { if (m_nCurrentFile < 0 || m_nCurrentFile >= cSize(m_vpMp3HandlerTagData)) { return 0; } return m_vpMp3HandlerTagData[m_nCurrentFile]; } bool s_bToldAboutPatternsInCrtRun (false); // to limit to 1 per run the number of times the user is told about support void TagWriter::onDelayedChangeNotif() { m_bWaitingChangeNotif = false; showCritical(m_pParentWnd, tr("Error"), tr("Some files have been modified by an external tool after the last scan. You won't be able to save any changes to those files until you rescan them.")); } //void TagWriter::reloadAll(string strCrt, ReloadOption eReloadOption/*, bool bKeepUnassgnImg*/) void TagWriter::reloadAll(string strCrt, bool bClearData, bool bClearAssgn) { TRACER("TagWriter::reloadAll()"); CursorOverrider crs; if (strCrt.empty() && !bClearData) { strCrt = getCurrentName(); } //ttt2 ??? q ? m_vpMp3HandlerTagData gets destroyed then prev values used?" x //ttt2 ??? ? push_back for m_vpMp3HandlerTagData ? vector<Mp3HandlerTagData*> v; v.swap(m_vpMp3HandlerTagData); const deque<const Mp3Handler*>& vpHndl (m_pCommonData->getCrtAlbum()); if (!v.empty() && cSize(v) != cSize(vpHndl)) { bClearData = true; // needed for the case when proc orig files are kept or when changed files are nor created } //qDebug("------------------------- dir %s -------------------------", vpHndl[0]->getDir().c_str()); if (bClearData) { //qDebug(">>>>>>>>>>>>>>>>>>>>>>>>> initial >>>>>>>>>>>>>>>>>>>>>>>>>"); clearPtrContainer(v); //qDebug("<<<<<<<<<<<<<<<<<<<<<<<<< initial <<<<<<<<<<<<<<<<<<<<<<<<<"); m_imageColl.clear(); m_vnMovedTo.clear(); m_vstrPastedValues.clear(); m_vAlbumInfo.clear(); m_snUnassignedImages.clear(); } if (bClearAssgn) { m_bAutoVarArtists = true; } m_vTagReaderInfo.clear(); set<pair<string, int> > sReaders; std::map<std::string, int> mReaderCount; if (vpHndl.empty()) { m_nCurrentFile = -1; //emit fileChanged(); // just to resize the rows emit albumChanged(); // this will cause the window to close return; // !!! don't add a column for which data doesn't make sense (without this, "pattern" readers would get added, because they exist independently of songs) } CB_ASSERT (v.empty() || cSize(v) == cSize(vpHndl)); int n (cSize(vpHndl)); bool bFullReaderNotFound (false); // ID3V1 is not full, while the others are for (int i = 0; i < n; ++i) { const Mp3Handler* p (vpHndl[i]); std::map<std::string, int> m; bool bTrackHasFullReader (false); const vector<DataStream*>& vStreams (p->getStreams()); for (int i = 0, n = cSize(vStreams); i < n; ++i) { TagReader* pRd (dynamic_cast<TagReader*>(vStreams[i])); if (0 != pRd) { sReaders.insert(make_pair(string(pRd->getName()), m[pRd->getName()])); ++m[pRd->getName()];// = m[pRd->getName()] + 1; if (pRd->getName() != Id3V1Stream::getClassDisplayName()) { bTrackHasFullReader = true; } mReaderCount[pRd->getName()] = max(mReaderCount[pRd->getName()], m[pRd->getName()]); if (bClearData) { // add images try { const vector<ImageInfo> vImg (pRd->getImages()); for (int i = 0; i < cSize(vImg); ++i) { m_imageColl.addImage(vImg[i]); // doesn't matter if it's null or already exists; m_imageColl handles it correctly } } catch (const NotSupportedOp&) { } } } } if (!bTrackHasFullReader) { bFullReaderNotFound = true; } } for (int i = 0, n = cSize(m_vpTrackTextParsers); i < n; ++i) { if (m_snActivePatterns.count(i) > 0) { sReaders.insert(make_pair(string(TrackTextReader::getClassDisplayName()), i)); } } mReaderCount[TrackTextReader::getClassDisplayName()] = cSize(m_vpTrackTextParsers); for (int i = 0, n = cSize(m_vAlbumInfo); i < n; ++i) { sReaders.insert(make_pair(string(WebReader::getClassDisplayName()), i)); } mReaderCount[WebReader::getClassDisplayName()] = cSize(m_vAlbumInfo); for (set<pair<string, int> >::iterator it = sReaders.begin(), end = sReaders.end(); it != end; ++it) { const pair<string, int>& rd (*it); m_vTagReaderInfo.push_back(TagReaderInfo(rd.first, rd.second, 1 == mReaderCount[rd.first])); } sortTagReaders(); if (m_vnMovedTo.empty()) { // needed both at the first run and after eReloadOption is CLEAR m_vnMovedTo.resize(vpHndl.size()); for (int i = 0; i < n; ++i) { m_vnMovedTo[i] = i; } } bool bRescanNeeded (false); { // process handlers in the order in which they appear in vpHndl; m_vpMp3HandlerTagData.resize(n); int nPastedSize (cSize(m_vstrPastedValues)); for (int i = 0; i < n; ++i) { int k (m_vnMovedTo[i]); const Mp3Handler* p (vpHndl[i]); bRescanNeeded = bRescanNeeded || p->needsReload(m_pCommonData->useFastSave()); m_vpMp3HandlerTagData[k] = new Mp3HandlerTagData(this, p, k, i, k < nPastedSize ? m_vstrPastedValues[k] : ""); //cout << "k=" << k << ", i=" << i << ", val=" << (k < nPastedSize ? m_vstrPastedValues[k] : "") << endl; if (!v.empty() && !bClearAssgn) { // copy existing value and status for "assigned" elements const Mp3HandlerTagData& src (*v[k]); Mp3HandlerTagData& dest (*m_vpMp3HandlerTagData[k]); //src.print(); //dest.print(); for (int j = 0; j < TagReader::LIST_END; ++j) { if (Mp3HandlerTagData::ASSIGNED == src.getStatus(j)) // !!! TagReader::FEATURE_ON_POS[] doesn't matter, because it's just a permutation, which is applied to both src and dest { dest.setData(j, src.getData(j)); dest.setStatus(j, Mp3HandlerTagData::ASSIGNED); } } } } adjustVarArtists(); } if (bRescanNeeded && !m_bWaitingChangeNotif) { m_bWaitingChangeNotif = true; QTimer::singleShot(1, this, SLOT(onDelayedChangeNotif())); } m_bShouldShowPatternsNote = false; if (!m_pCommonData->m_bToldAboutPatterns && !s_bToldAboutPatternsInCrtRun) { bool bIncompleteInfo (false); // turns true if a track is found that has empty artist, track number, or title; or if it only has ID3V1 if (!bFullReaderNotFound) { for (int i = 0; i < n; ++i) { if (m_vpMp3HandlerTagData[i]->getData(TagReader::TRACK_NUMBER).empty() || m_vpMp3HandlerTagData[i]->getData(TagReader::TITLE).empty() || m_vpMp3HandlerTagData[i]->getData(TagReader::ALBUM).empty() || m_vpMp3HandlerTagData[i]->getData(TagReader::ARTIST).empty()) { bIncompleteInfo = true; break; } } } if (bIncompleteInfo || bFullReaderNotFound) { m_bShouldShowPatternsNote = true; } } if (v.empty()) { sortSongs(); } //qDebug(">>>>>>>>>>>>>>>>>>>>>>>>> after merge >>>>>>>>>>>>>>>>>>>>>>>>>"); clearPtrContainer(v); //qDebug("<<<<<<<<<<<<<<<<<<<<<<<<< after merge <<<<<<<<<<<<<<<<<<<<<<<<<"); if (bClearData) { // add images from crt dir if (n > 0) { // scan crt dir for images //ttt2 this needs rewrite if an album means something else than a directory CursorOverrider crs; string strDir (vpHndl[0]->getDir()); FileSearcher fs (strDir); while (fs) { if (fs.isFile()) { string s (fs.getName()); QString qs (convStr(s)); if (qs.endsWith(".jpg", Qt::CaseInsensitive) || qs.endsWith(".jpeg", Qt::CaseInsensitive) || qs.endsWith(".png", Qt::CaseInsensitive)) { addImgFromFile(qs, CONSIDER_ASSIGNED); } } fs.findNext(); } } //emit tagWriterChanged(); // !!! needed here to avoid an assert that would be triggered by pSelModel->setCurrentIndex() when detecting a mismatch between the number of images in m_pCommonData->m_imageColl and the widgets that show them } //printVec(m_vTagReaderInfo); //printVec(m_vSortedKnownTagReaders); emit albumChanged(/*DONT_CLEAR == eReloadOption*/); emit imagesChanged(); setCrt(strCrt); emit varArtistsUpdated(m_bVariousArtists); } void TagWriter::adjustVarArtists() { if (!m_bAutoVarArtists) { return; } // !!! this gets called from reloadAll(); without the test it would lead to overriding whatever the user set bool bPrev (m_bVariousArtists); m_bVariousArtists = false; int n (cSize(m_vpMp3HandlerTagData)); if (n > 1) { string strArtist (m_vpMp3HandlerTagData[0]->getData(TagReader::ARTIST)); for (int i = 1; i < n; ++i) { if (strArtist != m_vpMp3HandlerTagData[i]->getData(TagReader::ARTIST)) { m_bVariousArtists = true; break; } } } for (int i = 0; i < n; ++i) { m_vpMp3HandlerTagData[i]->adjustVarArtists(m_bVariousArtists); } if (bPrev != m_bVariousArtists) { emit varArtistsUpdated(m_bVariousArtists); } } void TagWriter::toggleVarArtists() { m_bAutoVarArtists = false; m_bVariousArtists = !m_bVariousArtists; string s (m_bVariousArtists ? TagReader::getVarArtistsValue() : ""); set<int> snSelSongs; for (set<OrigValue>::iterator it = m_sSelOrigVal.begin(), end = m_sSelOrigVal.end(); it != end; ++it) { const OrigValue& o (*it); if (o.m_nField == (int)TagReader::VARIOUS_ARTISTS) { snSelSongs.insert(o.m_nSong); } } for (int i = 0; i < cSize(m_vpMp3HandlerTagData); ++i) { Mp3HandlerTagData* p (m_vpMp3HandlerTagData[i]); if (Mp3HandlerTagData::ASSIGNED != p->getStatus(TagReader::VARIOUS_ARTISTS) || m_nCurrentFile == i || snSelSongs.count(i) > 0) { p->setData(TagReader::VARIOUS_ARTISTS, s); } } emit albumChanged(); emit varArtistsUpdated(m_bVariousArtists); } void TagWriter::delayedAdjVarArtists() { if (m_bDelayedAdjVarArtists) { return; } m_bDelayedAdjVarArtists = true; QTimer::singleShot(1, this, SLOT(onDelayedAdjVarArtists())); } void TagWriter::onDelayedAdjVarArtists() { m_bDelayedAdjVarArtists = false; adjustVarArtists(); emit albumChanged(); } void TagWriter::setCrt(const std::string& strCrt) { m_nCurrentFile = 0; for (int i = 0, n = cSize(m_vpMp3HandlerTagData); i < n; ++i) { if (strCrt == m_vpMp3HandlerTagData[i]->getMp3Handler()->getName()) { m_nCurrentFile = i; break; } } emit fileChanged(); } void TagWriter::setCrt(int nCrt) { CB_ASSERT (0 <= nCrt && nCrt < cSize(m_vpMp3HandlerTagData)); m_nCurrentFile = nCrt; //qDebug("2 m_nCurrentFile=%d", m_nCurrentFile); emit fileChanged(); } /* 1) updatePatterns() is called, which updates m_vpTrackTextParsers and m_vSortedKnownTagReaders, then calls reloadAll() 2) reloadAll() gets all readers from current album and from m_vpTrackTextParsers and puts them in m_vTagReaderInfo 3) reloadAll() sorts m_vTagReaderInfo, so that it matches m_vSortedKnownTagReaders; 4) whatever elements remain in m_vTagReaderInfo without a corresponding element in m_vSortedKnownTagReaders will be at the right; they also get added to m_vSortedKnownTagReaders and at app exit they get saved in the conf file; */ // the int tells which position a given pattern occupied before; (it's -1 for new patterns); // doesn't throw, but invalid patterns are discarded; it returns false if at least one pattern was discarded; bool TagWriter::updatePatterns(const std::vector<std::pair<std::string, int> >& v) { set<string> sstrActive; { vector<string> v (getPatterns()); set<int> s (getActivePatterns()); for (set<int>::const_iterator it = s.begin(); it != s.end(); ++it) { sstrActive.insert(v[*it]); } } clearPtrContainer(m_vpTrackTextParsers); bool bRes (true); for (int i = 0, n = cSize(v); i < n; ++i) { //cout << v[i].first << " " << v[i].second << endl; ? ok, but try { m_vpTrackTextParsers.push_back(new SongInfoParser::TrackTextParser(v[i].first)); } catch (const SongInfoParser::TrackTextParser::InvalidPattern&) { // ignore the pattern and report that there was an error bRes = false; } } { // update m_vSortedKnownTagReaders based on v[i].second vector<TagReaderInfo> vNew; for (int i = 0, n = cSize(m_vSortedKnownTagReaders); i < n; ++i) { bool bAdd (true); TagReaderInfo inf (m_vSortedKnownTagReaders[i]); if (TrackTextReader::getClassDisplayName() == inf.m_strName) { int j (0); const int m (cSize(v)); for (; j < m; ++j) { if (v[j].second == inf.m_nPos) { inf.m_nPos = j; break; } } if (m == j) { bAdd = false; } } if (bAdd) { vNew.push_back(inf); } } // !!! elements from v with a pos of -1 don't get added to vNew (therefore m_vSortedKnownTagReaders), which is OK: they will be visible anyway (because they are added to m_vpTrackTextParsers), but they will be placed at the end of the list (as having no correspondence in m_vSortedKnownTagReaders when sortTagReaders() gets called); vNew.swap(m_vSortedKnownTagReaders); } { vector<string> v (getPatterns()); m_snActivePatterns.clear(); for (int i = 0; i < cSize(v); ++i) { if (sstrActive.count(v[i]) > 0) { m_snActivePatterns.insert(i); //ttt2 not quite right: for 2 identical patterns of which one was selected before, both will be selected now; } } } reloadAll("", DONT_CLEAR_DATA, DONT_CLEAR_ASSGN); return bRes; } vector<string> TagWriter::getPatterns() const { vector<string> v; for (int i = 0, n = cSize(m_vpTrackTextParsers); i < n; ++i) { v.push_back(m_vpTrackTextParsers[i]->getPattern()); // !!! may throw } return v; } void TagWriter::setActivePatterns(const std::set<int>& s) { m_snActivePatterns = s; reloadAll("", DONT_CLEAR_DATA, DONT_CLEAR_ASSGN); } int TagWriter::getIndex(const ImageInfo& img) const { //return 0; return m_imageColl.find(img); } void TagWriter::moveReader(int nOldVisualIndex, int nNewVisualIndex) { int n (cSize(m_vTagReaderInfo)); CB_ASSERT (nOldVisualIndex >=0 && nOldVisualIndex < n && nNewVisualIndex >=0 && nNewVisualIndex < n); TagReaderInfo inf (m_vTagReaderInfo[nOldVisualIndex]); m_vTagReaderInfo.erase(m_vTagReaderInfo.begin() + nOldVisualIndex); m_vTagReaderInfo.insert(m_vTagReaderInfo.begin() + nNewVisualIndex, inf); //printVec(m_vTagReaderInfo); //printVec(m_vSortedKnownTagReaders); vector<TagReaderInfo>::iterator it (find(m_vSortedKnownTagReaders.begin(), m_vSortedKnownTagReaders.end(), inf)); CB_ASSERT (m_vSortedKnownTagReaders.end() != it); m_vSortedKnownTagReaders.erase(it); if (0 == nNewVisualIndex) { m_vSortedKnownTagReaders.insert(m_vSortedKnownTagReaders.begin(), inf); } else { if (nNewVisualIndex < n - 1) { it = find(m_vSortedKnownTagReaders.begin(), m_vSortedKnownTagReaders.end(), m_vTagReaderInfo[nNewVisualIndex + 1]); CB_ASSERT (m_vSortedKnownTagReaders.end() != it); //assert when move col 0 over 1 } else { CB_ASSERT (nNewVisualIndex > 0); // well, it's n-1 it = find(m_vSortedKnownTagReaders.begin(), m_vSortedKnownTagReaders.end(), m_vTagReaderInfo[nNewVisualIndex - 1]); CB_ASSERT (m_vSortedKnownTagReaders.end() != it); ++it; } m_vSortedKnownTagReaders.insert(it, inf); } reloadAll("", DONT_CLEAR_DATA, DONT_CLEAR_ASSGN); } // should be called when the selection changes; updates m_sSelOrigVal and returns the new state of m_pAssignedB; AssgnBtnWrp::State TagWriter::updateAssigned(const vector<pair<int, int> >& vFields) { //cout << ">>> updateAssigned()\n"; printContainer(m_sSelOrigVal, cout, "\n"); set<OrigValue> s; int nAsgnCount (0); for (int i = 0, n = cSize(vFields); i < n; ++i) { int nSong (vFields[i].first); int nField (vFields[i].second); //cout << "look at " << nSong << ":" << nField << endl; //nField = TagReader::FEATURE_ON_POS[nField]; //const Mp3HandlerTagData& d (*m_pTagWriter->m_vpMp3HandlerTagData[nSong]); OrigValue val (OrigValue(nSong, nField, getData(nSong, nField), getStatus(nSong, nField))); set<OrigValue>::iterator it (m_sSelOrigVal.find(val)); const OrigValue& insVal (it == m_sSelOrigVal.end() ? val : *it); // !!! the test is needed because after toggling from assigned to unassigned the shown value changes to reflect the first tag; then when toggling back we need the old value, to restore //cout << " adding " << insVal.m_nSong << ":" << insVal.m_nField << " " << insVal.m_strVal << endl; s.insert(insVal); //if (insVal.m_eStatus nAsgnCount if (Mp3HandlerTagData::ASSIGNED == val.m_eStatus) // !!! val, not insVal { ++nAsgnCount; } } s.swap(m_sSelOrigVal); //Mp3HandlerTagData::Status eStatus (m_pTagWriter->getStatus(index.row(), index.column())); AssgnBtnWrp::State eRes (0 == nAsgnCount ? AssgnBtnWrp::NONE_ASSGN : (nAsgnCount < cSize(m_sSelOrigVal) ? AssgnBtnWrp::SOME_ASSGN : AssgnBtnWrp::ALL_ASSGN)); //cout << nAsgnCount << " assgn, " << cSize(m_sSelOrigVal) << " total, " << listSel.size() << "listSel" << endl; m_bSomeSel = AssgnBtnWrp::SOME_ASSGN == eRes; //cout << "<<< updateAssigned()\n"; printContainer(m_sSelOrigVal, cout, "\n"); return eRes; } void TagWriter::eraseFields(const std::vector<std::pair<int, int> >& vFields) { for (int i = 0, n = cSize(vFields); i < n; ++i) { int nSong (vFields[i].first); int nField (vFields[i].second); try { m_vpMp3HandlerTagData[nSong]->setData(nField, ""); } catch (const Mp3HandlerTagData::InvalidValue&) { // nothing } } } // artist and album for the current song; empty if they don't exist void TagWriter::getAlbumInfo(std::string& strArtist, std::string& strAlbum) { if (m_vpMp3HandlerTagData.empty()) { strArtist.clear(); strAlbum.clear(); return; } /*int nCrt (m_pCommonData->m_pCurrentAlbumG->currentIndex().row()); const Mp3HandlerTagData& d (*m_vpMp3HandlerTagData[nCrt]);*/ /*for (int i = 0; i < 9; ++i) { cout << d.getData(i) << endl; }*/ /*strArtist = d.getData2(TagReader::ARTIST); strAlbum = d.getData2(TagReader::ALBUM);*/ //s = convStr(d.getData(TagReader::FEATURE_ON_POS[j - 1])); CB_ASSERT (m_nCurrentFile >= 0); //const Mp3HandlerTagData& d (*m_vpMp3HandlerTagData[m_nCurrentFile]); strArtist = getData(m_nCurrentFile, TagReader::ARTIST); strAlbum = getData(m_nCurrentFile, TagReader::ALBUM); } void TagWriter::onAssignImage(int nPos) { char a [10]; sprintf(a, g_szImageFmt, nPos + 1); set<int> snSelSongs; for (set<OrigValue>::iterator it = m_sSelOrigVal.begin(), end = m_sSelOrigVal.end(); it != end; ++it) { const OrigValue& o (*it); if (o.m_nField == (int)TagReader::IMAGE) { snSelSongs.insert(o.m_nSong); } } for (int i = 0; i < cSize(m_vpMp3HandlerTagData); ++i) { Mp3HandlerTagData* p (m_vpMp3HandlerTagData[i]); if (Mp3HandlerTagData::ASSIGNED != p->getStatus(TagReader::IMAGE) || m_nCurrentFile == i || snSelSongs.count(i) > 0) { p->setData(TagReader::IMAGE, a); } } m_snUnassignedImages.erase(nPos); //m_pCommonData->m_pCurrentAlbumG->repaint(); reloadAll("", DONT_CLEAR_DATA, DONT_CLEAR_ASSGN); } void TagWriter::onEraseFile(int nPos) { m_nFileToErase = nPos; QTimer::singleShot(1, this, SLOT(onEraseFileDelayed())); } void TagWriter::onEraseFileDelayed() { const TagWrtImageInfo& inf (m_imageColl[m_nFileToErase]); CB_ASSERT (!inf.m_sstrFiles.empty()); QString s; if (inf.m_sstrFiles.size() > 1) { for (set<string>::const_iterator it = inf.m_sstrFiles.begin(); it != inf.m_sstrFiles.end(); ++it) { s += "\n" + convStr(toNativeSeparators(*it)); } s = tr("Do you want to erase these files?%1").arg(s); } else { s = "\"" + convStr(toNativeSeparators(*inf.m_sstrFiles.begin())) + "\""; s = tr("Do you want to erase %1?").arg(s); } if (0 != showMessage(m_pParentWnd, QMessageBox::Question, 1, 1, tr("Confirm"), s, tr("Erase"), tr("Cancel"))) { return; } bool bAssigned, bNonId3V2; hasUnsaved(bAssigned, bNonId3V2); if (bAssigned) { if (0 != showMessage(m_pParentWnd, QMessageBox::Critical, 1, 1, tr("Error"), tr("You cannot erase image files if there are unsaved values. Do you want to save?"), tr("Save, then erase file"), tr("Cancel"))) { return; } emit requestSave(); hasUnsaved(bAssigned, bNonId3V2); if (bAssigned) { return; } } if (!m_vAlbumInfo.empty() || !m_vstrPastedValues.empty()) { if (0 != showMessage(m_pParentWnd, QMessageBox::Critical, 1, 1, tr("Warning"), tr("Erasing image files triggers a full reload, which results in downloaded and pasted data being lost. Erase anyway?"), tr("Erase"), tr("Cancel"))) { return; } } for (set<string>::const_iterator it = inf.m_sstrFiles.begin(); it != inf.m_sstrFiles.end(); ++it) { if (!QFile(convStr(*it)).remove()) { showCritical(m_pParentWnd, tr("Error"), tr("Couldn't erase file \"%1\"").arg(toNativeSeparators(convStr(*it)))); } } reloadAll(getCurrentName(), CLEAR_DATA, CLEAR_ASSGN); } // should be called when the user clicks on the assign button; changes status of selected cells and returns the new state of m_pAssignedB AssgnBtnWrp::State TagWriter::toggleAssigned(AssgnBtnWrp::State eCrtState) { if (m_sSelOrigVal.empty()) { return eCrtState; } // !!! covers the case when only fields in the "file name" column are selected //cout << " >>> toggleAssigned()\n"; printContainer(m_sSelOrigVal, cout, "\n"); AssgnBtnWrp::State eRes; //AssgnBtnWrp::State eState (m_pCommonData->m_assgnBtnWrp.getState()); switch (eCrtState) { case AssgnBtnWrp::ALL_ASSGN: // switch to "NONE" for (set<OrigValue>::iterator it = m_sSelOrigVal.begin(), end = m_sSelOrigVal.end(); it != end; ++it) { const OrigValue& val (*it); //setData(val.m_nSong, val.m_nField, val setStatus(val.m_nSong, val.m_nField, Mp3HandlerTagData::NON_ID3V2_VAL); } eRes = AssgnBtnWrp::NONE_ASSGN; break; case AssgnBtnWrp::NONE_ASSGN: // switch to "SOME" if it exists; otherwise switch to "ALL" if (m_bSomeSel) { for (set<OrigValue>::iterator it = m_sSelOrigVal.begin(), end = m_sSelOrigVal.end(); it != end; ++it) { const OrigValue& val (*it); setData(val.m_nSong, val.m_nField, val.m_strVal); setStatus(val.m_nSong, val.m_nField, val.m_eStatus); } eRes = AssgnBtnWrp::SOME_ASSGN; break; } // break; !!! don't "break" case AssgnBtnWrp::SOME_ASSGN: // switch to "ALL" for (set<OrigValue>::iterator it = m_sSelOrigVal.begin(), end = m_sSelOrigVal.end(); it != end; ++it) { const OrigValue& val (*it); setData(val.m_nSong, val.m_nField, val.m_strVal); setStatus(val.m_nSong, val.m_nField, Mp3HandlerTagData::ASSIGNED); } eRes = AssgnBtnWrp::ALL_ASSGN; break; default: CB_ASSERT(false); } reloadAll("", DONT_CLEAR_DATA, DONT_CLEAR_ASSGN); //cout << " <<< toggleAssigned()\n"; printContainer(m_sSelOrigVal, cout, "\n"); return eRes; } void TagWriter::copyFirst() { if (m_sSelOrigVal.empty()) { return; } set<int> sDupCols; for (set<OrigValue>::iterator it = m_sSelOrigVal.begin(), end = m_sSelOrigVal.end(); it != end; ++it) { sDupCols.insert(it->m_nField); } sDupCols.erase(TagReader::TITLE); sDupCols.erase(TagReader::RATING); sDupCols.erase(TagReader::TRACK_NUMBER); for (set<int>::iterator it = sDupCols.begin(), end = sDupCols.end(); it != end; ++it) { int nField (*it); string s (getData(0, nField)); for (int i = 1, n = cSize(m_vpMp3HandlerTagData); i < n; ++i) // !!! i starts at 1; { setData(i, nField, s); } } reloadAll("", DONT_CLEAR_DATA, DONT_CLEAR_ASSGN); } bool TagWriter::addImgFromFile(const QString& qs, bool bConsiderAssigned) { QFile f (qs); bool bRes (false); if (f.open(QIODevice::ReadOnly)) { CursorOverrider crsOv; int nSize ((int)f.size()); QByteArray comprImg (f.read(nSize)); QImage pic; if (pic.loadFromData(comprImg)) { bRes = true; ImageInfo::Compr eCompr; int nWidth, nHeight; if (nSize <= ImageInfo::MAX_IMAGE_SIZE) { nWidth = pic.width(); nHeight = pic.height(); eCompr = qs.endsWith(".png", Qt::CaseInsensitive) ? ImageInfo::PNG : ImageInfo::JPG; //ttt2 add more cases if supporting more image types } else { QImage scaledImg; ImageInfo::compress(pic, scaledImg, comprImg); nWidth = scaledImg.width(); nHeight = scaledImg.height(); eCompr = ImageInfo::JPG; } ImageInfo img (-1, ImageInfo::OK, eCompr, comprImg, nWidth, nHeight); //int nSize (m_imageColl.size()); int nPrevSize (m_imageColl.size()); int nPos (m_imageColl.addImage(img, convStr(qs))); CB_ASSERT (-1 != nPos); if (nPrevSize != m_imageColl.size()) { CB_ASSERT (m_imageColl.size() == nPos + 1); if (!bConsiderAssigned) { m_snUnassignedImages.insert(nPos); } } /*if (nSize == m_imageColl.size()) // !!! it seemed to make sense to warn the user about duplicates; however, this is also triggered when entering a directory that has contains images after one of those images is assigned to a file; { CursorOverrider crs (Qt::ArrowCursor); showWarning(m_pParentWnd, "Warning", QString("Image in file \"%1\" was already added to the image collection, so it won't be added a second time").arg(qs)); }*/ } } return bRes; } void TagWriter::paste() { CursorOverrider crs; QClipboard* pClp (QApplication::clipboard()); QPixmap pic (pClp->pixmap()); //ttt2 this leads in many cases to recompression; see how to avoid it; (probably ask the clipboard for other formats) if (!pic.isNull()) { ImageInfo img (-1, ImageInfo::OK, pic.toImage()); //ttt2 only reason to change image widget is because images were loaded, so perhaps adjust signals; (keep in mind first time, though) //emit imagesChanged(); addImage(img, CONSIDER_UNASSIGNED); return; } //ttt2 text file name, including "http://", "ftp://", "file://" (see KIO::NetAccess::download() at http://developer.kde.org/documentation/books/kde-2.0-development/ch07lev1sec5.html ) QString qs (pClp->text()); if (qs.isEmpty()) { const QMimeData* p (pClp->mimeData()); QList<QUrl> lst (p->urls()); bool bWarned (false); for (int i = 0; i < lst.size(); ++i) { QString qs1 (lst[i].toString()); //qDebug("#%s#", qs1.toUtf8().constData()); if (!qs1.isEmpty()) { if (qs.isEmpty()) { qs = qs1; } else { if (!bWarned) { bWarned = true; showWarning(m_pParentWnd, tr("Warning"), tr("Currently pasting multiple file names is not supported, so only the first one is considered.")); } } } else { } } if (lst.size() > 0) { qs = lst.front().toString(); } } if (!qs.isEmpty()) { if (-1 == qs.indexOf('\n')) { if (qs.startsWith("file://")) { qs.remove(0, 7); } #ifndef WIN32 #else // when copying paths from explorer we get something like "file:///E:/Multimedia/Images/img1.jpg". After removing first 7 chars we are left with "/E:/Multimedia/Images/img1.jpg", so one more has to be removed if (qs.size() > 8 && qs[0] == getPathSep() && qs[1].isLetter() && qs[2] == ':' && qs[3] == getPathSep()) { qs.remove(0, 1); } #endif #ifndef WIN32 if (qs.startsWith(getPathSep())) // ttt2 see if it makes sense to open files without full name #else qs = fromNativeSeparators(qs); //qDebug("qs=%s", qs.toUtf8().constData()); if (qs.size() > 7 && qs[0].isLetter() && qs[1] == ':' && qs[2] == getPathSep()) #endif { if (addImgFromFile(qs, CONSIDER_UNASSIGNED)) { emit imagesChanged(); return; } } { int nOk (0), nFail (0); for (set<OrigValue>::iterator it = m_sSelOrigVal.begin(); it != m_sSelOrigVal.end(); ++it) { const OrigValue& val (*it); //qDebug ("song %d, fld %d, val %s", val.m_nSong, val.m_nField, val.m_strVal.c_str()); if (TagReader::IMAGE != val.m_nField) // IMAGE is read-only from grid editing's point of view { Mp3HandlerTagData& dest (*m_vpMp3HandlerTagData[val.m_nSong]); try { dest.setData(val.m_nField, convStr(qs)); dest.setStatus(val.m_nField, Mp3HandlerTagData::ASSIGNED); ++nOk; } catch (const Mp3HandlerTagData::InvalidValue&) { ++nFail; } } } if (nFail > 0) { showCritical(m_pParentWnd, tr("Error"), nOk > 0 ? tr("The pasted value couldn't be assigned to some fields") : tr("The pasted value couldn't be assigned to any field")); //ttt2 some/one/several ... } reloadAll("", DONT_CLEAR_DATA, DONT_CLEAR_ASSGN); return; } } else { QStringList lst (qs.split("\n", QString::SkipEmptyParts)); if (lst.size() != cSize(m_vpMp3HandlerTagData)) { if (0 != showMessage(m_pParentWnd, QMessageBox::Warning, 1, 1, tr("Warning"), tr("The number of lines in the clipboard is different from the number of files. Paste anyway?"), tr("Paste"), tr("Cancel"))) { return; } } if (m_bNonStandardTrackNo && m_pCommonData->m_bWarnPastingToNonSeqTracks) { if (0 != showMessage(m_pParentWnd, QMessageBox::Warning, 1, 1, tr("Warning"), tr("The track numbers aren't consecutive numbers starting at 1, so the pasted track information might not match the tracks. Paste anyway?"), tr("Paste"), tr("Cancel"))) { return; } } sort(); m_vstrPastedValues.clear(); //int n (min(lst.size(), cSize(m_vpMp3HandlerTagData))); int n (lst.size()); for (int i = 0; i < n; ++i) { //m_vpMp3HandlerTagData[i]->m_strPastedVal = string(lst[m_vnMovedTo[i]].toUtf8()); //m_vstrPastedValues.push_back(string(lst[m_vnMovedTo[i]].toUtf8())); m_vstrPastedValues.push_back(convStr(lst[i])); //cout << "paste " << m_vstrPastedValues[i] << endl; } /*m_pCurrentAlbumModel->emitLayoutChanged(); m_pCurrentFileModel->emitLayoutChanged();*/ reloadAll("", DONT_CLEAR_DATA, DONT_CLEAR_ASSGN); return; } } showCritical(m_pParentWnd, tr("Error"), tr("Unrecognized clipboard content")); } void TagWriter::sort() { sortSongs(); emit albumChanged(/*false*/); } int TagWriter::addImage(const ImageInfo& img, bool bConsiderAssigned) // returns the index of the image; if it already exists it's not added again; if it's invalid returns -1 { int nPrevSize (m_imageColl.size()); int nPos (m_imageColl.addImage(img)); if (nPrevSize != m_imageColl.size()) { CB_ASSERT (m_imageColl.size() == nPos + 1); if (!bConsiderAssigned) { m_snUnassignedImages.insert(nPos); } } emit imagesChanged(); return nPos; } void TagWriter::selectImg(int n) { m_imageColl.select(n); } void TagWriter::addImgWidget(ImageInfoPanelWdgImpl* p) { CB_ASSERT (0 != p); m_imageColl.addWidget(p); } void TagWriter::hasUnsaved(int nSong, bool& bAssigned, bool& bNonId3V2) // sets bAssigned and bNonId3V2 if at least one field has the corresponding status; { bAssigned = false; bNonId3V2 = false; const Mp3HandlerTagData* p (m_vpMp3HandlerTagData.at(nSong)); for (int i = 0; i < TagReader::LIST_END; ++i) { switch (p->getStatus(i)) { case Mp3HandlerTagData::NON_ID3V2_VAL: bNonId3V2 = true; break; case Mp3HandlerTagData::ASSIGNED: bAssigned = true; break; default:; // nothing } } } void TagWriter::hasUnsaved(bool& bAssigned, bool& bNonId3V2) // sets bAssigned and bNonId3V2 if at least one field in at least a song has the corresponding status; { bAssigned = false; bNonId3V2 = false; bool bAssignedSong, bNonId3V2Song; for (int i = 0; i < cSize(m_vpMp3HandlerTagData); ++i) { hasUnsaved(i, bAssignedSong, bNonId3V2Song); bAssigned |= bAssignedSong; bNonId3V2 |= bNonId3V2Song; } } // sets m_eState and changes the button icon accordingly void AssgnBtnWrp::setState(State eState) { static QPixmap picAll (":/images/assgn-all.svg"); static QPixmap picSome (":/images/assgn-some.svg"); static QPixmap picNone (":/images/assgn-none.svg"); m_eState = eState; //const char* szCap (ALL_ASSGN == eState ? "A" : (SOME_ASSGN == eState ? "S" : "N")); m_pButton->setText(szCap); const QPixmap& pic (ALL_ASSGN == eState ? picAll : (SOME_ASSGN == eState ? picSome : picNone)); m_pButton->setIcon(pic); } ����������������������������������������������������������������������������������������������������������������������������������������MP3Diags-1.2.02/src/Patterns.ui���������������������������������������������������������������������0000644�0001750�0000144�00000006435�11700345647�015114� 0����������������������������������������������������������������������������������������������������ustar �ciobi���������������������������users������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>PatternsDlg</class> <widget class="QDialog" name="PatternsDlg"> <property name="geometry"> <rect> <x>0</x> <y>0</y> <width>744</width> <height>528</height> </rect> </property> <property name="windowTitle"> <string>Patterns</string> </property> <property name="sizeGripEnabled"> <bool>true</bool> </property> <layout class="QGridLayout"> <item row="0" column="0"> <widget class="QTextEdit" name="m_pTextM"> <property name="sizePolicy"> <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> <horstretch>10</horstretch> <verstretch>0</verstretch> </sizepolicy> </property> <property name="tabChangesFocus"> <bool>true</bool> </property> <property name="acceptRichText"> <bool>false</bool> </property> </widget> </item> <item row="0" column="1"> <widget class="QTextBrowser" name="m_infoM"> <property name="sizePolicy"> <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> <horstretch>9</horstretch> <verstretch>0</verstretch> </sizepolicy> </property> </widget> </item> <item row="1" column="0" colspan="2"> <widget class="QWidget" name="widget" native="true"> <layout class="QHBoxLayout"> <property name="margin"> <number>0</number> </property> <item> <widget class="QPushButton" name="m_pAddPredefB"> <property name="text"> <string>Add predefined patterns</string> </property> </widget> </item> <item> <widget class="QWidget" name="m_pSpacerW" native="true"> <layout class="QHBoxLayout"> <property name="margin"> <number>0</number> </property> <item> <spacer> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> <property name="sizeHint" stdset="0"> <size> <width>40</width> <height>20</height> </size> </property> </spacer> </item> <item> <widget class="QLabel" name="label"> <property name="text"> <string/> </property> </widget> </item> </layout> </widget> </item> <item> <widget class="QLabel" name="m_pCrtPosL"> <property name="text"> <string>Line 1, Col 1</string> </property> </widget> </item> <item> <spacer> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> <property name="sizeHint" stdset="0"> <size> <width>40</width> <height>20</height> </size> </property> </spacer> </item> <item> <widget class="QPushButton" name="m_pOkB"> <property name="text"> <string>O&K</string> </property> </widget> </item> <item> <widget class="QPushButton" name="m_pCancelB"> <property name="text"> <string>Cancel</string> </property> </widget> </item> </layout> </widget> </item> </layout> </widget> <resources/> <connections/> </ui> �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������MP3Diags-1.2.02/src/UniqueNotesModel.cpp������������������������������������������������������������0000644�0001750�0000144�00000013122�11724340543�016704� 0����������������������������������������������������������������������������������������������������ustar �ciobi���������������������������users������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include <QPainter> #include <QTableView> #include "UniqueNotesModel.h" #include "CommonData.h" using namespace std; //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== UniqueNotesModel::UniqueNotesModel(CommonData* pCommonData) : QAbstractTableModel(pCommonData->m_pUniqueNotesG), m_pCommonData(pCommonData) { } /*override*/ int UniqueNotesModel::rowCount(const QModelIndex&) const { return m_pCommonData->getUniqueNotes().getFltCount(); } /*override*/ int UniqueNotesModel::columnCount(const QModelIndex&) const { return 2; } /*override*/ QVariant UniqueNotesModel::data(const QModelIndex& index, int nRole) const { LAST_STEP("UniqueNotesModel::data()"); if (!index.isValid() || nRole != Qt::DisplayRole) { return QVariant(); } if (0 == index.column()) { const vector<const Note*>& v (m_pCommonData->getUniqueNotes().getFltVec()); return getNoteLabel(v[index.row()]); } return Notes::tr(m_pCommonData->getUniqueNotes().getFlt(index.row())->getDescription()); } /*override*/ QVariant UniqueNotesModel::headerData(int nSection, Qt::Orientation eOrientation, int nRole /* = Qt::DisplayRole*/) const { LAST_STEP("UniqueNotesModel::headerData"); if (nRole == Qt::SizeHintRole) { return getNumVertHdrSize(m_pCommonData->getUniqueNotes().getFltCount(), eOrientation); } if (nRole != Qt::DisplayRole) { return QVariant(); } if (Qt::Horizontal == eOrientation) { switch (nSection) { case 0: return tr("L"); case 1: return tr("Note"); default: CB_ASSERT (false); } } return nSection + 1; } void UniqueNotesModel::selectTopLeft() { QItemSelectionModel* pSelModel (m_pCommonData->m_pUniqueNotesG->selectionModel()); pSelModel->clear(); emit layoutChanged(); if (m_pCommonData->getUniqueNotes().getFltCount() > 0) { //pSelModel->select(index(0, 0), QItemSelectionModel::Current); //pSelModel->select(index(0, 0), QItemSelectionModel::Select); //m_pCommonData->printFilesCrt(); m_pCommonData->m_pUniqueNotesG->setCurrentIndex(index(0, 0)); //m_pCommonData->printFilesCrt(); } } //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== UniqueNotesGDelegate::UniqueNotesGDelegate(CommonData* pCommonData) : MultiLineTvDelegate(pCommonData->m_pUniqueNotesG), m_pCommonData(pCommonData) { } /*override*/ void UniqueNotesGDelegate::paint(QPainter* pPainter, const QStyleOptionViewItem& option, const QModelIndex& index) const { pPainter->save(); QStyleOptionViewItemV2 myOption (option); const Note* pNote (m_pCommonData->getUniqueNotes().getFlt(index.row())); if (0 == index.column()) { myOption.displayAlignment |= Qt::AlignHCenter; if (Note::ERR == pNote->getSeverity()) { myOption.palette.setColor(QPalette::Text, ERROR_PEN_COLOR()); } else if (Note::SUPPORT == pNote->getSeverity()) { myOption.palette.setColor(QPalette::Text, SUPPORT_PEN_COLOR()); } } QColor colNote; double dGradStart, dGradEnd; m_pCommonData->getNoteColor(*pNote, m_pCommonData->getUniqueNotes().getFltVec(), colNote, dGradStart, dGradEnd); QLinearGradient grad (0, option.rect.y(), 0, option.rect.y() + option.rect.height()); configureGradient(grad, colNote, dGradStart, dGradEnd); pPainter->fillRect(option.rect, grad); MultiLineTvDelegate::paint(pPainter, myOption, index); pPainter->restore(); } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������MP3Diags-1.2.02/src/fstream_unicode.h���������������������������������������������������������������0000644�0001750�0000144�00000062161�12265760240�016270� 0����������������������������������������������������������������������������������������������������ustar �ciobi���������������������������users������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef FStreamUtf8H #define FStreamUtf8H /* Drop-in replacements for ifstream, ofstream, and fstream, which take Unicode strings for filenames (instead of the char* strings in the system codepage that MinGW uses.) (There are ifstream, ofstream, and fstream correspondents, but only fstream is mentioned, for brevity.) Provides the class fstream_unicode, which allows opening of a file with the name given as a UTF-8 or a UTF-16 string. Provides fstream_utf8, which on Windows is a typedef for fstream_unicode, while elsewhere it's a typedef for std::basic_fstream (currently the assumption is that outside Windows everything is UTF-8, although the older a Linux system is, the more likely it is to have non-UTF-8 file names.) Normally fstream_utf8 seems the best to use, because Linux users are shielded from bugs in fstream_unicode. However, the situation of a particular project may dictate otherwise. Currently using wchar_t* to open files on Linux throws an exception. It would be easy to implement, but there's no use for it right now. While the name reflects the intended use, fstream_unicode can do more than just open files with names given as Unicode strings: you can pass an existing file descriptor on its constructor, or you can add a specialization of unicodeOpenHlp that turns some custom objects into file descriptors or into UTF-8 or UTF-16 strings, and then pass those objects to fstream_unicode's constructor or to its open() function. For example you can easily make fstream_unicode accept a std::string, without the need of calling c_str(). */ #if defined(__GNUC__) && !defined(__llvm__) #include <ext/stdio_filebuf.h> #include <istream> #include <ostream> #include <fstream> #include <fcntl.h> // for open() template<class T> int unicodeOpenHlp(const T& handler, std::ios_base::openmode __mode); // converts __mode to flags that can be used by the POSIX open() function int getOpenFlags(std::ios_base::openmode __mode); //******************************************************************************************** //******************************************************************************************** //***************** ***************** //***************** Adapted from fstream included in the GNU ISO C++ Library ***************** //***************** ***************** //***************** From the Free Software Foundation, Inc. ***************** //***************** ***************** //***************** http://gcc.gnu.org/libstdc++/ ***************** //***************** ***************** //******************************************************************************************** //******************************************************************************************** // needed because the base class doesn't have open() template<typename _CharT, typename _Traits = std::char_traits<_CharT> > class stdio_filebuf_open : public __gnu_cxx::stdio_filebuf<_CharT, _Traits> { public: stdio_filebuf_open() : __gnu_cxx::stdio_filebuf<_CharT, _Traits>() {} stdio_filebuf_open(int __fd, std::ios_base::openmode __mode, size_t __size = static_cast<size_t>(BUFSIZ)) : __gnu_cxx::stdio_filebuf<_CharT, _Traits>(__fd, __mode, __size) {} /*override*/ ~stdio_filebuf_open() {} /* typedef _Traits traits_type; typedef typename traits_type::off_type off_type; */ typedef _CharT char_type; typedef _Traits traits_type; typedef typename traits_type::int_type int_type; typedef typename traits_type::pos_type pos_type; typedef typename traits_type::off_type off_type; typedef std::basic_streambuf<char_type, traits_type> __streambuf_type; typedef typename __gnu_cxx::stdio_filebuf<_CharT, _Traits>::__filebuf_type __filebuf_type; typedef typename __gnu_cxx::stdio_filebuf<_CharT, _Traits>::__file_type __file_type; typedef typename traits_type::state_type __state_type; typedef typename __gnu_cxx::stdio_filebuf<_CharT, _Traits>::__codecvt_type __codecvt_type; //using __gnu_cxx::stdio_filebuf<_CharT, _Traits>::open; __filebuf_type* open(int __fd, std::ios_base::openmode __mode) { //close(); __filebuf_type *__ret = NULL; if (!this->is_open()) { this->_M_file.sys_open(__fd, __mode); if (this->is_open()) { this->_M_allocate_internal_buffer(); this->_M_mode = __mode; // Setup initial buffer to 'uncommitted' mode. this->_M_reading = false; this->_M_writing = false; this->_M_set_buffer(-1); // Reset to initial state. this->_M_state_last = this->_M_state_cur = this->_M_state_beg; // 27.8.1.3,4 if ((__mode & std::ios_base::ate) && this->seekoff(0, std::ios_base::end, __mode) == pos_type(off_type(-1))) this->close(); else __ret = this; // this->_M_mode = __mode; //this->_M_buf_size = __size; // this->_M_allocate_internal_buffer(); // this->_M_reading = false; // this->_M_writing = false; // this->_M_set_buffer(-1); } } return __ret; } }; // [27.8.1.5] Template class basic_ifstream /** * @brief Controlling input for files. * * This class supports reading from named files, using the inherited * functions from std::basic_istream. To control the associated * sequence, an instance of std::basic_filebuf is used, which this page * refers to as @c sb. */ template<typename _CharT, typename _Traits = std::char_traits<_CharT> > class basic_ifstream_unicode : public std::basic_istream<_CharT, _Traits> { public: // Types: typedef _CharT char_type; typedef _Traits traits_type; typedef typename traits_type::int_type int_type; typedef typename traits_type::pos_type pos_type; typedef typename traits_type::off_type off_type; // Non-standard types: typedef stdio_filebuf_open<char_type, traits_type> __filebuf_type; typedef std::basic_istream<char_type, traits_type> __istream_type; private: __filebuf_type _M_filebuf; public: // Constructors/Destructors: /** * @brief Default constructor. * * Initializes @c sb using its default constructor, and passes * @c &sb to the base class initializer. Does not open any files * (you haven't given it a filename to open). */ basic_ifstream_unicode() : __istream_type(), _M_filebuf() { this->init(&_M_filebuf); } /** * @brief Create an input file stream. * @param x Null terminated string specifying the filename, or a file descriptor, or anything for which a specialization of unicodeOpenHlp exists. * @param mode Open file in specified mode (see std::ios_base). * * @c ios_base::in is automatically included in @a mode. * * Tip: When using std::string to hold the filename, you must use * .c_str() before passing it to this constructor. */ template<class T> explicit basic_ifstream_unicode(T x, std::ios_base::openmode __mode = std::ios_base::in) : __istream_type(), _M_filebuf() { this->init(&_M_filebuf); this->open(x, __mode); } /** * @brief The destructor does nothing. * * The file is closed by the filebuf object, not the formatting * stream. */ ~basic_ifstream_unicode() { } // Members: /** * @brief Accessing the underlying buffer. * @return The current basic_filebuf buffer. * * This hides both signatures of std::basic_ios::rdbuf(). */ __filebuf_type* rdbuf() const { return const_cast<__filebuf_type*>(&_M_filebuf); } /** * @brief Wrapper to test for an open file. * @return @c rdbuf()->is_open() */ bool is_open() { return _M_filebuf.is_open(); } // _GLIBCXX_RESOLVE_LIB_DEFECTS // 365. Lack of const-qualification in clause 27 bool is_open() const { return _M_filebuf.is_open(); } /** * @brief Opens an external file. * @param x Null terminated string specifying the filename, or a file descriptor, or anything for which a specialization of unicodeOpenHlp exists. * @param mode The open mode flags. * * Calls @c std::basic_filebuf::open(s,mode|in). If that function * fails, @c failbit is set in the stream's error state. * * Tip: When using std::string to hold the filename, you must use * .c_str() before passing it to this constructor. */ template<class T> void open(const T& x, std::ios_base::openmode __mode = std::ios_base::in) { if (!_M_filebuf.open(unicodeOpenHlp(x, __mode | std::ios_base::in), __mode | std::ios_base::in)) this->setstate(std::ios_base::failbit); else // _GLIBCXX_RESOLVE_LIB_DEFECTS // 409. Closing an fstream should clear error state this->clear(); } /** * @brief Close the file. * * Calls @c std::basic_filebuf::close(). If that function * fails, @c failbit is set in the stream's error state. */ void close() { if (!_M_filebuf.close()) this->setstate(std::ios_base::failbit); } }; // [27.8.1.8] Template class basic_ofstream /** * @brief Controlling output for files. * * This class supports reading from named files, using the inherited * functions from std::basic_ostream. To control the associated * sequence, an instance of std::basic_filebuf is used, which this page * refers to as @c sb. */ template<typename _CharT, typename _Traits = std::char_traits<_CharT> > class basic_ofstream_unicode : public std::basic_ostream<_CharT,_Traits> { public: // Types: typedef _CharT char_type; typedef _Traits traits_type; typedef typename traits_type::int_type int_type; typedef typename traits_type::pos_type pos_type; typedef typename traits_type::off_type off_type; // Non-standard types: typedef stdio_filebuf_open<char_type, traits_type> __filebuf_type; typedef std::basic_ostream<char_type, traits_type> __ostream_type; private: __filebuf_type _M_filebuf; public: // Constructors: /** * @brief Default constructor. * * Initializes @c sb using its default constructor, and passes * @c &sb to the base class initializer. Does not open any files * (you haven't given it a filename to open). */ basic_ofstream_unicode(): __ostream_type(), _M_filebuf() { this->init(&_M_filebuf); } /** * @brief Create an output file stream. * @param x Null terminated string specifying the filename, or a file descriptor, or anything for which a specialization of unicodeOpenHlp exists. * @param mode Open file in specified mode (see std::ios_base). * * @c ios_base::out|ios_base::trunc is automatically included in * @a mode. * * Tip: When using std::string to hold the filename, you must use * .c_str() before passing it to this constructor. */ template<class T> explicit basic_ofstream_unicode(T x, std::ios_base::openmode __mode = std::ios_base::out|std::ios_base::trunc) : __ostream_type(), _M_filebuf() { this->init(&_M_filebuf); this->open(x, __mode); } /** * @brief The destructor does nothing. * * The file is closed by the filebuf object, not the formatting * stream. */ ~basic_ofstream_unicode() { } // Members: /** * @brief Accessing the underlying buffer. * @return The current basic_filebuf buffer. * * This hides both signatures of std::basic_ios::rdbuf(). */ __filebuf_type* rdbuf() const { return const_cast<__filebuf_type*>(&_M_filebuf); } /** * @brief Wrapper to test for an open file. * @return @c rdbuf()->is_open() */ bool is_open() { return _M_filebuf.is_open(); } // _GLIBCXX_RESOLVE_LIB_DEFECTS // 365. Lack of const-qualification in clause 27 bool is_open() const { return _M_filebuf.is_open(); } /** * @brief Opens an external file. * @param x Null terminated string specifying the filename, or a file descriptor, or anything for which a specialization of unicodeOpenHlp exists. * @param mode The open mode flags. * * Calls @c std::basic_filebuf::open(s,mode|out|trunc). If that * function fails, @c failbit is set in the stream's error state. * * Tip: When using std::string to hold the filename, you must use * .c_str() before passing it to this constructor. */ template<class T> void open(const T& x, std::ios_base::openmode __mode = std::ios_base::out | std::ios_base::trunc) { if (!_M_filebuf.open(unicodeOpenHlp(x, __mode | std::ios_base::out), __mode | std::ios_base::out)) this->setstate(std::ios_base::failbit); else // _GLIBCXX_RESOLVE_LIB_DEFECTS // 409. Closing an fstream should clear error state this->clear(); } /** * @brief Close the file. * * Calls @c std::basic_filebuf::close(). If that function * fails, @c failbit is set in the stream's error state. */ void close() { if (!_M_filebuf.close()) this->setstate(std::ios_base::failbit); } }; // [27.8.1.11] Template class basic_fstream /** * @brief Controlling input and output for files. * * This class supports reading from and writing to named files, using * the inherited functions from std::basic_iostream. To control the * associated sequence, an instance of std::basic_filebuf is used, which * this page refers to as @c sb. */ template<typename _CharT, typename _Traits = std::char_traits<_CharT> > class basic_fstream_unicode : public std::basic_iostream<_CharT, _Traits> { public: // Types: typedef _CharT char_type; typedef _Traits traits_type; typedef typename traits_type::int_type int_type; typedef typename traits_type::pos_type pos_type; typedef typename traits_type::off_type off_type; // Non-standard types: typedef stdio_filebuf_open<char_type, traits_type> __filebuf_type; typedef std::basic_ios<char_type, traits_type> __ios_type; typedef std::basic_iostream<char_type, traits_type> __iostream_type; private: __filebuf_type _M_filebuf; public: // Constructors/destructor: /** * @brief Default constructor. * * Initializes @c sb using its default constructor, and passes * @c &sb to the base class initializer. Does not open any files * (you haven't given it a filename to open). */ basic_fstream_unicode() : __iostream_type(), _M_filebuf() { this->init(&_M_filebuf); } /** * @brief Create an input/output file stream. * @param x Null terminated string specifying the filename, or a file descriptor, or anything for which a specialization of unicodeOpenHlp exists. * @param mode Open file in specified mode (see std::ios_base). * * Tip: When using std::string to hold the filename, you must use * .c_str() before passing it to this constructor. */ template<class T> explicit basic_fstream_unicode(T x, std::ios_base::openmode __mode = std::ios_base::in | std::ios_base::out) : __iostream_type(NULL), _M_filebuf() { this->init(&_M_filebuf); this->open(x, __mode); } /** * @brief The destructor does nothing. * * The file is closed by the filebuf object, not the formatting * stream. */ ~basic_fstream_unicode() { } // Members: /** * @brief Accessing the underlying buffer. * @return The current basic_filebuf buffer. * * This hides both signatures of std::basic_ios::rdbuf(). */ __filebuf_type* rdbuf() const { return const_cast<__filebuf_type*>(&_M_filebuf); } /** * @brief Wrapper to test for an open file. * @return @c rdbuf()->is_open() */ bool is_open() { return _M_filebuf.is_open(); } // _GLIBCXX_RESOLVE_LIB_DEFECTS // 365. Lack of const-qualification in clause 27 bool is_open() const { return _M_filebuf.is_open(); } /** * @brief Opens an external file. * @param x Null terminated string specifying the filename, or a file descriptor, or anything for which a specialization of unicodeOpenHlp exists. * @param mode The open mode flags. * * Calls @c std::basic_filebuf::open(s,mode). If that * function fails, @c failbit is set in the stream's error state. * * Tip: When using std::string to hold the filename, you must use * .c_str() before passing it to this constructor. */ template<class T> void open(const T& x, std::ios_base::openmode __mode = std::ios_base::in | std::ios_base::out) { if (!_M_filebuf.open(unicodeOpenHlp(x, __mode), __mode)) this->setstate(std::ios_base::failbit); else // _GLIBCXX_RESOLVE_LIB_DEFECTS // 409. Closing an fstream should clear error state this->clear(); } /** * @brief Close the file. * * Calls @c std::basic_filebuf::close(). If that function * fails, @c failbit is set in the stream's error state. */ void close() { if (!_M_filebuf.close()) this->setstate(std::ios_base::failbit); } }; #if defined(WIN32) || defined(__OS2__) typedef basic_ifstream_unicode<char> ifstream_utf8; typedef basic_ofstream_unicode<char> ofstream_utf8; typedef basic_fstream_unicode<char> fstream_utf8; #else //inline const char* unicodeOpenHlp(const char* szUtf8Name) { return szUtf8Name; } //int unicodeOpenHlp(const char* szUtf8Name, std::ios_base::openmode __mode); #include <fstream> typedef std::basic_ifstream<char> ifstream_utf8; typedef std::basic_ofstream<char> ofstream_utf8; typedef std::basic_fstream<char> fstream_utf8; #endif // #ifndef WIN32 / else typedef basic_ifstream_unicode<char> ifstream_unicode; typedef basic_ofstream_unicode<char> ofstream_unicode; typedef basic_fstream_unicode<char> fstream_unicode; #elif defined(_MSC_VER) && _MSC_VER>=1400 // #if defined(__GNUC__) && !defined(__llvm__) // Visual Studio port by Sebastian Schuberth // As of Visual Studio 2005 (aka version 8.0), the supplied STL has extensions // that accept filenames of type wchar_t. #include <fstream> #define NOMINMAX 1 #define WIN32_LEAN_AND_MEAN 1 #include <windows.h> typedef std::ifstream ifstream_unicode; typedef std::ofstream ofstream_unicode; typedef std::fstream fstream_unicode; class ifstream_utf8:public std::ifstream { public: explicit ifstream_utf8(char const* _Filename,ios_base::openmode _Mode=ios_base::in,int _Prot=(int)ios_base::_Openprot) { open(_Filename,_Mode,_Prot); } void open(char const* _Filename,ios_base::openmode _Mode=ios_base::in,int _Prot=(int)ios_base::_Openprot) { int length=MultiByteToWideChar(CP_UTF8,0,_Filename,-1,NULL,0); if (length>0) { wchar_t* buffer=new wchar_t[length+1]; MultiByteToWideChar(CP_UTF8,0,_Filename,-1,buffer,length); std::ifstream::open(buffer,_Mode,_Prot); delete [] buffer; } else { setstate(ios_base::failbit); } } void open(char const* _Filename,ios_base::open_mode _Mode) { open(_Filename,_Mode); } }; class ofstream_utf8:public std::ofstream { public: explicit ofstream_utf8(char const* _Filename,ios_base::openmode _Mode=ios_base::out,int _Prot=(int)ios_base::_Openprot) { open(_Filename,_Mode,_Prot); } void open(char const* _Filename,ios_base::openmode _Mode=ios_base::out,int _Prot=(int)ios_base::_Openprot) { int length=MultiByteToWideChar(CP_UTF8,0,_Filename,-1,NULL,0); if (length>0) { wchar_t* buffer=new wchar_t[length+1]; MultiByteToWideChar(CP_UTF8,0,_Filename,-1,buffer,length); std::ofstream::open(buffer,_Mode,_Prot); delete [] buffer; } else { setstate(ios_base::failbit); } } void open(char const* _Filename,ios_base::open_mode _Mode) { open(_Filename,_Mode); } }; class fstream_utf8:public std::fstream { public: explicit fstream_utf8(char const* _Filename,ios_base::openmode _Mode=ios_base::in|ios_base::out,int _Prot=(int)ios_base::_Openprot) { open(_Filename,_Mode,_Prot); } void open(char const* _Filename,ios_base::openmode _Mode=ios_base::in|ios_base::out,int _Prot=(int)ios_base::_Openprot) { int length=MultiByteToWideChar(CP_UTF8,0,_Filename,-1,NULL,0); if (length>0) { wchar_t* buffer=new wchar_t[length+1]; MultiByteToWideChar(CP_UTF8,0,_Filename,-1,buffer,length); std::fstream::open(buffer,_Mode,_Prot); delete [] buffer; } else { setstate(ios_base::failbit); } } void open(char const* _Filename,ios_base::open_mode _Mode) { open(_Filename,_Mode); } }; #elif defined(__llvm__) #include <fstream> //ttt2 not sure these are enough outside Linux typedef std::basic_ifstream<char> ifstream_utf8; typedef std::basic_ofstream<char> ofstream_utf8; typedef std::basic_fstream<char> fstream_utf8; #else // _MSC_VER / gcc / __llvm__ //#error classes i/ofstream_utf8 need to be ported to this compiler #warning classes i/ofstream_utf8 need to be ported to your compiler #include <fstream> typedef std::basic_ifstream<char> ifstream_utf8; typedef std::basic_ofstream<char> ofstream_utf8; typedef std::basic_fstream<char> fstream_utf8; #endif // _MSC_VER / gcc / __llvm__ #endif // FStreamUtf8H ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������MP3Diags-1.2.02/src/StructuralTransformation.cpp����������������������������������������������������0000644�0001750�0000144�00000075574�11711047560�020565� 0����������������������������������������������������������������������������������������������������ustar �ciobi���������������������������users������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "StructuralTransformation.h" #include "Helpers.h" #include "Mp3Manip.h" #include "MpegStream.h" #include "Id3V230Stream.h" #include "Id3V240Stream.h" #include "ApeStream.h" using namespace std; using namespace pearl; //=============================================================================================================== //=============================================================================================================== //=============================================================================================================== // locates a frame that starts about 2 seconds before pos and is compatible with pAudio; // if the stream is too short or such a frame can't be found, it returns pos; // doesn't change the pointer for "in"; static streampos findNearMpegFrameAtLeft(streampos pos, istream& in, MpegStream* pAudio) { StreamStateRestorer rst (in); streampos posRes (pos); in.seekg(pos); int nSize (pAudio->getBitrate()/8*2); // about 2 seconds if (posRes > nSize) { posRes -= nSize; } else { posRes = 0; } in.seekg(posRes); if (pAudio->findNextCompatFrame(in, pos)) { posRes = in.tellg(); return posRes; } return pos; } // locates a frame that starts about 2 seconds after pos and is compatible with pAudio; // if the stream is too short or such a frame can't be found, it returns pos; // doesn't change the pointer for "in"; static streampos findNearMpegFrameAtRight(streampos pos, istream& in, MpegStream* pAudio) { StreamStateRestorer rst (in); streampos posRes (pos); in.seekg(pos); int nSize (pAudio->getBitrate()/8*2); // about 2 seconds posRes += nSize; if (posRes > getSize(in)) { posRes = pos; // ttt3 perhaps should go a little farther } in.seekg(posRes); streampos posLimit (pos); posLimit += nSize*2; if (pAudio->findNextCompatFrame(in, posLimit)) { posRes = in.tellg(); return posRes; } return pos; } //=============================================================================================================== //=============================================================================================================== //=============================================================================================================== /*static*/ bool SingleBitRepairer::isUnknown(const DataStream* p) { // UnknownDataStreamBase covers UnknownDataStream, BrokenDataStream, UnsupportedDataStream and TruncatedMpegDataStream, which is probably OK; doesn't matter much if it returns true for types that it shouldn't - there's just a small performance penalty return 0 != dynamic_cast<const UnknownDataStreamBase*>(p); } /*override*/ Transformation::Result SingleBitRepairer::apply(const Mp3Handler& h, const TransfConfig& transfConfig, const string& strOrigSrcName, string& strTempName) { //CB_ASSERT(UnknownDataStreamBase::BEGIN_SIZE >= x) //if (strOrigSrcName in tmp or proc) throw; //ttt2 const vector<DataStream*>& vpStreams (h.getStreams()); for (int i = 0, n = cSize(vpStreams); i < n - 2; ++i) { DataStream* pDataStream (vpStreams[i]); MpegStream* pAudio (dynamic_cast<MpegStream*>(pDataStream)); if (0 != pAudio) { UnknownDataStreamBase* pFrameToChange (dynamic_cast<UnknownDataStreamBase*>(vpStreams[i + 1])); if (0 != pFrameToChange) { for (int k = 0; k < 32; ++k) { try { NoteColl notes (100); char bfr [4]; const char* q (pFrameToChange->getBegin()); bfr[0] = q[0]; bfr[1] = q[1]; bfr[2] = q[2]; bfr[3] = q[3]; bfr[k / 8] ^= 1 << (k % 8); MpegFrameBase frame (notes, pFrameToChange->getPos(), bfr); if (pAudio->isCompatible(frame)) { streampos posNewFrameEnd (frame.getPos()); posNewFrameEnd += frame.getSize(); int j (i + 1); for (; j < n; ++j) { UnknownDataStreamBase* pLastReplacedFrame (dynamic_cast<UnknownDataStreamBase*>(vpStreams[j])); if (0 == pLastReplacedFrame) { break; } streampos posLastReplacedFrameEnd (pLastReplacedFrame->getPos()); posLastReplacedFrameEnd += pLastReplacedFrame->getSize(); if (posLastReplacedFrameEnd == posNewFrameEnd) { //qDebug("found"); ifstream_utf8 in (h.getName().c_str(), ios::binary); { // comp switch (transfConfig.getCompAction()) { case TransfConfig::TRANSF_DONT_CREATE: break; case TransfConfig::TRANSF_CREATE: { streampos posCompBeg (findNearMpegFrameAtLeft(pFrameToChange->getPos(), in, pAudio)); streampos posCompEnd (findNearMpegFrameAtRight(posLastReplacedFrameEnd, in, pAudio)); in.seekg(posCompBeg); string strCompBefore; string strCompAfter; transfConfig.getCompNames(strOrigSrcName, getActionName(), strCompBefore, strCompAfter); { // tmp before ofstream_utf8 out (strCompBefore.c_str(), ios::binary); //partialCopy(in, out, 10000); //appendFilePart(in, out, pos, 2*nHalfSize); appendFilePart(in, out, posCompBeg, posCompEnd - posCompBeg); } { // tmp after ofstream_utf8 out (strCompAfter.c_str(), ios::binary); appendFilePart(in, out, posCompBeg, posCompEnd - posCompBeg); out.seekp(pFrameToChange->getPos() - posCompBeg); out.write(bfr, 4); } } break; default: CB_ASSERT (false); } } { // temp transfConfig.getTempName(strOrigSrcName, getActionName(), strTempName); ofstream_utf8 out (strTempName.c_str(), ios::binary); in.seekg(0); for (int i1 = 0; i1 < n; ++i1) { DataStream* q (vpStreams[i1]); q->copy(in, out); } out.seekp(pFrameToChange->getPos()); out.write(bfr, 4); CB_CHECK1 (out, WriteError()); } return CHANGED; } if (posLastReplacedFrameEnd > posNewFrameEnd) { break; } } } } catch (const MpegFrameBase::NotMpegFrame&) { } } } } /*BrokenMpegDataStream* pBroken (dynamic_cast<BrokenMpegDataStream*>(pDataStream)); UnknownDataStream* pUnknown (dynamic_cast<UnknownDataStream*>(pDataStream)); TruncatedMpegDataStream* pTruncated (dynamic_cast<TruncatedMpegDataStream*>(pDataStream));*/ } return NOT_CHANGED; } //================================================================================================================================ //================================================================================================================================ //================================================================================================================================ /*override*/ Transformation::Result InnerNonAudioRemover::apply(const Mp3Handler& h, const TransfConfig& cfg, const std::string& strOrigSrcName, std::string& strTempName) { setupDiscarded(h); Result eRes (GenericRemover::apply(h, cfg, strOrigSrcName, strTempName)); return NOT_CHANGED == eRes ? NOT_CHANGED : CHANGED_NO_RECALL; } void InnerNonAudioRemover::setupDiscarded(const Mp3Handler& h) { m_spStreamsToDiscard.clear(); const vector<DataStream*>& vpStreams (h.getStreams()); int nFirstAudioPos (-1); int nLastAudioPos (-1); for (int i = 0, n = cSize(vpStreams); i < n; ++i) { DataStream* pDataStream (vpStreams[i]); //qDebug("%s", pDataStream->getDisplayName()); //?? probably want to remove garbage between vbri and audio, but definitely not the 16 bytes between xing and audio if (0 != dynamic_cast<MpegStream*>(pDataStream) || 0 != dynamic_cast<VbriStream*>(pDataStream))// ttt2 should consider these? : || 0 != dynamic_cast<XingStreamBase*>(pDataStream) || 0 != dynamic_cast<VbriStream*>(pDataStream)) // with them, there's a risk of destroying Xing headers created by Mp3Fixer { if (-1 == nFirstAudioPos) { nFirstAudioPos = i; } nLastAudioPos = i; } } if (nFirstAudioPos == nLastAudioPos) { return; } for (int i = nFirstAudioPos + 1; i < nLastAudioPos; ++i) { if (0 == dynamic_cast<MpegStream*>(vpStreams[i]) && 0 == dynamic_cast<VbriStream*>(vpStreams[i])) { m_spStreamsToDiscard.insert(vpStreams[i]); } } } /* ttt2 see about unsynch audio (when some frames use data from other frames - bit reservoirs) diagram at http://www.hydrogenaudio.org/forums/index.php?showtopic=36445&pid=321867&mode=threaded&start=#entry321867 http://www.hydrogenaudio.org/forums/index.php?showtopic=35654&st=25&p=354991&#entry354991 http://www.hydrogenaudio.org/forums/lofiversion/index.php/t42194.html http://www.hydrogenaudio.org/forums/index.php?showtopic=38510&pid=347136&mode=threaded&start=0#entry347136 the issue is that this is stored in the "side info" area, which is poorly documented, the main way to figure out what's going on being to look at working code; so it's probably not worth the bother; something that could probably be done is to remove some/most of the "truncated audio" notes, where the last frame only uses the beginning of the area; still, padding with 0 is probably better, as most stand-alone taggers shoudln't care about side info, and probably just expect a full frame; note that inserting a blank frame before a cut duesn't really solve the warning in mplayer about being unable to rewind, because the side info needs to be modified in the affected frames */ /*override*/ bool InnerNonAudioRemover::matches(DataStream* p) const { bool b (m_spStreamsToDiscard.count(p) > 0); CB_ASSERT (!b || (0 == dynamic_cast<MpegStream*>(p) && 0 == dynamic_cast<VbriStream*>(p))); return b; } //================================================================================================================================ //================================================================================================================================ //================================================================================================================================ /*override*/ Transformation::Result GenericRemover::apply(const Mp3Handler& h, const TransfConfig& transfConfig, const std::string& strOrigSrcName, std::string& strTempName) { const vector<DataStream*>& vpStreams (h.getStreams()); bool bFoundMatch (false); for (int i = 0, n = cSize(vpStreams); i < n; ++i) { if (matches(vpStreams[i])) { bFoundMatch = true; break; } } if (!bFoundMatch) { return NOT_CHANGED; } //ttt2 see InnerNonAudioRemover: Lambo has an error in mplayer, because of bit reservoir ifstream_utf8 in (h.getName().c_str(), ios::binary); { // comp switch (transfConfig.getCompAction()) { case TransfConfig::TRANSF_DONT_CREATE: break; case TransfConfig::TRANSF_CREATE: { in.seekg(0); for (int i = 0, n = cSize(vpStreams); i < n; ++i) { DataStream* p (vpStreams[i]); if (matches(p)) { MpegStream* pPrevAudio (i > 0 ? dynamic_cast<MpegStream*>(vpStreams[i - 1]) : 0); MpegStream* pNextAudio (i < n - 1 ? dynamic_cast<MpegStream*>(vpStreams[i + 1]) : 0); string strCompBefore; string strCompAfter; transfConfig.getCompNames(strOrigSrcName, getActionName(), strCompBefore, strCompAfter); streampos posCompBeg (0 != pPrevAudio ? findNearMpegFrameAtLeft(pPrevAudio->getEnd(), in, pPrevAudio) : p->getPos()); streampos posCompEnd (0 != pNextAudio ? findNearMpegFrameAtRight(pNextAudio->getPos(), in, pNextAudio) : p->getEnd()); { // comp before ofstream_utf8 out (strCompBefore.c_str(), ios::binary); appendFilePart(in, out, posCompBeg, posCompEnd - posCompBeg); } { // comp after if (0 != pPrevAudio || 0 != pNextAudio) { ofstream_utf8 out (strCompAfter.c_str(), ios::binary); appendFilePart(in, out, posCompBeg, p->getPos() - posCompBeg); appendFilePart(in, out, p->getEnd(), posCompEnd - p->getEnd()); } } } } } break; default: CB_ASSERT (false); } } { // temp transfConfig.getTempName(strOrigSrcName, getActionName(), strTempName); ofstream_utf8 out (strTempName.c_str(), ios::binary); in.seekg(0); for (int i = 0, n = cSize(vpStreams); i < n; ++i) { DataStream* pDataStream (vpStreams[i]); if (!matches(pDataStream)) { //qDebug("copying %d for %s", (int)pDataStream->getSize(), pDataStream->getDisplayName()); pDataStream->copy(in, out); } /*else { qDebug("skipping %d for %s", (int)pDataStream->getSize(), pDataStream->getDisplayName()); }*/ } } return CHANGED; // CHANGED_NO_RECALL would be ok in some cases but it's hard to tell when } /*override*/ bool UnknownDataStreamRemover::matches(DataStream* p) const { return 0 != dynamic_cast<UnknownDataStream*>(p); } /*override*/ bool BrokenDataStreamRemover::matches(DataStream* p) const { return 0 != dynamic_cast<BrokenDataStream*>(p); } /*override*/ bool UnsupportedDataStreamRemover::matches(DataStream* p) const { return 0 != dynamic_cast<UnsupportedDataStream*>(p); } /*override*/ bool TruncatedMpegDataStreamRemover::matches(DataStream* p) const { return 0 != dynamic_cast<TruncatedMpegDataStream*>(p); } /*override*/ bool NullStreamRemover::matches(DataStream* p) const { return 0 != dynamic_cast<NullDataStream*>(p); } /*override*/ bool BrokenId3V2Remover::matches(DataStream* pDataStream) const { BrokenDataStream* p (dynamic_cast<BrokenDataStream*>(pDataStream)); if (0 == p) { return false; } string strBaseName (p->getBaseName()); return strBaseName == Id3V230Stream::getClassDisplayName() || strBaseName == Id3V240Stream::getClassDisplayName(); } /*override*/ bool UnsupportedId3V2Remover::matches(DataStream* pDataStream) const { UnsupportedDataStream* p (dynamic_cast<UnsupportedDataStream*>(pDataStream)); if (0 == p) { return false; } string strBaseName (p->getBaseName()); return strBaseName == Id3V2StreamBase::getClassDisplayName(); } /*override*/ bool MultipleId3StreamRemover::matches(DataStream* p) const { return m_spStreamsToDiscard.count(p) > 0; } void MultipleId3StreamRemover::setupDiscarded(const Mp3Handler& h) { m_spStreamsToDiscard.clear(); vector<DataStream*> v1, v2; const vector<DataStream*>& vpStreams (h.getStreams()); for (int i = 0, n = cSize(vpStreams); i < n; ++i) { DataStream* p (vpStreams[i]); if (0 != dynamic_cast<Id3V2StreamBase*>(p)) { v2.push_back(p); } if (0 != dynamic_cast<Id3V1Stream*>(p)) { v1.push_back(p); } } if (cSize(v1) > 1) { m_spStreamsToDiscard.insert(v1.begin(), v1.end() - 1); } if (cSize(v2) > 1) { m_spStreamsToDiscard.insert(v2.begin() + 1, v2.end()); } } /*override*/ bool MismatchedXingRemover::matches(DataStream* p) const { return m_spStreamsToDiscard.count(p) > 0; } void MismatchedXingRemover::setupDiscarded(const Mp3Handler& h) { m_spStreamsToDiscard.clear(); const vector<DataStream*>& vpStreams (h.getStreams()); for (int i = 0, n = cSize(vpStreams); i < n - 1; ++i) { XingStreamBase* pXing (dynamic_cast<XingStreamBase*>(vpStreams[i])); if (0 != pXing) { MpegStream* pAudio (dynamic_cast<MpegStream*>(vpStreams[i + 1])); if (0 != pAudio && pXing->getFrameCount() != pAudio->getFrameCount()) { m_spStreamsToDiscard.insert(pXing); } } } } /*override*/ bool Id3V1Remover::matches(DataStream* pDataStream) const { Id3V1Stream* p (dynamic_cast<Id3V1Stream*>(pDataStream)); return 0 != p; } /*override*/ bool ApeRemover::matches(DataStream* pDataStream) const { ApeStream* p (dynamic_cast<ApeStream*>(pDataStream)); return 0 != p; } /*override*/ bool NonAudioRemover::matches(DataStream* pDataStream) const { MpegStream* p (dynamic_cast<MpegStream*>(pDataStream)); return 0 == p; } //================================================================================================================================ //================================================================================================================================ //================================================================================================================================ /*override*/ Transformation::Result TruncatedAudioPadder::apply(const Mp3Handler& h, const TransfConfig& transfConfig, const string& strOrigSrcName, string& strTempName) { const vector<DataStream*>& vpStreams (h.getStreams()); ifstream_utf8 in (h.getName().c_str(), ios::binary); for (int i = 0, n = cSize(vpStreams); i < n; ++i) { TruncatedMpegDataStream* pTruncStream (dynamic_cast<TruncatedMpegDataStream*>(vpStreams[i])); if (0 != pTruncStream) { int nPaddingSize (pTruncStream->getExpectedSize() - pTruncStream->getSize()); { // comp CB_ASSERT (i > 0); // !!! a stream can be "truncated audio" only if it follows another audio; even if the user is allowed to erase streams at will, after removing an audio stream and rescanning what was "truncated audio" will become something else (perhaps "broken audio") MpegStream* pAudio (dynamic_cast<MpegStream*>(vpStreams[i - 1])); CB_ASSERT (0 != pAudio); switch (transfConfig.getCompAction()) { case TransfConfig::TRANSF_DONT_CREATE: break; case TransfConfig::TRANSF_CREATE: { streampos posCompBeg (findNearMpegFrameAtLeft(pTruncStream->getPos(), in, pAudio)); streampos posCompEnd (findNearMpegFrameAtRight(pTruncStream->getPos(), in, pAudio)); CB_ASSERT(posCompEnd >= pTruncStream->getPos()); in.seekg(posCompBeg); string strCompBefore; string strCompAfter; transfConfig.getCompNames(strOrigSrcName, getActionName(), strCompBefore, strCompAfter); { // tmp before ofstream_utf8 out (strCompBefore.c_str(), ios::binary); //partialCopy(in, out, 10000); //appendFilePart(in, out, pos, 2*nHalfSize); appendFilePart(in, out, posCompBeg, posCompEnd - posCompBeg); } { // tmp after ofstream_utf8 out (strCompAfter.c_str(), ios::binary); appendFilePart(in, out, posCompBeg, pTruncStream->getPos() - posCompBeg); writeZeros(out, nPaddingSize); appendFilePart(in, out, pTruncStream->getEnd(), posCompEnd - pTruncStream->getEnd()); } } break; default: CB_ASSERT (false); } } { // temp transfConfig.getTempName(strOrigSrcName, getActionName(), strTempName); ofstream_utf8 out (strTempName.c_str(), ios::binary); in.seekg(0); for (int i1 = 0; i1 < n; ++i1) { DataStream* q (vpStreams[i1]); q->copy(in, out); if (q == pTruncStream) { writeZeros(out, nPaddingSize); } } } return CHANGED; } } return NOT_CHANGED; } //================================================================================================================================ //================================================================================================================================ //================================================================================================================================ //ttt2 see how adding a Xing header affects gapless playing. split a VBR file //ttt2 in a way this could take care of Xing headers for CBR streams as well, but it doesn't seem the best place to do it, especially as we ignore most of the data in the Lame header and "restoring a header" means just putting back byte count and frame count /*override*/ Transformation::Result VbrRepairerBase::repair(const Mp3Handler& h, const TransfConfig& transfConfig, const std::string& strOrigSrcName, std::string& strTempName, bool bForceRebuild) { const vector<DataStream*>& vpStreams (h.getStreams()); ifstream_utf8 in (h.getName().c_str(), ios::binary); set<int> sVbriPos; int nAudioPos (-1); set<int> sXingPos; int n (cSize(vpStreams)); int nXingPos (-1); XingStreamBase* pXingStreamBase (0); for (int i = 0; i < n; ++i) { MpegStream* pAudio (dynamic_cast<MpegStream*>(vpStreams[i])); if (0 != pAudio) { nAudioPos = i; break; } VbriStream* pVbriStream (dynamic_cast<VbriStream*>(vpStreams[i])); if (0 != pVbriStream) { sVbriPos.insert(i); } XingStreamBase* q (dynamic_cast<XingStreamBase*>(vpStreams[i])); if (0 != q) { sXingPos.insert(i); nXingPos = i; pXingStreamBase = q; } } if (-1 == nAudioPos) { return NOT_CHANGED; } // no audio MpegStream* pAudio (dynamic_cast<MpegStream*>(vpStreams[nAudioPos])); if (!pAudio->isVbr()) { return NOT_CHANGED; } // CBR audio bool bXingOk (1 == cSize(sXingPos) && 1 == sXingPos.count(nAudioPos - 1) && pXingStreamBase->matches(pAudio)); if (!bForceRebuild && sVbriPos.empty() && bXingOk) { return NOT_CHANGED; } // exit if there's no VBRI and there's one matching Xing right before the audio bool bRepairMp3Fixer (false); bool bRemoveXing (true); // if true, existing headers are removed bool bAddXing (true); // if true, a header is added before the first Audio if (1 == cSize(sXingPos) && nXingPos < n - 2 && pXingStreamBase->isBrokenByMp3Fixer(vpStreams[nXingPos + 1], vpStreams[nXingPos + 2])) { // ttt2 see also http://www.kde-apps.org/content/show.php/Mp3Fixer?content=31539 for a bug that makes mp3fix.rb write its fix at a wrong address for mono files, but that's not going to be fixed now; at least don't try to fix this on mono files (or better: fix only stereo mpeg1 L III) bRemoveXing = bAddXing = false; bRepairMp3Fixer = true; } { // temp transfConfig.getTempName(strOrigSrcName, getActionName(), strTempName); ofstream_utf8 out (strTempName.c_str(), ios::binary); in.seekg(0); int i (0); for (; i < n; ++i) { DataStream* p (vpStreams[i]); if (0 != dynamic_cast<VbriStream*>(p)) { // nothing to do; this gets discarded } else if (0 != dynamic_cast<XingStreamBase*>(p)) { if (bRepairMp3Fixer) { XingStreamBase* pXing (dynamic_cast<XingStreamBase*>(p)); MpegStream* pAudio (dynamic_cast<MpegStream*>(vpStreams[i + 2])); CB_ASSERT(0 != pXing && 0 != pAudio); // that's what isBrokenByMp3Fixer() is about int nOffs (pXing->getFirstFrame().getSideInfoSize() + MpegFrame::MPEG_FRAME_HDR_SIZE); createXing(out, pXing->getFirstFrame(), pAudio->getFrameCount() + 1, pAudio->getSize() + pXing->getSize()); appendFilePart(in, out, p->getPos(), nOffs); streampos pos (p->getPos()); /*#ifdef GENERATE_TOC //!!! Doesn't matter whether GENERATE_TOC is defined or not. Mp3Fixer broke the first audio frame by adding length info and no TOC (although it claims to add TOC as well). What we do here is create a new Xing header (by calling createXing()) and remove the 16 bytes Mp3Fixer added, to restore the first frame to an audio frame, the way it was before Mp3Fixer messed it up. It's up to createXing() to add TOC or not. //const int nXingSize (16 + 100); #else const int nXingSize (16); #endif*/ const int nMp3FixXingSize (16); // Mp3Fix always adds 16 bytes, because it doesn't use a TOC pos += nMp3FixXingSize + nOffs; appendFilePart(in, out, pos, p->getSize() - nOffs - nMp3FixXingSize); } else if (!bRemoveXing) { p->copy(in, out); } } else if (0 != dynamic_cast<MpegStream*>(p)) { if (bAddXing) { dynamic_cast<MpegStream*>(p)->createXing(out); } break; } else { // any other stream p->copy(in, out); } } for (; i < n; ++i) { DataStream* p (vpStreams[i]); p->copy(in, out); } } return CHANGED_NO_RECALL; } //================================================================================================================================ //================================================================================================================================ //================================================================================================================================ /*override*/ Transformation::Result VbrRepairer::apply(const Mp3Handler& h, const TransfConfig& transfConfig, const std::string& strOrigSrcName, std::string& strTempName) { return repair(h, transfConfig, strOrigSrcName, strTempName, DONT_FORCE_REBUILD); } /*override*/ Transformation::Result VbrRebuilder::apply(const Mp3Handler& h, const TransfConfig& transfConfig, const std::string& strOrigSrcName, std::string& strTempName) { return repair(h, transfConfig, strOrigSrcName, strTempName, FORCE_REBUILD); } //================================================================================================================================ //================================================================================================================================ //================================================================================================================================ //ttt2 https://sourceforge.net/projects/mp3diags/forums/forum/947206/topic/3389395?message=8033754 - remove Ape, Lyrics, ... // email on Mon, 15 Mar 2010 - remove short audio streams ������������������������������������������������������������������������������������������������������������������������������������MP3Diags-1.2.02/src/OsFile.h������������������������������������������������������������������������0000644�0001750�0000144�00000020335�11601630217�014270� 0����������������������������������������������������������������������������������������������������ustar �ciobi���������������������������users������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef OsFileH #define OsFileH #include <memory> #include <string> #define CB_LIB_CALL class FileSearcherImpl; class FileSearcher { CB_LIB_CALL FileSearcher(const FileSearcher&); FileSearcher& CB_LIB_CALL operator=(const FileSearcher&); std::auto_ptr<FileSearcherImpl> m_pImpl; bool CB_LIB_CALL goToNextValidEntry(); // skips "." and "..", as well as any invalid item (usually file that has been removed after entryInfoList() got called) std::string m_strDir; public: CB_LIB_CALL FileSearcher(); CB_LIB_CALL FileSearcher(const std::string& strDirName); // accepts both "/"-terminated and non-'/'-terminated names CB_LIB_CALL ~FileSearcher(); //ttt2 this seems bad design: getName() & Co shouldn't be callable if the search is not open std::string CB_LIB_CALL getName() const; // returns the full name found; throws if nothing found std::string CB_LIB_CALL getShortName() const; // returns the short name found; throws if nothing found //ttt2 have short/long/default (default is for "../tst/1" and is what is implemented now; long should be "/dir/tst/1") int CB_LIB_CALL getAttribs() const; //ddd maybe filter out volume attributes / or include UNIX attribs long long CB_LIB_CALL getSize() const; //long long CB_LIB_CALL getCreationTime() const; //int64FromFileDateTime(sr.sr.FindData.ftCreationTime) //ttt2 replace "long long" with a class representing time with nanosecond resolution, with conversions to what various OSs are using for various tasks long long CB_LIB_CALL getChangeTime() const; //int64FromFileDateTime(sr.sr.FindData.ftLastWriteTime), bool CB_LIB_CALL isFile() const; bool CB_LIB_CALL isDir() const; bool CB_LIB_CALL isSymLink() const; CB_LIB_CALL operator bool() const; // it's easier to use the constructor, but sometimes may be more convenient to leave the object in an outer loop bool CB_LIB_CALL findFirst(const std::string& strDirName); bool CB_LIB_CALL findNext(); void CB_LIB_CALL close(); struct InvalidOperation {}; }; // result is in UTC //TDateTime dateTimeFromFileDateTime(const FILETIME& utcFileTime); //long long int64FromFileDateTime(const FILETIME& utcFileTime); //FILETIME fileDateTimeFromint64(__int64 intTime); /*struct CantOpenFile// : public RuntimeError { // CB_LIB_CALL CantOpenFile(const char* szFileName, int nLn, const char* szFile, RuntimeError* pPrv = 0) : // RuntimeError(TMP_PARAM_CHG2, "Error opening file", "Couldn't open file " + strFileName, nLn, szFile, pPrv) {} };*/ void CB_LIB_CALL getFileInfo(const std::string& strFileName, long long& nChangeTime, long long& nSize); // throws NameNotFound // ttt2 currently doesn't really matter the format of nChangeTime, because it is only used in equality comparisons; so it uses time_t; however, this should be reviewed and probably choose nanoseconds from 01.01.1970 (QDateTime is better avoided, because of several issues, like serialization or low resolution) void CB_LIB_CALL setFileDate(const std::string& strFileName, long long nChangeTime); struct CannotCreateDir { std::string m_strDir; CannotCreateDir(const std::string& strDir) : m_strDir(strDir) {} }; // creates all intermediate dirs; throws CannotCreateDir on failure (if the directory already exists it's a success) void CB_LIB_CALL createDir(const std::string& strDirName); void CB_LIB_CALL createDirForFile(const std::string& strFileName); // creates the directory where the given file can be created std::string replaceDriveLetter(const std::string& strFileName); // does nothing on Linux; replaces "D:" with "/D" on Windows, only when "D:" isn't at the beggining of the string; // returns true if there is a file with that name; // returns false if the name doesn't exist or it's a directory; doesn't throw bool CB_LIB_CALL fileExists(const std::string& strFileName); // returns true if there is a directory with that name; // returns false if the name doesn't exist or it's a file; //ttt2 throws IncorrectDirName bool CB_LIB_CALL dirExists(const std::string& strDirName); struct IncorrectDirName {}; // invalid dir name (e.g. ends with a path separator) // throws IncorrectDirName if the name ends with a path separator void CB_LIB_CALL checkDirName(const std::string& strDirName); std::string getSepTerminatedDir(const std::string& strDirName); // adds a path separator at the end if none is present; throws IncorrectDirName on invalid file names std::string getNonSepTerminatedDir(const std::string& strDirName); // removes the path separator at the end if present; throws IncorrectDirName on invalid file names struct FoundDir {}; struct AlreadyExists {}; struct NameNotFound {}; struct CannotRenameFile {}; struct CannotCopyFile {}; // renames a file; // throws FoundDir, AlreadyExists, NameNotFound, CannotRenameFile, ?IncorrectDirName, void CB_LIB_CALL renameFile(const std::string& strOldName, const std::string& strNewName); // creates a copy a file; // doesn't preserve any attributes; // ttt2 add option // throws WriteError or EndOfFile from Helpers //ttt2 switch to: throws FoundDir, AlreadyExists, NameNotFound, CannotCopyFile, ?IncorrectDirName, void CB_LIB_CALL copyFile(const std::string& strSourceName, const std::string& strDestName /*, OverwriteOption eOverwriteOption*/); // throws FoundDir, AlreadyExists, NameNotFound, CannotCopyFile, ?IncorrectDirName, //ttt2 unify with copyFile(), or rather restructure all file operations (perhaps see what Qt has) void CB_LIB_CALL copyFile2(const std::string& strSourceName, const std::string& strDestName /*, OverwriteOption eOverwriteOption*/); // deletes a file; throws FoundDir, // CannotDeleteFile, ?IncorrectDirName; it is OK if the file didn't exist to begin with struct CannotDeleteFile {}; void CB_LIB_CALL deleteFile(const std::string& strFileName); // just a name that doesn't exist; the file won't be deleted automatically; normally the name is obtained by appending something to strMasterFileName, but a more generic temp is used if the name is too long on wnd std::string getTempFile(const std::string& strMasterFileName); std::string getParent(const std::string& strName); // the name of a parent directory; returns an empty string if no parent exists; may throw IncorrectDirName bool isInsideDir(const std::string& strName, const std::string& strDirName); // if strName is in strDirName or in one of its subdirectories; may throw IncorrectDirName for either param std::string getExistingDir(const std::string& strName); // if strName exists and is a dir, it is returned; otherwise it returns the closest ancestor that exists; accepts names ending with file separator std::string eraseFiles(const std::string& strRoot); // erases files matching strRoot followed by anything; returns the name of the first file that couldn't be erased #endif // #ifndef OsFileH ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������MP3Diags-1.2.02/src/DebugDlgImpl.cpp����������������������������������������������������������������0000644�0001750�0000144�00000064222�11724736234�015760� 0����������������������������������������������������������������������������������������������������ustar �ciobi���������������������������users������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include <algorithm> #include <sstream> #include <QFileDialog> #include <QMessageBox> #include <QHeaderView> //#include "SerSupport.h" #include "DebugDlgImpl.h" #include "CommonData.h" #include "DataStream.h" #include "Helpers.h" #include "StoredSettings.h" #include "LogModel.h" #include "Widgets.h" //#include "Serializable.h" /*#include <boost/archive/binary_oarchive.hpp> #include <boost/archive/binary_iarchive.hpp> #include <boost/serialization/vector.hpp> */ using namespace std; using namespace pearl; DebugDlgImpl::DebugDlgImpl(QWidget* pParent, CommonData* pCommonData) : QDialog(pParent, getDialogWndFlags()), Ui::DebugDlg(), m_pCommonData(pCommonData) { setupUi(this); int nWidth, nHeight; m_pCommonData->m_settings.loadDebugSettings(nWidth, nHeight); if (nWidth > 400 && nHeight > 200) { resize(nWidth, nHeight); } else { defaultResize(*this); } { m_pEnableTracingCkB->setChecked(m_pCommonData->m_bTraceEnabled); m_pUseAllNotesCkB->setChecked(m_pCommonData->m_bUseAllNotes); m_pLogTransfCkB->setChecked(m_pCommonData->m_bLogTransf); m_pSaveDownloadedDataCkB->setChecked(m_pCommonData->m_bSaveDownloadedData); } { m_pLogModel = new LogModel(m_pCommonData, m_pLogG); m_pLogG->setModel(m_pLogModel); m_pLogG->verticalHeader()->setMinimumSectionSize(CELL_HEIGHT); m_pLogG->verticalHeader()->setDefaultSectionSize(CELL_HEIGHT); m_pLogG->horizontalHeader()->setResizeMode(0, QHeaderView::Stretch); m_pLogG->verticalHeader()->setDefaultAlignment(Qt::AlignRight | Qt::AlignVCenter); //m_pLogG->verticalHeader()->setResizeMode(QHeaderView::Fixed); m_pLogG->verticalHeader()->setResizeMode(QHeaderView::Interactive); } m_pUseAllNotesCkB->setToolTip( tr("If this is checked, ignored notes and trace notes\n" "are shown in the note list and exported, regardless\n" "of the \"Ignored\" settings.\n\n" "Note that if this is not checked, the trace notes\n" "are discarded during file scanning, so checking it\n" "later won't bring them back. A new scan is needed\n" "to see them.", "this is a multiline tooltip")); m_pLogG->setFocus(); if (!m_pCommonData->m_bShowCustomCloseButtons) { m_pCloseB->hide(); } { QAction* p (new QAction(this)); p->setShortcut(QKeySequence("F1")); connect(p, SIGNAL(triggered()), this, SLOT(onHelp())); addAction(p); } } void DebugDlgImpl::run() { //if (QDialog::Accepted != exec()) { return false; } exec(); m_pCommonData->m_settings.saveDebugSettings(width(), height()); m_pCommonData->m_bTraceEnabled = m_pEnableTracingCkB->isChecked(); m_pCommonData->m_bUseAllNotes = m_pUseAllNotesCkB->isChecked(); m_pCommonData->m_bLogTransf = m_pLogTransfCkB->isChecked(); m_pCommonData->m_bSaveDownloadedData = m_pSaveDownloadedDataCkB->isChecked(); //return true; } DebugDlgImpl::~DebugDlgImpl() { } void DebugDlgImpl::on_m_pCloseB_clicked() { reject(); // !!! doesn't matter if it's accept() } void DebugDlgImpl::exportLog(const string& strFileName) { const deque<std::string>& v (m_pCommonData->getLog()); ofstream_utf8 out (strFileName.c_str()); for (int i = 0, n = cSize(v); i < n; ++i) { out << v[i] << endl; } } void DebugDlgImpl::on_m_pSaveLogB_clicked() { QFileDialog dlg (this, tr("Choose destination file"), "", tr("Text files (*.txt)")); dlg.setFileMode(QFileDialog::AnyFile); if (QDialog::Accepted != dlg.exec()) { return; } QStringList fileNames (dlg.selectedFiles()); if (1 != fileNames.size()) { return; } QString s (fileNames.first()); exportLog(convStr(s)); } void DebugDlgImpl::on_m_pDecodeMpegFrameB_clicked() { istringstream s (m_pFrameHdrE->text().toUtf8().constData()); s >> hex; unsigned int n; s >> n; showInfo(this, tr("Decoded MPEG frame header"), convStr(decodeMpegFrame(n, "\n"))); } void DebugDlgImpl::onHelp() { openHelp("310_advanced.html"); } //======================================================================================================================================================== //======================================================================================================================================================== //======================================================================================================================================================== void tstFont(); void tstGenre(); void tstSer01(); void DebugDlgImpl::on_m_pTst01B_clicked() { //tstSer01(); //m_pLogModel->selectTopLeft(); /*TestThread01* p (new TestThread01()); ThreadRunnerDlgImpl dlg (p, true, this); dlg.exec();*/ /*bool b; QFont f (QFontDialog::getFont(&b, this)); //cout << "font: " << QFontInfo(myOption.font).family().toStdString() << endl; //cout << "font from dlg: " << f.toString().toStdString() << endl; printFontInfo("font from dlg", f); for (int i = 6; i < 18; ++i) { QFont f ("B&H LucidaTypewriter", i); QFontInfo info (f); cout << i << ": " << info.family().toStdString() << ", exactMatch:" << info.exactMatch() << ", fixedPitch:" << info.fixedPitch() << ", italic:" << info.italic() << ", pixelSize:" << info.pixelSize() << ", pointSize" << info.pointSize() << endl; } */ //tstFont(); /* { const char* a ("1998-10-04"); QDateTime dt (QDateTime::fromString(a, Qt::ISODate)); if (!dt.isValid()) cout << a << " is invalid\n"; else cout << dt.toString(Qt::ISODate).toStdString() << endl; } { const char* a ("1998-10"); QDateTime dt (QDateTime::fromString(a, Qt::ISODate)); if (!dt.isValid()) cout << a << " is invalid\n"; else cout << dt.toString(Qt::ISODate).toStdString() << endl; } { const char* a ("1998-1-4"); QDateTime dt (QDateTime::fromString(a, Qt::ISODate)); if (!dt.isValid()) cout << a << " is invalid\n"; else cout << dt.toString(Qt::ISODate).toStdString() << endl; } { const char* a ("1998-1"); QDateTime dt (QDateTime::fromString(a, Qt::ISODate)); if (!dt.isValid()) cout << a << " is invalid\n"; else cout << dt.toString(Qt::ISODate).toStdString() << endl; } { const char* a ("1998"); QDateTime dt (QDateTime::fromString(a, Qt::ISODate)); if (!dt.isValid()) cout << a << " is invalid\n"; else cout << dt.toString(Qt::ISODate).toStdString() << endl; } { const char* a ("98"); QDateTime dt (QDateTime::fromString(a, Qt::ISODate)); if (!dt.isValid()) cout << a << " is invalid\n"; else cout << dt.toString(Qt::ISODate).toStdString() << endl; } { const char* a ("1998-10-04T11:12:13"); QDateTime dt (QDateTime::fromString(a, Qt::ISODate)); if (!dt.isValid()) cout << a << " is invalid\n"; else cout << dt.toString(Qt::ISODate).toStdString() << endl; } { const char* a ("1998-10-04 11:12:13"); QDateTime dt (QDateTime::fromString(a, Qt::ISODate)); if (!dt.isValid()) cout << a << " is invalid\n"; else cout << dt.toString(Qt::ISODate).toStdString() << endl; } { const char* a ("1998-10-04T11:12"); QDateTime dt (QDateTime::fromString(a, Qt::ISODate)); if (!dt.isValid()) cout << a << " is invalid\n"; else cout << dt.toString(Qt::ISODate).toStdString() << endl; } { const char* a ("1998-10-04T11"); QDateTime dt (QDateTime::fromString(a, Qt::ISODate)); if (!dt.isValid()) cout << a << " is invalid\n"; else cout << dt.toString(Qt::ISODate).toStdString() << endl; } { const char* a ("1998-10-04T11:6"); QDateTime dt (QDateTime::fromString(a, Qt::ISODate)); if (!dt.isValid()) cout << a << " is invalid\n"; else cout << dt.toString(Qt::ISODate).toStdString() << endl; } */ //m_transfConfig.testRemoveSuffix(); //tstGenre(); /*m_pFilesModel->notifyModelChanged(); char a [100]; sprintf(a, "%d %d", m_pFilesModel->rowCount(QModelIndex()), m_pFilesModel->columnCount(QModelIndex())); m_pStreamsM->append(a);*/ //m_pContentM->setTextFormat(LogText); /* QItemSelectionModel* pSelModel (m_pCommonData->m_pFilesG->selectionModel()); m_pCommonData->printFilesCrt(); pSelModel->clear(); m_pCommonData->printFilesCrt(); if (m_pCommonData->getFilesGCrtRow() > 400000) exit(2); return; */ //resizeEvent(0); //m_pContentM->setReadOnly(TRUE); #if 0 static bool b1 (false); b1 = !b1; if (b1) { char a [100]; for (int i = 0; i < 10000; ++ i) { sprintf(a, "%d //Mp3Handler* pMp3Handler (new Mp3Handler(fs.getName())); Mp3Handler", i); m_pContentM->append(a); } /*char a [100]; string s; for (int i = 0; i < 1000; ++ i) { sprintf(a, "%d //Mp3Handler* pMp3Handler (new Mp3Handler(fs.getName())); Mp3Handler\n", i); s += a; } m_pContentM->append(s.c_str());*/ } else { m_pContentM->setText(""); } #endif } //======================================================================================================================================================== //======================================================================================================================================================== //======================================================================================================================================================== #if 0 #include <iostream> struct Base { ciobi_ser::SerHelper<Base> m_serHelper; Base(ciobi_ser::Univ* pUniv, int n1) : m_serHelper(*this, pUniv), m_n1(n1) {} //template <class T> Base(T& obj, ciobi_ser::Univ* pUniv, int n1) : m_serHelper(obj, pUniv), m_n1(n1) {} Base(ciobi_ser::ForSerOnly*) : m_serHelper(*this, 0) {} virtual ~Base() { } int m_n1; virtual void print(ostream& out) const { out << "Base: " << m_n1 << endl; } virtual void save(const ciobi_ser::Univ& univ) { //univ.saveCPE(m_vpMasterDirs); univ.save(m_n1); cout << "saved base: " << m_n1 << endl; } virtual void load(ciobi_ser::Univ& univ) { //univ.loadCPE(m_vpMasterDirs); univ.load(m_n1); cout << "loaded base: " << m_n1 << endl; } virtual const char* getClassName() const { return ciobi_ser::ClassName<Base>::szClassName; } }; struct Der : public Base { //ciobi_ser::SerHelper<Der> m_serHelper; //Der(ciobi_ser::Univ* pUniv, int n1, int n2) : Base(pUniv, n1), m_serHelper(*this, pUniv), m_n2(n2) {} //Der(ciobi_ser::Univ* pUniv, int n1, int n2) : Base(*this, pUniv, n1), m_n2(n2) {} Der(ciobi_ser::Univ* pUniv, int n1, int n2) : Base(pUniv, n1), m_n2(n2) {} Der(ciobi_ser::ForSerOnly* p) : Base(p) {} int m_n2; /*override*/ void print(ostream& out) const { out << "Der: " << m_n1 << ", " << m_n2 << endl; } /*override*/ void save(const ciobi_ser::Univ& univ) { Base::save(univ); univ.save(m_n2); cout << "saved der: " << m_n1 << ", " << m_n2 << endl; } /*override*/ void load(ciobi_ser::Univ& univ) { Base::load(univ); univ.load(m_n2); cout << "loaded der: " << m_n1 << ", " << m_n2 << endl; } /*override*/ const char* getClassName() const { return ciobi_ser::ClassName<Der>::szClassName; } }; template<> /*static*/ const char* ciobi_ser::ClassName<Base>::szClassName ("Base"); template<> /*static*/ const char* ciobi_ser::ClassName<Der>::szClassName ("Der"); // explicit instantiation template class ciobi_ser::SerHelper<Der>; ostream& operator<<(ostream& out, const Base& o) { o.print(out); return out; } //================================================================================================================= struct Container { ciobi_ser::SerHelper<Container> m_serHelper; Container(ciobi_ser::Univ* pUniv, int n) : m_serHelper(*this, pUniv) { int k (1); for (int i = 0; i < n; ++i, k *= 2) { Base* p (i % 2 ? new Base(pUniv, k) : new Der(pUniv, k, k + 1)); //Base* p (new Base(pUniv, k)); m_v.push_back(p); } } Container(ciobi_ser::ForSerOnly*) : m_serHelper(*this, 0) {} virtual ~Container() { } vector<Base*> m_v; void save(const ciobi_ser::Univ& univ) { //univ.saveCPE(m_vpMasterDirs); univ.saveCP(m_v); cout << "saved container: " << endl; } void load(ciobi_ser::Univ& univ) { //univ.loadCPE(m_vpMasterDirs); univ.loadCP(m_v); cout << "loaded container, size " << cSize(m_v) << endl; for (int i = 0; i < cSize(m_v); ++i) { cout << *m_v[i]; } } const char* getClassName() const { return ciobi_ser::ClassName<Container>::szClassName; } }; template<> /*static*/ const char* ciobi_ser::ClassName<Container>::szClassName ("Container"); void tstVec01(bool bSave) { ciobi_ser::Univ univ; univ.setDestroyOption(ciobi_ser::Univ::DO_NOTHING); // DELETE_SORTED_OBJECTS const char* szFile ("savePolymVec.tser"); if (bSave) { Container c (&univ, 4); ciobi_ser::SaveOptions so(ciobi_ser::SaveOptions::TEXT, 1, 1, ciobi_ser::SaveOptions::USE_OBJ_INFO, ciobi_ser::SaveOptions::DONT_USE_SEP, ciobi_ser::SaveOptions::USE_COMMENTS); univ.saveToFile(&c, szFile, "tst polym univ name", so); return; } Container* p; univ.loadFromFile(p, szFile); } //================================================================================================================= void tstBase01(bool bSave) { ciobi_ser::Univ univ; univ.setDestroyOption(ciobi_ser::Univ::DO_NOTHING); // DELETE_SORTED_OBJECTS const char* szFile ("saveBase01.tser"); if (bSave) { Base b1 (&univ, 3); ciobi_ser::SaveOptions so(ciobi_ser::SaveOptions::TEXT, 1, 1/*, ciobi_ser::SaveOptions::DONT_USE_OBJ_INFO, ciobi_ser::SaveOptions::DONT_USE_SEP, ciobi_ser::SaveOptions::DONT_USE_COMMENTS*/); //cout << 1 << endl; univ.saveToFile(&b1, szFile, "tst base 01 univ name", so); //cout << 2 << endl; return; } Base* pb1; univ.loadFromFile(pb1, szFile); } void tstDer01(bool bSave) { ciobi_ser::Univ univ; univ.setDestroyOption(ciobi_ser::Univ::DO_NOTHING); // DELETE_SORTED_OBJECTS const char* szFile ("saveDer01.tser"); if (bSave) { Der d (&univ, 4, 6); ciobi_ser::SaveOptions so(ciobi_ser::SaveOptions::TEXT, 1, 1); univ.saveToFile(&d, szFile, "tst der 01 univ name", so); return; } Der* p; univ.loadFromFile(p, szFile); } //================================================================================================================= //================================================================================================================= struct Emb { //ciobi_ser::SerHelper<Base> m_serHelper; Emb(int n1) : /*m_serHelper(*this, pUniv),*/ m_n1(n1) {} Emb(ciobi_ser::ForSerOnly*) /*m_serHelper(*this, 0)*/ {} int m_n1; void save(const ciobi_ser::Univ& univ) { univ.save(m_n1); } void load(ciobi_ser::Univ& univ) { univ.load(m_n1); } void print(ostream& out) const { out << "Emb: " << m_n1 << endl; } }; ostream& operator<<(ostream& out, const Emb& emb) { emb.print(out); return out; } struct Encl { ciobi_ser::SerHelper<Encl> m_serHelper; Encl(ciobi_ser::Univ* pUniv, int n) : m_serHelper(*this, pUniv) { int k (1); for (int i = 0; i <= n; ++i) { Emb* p (new Emb(k)); m_v.push_back(p); k *= 2; } } Encl(ciobi_ser::ForSerOnly*) : m_serHelper(*this, 0) {} vector<Emb*> m_v; void save(const ciobi_ser::Univ& univ) { univ.saveCPE(m_v); cout << "saved encl\n"; } void load(ciobi_ser::Univ& univ) { univ.loadCPE(m_v); cout << "loaded encl:"; int n (cSize(m_v)); for (int i = 0; i < n; ++i) { cout << *m_v[i]; } cout << endl; } const char* getClassName() const { return ciobi_ser::ClassName<Encl>::szClassName; } }; template<> /*static*/ const char* ciobi_ser::ClassName<Encl>::szClassName ("Encl"); void tstEmbVec01(bool bSave) { ciobi_ser::Univ univ; univ.setDestroyOption(ciobi_ser::Univ::DO_NOTHING); // DELETE_SORTED_OBJECTS const char* szFile ("saveEncl01.tser"); if (bSave) { Encl e (&univ, 8); ciobi_ser::SaveOptions so(ciobi_ser::SaveOptions::TEXT, 1, 1); univ.saveToFile(&e, szFile, "tst encl univ name", so); return; } Encl* p; univ.loadFromFile(p, szFile); } #endif //======================================================================================================================================================== //======================================================================================================================================================== //======================================================================================================================================================== #if 0 #include <iostream> struct BoostTstBase { BoostTstBase(int n1) : m_n1(n1) {} virtual ~BoostTstBase() { } int m_n1; virtual void print(ostream& out) const { out << "BoostTstBase: " << m_n1 << endl; } protected: BoostTstBase() {} private: friend class boost::serialization::access; template<class Archive> void serialize(Archive& ar, const unsigned int /*nVersion*/) { ar & m_n1; } }; struct BoostTstDer : public BoostTstBase { BoostTstDer(int n1, int n2) : BoostTstBase(n1), m_n2(n2) {} int m_n2; /*override*/ void print(ostream& out) const { out << "BoostTstDer: " << m_n1 << ", " << m_n2 << endl; } private: friend class boost::serialization::access; BoostTstDer() {} template<class Archive> void serialize(Archive& ar, const unsigned int /*nVersion*/) { ar & boost::serialization::base_object<BoostTstBase>(*this); ar & m_n2; } }; ostream& operator<<(ostream& out, const BoostTstBase& o) { o.print(out); return out; } //================================================================================================================= struct BoostContainer { BoostContainer(int n) { int k (1); for (int i = 0; i < n; ++i, k *= 2) { BoostTstBase* p (i % 2 ? new BoostTstBase(k) : new BoostTstDer(k, k + 1)); m_v.push_back(p); } } virtual ~BoostContainer() { } vector<BoostTstBase*> m_v; /*void save() { //univ.saveCPE(m_vpMasterDirs); univ.saveCP(m_v); cout << "saved container: " << endl; } void load() { //univ.loadCPE(m_vpMasterDirs); univ.loadCP(m_v); }*/ private: friend class boost::serialization::access; BoostContainer() {} template<class Archive> void serialize(Archive& ar, const unsigned int /*nVersion*/) { ar & m_v; } }; void tstBoostVec01(bool bSave) { const char* szFile ("savePolymVec.boost.ser"); if (bSave) { BoostContainer* p (new BoostContainer(4)); ofstream_utf8 out (szFile); //boost::archive::text_oarchive oar (out); boost::archive::binary_oarchive oar (out); oar.register_type<BoostTstDer>(); oar.register_type<BoostTstBase>(); oar.register_type<BoostContainer>(); //BoostContainer* q (p); //BoostContainer* const q (p); //const BoostContainer* const q2 (p); //oar << const_cast<const BoostContainer const*>(p); //oar << q; oar << (BoostContainer* const)p; //oar << q; return; } BoostContainer* p; ifstream_utf8 in (szFile); //boost::archive::text_iarchive iar (in); boost::archive::binary_iarchive iar (in); iar.register_type<BoostTstDer>(); iar.register_type<BoostTstBase>(); iar.register_type<BoostContainer>(); iar >> p; cout << "loaded container, size " << cSize(p->m_v) << endl; for (int i = 0; i < cSize(p->m_v); ++i) { cout << *p->m_v[i]; } } //================================================================================================================= #if 0 void tstBase01(bool bSave) { const char* szFile ("saveBase01.tser"); if (bSave) { BoostTstBase b1 (&univ, 3); univ.saveToFile(&b1, szFile, "tst base 01 univ name", so); //cout << 2 << endl; return; } BoostTstBase* pb1; univ.loadFromFile(pb1, szFile); } void tstDer01(bool bSave) { const char* szFile ("saveDer01.tser"); if (bSave) { BoostTstDer d (4, 6); univ.saveToFile(&d, szFile, "tst der 01 univ name", so); return; } BoostTstDer* p; univ.loadFromFile(p, szFile); } //================================================================================================================= //================================================================================================================= //================================================================================================================= struct Emb { Emb(int n1) : /*m_serHelper(*this, pUniv),*/ m_n1(n1) {} int m_n1; void save() { univ.save(m_n1); } void load() { univ.load(m_n1); } void print(ostream& out) const { out << "Emb: " << m_n1 << endl; } }; ostream& operator<<(ostream& out, const Emb& emb) { emb.print(out); return out; } struct Encl { Encl(int n) { int k (1); for (int i = 0; i <= n; ++i) { Emb* p (new Emb(k)); m_v.push_back(p); k *= 2; } } vector<Emb*> m_v; void save() { univ.saveCPE(m_v); cout << "saved encl\n"; } void load() { univ.loadCPE(m_v); cout << "loaded encl:"; int n (cSize(m_v)); for (int i = 0; i < n; ++i) { cout << *m_v[i]; } cout << endl; } }; void tstEmbVec01(bool bSave) { const char* szFile ("saveEncl01.tser"); if (bSave) { Encl e (&univ, 8); univ.saveToFile(&e, szFile, "tst encl univ name", so); return; } Encl* p; univ.loadFromFile(p, szFile); } #endif #endif //================================================================================================================= #if 0 #include "fstream_unicode.h" // include headers that implement a archive in simple text format #include <boost/archive/text_oarchive.hpp> #include <boost/archive/text_iarchive.hpp> ///////////////////////////////////////////////////////////// // gps coordinate // // illustrates serialization for a simple type // class gps_position { private: friend class boost::serialization::access; // When the class Archive corresponds to an output archive, the // & operator is defined similar to <<. Likewise, when the class Archive // is a type of input archive the & operator is defined similar to >>. template<class Archive> void serialize(Archive& ar, const unsigned int version) { ar & degrees; ar & minutes; ar & seconds; } int degrees; int minutes; float seconds; public: gps_position(){}; gps_position(int d, int m, float s) : degrees(d), minutes(m), seconds(s) {} }; int tutorial() { // create and open a character archive for output ofstream_utf8 ofs("filename"); // create class instance const gps_position g(35, 59, 24.567f); // save data to archive { boost::archive::text_oarchive oa(ofs); // write class instance to archive oa << g; // archive and stream closed when destructors are called } // ... some time later restore the class instance to its orginal state gps_position newg; { // create and open an archive for input ifstream_utf8 ifs("filename", std::ios::binary); boost::archive::text_iarchive ia(ifs); // read class state from archive ia >> newg; // archive and stream closed when destructors are called } return 0; } #endif #if 0 void tstSer01() { //tstBase01(1 == argc); //tstDer01(1 == argc); //tstVec01(true); //tstVec01(false); //tstEmbVec01(1 == argc); tstBoostVec01(true); tstBoostVec01(false); //tutorial(); } #endif /*struct TstSer { TstSer() { //tstBase01(1 == argc); //tstDer01(1 == argc); tstVec01(true); //tstVec01(false); //tstEmbVec01(1 == argc); ::exit(0); } }; TstSer tstSer01; */ // -lboost_serialization-mt-1_37 //ttt2 if "use all notes" is checked, it keeps rescanning at startup ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������MP3Diags-1.2.02/src/FullSizeImgDlg.cpp��������������������������������������������������������������0000644�0001750�0000144�00000006466�11724475602�016307� 0����������������������������������������������������������������������������������������������������ustar �ciobi���������������������������users������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include <QDialog> #include <QLabel> #include <QVBoxLayout> #include <QAction> #include <QClipboard> #include <QApplication> #include <QMimeData> #include "FullSizeImgDlg.h" #include "Helpers.h" #include "DataStream.h" // for translations FullSizeImgDlg::FullSizeImgDlg(QWidget* pParent, const ImageInfo& imageInfo) : QDialog(pParent, getNoResizeWndFlags()), m_imageInfo(imageInfo) { QVBoxLayout* pLayout (new QVBoxLayout(this)); //dlg.setLayout(pGridLayout); QLabel* p (new QLabel(this)); p->setPixmap(QPixmap::fromImage(imageInfo.getImage())); //ttt2 see if it should limit size (IIRC QLabel scaled down once a big image) pLayout->addWidget(p, 0, Qt::AlignHCenter); QString s; s.sprintf("%dx%d / %dkB\n", imageInfo.getWidth(), imageInfo.getHeight(), imageInfo.getSize()/1024); s += TagReader::tr(imageInfo.getImageType()); p = new QLabel(s, this); p->setAlignment(Qt::AlignHCenter); pLayout->addWidget(p, 0, Qt::AlignHCenter); { QAction* pAct (new QAction(this)); pAct->setShortcut(QKeySequence("Ctrl+C")); connect(pAct, SIGNAL(triggered()), this, SLOT(onCopy())); addAction(pAct); } } void FullSizeImgDlg::onCopy() { #ifndef WIN32 QByteArray ba (m_imageInfo.getComprData(), m_imageInfo.getSize()); QMimeData* pMimeData (new QMimeData()); const char* szFmt (0); switch (m_imageInfo.getCompr()) { case ImageInfo::JPG: szFmt = "image/jpeg"; break; case ImageInfo::PNG: szFmt = "image/png"; break; //ttt2 doesn't work for BMP, GIF, ... default: break; } if (0 != szFmt) { pMimeData->setData(szFmt, ba); QApplication::clipboard()->setMimeData(pMimeData); } else { delete pMimeData; } #else QApplication::clipboard()->setPixmap(QPixmap::fromImage(m_imageInfo.getImage())); #endif } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������MP3Diags-1.2.02/src/NoteFilterDlgImpl.cpp�����������������������������������������������������������0000644�0001750�0000144�00000023602�11724661436�017003� 0����������������������������������������������������������������������������������������������������ustar �ciobi���������������������������users������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include <algorithm> #include "NoteFilterDlgImpl.h" #include "Helpers.h" #include "StoredSettings.h" #include "CommonData.h" using namespace std; using namespace pearl; //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== /*override*/ std::string NoteListElem::getText(int nCol) const { if (0 == nCol) { //return m_pCommonData->getNoteLabel(m_pNote).toUtf8().constData(); return convStr(getNoteLabel(m_pNote)); } return convStr(Notes::tr(m_pNote->getDescription())); } //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== NoteFilterDlgImpl::NoteFilterDlgImpl(CommonData* pCommonData, QWidget* pParent /* =0*/) : QDialog(pParent, getDialogWndFlags()), NoteListPainterBase(pCommonData, convStr(tr("<all notes>"))) { TRACER("NoteFilterDlgImpl constr"); setupUi(this); m_pListHldr->setLayout(new QHBoxLayout()); for (int i = 0, n = pCommonData->getUniqueNotes().getCount(); i < n; ++i) { m_vpOrigAll.push_back(new NoteListElem(pCommonData->getUniqueNotes().get(i), m_pCommonData)); } for (int i = 0, n = cSize(pCommonData->m_filter.getNotes()); i < n; ++i) { int k (pCommonData->getUniqueNotes().getPos(pCommonData->m_filter.getNotes()[i])); if (k >= 0) { m_vOrigSel.push_back(k); } } m_vSel = m_vOrigSel; m_pDoubleList = new DoubleList( *this, DoubleList::ADD_ALL | DoubleList::DEL_ALL | DoubleList::RESTORE_OPEN, DoubleList::SINGLE_UNSORTABLE, convStr(tr("Available notes")), convStr(tr("Include notes")), this); m_pListHldr->layout()->addWidget(m_pDoubleList); m_pListHldr->layout()->setContentsMargins(0, 0, 0, 0); int nWidth, nHeight; m_pCommonData->m_settings.loadNoteFilterSettings(nWidth, nHeight); if (nWidth > 400 && nHeight > 400) { resize(nWidth, nHeight); } else { defaultResize(*this); } connect(m_pDoubleList, SIGNAL(avlDoubleClicked(int)), this, SLOT(onAvlDoubleClicked(int))); { QAction* p (new QAction(this)); p->setShortcut(QKeySequence("F1")); connect(p, SIGNAL(triggered()), this, SLOT(onHelp())); addAction(p); } } NoteFilterDlgImpl::~NoteFilterDlgImpl() { clearPtrContainer(m_vpOrigAll); } void NoteFilterDlgImpl::logState(const char* /*szPlace*/) const { /*cout << szPlace << ": m_filter.m_vSelDirs=" << m_pCommonData->m_filter.m_vSelDirs.size() << " m_availableDirs.m_vstrDirs=" << m_availableDirs.m_vstrDirs.size() << " m_selectedDirs.m_vSelDirs=" << m_selectedDirs.m_vstrDirs.size() << endl;*/ } //--------------------------------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------------------------------- void NoteFilterDlgImpl::on_m_pOkB_clicked() { //logState("on_m_pOkB_clicked 1"); //m_pCommonData->m_filter.m_vpSelNotes = m_selectedNotes.m_vpNotes; vector<const Note*> v; //m_pCommonData->m_filter.m_vpSelNotes.clear(); for (int i = 0, n = cSize(m_vSel); i < n; ++i) { const NoteListElem* p (dynamic_cast<const NoteListElem*>(m_vpOrigAll[m_vSel[i]])); CB_ASSERT(0 != p); v.push_back(p->getNote()); } m_pCommonData->m_filter.setNotes(v); //ttt2 in other case changing a parent window before the modal dialog was closed led to incorrect resizing; however, here it seems OK //logState("on_m_pOkB_clicked 2"); m_pCommonData->m_settings.saveNoteFilterSettings(width(), height()); accept(); } void NoteFilterDlgImpl::on_m_pCancelB_clicked() { reject(); } void NoteFilterDlgImpl::onAvlDoubleClicked(int nRow) { //m_pCommonData->m_filter.m_vpSelNotes.clear(); vector<const Note*> v; const NoteListElem* p (dynamic_cast<const NoteListElem*>(m_vpOrigAll[getAvailable()[nRow]])); CB_ASSERT(0 != p); v.push_back(p->getNote()); m_pCommonData->m_filter.setNotes(v); accept(); } //--------------------------------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------------------------------- /*override*/ string NoteFilterDlgImpl::getTooltip(TooltipKey eTooltipKey) const { switch (eTooltipKey) { case SELECTED_G: return "";//"Notes to be included"; case AVAILABLE_G: return "";//"Available notes"; case ADD_B: return convStr(tr("Add selected note(s)")); case DELETE_B: return convStr(tr("Remove selected note(s)")); case ADD_ALL_B: return convStr(tr("Add all notes")); case DELETE_ALL_B: return convStr(tr("Remove all notes")); case RESTORE_DEFAULT_B: return ""; case RESTORE_OPEN_B: return convStr(tr("Restore lists to the configuration they had when the window was open")); default: CB_ASSERT(false); } } /*override*/ void NoteFilterDlgImpl::reset() { CB_ASSERT(false); } void NoteFilterDlgImpl::onHelp() { openHelp("170_note_filter.html"); } //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== /*override*/ std::string NoteListPainterBase::getColTitle(int nCol) const { switch (nCol) { case 0: return convStr(NoteFilterDlgImpl::tr("L")); case 1: return convStr(NoteFilterDlgImpl::tr("Note")); default: return "???"; } } /*override*/ void NoteListPainterBase::getColor(int nIndex, int nColumn, bool bSubList, QColor& bckgColor, QColor& penColor, double& dGradStart, double& dGradEnd) const { LAST_STEP("NoteListPainterBase::getColor()"); const NoteListElem* p (dynamic_cast<const NoteListElem*>(getAll()[nIndex])); CB_ASSERT(0 != p); const Note* pNote (p->getNote()); { const SubList& v (getAvailable()); if (m_vpAvail.size() != v.size()) // !!! there's no need for a "dirty" flag; after the content of getAvailable() changes, a paint is executed, and when it gets here sizes will be different { m_vpAvail.clear(); for (int i = 0; i < cSize(v); ++i) { const NoteListElem* p (dynamic_cast<const NoteListElem*>(getAll()[v[i]])); m_vpAvail.push_back(p->getNote()); } } } { const SubList& v (getSel()); if (m_vpSel.size() != v.size()) { m_vpSel.clear(); for (int i = 0; i < cSize(v); ++i) { const NoteListElem* p (dynamic_cast<const NoteListElem*>(getAll()[v[i]])); m_vpSel.push_back(p->getNote()); } } } if (0 == nColumn) { if (Note::ERR == pNote->getSeverity()) { penColor = ERROR_PEN_COLOR(); } else if (Note::SUPPORT == pNote->getSeverity()) { penColor = SUPPORT_PEN_COLOR(); } } m_pCommonData->getNoteColor(*pNote, bSubList ? m_vpSel : m_vpAvail, bckgColor, dGradStart, dGradEnd); } // positive values are used for fixed widths, while negative ones are for "stretched" /*override*/ int NoteListPainterBase::getColWidth(int nCol) const { switch (nCol) { case 0: return CELL_WIDTH + 10; case 1: return -1; } CB_ASSERT(false); } /*override*/ int NoteListPainterBase::getHdrHeight() const { return CELL_HEIGHT; } /*override*/ Qt::Alignment NoteListPainterBase::getAlignment(int nCol) const { if (0 == nCol) { //return Qt::AlignTop | Qt::AlignHCenter; return Qt::AlignVCenter | Qt::AlignHCenter; } return Qt::AlignTop | Qt::AlignLeft; } ������������������������������������������������������������������������������������������������������������������������������MP3Diags-1.2.02/src/CommonData.cpp������������������������������������������������������������������0000644�0001750�0000144�00000257622�12131210422�015466� 0����������������������������������������������������������������������������������������������������ustar �ciobi���������������������������users������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifdef MSVC_QMAKE #pragma warning (disable : 4100) #endif #include <cmath> #include <algorithm> #include <sstream> #include <QApplication> #include <QToolButton> #include <QTableView> #include <QDesktopWidget> #include <QSettings> #include <QTextCodec> #include <QHeaderView> #include <QMessageBox> #include <QPainter> #include "CommonData.h" #include "Helpers.h" #include "StructuralTransformation.h" #include "Id3Transf.h" #include "OsFile.h" #include "StoredSettings.h" #include "FilesModel.h" // all files #include "NotesModel.h" // current notes #include "StreamsModel.h" // current streams #include "UniqueNotesModel.h" // all notes #include "CommonTypes.h" #include "Widgets.h" using namespace std; using namespace pearl; //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== UniqueNotes::UniqueNotes() : m_bAllDirty(true), m_bFltDirty(true) { } /*UniqueNotes::UniqueNotes(const UniqueNotes& other) { addColl(other.getAllVec()); setSel(other.getSelVec()); }*/ UniqueNotes::~UniqueNotes() { clear(); } void UniqueNotes::clear() { m_spFlt.clear(); //clearPtrContainer(m_spAll); m_spAll.clear(); m_bAllDirty = true; m_bFltDirty = true; } bool UniqueNotes::addNote(const Note* pNote) // if the note doesn't exist in m_spAll, it adds the corresponding note from Notes; returns true if the param really was added; { if (m_spAll.count(pNote) > 0) { return false; } const Note* p (Notes::getMaster(pNote)); CB_ASSERT (0 != p); m_spAll.insert(p); m_bAllDirty = true; return true; } void UniqueNotes::updateVAll() const { if (m_bAllDirty) { m_vpAll.clear(); m_vpAll.insert(m_vpAll.end(), m_spAll.begin(), m_spAll.end()); m_bAllDirty = false; } } void UniqueNotes::updateVFlt() const { if (m_bFltDirty) { m_vpFlt.clear(); m_vpFlt.insert(m_vpFlt.end(), m_spFlt.begin(), m_spFlt.end()); m_bFltDirty = false; } } int UniqueNotes::getPos(const Note* pNote) const // position in the "all" notes; -1 if the note wasn't found; { updateVAll(); vector<const Note*>::const_iterator it (lower_bound(m_vpAll.begin(), m_vpAll.end(), pNote, CmpNotePtrById())); // easier than equal_range() in this case if (it == m_vpAll.end() || !CmpNotePtrById::equals(*it, pNote)) { // trace or info return -1; } return it - m_vpAll.begin(); } int UniqueNotes::getFltPos(const Note* pNote) const // position in the "sel" notes; -1 if the note wasn't found; { updateVFlt(); vector<const Note*>::const_iterator it (lower_bound(m_vpFlt.begin(), m_vpFlt.end(), pNote, CmpNotePtrById())); if (it == m_vpFlt.end() || !CmpNotePtrById::equals(*it, pNote)) { // trace or info return -1; } return it - m_vpFlt.begin(); } const Note* UniqueNotes::getFlt(int n) const { updateVFlt(); return m_vpFlt.at(n); } const Note* UniqueNotes::get(int n) const { updateVAll(); return m_vpAll.at(n); } //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== void SessionSettings::saveMiscConfigSettings(const CommonData* p) { { // quality m_pSettings->setValue("quality/stereoCbrMinBitrate", p->getQualThresholds().m_nStereoCbr); m_pSettings->setValue("quality/jntStereoCbrMinBitrate", p->getQualThresholds().m_nJointStereoCbr); m_pSettings->setValue("quality/dualChnlCbrMinBitrate", p->getQualThresholds().m_nDoubleChannelCbr); m_pSettings->setValue("quality/stereoVbrMinBitrate", p->getQualThresholds().m_nStereoVbr); m_pSettings->setValue("quality/jntStereoVbrMinBitrate", p->getQualThresholds().m_nJointStereoVbr); m_pSettings->setValue("quality/dualChnlVbrMinBitrate", p->getQualThresholds().m_nDoubleChannelVbr); } { // ID3V2 transf m_pSettings->setValue("id3V2Transf/locale", p->m_locale); m_pSettings->setValue("id3V2Transf/caseForArtists", (int)p->m_eCaseForArtists); m_pSettings->setValue("id3V2Transf/caseForOthers", (int)p->m_eCaseForOthers); } { // tag editor m_pSettings->setValue("tagEditor/warnOnNonSeqTracks", p->m_bWarnOnNonSeqTracks); m_pSettings->setValue("tagEditor/warnOnPasteToNonSeqTracks", p->m_bWarnPastingToNonSeqTracks); m_pSettings->setValue("tagEditor/saveAssigned", (int)p->m_eAssignSave); m_pSettings->setValue("tagEditor/saveNonId3v2", (int)p->m_eNonId3v2Save); m_pSettings->setValue("tagEditor/maxImageSize", ImageInfo::MAX_IMAGE_SIZE); } { // misc m_pSettings->setValue("main/showExport", p->m_bShowExport); m_pSettings->setValue("main/showDebug", p->m_bShowDebug); m_pSettings->setValue("main/showSessions", p->m_bShowSessions); m_pSettings->setValue("main/showCustomCloseButtons", p->m_bShowCustomCloseButtons); m_pSettings->setValue("normalizer/command", convStr(p->m_strNormalizeCmd)); m_pSettings->setValue("main/keepNormWndOpen", p->m_bKeepNormWndOpen); m_pSettings->setValue("debug/enableTracing", p->m_bTraceEnabled); m_pSettings->setValue("debug/useAllNotes", p->m_bUseAllNotes); m_pSettings->setValue("debug/logTransf", p->m_bLogTransf); m_pSettings->setValue("debug/saveDownloadedData", p->m_bSaveDownloadedData); m_pSettings->setValue("main/autoSizeIcons", p->m_bAutoSizeIcons); m_pSettings->setValue("main/keepOneValidImg", p->m_bKeepOneValidImg); m_pSettings->setValue("main/processWmpVarArtists", p->m_bWmpVarArtists); m_pSettings->setValue("main/processItunesVarArtists", p->m_bItunesVarArtists); m_pSettings->setValue("main/fastSave", p->useFastSave()); m_pSettings->setValue("debug/traceToFile", p->isTraceToFileEnabled()); QFont genFnt (p->getNewGeneralFont()); m_pSettings->setValue("main/generalFontName", genFnt.family()); m_pSettings->setValue("main/generalFontSize", genFnt.pointSize()); m_pSettings->setValue("main/labelFontSizeDecr", p->getLabelFontSizeDecr()); QFont fixedFnt (p->getNewFixedFont()); m_pSettings->setValue("main/fixedFontName", fixedFnt.family()); m_pSettings->setValue("main/fixedFontSize", fixedFnt.pointSize()); m_pSettings->setValue("main/warnedAboutSel", p->m_bWarnedAboutSel); m_pSettings->setValue("main/warnedAboutBackup", p->m_bWarnedAboutBackup); m_pSettings->setValue("tagEditor/toldAboutPatterns", p->m_bToldAboutPatterns); m_pSettings->setValue("main/toldAboutSupport", p->m_bToldAboutSupport); m_pSettings->setValue("fileRenamer/invalidChars", convStr(p->m_strRenamerInvalidChars)); m_pSettings->setValue("fileRenamer/replacementForInvalid", convStr(p->m_strRenamerReplacementString)); m_pSettings->setValue("main/checkForNewVersions", convStr(p->m_strCheckForNewVersions)); m_pSettings->setValue("main/timeLastNewVerCheck", p->m_timeLastNewVerCheck); m_pSettings->setValue("main/dontTellAboutVer", convStr(p->m_strDontTellAboutVer)); m_pSettings->setValue("main/translation", convStr(p->m_strTranslation)); } { // note categ colors vector<string> v; for (int i = 0; i < Note::CATEG_CNT; ++i) { QColor c (p->m_vNoteCategColors[i]); char a [20]; sprintf(a, "%d %d %d", c.red(), c.green(), c.blue()); v.push_back(a); } p->m_settings.saveVector("categories/colors", v); } { // tag edt colors vector<string> v; for (int i = 0; i < CommonData::COLOR_COL_CNT; ++i) { QColor c (p->m_vTagEdtColors[i]); char a [20]; sprintf(a, "%d %d %d", c.red(), c.green(), c.blue()); v.push_back(a); } p->m_settings.saveVector("tagEditor/colors", v); } } static bool isWhite(const QColor& c) { //qDebug("%d %d %d", c.red(), c.green(), c.blue()); return c.red() >= 254 && c.green() >= 254 && c.blue() >= 254; } void SessionSettings::loadMiscConfigSettings(CommonData* p, bool bInitGui) const { { // quality QualThresholds q (QualThresholds::getDefaultQualThresholds()); q.m_nStereoCbr = m_pSettings->value("quality/stereoCbrMinBitrate", q.m_nStereoCbr).toInt(); q.m_nJointStereoCbr = m_pSettings->value("quality/jntStereoCbrMinBitrate", q.m_nJointStereoCbr).toInt(); q.m_nDoubleChannelCbr = m_pSettings->value("quality/dualChnlCbrMinBitrate", q.m_nDoubleChannelCbr).toInt(); q.m_nStereoVbr = m_pSettings->value("quality/stereoVbrMinBitrate", q.m_nStereoVbr).toInt(); q.m_nJointStereoVbr = m_pSettings->value("quality/jntStereoVbrMinBitrate", q.m_nJointStereoVbr).toInt(); q.m_nDoubleChannelVbr = m_pSettings->value("quality/dualChnlVbrMinBitrate", q.m_nDoubleChannelVbr).toInt(); p->setQualThresholds(q); } { // ID3V2 transf p->m_locale = m_pSettings->value("id3V2Transf/locale", "ISO 8859-1").toByteArray(); p->m_pCodec = (QTextCodec::codecForName(p->m_locale)); if (0 == p->m_pCodec) { /*QList<QByteArray> l (QTextCodec::availableCodecs()); CB_ASSERT (l.size() > 0); p->m_locale = l.front();*/ p->m_locale = "System"; // ttt2 2009.11.03 - actually this seems a bad idea; at least on 10.3 / 4.3.1 setting the locale to System shows no characters for codes > 128; doesn't happen in W7, though p->m_pCodec = (QTextCodec::codecForName(p->m_locale)); } else { p->m_locale = p->m_pCodec->name(); // !!! needed because the names may be different ("ISO 8859-1" vs. "ISO-8859-1") } CB_ASSERT (0 != p->m_pCodec); p->m_eCaseForArtists = (TextCaseOptions)m_pSettings->value("id3V2Transf/caseForArtists", 2).toInt(); p->m_eCaseForOthers = (TextCaseOptions)m_pSettings->value("id3V2Transf/caseForOthers", 3).toInt(); } { // tag editor p->m_bWarnOnNonSeqTracks = m_pSettings->value("tagEditor/warnOnNonSeqTracks", true).toBool(); p->m_bWarnPastingToNonSeqTracks = m_pSettings->value("tagEditor/warnOnPasteToNonSeqTracks", true).toBool(); { int k (m_pSettings->value("tagEditor/saveAssigned", 2).toInt()); if (k < 0 || k > 2) { k = 2; } p->m_eAssignSave = CommonData::Save(k); } { int k (m_pSettings->value("tagEditor/saveNonId3v2", 2).toInt()); if (k < 0 || k > 2) { k = 2; } p->m_eNonId3v2Save = CommonData::Save(k); } ImageInfo::MAX_IMAGE_SIZE = m_pSettings->value("tagEditor/maxImageSize", 102400).toInt(); } { // misc p->m_bShowExport = m_pSettings->value("main/showExport", false).toBool(); p->m_bShowDebug = m_pSettings->value("main/showDebug", false).toBool(); p->m_bShowSessions = m_pSettings->value("main/showSessions", p->getDefaultForVisibleSessBtn()).toBool(); p->m_bShowCustomCloseButtons = m_pSettings->value("main/showCustomCloseButtons", ::getDefaultForShowCustomCloseButtons()).toBool(); p->m_strNormalizeCmd = convStr(m_pSettings->value("normalizer/command", "mp3gain -a -k -p -t").toString()); p->m_bKeepNormWndOpen = m_pSettings->value("main/keepNormWndOpen", false).toBool(); p->m_bTraceEnabled = m_pSettings->value("debug/enableTracing", false).toBool(); p->m_bUseAllNotes = m_pSettings->value("debug/useAllNotes", false).toBool(); p->m_bLogTransf = m_pSettings->value("debug/logTransf", false).toBool(); p->m_bSaveDownloadedData = m_pSettings->value("debug/saveDownloadedData", false).toBool(); p->m_bAutoSizeIcons = m_pSettings->value("main/autoSizeIcons", true).toBool(); p->m_bKeepOneValidImg = m_pSettings->value("main/keepOneValidImg", false).toBool(); p->m_bWmpVarArtists = m_pSettings->value("main/processWmpVarArtists", false).toBool(); p->m_bItunesVarArtists = m_pSettings->value("main/processItunesVarArtists", false).toBool(); p->setFastSave(m_pSettings->value("main/fastSave", false).toBool(), CommonData::DONT_UPDATE_TRANSFORMS); p->setTraceToFile(m_pSettings->value("debug/traceToFile", false).toBool()); QFont fnt; //qDebug("%d ==========================", fnt.pointSize()); if (bInitGui) { QFontInfo inf1 (QFont(m_pSettings->value("main/generalFontName", "SansSerif").toString(), m_pSettings->value("main/generalFontSize", fnt.pointSize()).toInt())); // ttt2 try and get the system defaults QFontInfo inf2 (QFont(m_pSettings->value("main/fixedFontName", "Courier").toString(), m_pSettings->value("main/fixedFontSize", fnt.pointSize()).toInt())); p->setFontInfo(convStr(inf1.family()), inf1.pointSize(), m_pSettings->value("main/labelFontSizeDecr", 0).toInt(), convStr(inf2.family()), inf2.pointSize()); } p->m_bWarnedAboutSel = m_pSettings->value("main/warnedAboutSel", false).toBool(); p->m_bWarnedAboutBackup = m_pSettings->value("main/warnedAboutBackup", false).toBool(); p->m_bToldAboutPatterns = m_pSettings->value("tagEditor/toldAboutPatterns", false).toBool(); p->m_bToldAboutSupport = m_pSettings->value("main/toldAboutSupport", false).toBool(); #ifndef WIN32 const char* DEFAULT_INVALID ("/\"\\*?<>|"); //ttt2 even in Unix, it might be a good idea to not allow ":" as well in some cases, depending on the file system #else const char* DEFAULT_INVALID ("/\"\\*?<>|:"); //ttt2 perhaps have yellow background instead of underline, to be able to tell if there's a space or not; or have something like a progres bar below; or have each letter in its own rectangle; #endif p->m_strRenamerInvalidChars = convStr(m_pSettings->value("fileRenamer/invalidChars", DEFAULT_INVALID).toString()); p->m_strRenamerReplacementString = convStr(m_pSettings->value("fileRenamer/replacementForInvalid", "_").toString()); p->m_strCheckForNewVersions = convStr(m_pSettings->value("main/checkForNewVersions", "").toString()); p->m_timeLastNewVerCheck = m_pSettings->value("main/timeLastNewVerCheck").toDateTime(); if (p->m_timeLastNewVerCheck.isNull()) { QDateTime t1 (QDateTime::currentDateTime()); p->m_timeLastNewVerCheck = t1.addYears(-1); } p->m_strDontTellAboutVer = convStr(m_pSettings->value("main/dontTellAboutVer", "").toString()); p->m_strTranslation = convStr(m_pSettings->value("main/translation", "mp3diags_en_US.qm").toString()); //ttt0 get default language from GlobalSettings (or maybe not; a new session gets a language when it is created, and the old ones were in en_US anyway) } { // note categ colors //m_settings.saveVector("categories/colors", u); bool bErr; vector<string> v (loadVector("categories/colors", bErr)); int i (0); for (; i < Note::CATEG_CNT && i < cSize(v); ++i) { int r (230), g (230), b (230); istringstream in (v[i]); in >> r >> g >> b; p->m_vNoteCategColors.push_back(QColor(r, g, b)); } QColor c (getDefaultBkgCol()); for (; i < Note::CATEG_CNT; ++i) { //p->m_vNoteCategColors.push_back(QColor(240, 240 + i, 240 - i)); p->m_vNoteCategColors.push_back(c); } } { // tag edt colors bool bErr; vector<string> v (loadVector("tagEditor/colors", bErr)); for (int i = 0; i < CommonData::COLOR_COL_CNT && i < cSize(v); ++i) { int r (230), g (230), b (230); istringstream in (v[i]); in >> r >> g >> b; p->m_vTagEdtColors.push_back(QColor(r, g, b)); } QColor defNormal (QPalette().color(QPalette::Active, QPalette::Base)); if (cSize(p->m_vTagEdtColors) <= 0) { p->m_vTagEdtColors.push_back(defNormal); } // COLOR_ALB_NORM if (cSize(p->m_vTagEdtColors) <= 1) { p->m_vTagEdtColors.push_back(QColor(0xffffdd)); } // COLOR_ALB_NONID3V2 if (cSize(p->m_vTagEdtColors) <= 2) { p->m_vTagEdtColors.push_back(QColor(0xccccff)); } // COLOR_ALB_ASSIGNED if (cSize(p->m_vTagEdtColors) <= 3) { p->m_vTagEdtColors.push_back(QColor(defNormal)); } // COLOR_FILE_NORM if (cSize(p->m_vTagEdtColors) <= 4) { p->m_vTagEdtColors.push_back(QColor(0xdddddd)); } // COLOR_FILE_TAG_MISSING if (cSize(p->m_vTagEdtColors) <= 5) { p->m_vTagEdtColors.push_back(QColor(0xf0f0ff)); } // COLOR_FILE_NA if (cSize(p->m_vTagEdtColors) <= 6) { p->m_vTagEdtColors.push_back(QColor(0xffffdd)); } // COLOR_FILE_NO_DATA } } // adjusts the global font so it displays legible characters (on Windows it is possible under some unclear circumstances for all characters to be shown as small rectangles) void fixAppFont(QFont& font, string& strNewFont, int& nNewSize) { const char* aszFonts[] = { "Arial", "Helvetica", "Sans", "Courier", 0 }; for (int i = 0; ; ++i) { const int SIZE (50); QImage img1 (SIZE, SIZE, QImage::Format_RGB32); QImage img2 (SIZE, SIZE, QImage::Format_RGB32); { QPainter pntr (&img1); pntr.fillRect(0, 0, SIZE, SIZE, QColor(255, 255, 255)); pntr.drawText(5, SIZE/2 + 10, "ab"); } { QPainter pntr (&img2); pntr.fillRect(0, 0, SIZE, SIZE, QColor(255, 255, 255)); pntr.drawText(5, SIZE/2 + 10, "cd"); //pntr.drawText(5, SIZE/2 + 10, "ab"); } if (img1 != img2) { break; } qDebug("invalid font %s", strNewFont.c_str()); const char* szFont (aszFonts[i]); if (0 == szFont) { exit(1); // don't know what else to do } strNewFont = szFont; if (nNewSize < 5 || nNewSize > 20) { nNewSize = 9; }; font.setFamily(convStr(strNewFont)); font.setPointSize(nNewSize); QApplication::setFont(font); } } static int MIN_FILE_WIDTH; // minimum width of the "file" field void CommonData::setFontInfo(const std::string& strGenName, int nGenSize, int nLabelFontSizeDecr, const std::string& strFixedName, int nFixedSize) { if (nGenSize < 5 || nGenSize > 20) { nGenSize = 9; } if (nFixedSize < 5 || nFixedSize > 20) { nFixedSize = 9; } if (nLabelFontSizeDecr < 0 || nLabelFontSizeDecr > 5) { nLabelFontSizeDecr = 0; } if (m_strGenFontName == strGenName && m_nGenFontSize == nGenSize && m_nLabelFontSizeDecr == nLabelFontSizeDecr && m_strFixedFontName == strFixedName && m_nFixedFontSize == nFixedSize) { return; } // nothing changed bool bFirstTime (m_strGenFontName.empty()); m_strGenFontName = strGenName; m_nGenFontSize = nGenSize; m_nLabelFontSizeDecr = nLabelFontSizeDecr; m_strFixedFontName = strFixedName; m_nFixedFontSize = nFixedSize; if (!bFirstTime) { showWarning(m_pFilesG, tr("Info"), tr("The font changes will only be used after restarting the application.")); //ttt2 try to get this work, probably needs to call QHeaderView::resizeSection(), as well as review all setMinimumSectionSize() and setDefaultSectionSize() calls; return; } m_generalFont.setFamily(convStr(strGenName)); m_generalFont.setPointSize(nGenSize); QApplication::setFont(m_generalFont); fixAppFont(m_generalFont, m_strGenFontName, m_nGenFontSize); m_labelFont = m_generalFont; m_labelFont.setPointSize(nGenSize - nLabelFontSizeDecr); m_fixedFont.setFamily(convStr(strFixedName)); //m_fixedFont.setFamily("B&H LucidaTypewriter"); //m_fixedFont.setFamily("B&H LucidaTypewriter12c"); //m_fixedFont.setFixedPitch(true); // !!! needed to select some fixed font in case there's no "B&H LucidaTypewriter" installed m_fixedFont.setFixedPitch(true); m_fixedFont.setStyleHint(QFont::Courier); //f.setPixelSize(12); //ttt2 hard-coded "12"; //ttt2 some back-up font instead of just letting the system decide m_fixedFont.setPointSize(nFixedSize); CELL_HEIGHT = QApplication::fontMetrics().height() + 3; { QPixmap img (100, 100); //ttt2 revisit the size; might need increase in the future QPainter pntr (&img); QFont f (m_labelFont); f.setWeight(QFont::Bold); pntr.setFont(f); int n (0); for (char c1 = 'a'; c1 <= 'z'; ++c1) //ttt3 ASCII-specific { for (char c2 = 'a'; c2 <= 'z'; ++c2) { if (c1 != 'm' && c1 != 'w' && c2 != 'm' && c2 != 'w') { QString s; s += c1; s += c2; QRect r (10, 10, 100, 100); r = pntr.boundingRect(r, Qt::AlignLeft | Qt::AlignTop, s); //CB_ASSERT (10 == r.x()); n = max(n, r.width()); } } //qDebug("%s - %d %d %2d %2d", s.toUtf8().constData(), r.x(), r.y(), r.width(), r.height()); } CELL_WIDTH = n + 4; //ttt2 hard-coded "4"; see how to get the correct value } MIN_FILE_WIDTH = QApplication::fontMetrics().width("ABCDEFGHIJKLMNopqrstuvwxyz12345"); /*m_pFilesG->horizontalHeader()->setMinimumSectionSize(CELL_WIDTH); m_pFilesG->verticalHeader()->setMinimumSectionSize(CELL_HEIGHT); m_pFilesG->verticalHeader()->setDefaultSectionSize(CELL_HEIGHT);*/ } QFont CommonData::getNewGeneralFont() const { QFont f; f.setFamily(convStr(m_strGenFontName)); f.setPointSize(m_nGenFontSize); QFontInfo info (f); return QFont (info.family(), info.pointSize()); } QFont CommonData::getNewFixedFont() const { QFont f; f.setFamily(convStr(m_strFixedFontName)); f.setPointSize(m_nFixedFontSize); f.setStyleHint(QFont::Courier); f.setFixedPitch(true); QFontInfo info (f); return QFont (info.family(), info.pointSize()); } //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== namespace { CommonData* g_pCommonData (0); } CommonData* getCommonData() // ttt2 get rid of all places passing CommonData* as a param { return g_pCommonData; } CommonData::CommonData( SessionSettings& settings, QTableView* pFilesG, QTableView* pNotesG, QTableView* pStreamsG, QTableView* pUniqueNotesG, QToolButton* pNoteFilterB, QToolButton* pDirFilterB, QToolButton* pModeAllB, QToolButton* pModeAlbumB, QToolButton* pModeSongB, bool bDefaultForVisibleSessBtn) : m_pFilesModel(0), m_pNotesModel(0), m_pStreamsModel(0), m_pUniqueNotesModel(0), m_pFilesG(pFilesG), m_pNotesG(pNotesG), m_pStreamsG(pStreamsG), m_pUniqueNotesG(pUniqueNotesG), m_bChangeGuard(false), m_pCodec(0), m_bUseAllNotes(false), m_bTraceEnabled(false), m_bAutoSizeIcons(true), m_nMainWndIconSize(40), m_settings(settings), m_bKeepOneValidImg(false), m_bWmpVarArtists(false), m_bItunesVarArtists(false), m_bLogTransf(false), m_bSaveDownloadedData(false), m_vvnCustomTransf(CUSTOM_TRANSF_CNT), //m_bDirty(false), m_nLabelFontSizeDecr(0), m_bDefaultForVisibleSessBtn(bDefaultForVisibleSessBtn), m_bFastSave(false), m_eViewMode(ALL), m_pNoteFilterB(pNoteFilterB), m_pDirFilterB(pDirFilterB), m_pModeAllB(pModeAllB), m_pModeAlbumB(pModeAlbumB), m_pModeSongB(pModeSongB) { g_pCommonData = this; m_vpAllTransf.push_back(new SingleBitRepairer()); m_vpAllTransf.push_back(new InnerNonAudioRemover()); m_vpAllTransf.push_back(new UnknownDataStreamRemover()); m_vpAllTransf.push_back(new BrokenDataStreamRemover()); m_vpAllTransf.push_back(new UnsupportedDataStreamRemover()); m_vpAllTransf.push_back(new TruncatedMpegDataStreamRemover()); m_vpAllTransf.push_back(new NullStreamRemover()); m_vpAllTransf.push_back(new BrokenId3V2Remover()); m_vpAllTransf.push_back(new UnsupportedId3V2Remover()); m_vpAllTransf.push_back(new IdentityTransformation()); m_vpAllTransf.push_back(new MultipleId3StreamRemover()); m_vpAllTransf.push_back(new MismatchedXingRemover()); m_vpAllTransf.push_back(new TruncatedAudioPadder()); m_vpAllTransf.push_back(new VbrRepairer()); m_vpAllTransf.push_back(new VbrRebuilder()); m_vpAllTransf.push_back(new Id3V2Cleaner(this)); m_vpAllTransf.push_back(new Id3V2Rescuer(this)); m_vpAllTransf.push_back(new Id3V2UnicodeTransformer(this)); m_vpAllTransf.push_back(new Id3V2CaseTransformer(this)); m_vpAllTransf.push_back(new Id3V2ComposerAdder(this)); m_vpAllTransf.push_back(new Id3V2ComposerRemover(this)); m_vpAllTransf.push_back(new Id3V2ComposerCopier(this)); m_vpAllTransf.push_back(new SmallerImageRemover(this)); m_vpAllTransf.push_back(new Id3V1ToId3V2Copier(this)); m_vpAllTransf.push_back(new Id3V1Remover()); m_vpAllTransf.push_back(new ApeRemover()); m_vpAllTransf.push_back(new NonAudioRemover()); m_vpAllTransf.push_back(new Id3V2Expander(this)); m_vpAllTransf.push_back(new Id3V2Compactor(this)); m_settings.loadDirs(m_vstrIncludeDirs, m_vstrExcludeDirs); try { m_dirTreeEnum.reset(m_vstrIncludeDirs, m_vstrExcludeDirs); } catch (const DirTreeEnumerator::InvalidDirs&) { showCritical(m_pFilesG, tr("Error"), tr("There was an error setting up the directories containing MP3 files. You will have to define them again.")); m_vstrIncludeDirs.clear(); m_vstrExcludeDirs.clear(); } connect(&m_filter, SIGNAL(filterChanged()), this, SLOT(onFilterChanged())); } CommonData::~CommonData() { clearPtrContainer(m_vpAllHandlers); clearPtrContainer(m_vpAllTransf); g_pCommonData = 0; } // finds the position of a note in the global vector with notes sorted by severity and description; // the position can then be used to find the corresponding label; // returns -1 if the note is not found; (needed because trace and info notes aren't shown in the grid, so they are not found) int CommonData::findPos(const Note* pNote) const { return m_uniqueNotes.getFltPos(pNote); } // for debugging; prints what's current in m_pFilesG, using several methods void CommonData::printFilesCrt() const { QModelIndex x (m_pFilesG->selectionModel()->currentIndex()); QModelIndex y (m_pFilesG->currentIndex()); printf("SelModel: %dx%d View: %dx%d getFilesGCrtRow(): %d vector.size(): %d\n", x.row(), x.column(), y.row(), y.column(), getFilesGCrtRow(), cSize(m_vpAllHandlers));//*/ } const Mp3Handler* CommonData::getCrtMp3Handler() const // returns 0 if the list is empty { int n (getFilesGCrtRow()); return -1 == n ? 0 : m_vpViewHandlers[n]; } const string& CommonData::getCrtName() const { const Mp3Handler* p (getCrtMp3Handler()); if (0 != p) { return p->getName(); } static string strEmpty; return strEmpty; } const vector<DataStream*>& CommonData::getCrtStreams() const { int n (getFilesGCrtRow()); //static int yy; qDebug("row %d %d", n, yy++); static std::vector<DataStream*> s_vEmpty; if (-1 == n) { return s_vEmpty; } return m_vpViewHandlers.at(n)->getStreams(); } // copies to spUniqueNotes all the unique notes from commonData.m_vpFltHandlers, with comparison done by CmpNotePtrById; // ignored notes (given by m_vnIgnoredNotes) are not included; // not actual pointers from vpHandlers are stored, but the corresponding ones from given by Notes::getMaster(); so they must not be freed; void CommonData::getUniqueNotes(const std::deque<const Mp3Handler*>& vpHandlers, set<const Note*, CmpNotePtrById>& spUniqueNotes) { set<int> snIgnored (m_vnIgnoredNotes.begin(), m_vnIgnoredNotes.end()); for (int i = 0; i < cSize(vpHandlers); ++i) { const Mp3Handler* pHndl (vpHandlers[i]); const vector<Note*>& vpNotes (pHndl->getNotes().getList()); for (int j = 0, n = cSize(vpNotes); j < n; ++j) { const Note* pNote (vpNotes[j]); if (Note::ERR == pNote->getSeverity() || Note::WARNING == pNote->getSeverity() || Note::SUPPORT == pNote->getSeverity()) { if (0 == snIgnored.count(pNote->getNoteId())) { // not an ignored note, so it can be added; spUniqueNotes.insert(Notes::getMaster(pNote)); // most of the time the note already exists in spUniqueNotes, but that's OK: it doesn't take a lot of time and the pointers are not owned; } } } } } // although the current elem can be identified (so it "shouldn't" be passed) and most of the time pMp3Handler will be just that, sometimes it will deliberately be 0, so a param is actually needed; void CommonData::setViewMode(ViewMode eViewMode, const Mp3Handler* pMp3Handler /* = 0*/) { int nRes (setViewModeHlp(eViewMode, pMp3Handler)); m_pFilesModel->selectRow(nRes); switch (eViewMode) { case ALL: m_pModeAllB->setChecked(true); break; case FOLDER: m_pModeAlbumB->setChecked(true); break; case FILE: m_pModeSongB->setChecked(true); break; default: CB_ASSERT (false); } resizeFilesGCols(); } // returns the position of the "current" elem in m_vpViewHandlers (so it can be selected in the grid); if m_vpViewHandlers is empty, it returns -1; if pMp3Handler is 0, it returns 0 (unless m_vpViewHandlers is empty); int CommonData::setViewModeHlp(ViewMode eViewMode, const Mp3Handler* pMp3Handler) { m_eViewMode = eViewMode; if (m_vpFltHandlers.empty()) { m_vpViewHandlers.clear(); return -1; } switch (eViewMode) { case ALL: m_vpViewHandlers = m_vpFltHandlers; return 0 == pMp3Handler ? 0 : getPosInFlt(pMp3Handler); case FILE: { m_vpViewHandlers.clear(); m_vpViewHandlers.push_back(0 == pMp3Handler ? m_vpFltHandlers[0] : pMp3Handler); return 0; } case FOLDER: { m_vpViewHandlers.clear(); if (0 == pMp3Handler) { pMp3Handler = m_vpFltHandlers[0]; } string strDir (pMp3Handler->getDir()); int nRes (-1); for (int i = 0, n = cSize(m_vpFltHandlers); i < n; ++i) { const Mp3Handler* p (m_vpFltHandlers[i]); if (p->getDir() == strDir) { if (p == pMp3Handler) { nRes = cSize(m_vpViewHandlers); } m_vpViewHandlers.push_back(p); } } CB_ASSERT (nRes >= 0); return nRes; } } CB_ASSERT(false); } void CommonData::next() { int k (nextHlp()); m_pFilesModel->selectRow(k); resizeFilesGCols(); } // returns the position of the "current" elem in m_vpViewHandlers (see setViewMode() for details) int CommonData::nextHlp() { if (m_vpViewHandlers.empty()) { return -1; } switch (m_eViewMode) { case ALL: { // move to beginning of next folder int i (getFilesGCrtRow()); int n (cSize(m_vpViewHandlers)); string strDir (m_vpViewHandlers[i]->getDir()); for (; i < n - 1; ++i) { if (m_vpViewHandlers[i]->getDir() != strDir) { return i; } } return n - 1; } //ttt2 the layout doesn't change for ALL, but it does for FILE and FOLDER, so perhaps a means to tell if the layout changed or not is needed case FILE: { // next file CB_ASSERT(1 == cSize(m_vpViewHandlers)); int i (getPosInFlt(getCrtMp3Handler())); int n (cSize(m_vpFltHandlers)); if (n - 1 == i) { return 0; } ++i; m_vpViewHandlers[0] = m_vpFltHandlers[i]; return 0; } case FOLDER: { // next folder int i (getPosInFlt(getCrtMp3Handler())); int n (cSize(m_vpFltHandlers)); if (n - 1 == i) { return cSize(m_vpViewHandlers) - 1; } string strDir (m_vpFltHandlers[i]->getDir()); ++i; for (; i < n; ++i) { if (m_vpFltHandlers[i]->getDir() != strDir) { break; } } if (n == i) { return cSize(m_vpViewHandlers) - 1; } // it was the last dir; don't change m_vpViewHandlers, just move to the end // new folder found m_vpViewHandlers.clear(); strDir = m_vpFltHandlers[i]->getDir(); while (strDir == m_vpFltHandlers[i]->getDir()) { m_vpViewHandlers.push_back(m_vpFltHandlers[i]); ++i; if (i >= n) { break; } } return 0; } } CB_ASSERT(false); } void CommonData::previous() { int k (previousHlp()); m_pFilesModel->selectRow(k); resizeFilesGCols(); } // returns the position of the "current" elem in m_vpViewHandlers (see setViewMode() for details) int CommonData::previousHlp() { if (m_vpViewHandlers.empty()) { return -1; } switch (m_eViewMode) { case ALL: { // move to beginning of previous folder int i (getFilesGCrtRow()); string strDir (m_vpViewHandlers[i]->getDir()); for (; i > 0; --i) { if (m_vpViewHandlers[i]->getDir() != strDir) { return i; } } return 0; } case FILE: { // previous file CB_ASSERT(1 == cSize(m_vpViewHandlers)); int i (getPosInFlt(getCrtMp3Handler())); if (0 == i) { return 0; } --i; m_vpViewHandlers[0] = m_vpFltHandlers[i]; return 0; } case FOLDER: { // previous folder int i (getPosInFlt(getCrtMp3Handler())); if (0 == i) { return 0; } string strDir (m_vpFltHandlers[i]->getDir()); --i; for (; i >= 0; --i) { if (m_vpFltHandlers[i]->getDir() != strDir) { break; } } if (-1 == i) { return 0; } // it was the first dir; don't change m_vpViewHandlers, just move to the beginning // new folder found m_vpViewHandlers.clear(); strDir = m_vpFltHandlers[i]->getDir(); while (strDir == m_vpFltHandlers[i]->getDir()) { m_vpViewHandlers.push_front(m_vpFltHandlers[i]); // push_front() is OK for a deque --i; if (i < 0) { break; } } return 0; } } CB_ASSERT(false); } // finds the position in m_vpFltHandlers; returns -1 if not found; 0 is a valid argument, in which case -1 is returned; needed by next() and previous(), to help with navigation when going into "folder" mode int CommonData::getPosInFlt(const Mp3Handler* pMp3Handler) const { if (0 == pMp3Handler) { return -1; } /*deque<Mp3Handler*>::const_iterator it (find(m_vpFltHandlers.begin(), m_vpFltHandlers.end(), pMp3Handler)); if (m_vpFltHandlers.end() == it) { return -1; }*/ deque<const Mp3Handler*>::const_iterator it (lower_bound(m_vpFltHandlers.begin(), m_vpFltHandlers.end(), pMp3Handler, CmpMp3HandlerPtrByName())); if (m_vpFltHandlers.end() == it || *it != pMp3Handler) { return -1; } int n (it - m_vpFltHandlers.begin()); return n; } void CommonData::onCrtFileChanged() { CursorOverrider crs; updateCurrentNotes(); updateCurrentStreams(); } // updates m_vpCrtNotes, to hold the notes corresponding to the current file and resizes the rows to fit the data void CommonData::updateCurrentNotes() { int nRow (getFilesGCrtRow()); CB_ASSERT (nRow >= -1 && nRow < cSize(getViewHandlers())); m_vpCrtNotes.clear(); if (-1 == nRow) { // an empty file list m_pNotesModel->emitLayoutChanged(); return; } const vector<Note*> vNotes (getViewHandlers()[nRow]->getNotes().getList()); for (int i = 0, n = cSize(vNotes); i < n; ++i) { const Note* pNote (vNotes[i]); if (m_bUseAllNotes || findPos(pNote) >= 0) // findPos(pNote) is -1 for TRACE as well as for ignored notes { m_vpCrtNotes.push_back(pNote); } } std::sort(m_vpCrtNotes.begin(), m_vpCrtNotes.end(), CmpNotePtrByPosAndId()); // the list may come sorted by some other criteria, e.g. severity first, //reset(); //ttt2 see what is the difference between "emit layoutChanged()" and "reset()"; here, they are similar now, but in a prior version calling "reset()" didn't show the selection (this changed after implementing "NotesModel::matchSelToMain()" but as of 2008.06.30 even that seems unncecessary m_pNotesModel->emitLayoutChanged(); //m_pNotesG->resizeColumnsToContents(); //ttt2 see why uncommenting this results in column 0 (and perhaps 2) increasing their size each time NotesModel::updateCurrentNotes() gets called m_pNotesG->resizeRowsToContents(); } void CommonData::updateCurrentStreams() { int nRow (getFilesGCrtRow()); CB_ASSERT (nRow >= -1 && nRow < cSize(getViewHandlers())); m_pStreamsModel->emitLayoutChanged(); m_pStreamsG->resizeRowsToContents(); } int CommonData::getFilesGCrtRow() const // returns -1 if no current element exists (e.g. because the table is empty) { QModelIndex index (m_pFilesG->currentIndex()); // the documentation for QTableView isn't very clear, but in the code it just calls QItemSelectionModel::currentIndex(), for which the documentation says that it returns an ivalid index if there's no current element if (!index.isValid()) { return - 1; } // not sure if this is NECESSARY; for an empty table -1 is returned anyway as index.row(); but it doesn't hurt, anyway return index.row(); } int CommonData::getFilesGCrtCol() const // returns -1 if no current element exists (e.g. because the table is empty) { QModelIndex index (m_pFilesG->currentIndex()); if (!index.isValid()) { return - 1; } // not sure if this is NECESSARY; for an empty table -1 is returned anyway as index.column(); but it doesn't hurt, anyway return index.column(); } #if 0 struct AAA { int a; }; struct Cmp { /*bool operator()(int x1, int x2) const { return x1 < x2; }*/ bool operator()(AAA* a1, int x2) const // ttt2 review if the fact that this code compiles (without requiring any of the other operators) is due to the standard or to GCC's STL; probably it's standard, because we don't need an equality test for lower_bound { //return operator()(a1->a, x2); return true; } /*bool operator()(int x1, AAA* a2) const { return operator()(x1, a2->a); } bool operator()(AAA* a1, AAA* a2) const { return operator()(a1->a, a2->a); }*/ }; void opppo() { vector<AAA*> v; lower_bound(v.begin(), v.end(), 7, Cmp()); } #endif // if bExactMatch is false, it finds the nearest position in m_vpViewHandlers (so even if a file was deleted, it still finds something close); // returns -1 only if not found (either m_vpViewHandlers is empty or bExactMatch is true and the file is missing); int CommonData::getPosInView(const std::string& strName/*, bool bUsePrevIfNotFound = true*/, bool bExactMatch /* = false*/) const { /* if (strName.empty()) { return -1; } deque<Mp3Handler*>::const_iterator it (lower_bound(m_vpViewHandlers.begin(), m_vpViewHandlers.end(), strName, CmpMp3HandlerPtrByName())); if (m_vpViewHandlers.end() == it) { if (m_vpViewHandlers.empty()) { return -1; } --it; } else if (bUsePrevIfNotFound && m_vpViewHandlers.begin() != it && (*it)->getName() != strName) // position on previous elem if exact match wasn't found { --it; } int n (it - m_vpViewHandlers.begin()); return n;*/ deque<const Mp3Handler*>::const_iterator it (lower_bound(m_vpViewHandlers.begin(), m_vpViewHandlers.end(), strName, CmpMp3HandlerPtrByName())); if ((m_vpViewHandlers.end() == it || (*it)->getName() != strName) && (bExactMatch || m_vpViewHandlers.empty())) { return -1; } if (m_vpViewHandlers.end() == it) { --it; } int n (it - m_vpViewHandlers.begin()); return n; } void CommonData::updateSelList() { QModelIndexList lSelFiles (m_pFilesG->selectionModel()->selection().indexes()); set<int> sSel; for (QModelIndexList::iterator it = lSelFiles.begin(), end = lSelFiles.end(); it != end; ++it) { sSel.insert(it->row()); } m_vpSelHandlers.clear(); for (set<int>::iterator it = sSel.begin(), end = sSel.end(); it != end; ++it) { m_vpSelHandlers.push_back(m_vpViewHandlers[*it]); } sort(m_vpSelHandlers.begin(), m_vpSelHandlers.end(), CmpMp3HandlerPtrByName()); } vector<string> CommonData::getSelNames() // calls updateSelList() and returns the names ot the selected files; { updateSelList(); vector<string> v; for (int i = 0, n = cSize(m_vpSelHandlers); i < n; ++i) { v.push_back(m_vpSelHandlers[i]->getName()); } return v; } const deque<const Mp3Handler*>& CommonData::getSelHandlers() { updateSelList(); return m_vpSelHandlers; } // the index in m_vpAllTransf for a transformation with a given name; throws if the name doesn't exist; int CommonData::getTransfPos(const char* szTransfName) const { for (int i = 0, n = cSize(m_vpAllTransf); i < n; ++i) { //if (m_vpAllTransf[i]->getName() == szTransfName) // this works as of 2008.11.07, but it doesn't provide any performance advantage, so better without it if (0 == strcmp(m_vpAllTransf[i]->getActionName(), szTransfName)) { return i; } } CB_ASSERT (false); } set<string> CommonData::getAllDirs() const { set<string> sDirs; for (int i = 0, n = cSize(m_vpAllHandlers); i < n; ++i) { const Mp3Handler* p (m_vpAllHandlers[i]); const string& s (p->getName()); string::size_type k (s.rfind(getPathSep())); CB_ASSERT(string::npos != k); string strDir (s.substr(0, k)); sDirs.insert(strDir); } return sDirs; } // keeps existing handlers as long as they are still "included" void CommonData::setDirectories(const std::vector<std::string>& vstrIncludeDirs, const std::vector<std::string>& vstrExcludeDirs) { //const vector<string>& vstrSel (getSelNames()); //string strCrtName (getCrtName()); m_vstrIncludeDirs = vstrIncludeDirs; m_vstrExcludeDirs = vstrExcludeDirs; m_dirTreeEnum.reset(vstrIncludeDirs, vstrExcludeDirs); m_settings.saveDirs(vstrIncludeDirs, vstrExcludeDirs); vector<const Mp3Handler*> vpDel; for (int i = 0, n = cSize(m_vpAllHandlers); i < n; ++i) { const Mp3Handler* p (m_vpAllHandlers[i]); if (!m_dirTreeEnum.isIncluded(p->getName())) { vpDel.push_back(p); } } deque<const Mp3Handler*> vpAll, vpFlt, vpView; set_difference(m_vpAllHandlers.begin(), m_vpAllHandlers.end(), vpDel.begin(), vpDel.end(), back_inserter(vpAll), CmpMp3HandlerPtrByName()); set_difference(m_vpFltHandlers.begin(), m_vpFltHandlers.end(), vpDel.begin(), vpDel.end(), back_inserter(vpFlt), CmpMp3HandlerPtrByName()); set_difference(m_vpViewHandlers.begin(), m_vpViewHandlers.end(), vpDel.begin(), vpDel.end(), back_inserter(vpView), CmpMp3HandlerPtrByName()); vpAll.swap(m_vpAllHandlers); vpFlt.swap(m_vpFltHandlers); vpView.swap(m_vpViewHandlers); //updateWidgets(strCrtName, vstrSel); // !!! no point in keeping crt/sel; this is only called by scan(), and after that the first file gets selected anyway updateWidgets(); } /*QString CommonData::getNoteLabel(int nPosInFlt) // gets the label of a note based on its position in m_uniqueNotes.m_vpFlt { return ::getNoteLabel(m_uniqueNotes.getFltVec()[nPosInFlt]); }*/ //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== // For the vertical header of a QTableView whose labels are the current row number, it determines the width necessary to accomodate any of those labels. // Currently it uses a hard-coded value to add to the width. //ttt2 fix // It makes 2 assumptions: // - the TableView uses the same FontMetrics as the ones returned by QApplication::fontMetrics() (this is intended to be called from a TableModel's headerData(), for which finding the table is not easy; and anyway, a model can be connected to several tables) // - digits in the font have the same size, or at least there is no digit with a size larger than that of '9'; (in many fonts all the digits do have the same size, so it should be OK most of the time) // Only the width is calculated; the height is returned as "1". This allows the content of a cell to determine the width of a row. Returning the height actually needed to draw the label would cause the rows to be to large, because significant spacing is added to the result. This is the opposite of what happens to the width, where a big number of pixels must be added by getNumVertHdrSize() just to have everything displayed. // ttt3 is this a Qt bug? (adds spacing where it's not needed and doesn't add it where it should) // // If nRowCount<=0 it behaves as if nRowCount==1 // // Returns QVariant() for horizontal headers. // // The real reason this is needed: Qt can easily resize the header to accomodate all the header labels, and that's enough for fixed-height rows, whose height is set with verticalHeader()->setDefaultSectionSize(). However, if resizeRowsToContents() gets called (and it seems that it must be called to get variable-height working, and some flag to enable this doesn't seem to exist) the height of each row's header becomes too big. Using getNumVertHdrSize() we force the height to be 1, thus allowing the data cells to tell the height (the final height is the maximum between data cells and the header cell for each row). //ttt2 At some point it seemed that rows would get larger even without calling getNumVertHdrSize(). This should be looked at again. QVariant getNumVertHdrSize(int nRowCount, Qt::Orientation eOrientation) // ttt2 add optional param QTableView to take the metrics from { if (eOrientation == Qt::Vertical) { QFontMetrics fm (QApplication::fontMetrics()); if (nRowCount <= 0) { nRowCount = 1; } double d (1.01 + log10(double(nRowCount))); QString s (int(d), QChar('9')); int nWidth (fm.width(s)); return QSize(nWidth + 10, 1); //ttt2 hard-coded "10" } return QVariant(); } //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== #if 0 static QString getNoteLabel(int nPos) { if (-1 == nPos) { return ""; } //ttt2 perhaps have a vector where each string is added manually; then it's possible to skip "trouble" letters, like 'l' and 'I' (look the same in many fonts) or 'm' (too wide) //ttt2 perhaps use underline, strikeout, ... to have more single-letter labels QString s; { // currently accepts codes from 0 to 103 #if 1 if (nPos >= 52) { s = "'"; nPos -= 52; } if (nPos < 26) { //nPos = 0x03B1 + nPos; // greek alpha nPos += 'a'; } else if (nPos < 52) { nPos = 'A' + nPos - 26; } else { CB_ASSERT (false); } //ttt2 add more when needed #else if (nPos < 26) { //nPos = 0x03B1 + nPos; // greek alpha nPos += 'a'; } else if (nPos < 52) { nPos = 'A' + nPos - 26; } else if (nPos < 77) { nPos = 0x03B1 + nPos - 52; // greek alpha } //ttt2 add more when needed else { CB_ASSERT (false); } //return QString::number(nPos); #endif /*int n1 (nPos % 52); int n2 (nPos / 52); return getNoteLabel(n2) + getNoteLabel(n1);*/ } nPos %= 26; { //nPos = 0x03B1 + nPos; // greek alpha nPos += 'a'; } if ('m' == nPos || 'w' == nPos) { nPos = 'i'; } QChar c (nPos); s = c + s; //s += "\n"; s = c + s; return s; } #endif static QString getNoteLabel(int nCateg, int nPos) { if (nCateg < 0 || nCateg >= Note::CUSTOM || nPos < 0 || nPos > 'z' - 'a' - 2) { return ""; } if (nPos >= 'm' - 'a') { ++nPos; } if (nPos >= 'w' - 'a') { ++nPos; } if (nCateg >= 'm' - 'a') { ++nCateg; } if (nCateg >= 'w' - 'a') { ++nCateg; } char cp ('a' + nPos); char cc ('a' + nCateg); QString s; s += cc; s += cp; return s; } // returns a label for a note in a given position; first 26 notes get labels "a" .. " z", next 26 get "A" .. "Z", then "aa" .. "az", "aA" .. "aZ", "ba", ... QString getNoteLabel(const Note* pNote) { /*if (0 == nPos) return "l"; if (1 == nPos) return "m"; if (2 == nPos) return "wwwwww";//*/ //CB_ASSERT (nPos >= 0); return getNoteLabel(pNote->getCategory(), pNote->getLabelIndex()); } #if 0 struct TestLabel { TestLabel() { for (int i = 0; i < 104; ++i) { qDebug("%d %s", i, getNoteLabel(i).toUtf8().constData()); } } }; static TestLabel ppppppppp; #endif //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== const QColor& ERROR_PEN_COLOR() { static QColor col (112, 48, 0); return col; } const QColor& SUPPORT_PEN_COLOR() { static QColor col (24, 64, 190); return col; } //ttt2 inconsistency: note is ref while vpNoteSet has vectors; OTOH comparison is not done by address; perhaps just document this; // color is normally the category color, but for support notes it's a "support" color; if the note isn't found in vpNoteSet, dGradStart and dGradEnd are set to -1, but normally they get a segment obtained by dividing [0, 1] in equal parts; void CommonData::getNoteColor(const Note& note, const vector<const Note*>& vpNoteSet, QColor& color, double& dGradStart, double& dGradEnd) const { LAST_STEP("CommonData::getNoteColor()"); dGradStart = -1; dGradEnd = -1; if (Note::TRACE == note.getSeverity()) { color = QColor(255, 255, 255); return; } //ttt2 use a system color /*if (Note::SUPPORT == note.getSeverity()) { color = QColor(235, 235, 255); } else*/ { color = m_vNoteCategColors[note.getCategory()]; } vector<const Note*>::const_iterator it (lower_bound(vpNoteSet.begin(), vpNoteSet.end(), ¬e, CmpNotePtrById())); if (vpNoteSet.end() == it) { CB_ASSERT (vpNoteSet.empty()); return; } int nPos (it - vpNoteSet.begin()); int nFirstInGroup (nPos), nLastInGroup (nPos); for (; nFirstInGroup > 0 && vpNoteSet[nFirstInGroup - 1]->getCategory() == note.getCategory(); --nFirstInGroup) {} for (; nLastInGroup < cSize(vpNoteSet) - 1 && vpNoteSet[nLastInGroup + 1]->getCategory() == note.getCategory(); ++nLastInGroup) {} nLastInGroup -= nFirstInGroup; nPos -= nFirstInGroup; //nFirstInGroup = 0; dGradStart = double(nPos)/(nLastInGroup + 1); dGradEnd = double(nPos + 1)/(nLastInGroup + 1); } QColor getDefaultBkgCol() { QColor c (QPalette().color(QPalette::Active, QPalette::Light)); if (isWhite(c)) { c = QPalette().color(QPalette::Active, QPalette::Window).lighter(110); } if (isWhite(c)) { c = QPalette().color(QPalette::Active, QPalette::Window).lighter(103); } if (isWhite(c)) { c = QPalette().color(QPalette::Active, QPalette::Window); } if (isWhite(c)) { c = QColor(253, 250, 240); } return c; } //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== /*void printFontInfo(const char* szLabel, const QFont& font) { QFontInfo info (font); cout << szLabel << ": " << info.family().toStdString() << ", exactMatch:" << info.exactMatch() << ", fixedPitch:" << info.fixedPitch() << ", italic:" << info.italic() << ", pixelSize:" << info.pixelSize() << ", pointSize:" << info.pointSize() << ", real pointSize on 100dpi screen:" << info.pixelSize()*72.0/100 << endl; } */ #if 0 const QFont& getFixedFont() { static QFont s_font; // ttt3 not multithreaded, but doesn't matter static bool s_bInit (false); if (!s_bInit) { s_bInit = true; //myOption.font = QFont("Courier", myOption.font.pointSize()); //myOption.font = QFont("B&H LucidaTypewritter", myOption.font.pointSize()); //myOption.font = QFont("LucidaTypewritter", myOption.font.pointSize()); //myOption.font = QFont("B&H LucidaTypewritter", 9); //myOption.font = QFont("Lucidatypewriter"); //myOption.font = QFont("LucidaTypewriter"); //myOption.font = QFont(); //myOption.font.setPixelSize(12); //myOption.font.setRawName("B&H LucidaTypewritter"); //myOption.font.setRawName("LucidaTypewritter"); //myOption.font.setRawName("Typewritter"); //myOption.font.setRawName("-b&h-lucidatypewritter-*-*-*-*-*-*-*-*-*-*-*-*"); //myOption.font.setRawName("-b&h-lucidatypewriter-medium-r-normal-sans-26-190-100-100-m-159-iso8859-1"); //myOption.font.setRawName("-b&h-lucidatypewriter-medium-r-normal-sans-12-120-75-75-m-70-iso8859-1"); //myOption.font.setStyleHint(QFont::TypeWriter); //myOption.font.fromString("B&H LucidaTypewriter,11,-1,5,50,0,0,0,0,0"); // myOption.font.setSize... // myOption.font.setFixedPitch(true); // ttt2 is there a difference between setFixedPitch(true) and setStyleHint(QFont::TypeWriter) ? /*myOption.font.setFamily("B&H LucidaTypewriter"); myOption.font.setPixelSize(12);*/ //printFontInfo("orig font: ", s_font); //s_font = QFont("B&H LucidaTypewriter"); s_font.setFamily("B&H LucidaTypewriter"); //s_font.setFamily("B&H LucidaTypewriter12c"); s_font.setFixedPitch(true); // !!! needed to select some fixed font in case there's no "B&H LucidaTypewriter" installed s_font.setPixelSize(12); //ttt2 hard-coded "12"; //s_font.setPointSize(9); //ttt2 hard-coded "9"; //QFont f; qDebug("default font: pix %d, point: %d, fam: %s", f.pixelSize(), f.pointSize(), f.family().toLatin1().constData()); //myOption.font.setPointSize(12); // printFontInfo("new font: ", s_font); //font: B&H LucidaTypewriter,11,-1,5,50,0,0,0,0,0 //print map // xfontsel // "mmmiiiWWWlll"; } return s_font; } #endif #ifdef OKPOJOIJWOUIh void tstFont() { /* bool b; QFont f (QFontDialog::getFont(&b, this)); //cout << "font: " << QFontInfo(myOption.font).family().toStdString() << endl; //cout << "font from dlg: " << f.toString().toStdString() << endl; printFontInfo("font from dlg", f); */ for (int i = 6; i < 18; ++i) { QFont f ("B&H LucidaTypewriter", i); char a [20]; sprintf(a, "point size %d", i); printFontInfo(a, f); } cout << endl; for (int i = 6; i < 18; ++i) { QFont f ("B&H LucidaTypewriter"); f.setPixelSize(i); char a [20]; sprintf(a, "pixel size %d", i); printFontInfo(a, f); } cout << endl; { bool b; QFont f (QFontDialog::getFont(&b)); printFontInfo("font from QFontDialog" , f); /* QFont f; if (QDialog::Accepted == KFontDialog::getFont(f, true)) { printFontInfo("font from QFontDialog" , f); }*/ } cout << endl; for (int i = 6; i < 18; ++i) { QFont f ("B&H LucidaTypewriter12c", i); char a [50]; sprintf(a, "B&H LucidaTypewriter12c point size %d", i); printFontInfo(a, f); } } #endif /*extern*/ const int CUSTOM_TRANSF_CNT (4); void Filter::setNotes(const std::vector<const Note*>& v) { m_vpNotes.clear(); for (int i = 0; i < cSize(v); ++i) { const Note* p (Notes::getMaster(v[i])); // 2009.03.23 currently v comes with pointers from Notes, so this is pointless; CB_ASSERT (0 != p); m_vpNotes.push_back(p); } //qDebug("set v sz %d", cSize(m_vpNotes)); m_bNoteFilter = !m_vpNotes.empty(); emit filterChanged(); } void Filter::setDirs(const std::vector<string>& v) { //if (v == m_vstrDirs) { return; } m_vstrDirs = v; m_bDirFilter = !m_vstrDirs.empty(); emit filterChanged(); } // !!! needed because we need m_vstrDirs next time we press the filter button, so we don't want to delete it with a "setDirs(vector<string>())", but just ignore it void Filter::disableDir() { if (!m_bDirFilter) { return; } m_bDirFilter = false; emit filterChanged(); } void Filter::disableNote() { if (!m_bNoteFilter) { return; } m_bNoteFilter = false; emit filterChanged(); } void Filter::disableAll() // saves m_bNoteFilter to m_bSavedNoteFilter and m_bDirFilter to m_bSavedDirFilter, then disables the filters { m_bSavedNoteFilter = m_bNoteFilter; m_bSavedDirFilter = m_bDirFilter; if (m_bNoteFilter || m_bDirFilter) { m_bNoteFilter = m_bDirFilter = false; emit filterChanged(); } } void Filter::restoreAll() // loads m_bNoteFilter from m_bSavedNoteFilter and m_bDirFilter from m_bSavedDirFilter, then enables the filters, if they are true { if (m_bNoteFilter != m_bSavedNoteFilter || m_bDirFilter != m_bSavedDirFilter) { m_bNoteFilter = m_bSavedNoteFilter; m_bDirFilter = m_bSavedDirFilter; emit filterChanged(); } } //======================================================================================================================== //======================================================================================================================== //======================================================================================================================== // called after config change or filter change or mergeHandlerChanges(), mainly to update unique notes (which is reflected in columns in the file grid and in lines in the unique notes list); also makes sure that something is displayed if there are any files in m_vpFltHandlers (e.g. if a transform was applied that removed all the files in an album, the next album gets loaded); // this is needed after transforms/normalization/tag editing, but there's no need for an explicit call, because all these call mergeHandlerChanges() (directly or through MainFormDlgImpl::scan()) void CommonData::updateWidgets(const std::string& strCrtName /* = ""*/, const std::vector<std::string>& vstrSel /* = std::vector<std::string>()*/) { CursorOverrider crs; //if (m_vpViewHandlers.empty() && !m_vpFltHandlers.empty()) // 2009.09.25 - not sure why the test was here, but its presence prevented the right song to be selected when returning from tag editing, if in album mode; see if this takes a long time; if so, perhaps the idea was to run this only if needed, but the test was too restrictive { CommonData::ViewMode eViewMode (getViewMode()); setViewMode(CommonData::ALL); int nPos (getPosInView(strCrtName)); // doesn't matter if it's empty const Mp3Handler* pMp3Handler (nPos >= 0 ? m_vpViewHandlers.at(nPos) : 0); setViewMode(eViewMode, pMp3Handler); } int nRow (-1); if (!strCrtName.empty()) { nRow = getPosInView(strCrtName); } if (-1 == nRow) { nRow = 0; } vector<int> v; for (int i = 0, n = cSize(vstrSel); i < n; ++i) { int k (getPosInView(vstrSel[i], EXACT_MATCH)); if (-1 != k) { v.push_back(k); } } updateUniqueNotes(); m_pFilesModel->selectRow(nRow, v); //??? m_pUniqueNotesModel->selectTopLeft(); resizeFilesGCols(); m_pFilesG->setFocus(); } // updates m_uniqueNotes to reflect the current m_vpAllHandlers and m_vpFltHandlers; void CommonData::updateUniqueNotes() { set<const Note*, CmpNotePtrById> spAllNotes; getUniqueNotes(m_vpAllHandlers, spAllNotes); set<const Note*, CmpNotePtrById> spFltNotes; getUniqueNotes(m_vpFltHandlers, spFltNotes); // ttt2 not sure if it makes more sense for m_uniqueNotes to show notes for what is filtered or for what is "current"; it feels like the list would change too often if it is going to reflect "current"; anyway, if the change is made, the code must be moved; m_uniqueNotes.clear(); // if there's no filtering (and sometimes even if there is a filter applied), spAllNotes and spFltNotes have the same elements; it's probably not worth the trouble to try and figure out if they should be equal or not m_uniqueNotes.addColl(spAllNotes); m_uniqueNotes.setFlt(spFltNotes); } // updates m_vpFltHandlers and m_vpViewHandlers; also updates the state of the filter buttons (deselecting them if the user chose empty filters) void CommonData::onFilterChanged() { const vector<string>& vstrSel (getSelNames()); string strCrtName (getCrtName()); m_vpFltHandlers.clear(); const vector<string>& vSelDirs (m_filter.getDirs()); const vector<const Note*>& vpSelNotes (m_filter.getNotes()); int nSelNotesSize (cSize(vpSelNotes)); CmpNotePtrById cmp; // !!! same comparator used by NoteColl::sort() bool bDirFilter (m_filter.isDirEnabled()); bool bNoteFilter (m_filter.isNoteEnabled()); for (int i = 0, n = cSize(m_vpAllHandlers); i < n; ++i) { const Mp3Handler* pHandler (m_vpAllHandlers[i]); if (bDirFilter) { vector<string>::const_iterator it (lower_bound(vSelDirs.begin(), vSelDirs.end(), pHandler->getName())); if (vSelDirs.begin() == it) { goto e1; } --it; //cout << *it << " " << pHandler->getName() << endl; if (!isInsideDir(pHandler->getName(), *it)) { goto e1; } } if (bNoteFilter) { const NoteColl& coll (pHandler->getNotes()); const vector<Note*>& vpCrtNotes (coll.getList()); int i (0); int j (0); int nCrtNotesSize (cSize(vpCrtNotes)); for (;;) { for (; i < nSelNotesSize && j < nCrtNotesSize && cmp(vpSelNotes[i], vpCrtNotes[j]); ++i) {} for (; i < nSelNotesSize && j < nCrtNotesSize && cmp(vpCrtNotes[j], vpSelNotes[i]); ++j) {} if (i >= nSelNotesSize || j >= nCrtNotesSize) { goto e1; } if (CmpNotePtrById::equals(vpSelNotes[i], vpCrtNotes[j])) { break; } } } m_vpFltHandlers.push_back(pHandler); e1:; } m_pNoteFilterB->setChecked(bNoteFilter); // !!! no guard needed, because the event that calls the filter is "clicked", not "checked" m_pDirFilterB->setChecked(bDirFilter); m_vpViewHandlers.clear(); updateWidgets(strCrtName, vstrSel); if (m_vpViewHandlers.empty() && (bNoteFilter || bDirFilter)) { m_filter.disableAll(); // it makse some sense to not disable the filter, e.g. after reload } } void CommonData::setCustomTransf(const std::vector<std::vector<int> >& vv) { CB_ASSERT (cSize(vv) == CUSTOM_TRANSF_CNT); //ttt2 assert elements are withing range m_vvnCustomTransf = vv; } void CommonData::setCustomTransf(int nTransf, const std::vector<int>& v) { CB_ASSERT (nTransf >= 0 && nTransf < CUSTOM_TRANSF_CNT); //ttt2 assert elements are withing range m_vvnCustomTransf[nTransf] = v; } void CommonData::setVisibleTransf(const std::vector<int>& v) { //ttt2 assert elements are withing range m_vnVisibleTransf = v; } void CommonData::setIgnoredNotes(const std::vector<int>& v) { const int NOTE_CNT (cSize(Notes::getAllNotes())); for (int i = 0, n = cSize(v); i < n; ++i) { CB_ASSERT (v[i] >= 0 && v[i] < NOTE_CNT); } set<int> s (v.begin(), v.end()); CB_ASSERT (cSize(s) == cSize(v)); m_vnIgnoredNotes.clear(); m_vnIgnoredNotes.insert(m_vnIgnoredNotes.end(), s.begin(), s.end()); } void CommonData::setQualThresholds(const QualThresholds& q) { // ttt2 some checks m_qualThresholds = q; } // looks in m_vpAllHandlers; returns 0 if there's no such handler const Mp3Handler* CommonData::getHandler(const std::string& strName) const { deque<const Mp3Handler*>::const_iterator it (lower_bound(m_vpViewHandlers.begin(), m_vpViewHandlers.end(), strName, CmpMp3HandlerPtrByName())); if (m_vpViewHandlers.end() == it || (*it)->getName() != strName) { return 0; } return *it; } // elements from vpAdd that are not "included" are discarded; when a new version of a handler is in vpAdd, the old version may be in vpDel, but this is optional; takes ownership of elements from vpAdd; deletes the pointers from vpDel; void CommonData::mergeHandlerChanges(const vector<const Mp3Handler*>& vpAdd1, const vector<const Mp3Handler*>& vpDel1, int nKeepWhenUpdate) { //m_bDirty = m_bDirty || !vpAdd1.empty() || !vpDel1.empty(); string strSongInCrtAlbum (m_nSongInCrtAlbum >= 0 && m_nSongInCrtAlbum < cSize(m_vpAllHandlers) ? m_vpAllHandlers[m_nSongInCrtAlbum]->getName() : ""); const string& strCrtName (nKeepWhenUpdate & CURRENT ? getCrtName() : ""); const vector<string>& vstrSel (nKeepWhenUpdate & SEL ? getSelNames() : vector<string>()); vector<const Mp3Handler*> vpAdd; for (int i = 0, n = cSize(vpAdd1); i < n; ++i) { if (m_dirTreeEnum.isIncluded(vpAdd1[i]->getName())) { vpAdd.push_back(vpAdd1[i]); } else { delete vpAdd1[i]; } } sort(vpAdd.begin(), vpAdd.end(), CmpMp3HandlerPtrByName()); // !!! the vector is probably sorted, but file renaming might have an impact on the order (and if it doesn't have now, it might in the future) vector<const Mp3Handler*> vpDel; { // we want in vpDel elements from vpDel1, as well as elements from m_vpAllHandlers for which there is a newer version in vpAdd vector<const Mp3Handler*> vpDel2 (vpDel1); sort(vpDel2.begin(), vpDel2.end(), CmpMp3HandlerPtrByName()); vector<const Mp3Handler*> vpNewVer; // existing handlers for which there is a new version set_intersection(m_vpAllHandlers.begin(), m_vpAllHandlers.end(), vpAdd.begin(), vpAdd.end(), back_inserter(vpNewVer), CmpMp3HandlerPtrByName()); // !!! according to the standard (25.3.5.3) copying is made from the first range (i.e. from m_vpAllHandlers) set_union(vpNewVer.begin(), vpNewVer.end(), vpDel2.begin(), vpDel2.end(), back_inserter(vpDel), CmpMp3HandlerPtrByName()); } deque<const Mp3Handler*> vpAll, vpFlt, vpView; /*qDebug("1a %d %d %d", cSize(m_vpAllHandlers), cSize(m_vpFltHandlers), cSize(m_vpViewHandlers)); qDebug("1b %d %d", cSize(vpDel), cSize(vpAdd)); qDebug("1c %d %d %d", cSize(vpAll), cSize(vpFlt), cSize(vpView));*/ set_difference(m_vpAllHandlers.begin(), m_vpAllHandlers.end(), vpDel.begin(), vpDel.end(), back_inserter(vpAll), CmpMp3HandlerPtrByName()); set_difference(m_vpFltHandlers.begin(), m_vpFltHandlers.end(), vpDel.begin(), vpDel.end(), back_inserter(vpFlt), CmpMp3HandlerPtrByName()); set_difference(m_vpViewHandlers.begin(), m_vpViewHandlers.end(), vpDel.begin(), vpDel.end(), back_inserter(vpView), CmpMp3HandlerPtrByName()); //qDebug("m_vpAllHandlers.z()=%d, m_vpFltHandlers.size()=%d, m_nSongInCrtAlbum=%d, vpDel.sz=%d, vpDel1.sz=%d, vpAdd1.sz=%d", cSize(m_vpAllHandlers), cSize(m_vpFltHandlers), m_nSongInCrtAlbum, cSize(vpDel), cSize(vpDel1), cSize(vpAdd1)); clearPtrContainer(vpDel); m_vpAllHandlers.clear(); m_vpFltHandlers.clear(); m_vpViewHandlers.clear(); set_union(vpAll.begin(), vpAll.end(), vpAdd.begin(), vpAdd.end(), back_inserter(m_vpAllHandlers), CmpMp3HandlerPtrByName()); set_union(vpFlt.begin(), vpFlt.end(), vpAdd.begin(), vpAdd.end(), back_inserter(m_vpFltHandlers), CmpMp3HandlerPtrByName()); // !!! perhaps for view and flt we should check the directory/filter, but it seems more important that the new handlers are seen, so they can be compared to the old ones //ttt2 review set_union(vpView.begin(), vpView.end(), vpAdd.begin(), vpAdd.end(), back_inserter(m_vpViewHandlers), CmpMp3HandlerPtrByName()); updateWidgets(strCrtName, vstrSel); if (!strSongInCrtAlbum.empty() && !m_vpFltHandlers.empty()) { deque<const Mp3Handler*>::const_iterator it (lower_bound(m_vpFltHandlers.begin(), m_vpFltHandlers.end(), strSongInCrtAlbum, CmpMp3HandlerPtrByName())); if (m_vpFltHandlers.end() == it) { --it; } it = lower_bound(m_vpAllHandlers.begin(), m_vpAllHandlers.end(), *it, CmpMp3HandlerPtrByName()); m_nSongInCrtAlbum = it - m_vpAllHandlers.begin(); } } void CommonData::setSongInCrtAlbum() { m_nSongInCrtAlbum = 0; if (m_vpAllHandlers.empty()) { return; } const Mp3Handler* p (getCrtMp3Handler()); CB_ASSERT (0 != p); deque<const Mp3Handler*>::iterator it (lower_bound(m_vpAllHandlers.begin(), m_vpAllHandlers.end(), p, CmpMp3HandlerPtrByName())); CB_ASSERT (m_vpAllHandlers.end() != it && (*it) == p); m_nSongInCrtAlbum = it - m_vpAllHandlers.begin(); } deque<const Mp3Handler*> CommonData::getCrtAlbum() const //ttt2 perhaps sort by track, if there's an ID3V2; anyway, this is only used by TagWriter and FileRenamer, and TagWriter does its own sorting (there might be an issue if calling getCrtAlbum() multiple times for the same album returns songs in different order because tracks changed between the calls) { deque<const Mp3Handler*> v; if (m_vpFltHandlers.empty()) { return v; } //CB_ASSERT (m_nSongInCrtAlbum >= 0 && m_nSongInCrtAlbum < cSize(m_vpAllHandlers)); // !!! incorrect assert; files migth have been deleted without updating m_nSongInCrtAlbum (well, one of the purposes of this function is to update m_nSongInCrtAlbum) CB_ASSERT (m_nSongInCrtAlbum >= 0); m_nSongInCrtAlbum = min(m_nSongInCrtAlbum, cSize(m_vpAllHandlers) - 1); string s (m_vpAllHandlers[m_nSongInCrtAlbum]->getDir()); int nFirst (m_nSongInCrtAlbum), nLast (m_nSongInCrtAlbum); for (; nFirst >= 0 && m_vpAllHandlers[nFirst]->getDir() == s; --nFirst) {} ++nFirst; for (int n = cSize(m_vpAllHandlers); nLast < n && m_vpAllHandlers[nLast]->getDir() == s; ++nLast) {} --nLast; m_nSongInCrtAlbum = nFirst; v.insert(v.end(), m_vpAllHandlers.begin() + nFirst, m_vpAllHandlers.begin() + nLast + 1); return v; } // used for album navigation in tag editor and file renamer bool CommonData::nextAlbum() const { if (m_vpFltHandlers.empty()) { return false; } CB_ASSERT (m_nSongInCrtAlbum >= 0 && m_nSongInCrtAlbum < cSize(m_vpAllHandlers)); string s (m_vpAllHandlers[m_nSongInCrtAlbum]->getDir()); //qDebug("m_vpAllHandlers.z()=%d, m_vpFltHandlers.size()=%d, m_nSongInCrtAlbum=%d", cSize(m_vpAllHandlers), cSize(m_vpFltHandlers), m_nSongInCrtAlbum); deque<const Mp3Handler*>::const_iterator it (lower_bound(m_vpFltHandlers.begin(), m_vpFltHandlers.end(), m_vpAllHandlers[m_nSongInCrtAlbum], CmpMp3HandlerPtrByName())); //CB_ASSERT (m_vpAllHandlers[m_nSongInCrtAlbum] == *it); // !!! wrong - getCrtAlbum() moves m_nSongInCrtAlbum to the first song in album regardless of filter CB_ASSERT (m_vpFltHandlers.end() != it); int n (cSize(m_vpFltHandlers)); int nFltPos (it - m_vpFltHandlers.begin()); for (; nFltPos < n && m_vpFltHandlers[nFltPos]->getDir() == s; ++nFltPos) {} if (n == nFltPos) { return false; } it = lower_bound(m_vpAllHandlers.begin(), m_vpAllHandlers.end(), m_vpFltHandlers[nFltPos], CmpMp3HandlerPtrByName()); CB_ASSERT (m_vpFltHandlers[nFltPos] == *it); m_nSongInCrtAlbum = it - m_vpAllHandlers.begin(); return true; } bool CommonData::prevAlbum() const { if (m_vpFltHandlers.empty()) { return false; } CB_ASSERT (m_nSongInCrtAlbum >= 0 && m_nSongInCrtAlbum < cSize(m_vpAllHandlers)); string s (m_vpAllHandlers[m_nSongInCrtAlbum]->getDir()); deque<const Mp3Handler*>::const_iterator it (lower_bound(m_vpFltHandlers.begin(), m_vpFltHandlers.end(), m_vpAllHandlers[m_nSongInCrtAlbum], CmpMp3HandlerPtrByName())); //CB_ASSERT (m_vpAllHandlers[m_nSongInCrtAlbum] == *it); // !!! wrong - getCrtAlbum() moves m_nSongInCrtAlbum to the first song in album regardless of filter CB_ASSERT (m_vpFltHandlers.end() != it); int nFltPos (it - m_vpFltHandlers.begin()); for (; nFltPos >= 0 && m_vpFltHandlers[nFltPos]->getDir() == s; --nFltPos) {} if (nFltPos < 0) { return false; } it = lower_bound(m_vpAllHandlers.begin(), m_vpAllHandlers.end(), m_vpFltHandlers[nFltPos], CmpMp3HandlerPtrByName()); CB_ASSERT (m_vpFltHandlers[nFltPos] == *it); m_nSongInCrtAlbum = it - m_vpAllHandlers.begin(); return true; } // resizes the first column to use all the available space; doesn't shrink it to less than 400px, though void CommonData::resizeFilesGCols() { QHeaderView* p (m_pFilesG->horizontalHeader()); p->setResizeMode(0, QHeaderView::Stretch); int n (p->sectionSize(0)); if (n < MIN_FILE_WIDTH) { //n = 600; n = MIN_FILE_WIDTH; //n = m_pFilesG->width() } p->setResizeMode(0, QHeaderView::Interactive); p->resizeSection(0, n); /*if (nSection >= 1 + 26*2) // ttt3 this is needed to handle more than 52 notes // ttt3 keep in mind that bold fonts need more space { for ... QFontMetrics fm (m_pCommonData->m_pFilesG->fontMetrics()); int nWidth (fm.width(getNoteLabel(nSection - 1))); //return QSize(50, CELL_HEIGHT); resizeSection(i, nWidth); }*/ } void CommonData::trace(const std::string& s) { if (m_bTraceEnabled) { m_vLogs.push_back(s); } } void CommonData::clearLog() { m_vLogs.clear(); } void setupTraceToFile(bool bEnable); void CommonData::setTraceToFile(bool bTraceToFile) // also removes the file { m_bTraceToFile = bTraceToFile; setupTraceToFile(bTraceToFile); } void CommonData::setFastSave(bool bFastSave, bool bUpdateTransforms) { if (m_bFastSave == bFastSave) { return; } m_bFastSave = bFastSave; if (!bUpdateTransforms) { return; } } /* KDE 3.5.7 / Qt 3.3.8 point size 6: B&H LucidaTypewriter [B&H], exactMatch:1, fixedPitch:1, italic:0, pixelSize:8, pointSize:6, real pointSize on 100dpi screen:5.76 point size 7: B&H LucidaTypewriter [B&H], exactMatch:1, fixedPitch:1, italic:0, pixelSize:10, pointSize:7, real pointSize on 100dpi screen:7.2 point size 8: B&H LucidaTypewriter [B&H], exactMatch:1, fixedPitch:1, italic:0, pixelSize:11, pointSize:8, real pointSize on 100dpi screen:7.92 point size 9: B&H LucidaTypewriter [B&H], exactMatch:0, fixedPitch:1, italic:0, pixelSize:14, pointSize:10, real pointSize on 100dpi screen:10.08 point size 10: B&H LucidaTypewriter [B&H], exactMatch:1, fixedPitch:1, italic:0, pixelSize:14, pointSize:10, real pointSize on 100dpi screen:10.08 point size 11: B&H LucidaTypewriter [B&H], exactMatch:0, fixedPitch:1, italic:0, pixelSize:14, pointSize:10, real pointSize on 100dpi screen:10.08 point size 12: B&H LucidaTypewriter [B&H], exactMatch:1, fixedPitch:1, italic:0, pixelSize:17, pointSize:12, real pointSize on 100dpi screen:12.24 point size 13: B&H LucidaTypewriter [B&H], exactMatch:1, fixedPitch:1, italic:0, pixelSize:18, pointSize:13, real pointSize on 100dpi screen:12.96 point size 14: B&H LucidaTypewriter [B&H], exactMatch:1, fixedPitch:1, italic:0, pixelSize:20, pointSize:14, real pointSize on 100dpi screen:14.4 point size 15: B&H LucidaTypewriter [B&H], exactMatch:0, fixedPitch:1, italic:0, pixelSize:20, pointSize:14, real pointSize on 100dpi screen:14.4 point size 16: B&H LucidaTypewriter [B&H], exactMatch:0, fixedPitch:1, italic:0, pixelSize:24, pointSize:17, real pointSize on 100dpi screen:17.28 point size 17: B&H LucidaTypewriter [B&H], exactMatch:1, fixedPitch:1, italic:0, pixelSize:24, pointSize:17, real pointSize on 100dpi screen:17.28 pixel size 6: B&H LucidaTypewriter [B&H], exactMatch:0, fixedPitch:1, italic:0, pixelSize:8, pointSize:6, real pointSize on 100dpi screen:5.76 pixel size 7: B&H LucidaTypewriter [B&H], exactMatch:0, fixedPitch:1, italic:0, pixelSize:8, pointSize:6, real pointSize on 100dpi screen:5.76 pixel size 8: B&H LucidaTypewriter [B&H], exactMatch:1, fixedPitch:1, italic:0, pixelSize:8, pointSize:6, real pointSize on 100dpi screen:5.76 pixel size 9: B&H LucidaTypewriter [B&H], exactMatch:0, fixedPitch:1, italic:0, pixelSize:8, pointSize:6, real pointSize on 100dpi screen:5.76 pixel size 10: B&H LucidaTypewriter [B&H], exactMatch:1, fixedPitch:1, italic:0, pixelSize:10, pointSize:7, real pointSize on 100dpi screen:7.2 pixel size 11: B&H LucidaTypewriter [B&H], exactMatch:1, fixedPitch:1, italic:0, pixelSize:11, pointSize:8, real pointSize on 100dpi screen:7.92 pixel size 12: B&H LucidaTypewriter [B&H], exactMatch:1, fixedPitch:1, italic:0, pixelSize:12, pointSize:9, real pointSize on 100dpi screen:8.64 pixel size 13: B&H LucidaTypewriter [B&H], exactMatch:0, fixedPitch:1, italic:0, pixelSize:14, pointSize:10, real pointSize on 100dpi screen:10.08 pixel size 14: B&H LucidaTypewriter [B&H], exactMatch:1, fixedPitch:1, italic:0, pixelSize:14, pointSize:10, real pointSize on 100dpi screen:10.08 pixel size 15: B&H LucidaTypewriter [B&H], exactMatch:0, fixedPitch:1, italic:0, pixelSize:14, pointSize:10, real pointSize on 100dpi screen:10.08 pixel size 16: B&H LucidaTypewriter [B&H], exactMatch:0, fixedPitch:1, italic:0, pixelSize:17, pointSize:12, real pointSize on 100dpi screen:12.24 pixel size 17: B&H LucidaTypewriter [B&H], exactMatch:1, fixedPitch:1, italic:0, pixelSize:17, pointSize:12, real pointSize on 100dpi screen:12.24 font from QFontDialog: B&H LucidaTypewriter [B&H], exactMatch:0, fixedPitch:1, italic:0, pixelSize:14, pointSize:10, real pointSize on 100dpi screen:10.08 issues: 1) when building by point size, the 14px (10.08 points) is selected over the 12px (8.64 points) to approximate the "9 points" font 2) the 12px font can be selected in QFontDialog as "12 points" (as shown in the preview) but when the call returns the 14px font is passed */ /* Qt 4.3.1 point size 6: B&H LucidaTypewriter, exactMatch:0, fixedPitch:1, italic:0, pixelSize:8, pointSize:8, real pointSize on 100dpi screen:5.76 point size 7: B&H LucidaTypewriter, exactMatch:0, fixedPitch:1, italic:0, pixelSize:10, pointSize:10, real pointSize on 100dpi screen:7.2 point size 8: B&H LucidaTypewriter, exactMatch:0, fixedPitch:1, italic:0, pixelSize:11, pointSize:8, real pointSize on 100dpi screen:7.92 point size 9: B&H LucidaTypewriter, exactMatch:0, fixedPitch:1, italic:0, pixelSize:12, pointSize:12, real pointSize on 100dpi screen:8.64 point size 10: B&H LucidaTypewriter, exactMatch:0, fixedPitch:1, italic:0, pixelSize:14, pointSize:14, real pointSize on 100dpi screen:10.08 point size 11: B&H LucidaTypewriter, exactMatch:0, fixedPitch:1, italic:0, pixelSize:14, pointSize:14, real pointSize on 100dpi screen:10.08 point size 12: B&H LucidaTypewriter, exactMatch:0, fixedPitch:1, italic:0, pixelSize:17, pointSize:12, real pointSize on 100dpi screen:12.24 point size 13: B&H LucidaTypewriter, exactMatch:0, fixedPitch:1, italic:0, pixelSize:18, pointSize:18, real pointSize on 100dpi screen:12.96 point size 14: B&H LucidaTypewriter, exactMatch:0, fixedPitch:1, italic:0, pixelSize:20, pointSize:14, real pointSize on 100dpi screen:14.4 point size 15: B&H LucidaTypewriter, exactMatch:0, fixedPitch:1, italic:0, pixelSize:20, pointSize:14, real pointSize on 100dpi screen:14.4 point size 16: B&H LucidaTypewriter, exactMatch:0, fixedPitch:1, italic:0, pixelSize:24, pointSize:24, real pointSize on 100dpi screen:17.28 point size 17: B&H LucidaTypewriter, exactMatch:0, fixedPitch:1, italic:0, pixelSize:24, pointSize:24, real pointSize on 100dpi screen:17.28 pixel size 6: B&H LucidaTypewriter, exactMatch:0, fixedPitch:1, italic:0, pixelSize:8, pointSize:8, real pointSize on 100dpi screen:5.76 pixel size 7: B&H LucidaTypewriter, exactMatch:0, fixedPitch:1, italic:0, pixelSize:8, pointSize:8, real pointSize on 100dpi screen:5.76 pixel size 8: B&H LucidaTypewriter, exactMatch:1, fixedPitch:1, italic:0, pixelSize:8, pointSize:8, real pointSize on 100dpi screen:5.76 pixel size 9: B&H LucidaTypewriter, exactMatch:0, fixedPitch:1, italic:0, pixelSize:8, pointSize:8, real pointSize on 100dpi screen:5.76 pixel size 10: B&H LucidaTypewriter, exactMatch:1, fixedPitch:1, italic:0, pixelSize:10, pointSize:10, real pointSize on 100dpi screen:7.2 pixel size 11: B&H LucidaTypewriter, exactMatch:1, fixedPitch:1, italic:0, pixelSize:11, pointSize:8, real pointSize on 100dpi screen:7.92 pixel size 12: B&H LucidaTypewriter, exactMatch:1, fixedPitch:1, italic:0, pixelSize:12, pointSize:12, real pointSize on 100dpi screen:8.64 pixel size 13: B&H LucidaTypewriter, exactMatch:0, fixedPitch:1, italic:0, pixelSize:12, pointSize:12, real pointSize on 100dpi screen:8.64 pixel size 14: B&H LucidaTypewriter, exactMatch:1, fixedPitch:1, italic:0, pixelSize:14, pointSize:14, real pointSize on 100dpi screen:10.08 pixel size 15: B&H LucidaTypewriter, exactMatch:0, fixedPitch:1, italic:0, pixelSize:14, pointSize:14, real pointSize on 100dpi screen:10.08 pixel size 16: B&H LucidaTypewriter, exactMatch:0, fixedPitch:1, italic:0, pixelSize:17, pointSize:12, real pointSize on 100dpi screen:12.24 pixel size 17: B&H LucidaTypewriter, exactMatch:1, fixedPitch:1, italic:0, pixelSize:17, pointSize:12, real pointSize on 100dpi screen:12.24 font from QFontDialog: B&H LucidaTypewriter, exactMatch:0, fixedPitch:1, italic:0, pixelSize:14, pointSize:14, real pointSize on 100dpi screen:10.08 issues: 1) while building a font with a particular point size works OK, the value returned by pointSize() is wrong for fonts with 75dpi resolution 2) the smallest "B&H LucidaTypewriter" font that can be selected in the dialog is 14px */ /* Qt 4.4.0 (KDE 4.1): point size 6: B&H LucidaTypewriter, exactMatch:0, fixedPitch:1, italic:0, pixelSize:8, pointSize:8, real pointSize on 100dpi screen:5.76 point size 7: B&H LucidaTypewriter, exactMatch:0, fixedPitch:1, italic:0, pixelSize:10, pointSize:10, real pointSize on 100dpi screen:7.2 point size 8: B&H LucidaTypewriter, exactMatch:0, fixedPitch:1, italic:0, pixelSize:11, pointSize:8, real pointSize on 100dpi screen:7.92 point size 9: B&H LucidaTypewriter, exactMatch:0, fixedPitch:1, italic:0, pixelSize:14, pointSize:10, real pointSize on 100dpi screen:10.08 point size 10: B&H LucidaTypewriter, exactMatch:0, fixedPitch:1, italic:0, pixelSize:14, pointSize:10, real pointSize on 100dpi screen:10.08 point size 11: B&H LucidaTypewriter, exactMatch:0, fixedPitch:1, italic:0, pixelSize:14, pointSize:10, real pointSize on 100dpi screen:10.08 point size 12: B&H LucidaTypewriter, exactMatch:0, fixedPitch:1, italic:0, pixelSize:17, pointSize:12, real pointSize on 100dpi screen:12.24 point size 13: B&H LucidaTypewriter, exactMatch:0, fixedPitch:1, italic:0, pixelSize:18, pointSize:18, real pointSize on 100dpi screen:12.96 point size 14: B&H LucidaTypewriter, exactMatch:0, fixedPitch:1, italic:0, pixelSize:20, pointSize:14, real pointSize on 100dpi screen:14.4 point size 15: B&H LucidaTypewriter, exactMatch:0, fixedPitch:1, italic:0, pixelSize:20, pointSize:14, real pointSize on 100dpi screen:14.4 point size 16: B&H LucidaTypewriter, exactMatch:0, fixedPitch:1, italic:0, pixelSize:20, pointSize:14, real pointSize on 100dpi screen:14.4 point size 17: B&H LucidaTypewriter, exactMatch:0, fixedPitch:1, italic:0, pixelSize:24, pointSize:24, real pointSize on 100dpi screen:17.28 pixel size 6: B&H LucidaTypewriter, exactMatch:0, fixedPitch:1, italic:0, pixelSize:8, pointSize:8, real pointSize on 100dpi screen:5.76 pixel size 7: B&H LucidaTypewriter, exactMatch:0, fixedPitch:1, italic:0, pixelSize:8, pointSize:8, real pointSize on 100dpi screen:5.76 pixel size 8: B&H LucidaTypewriter, exactMatch:1, fixedPitch:1, italic:0, pixelSize:8, pointSize:8, real pointSize on 100dpi screen:5.76 pixel size 9: B&H LucidaTypewriter, exactMatch:0, fixedPitch:1, italic:0, pixelSize:8, pointSize:8, real pointSize on 100dpi screen:5.76 pixel size 10: B&H LucidaTypewriter, exactMatch:1, fixedPitch:1, italic:0, pixelSize:10, pointSize:10, real pointSize on 100dpi screen:7.2 pixel size 11: B&H LucidaTypewriter, exactMatch:1, fixedPitch:1, italic:0, pixelSize:11, pointSize:8, real pointSize on 100dpi screen:7.92 pixel size 12: B&H LucidaTypewriter, exactMatch:1, fixedPitch:1, italic:0, pixelSize:12, pointSize:12, real pointSize on 100dpi screen:8.64 pixel size 13: B&H LucidaTypewriter, exactMatch:0, fixedPitch:1, italic:0, pixelSize:14, pointSize:10, real pointSize on 100dpi screen:10.08 pixel size 14: B&H LucidaTypewriter, exactMatch:1, fixedPitch:1, italic:0, pixelSize:14, pointSize:10, real pointSize on 100dpi screen:10.08 pixel size 15: B&H LucidaTypewriter, exactMatch:0, fixedPitch:1, italic:0, pixelSize:14, pointSize:10, real pointSize on 100dpi screen:10.08 pixel size 16: B&H LucidaTypewriter, exactMatch:0, fixedPitch:1, italic:0, pixelSize:17, pointSize:12, real pointSize on 100dpi screen:12.24 pixel size 17: B&H LucidaTypewriter, exactMatch:1, fixedPitch:1, italic:0, pixelSize:17, pointSize:12, real pointSize on 100dpi screen:12.24 font from QFontDialog: B&H LucidaTypewriter, exactMatch:0, fixedPitch:1, italic:0, pixelSize:14, pointSize:10, real pointSize on 100dpi screen:10.08 issues: 1) when building by point size, the 14px (10.08 points) is selected over the 12px (8.64 points) to approximate the "9 points" font; this used to work in 4.3.1 2) the 12px font can't be selected at all in QFontDialog 3) in the dialog both smaller and larger fonts can be selected, but the 12px never shows up */ ��������������������������������������������������������������������������������������������������������������MP3Diags-1.2.02/src/ImageInfoPanel.ui���������������������������������������������������������������0000644�0001750�0000144�00000014660�11700345261�016121� 0����������������������������������������������������������������������������������������������������ustar �ciobi���������������������������users������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>ImageInfoPanelWdg</class> <widget class="QFrame" name="ImageInfoPanelWdg"> <property name="geometry"> <rect> <x>0</x> <y>0</y> <width>118</width> <height>124</height> </rect> </property> <property name="sizePolicy"> <sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding"> <horstretch>0</horstretch> <verstretch>0</verstretch> </sizepolicy> </property> <property name="windowTitle"> <string>Form</string> </property> <property name="autoFillBackground"> <bool>true</bool> </property> <property name="frameShape"> <enum>QFrame::Panel</enum> </property> <property name="frameShadow"> <enum>QFrame::Raised</enum> </property> <layout class="QHBoxLayout"> <property name="leftMargin"> <number>4</number> </property> <property name="topMargin"> <number>3</number> </property> <property name="rightMargin"> <number>4</number> </property> <property name="bottomMargin"> <number>4</number> </property> <item> <widget class="QLabel" name="m_pThumbL"> <property name="text"> <string>Thumb</string> </property> </widget> </item> <item> <widget class="QWidget" name="m_pRightSideW" native="true"> <layout class="QVBoxLayout"> <property name="spacing"> <number>0</number> </property> <property name="margin"> <number>0</number> </property> <item> <widget class="QLabel" name="m_pPosL"> <property name="text"> <string>Pos</string> </property> </widget> </item> <item> <widget class="QLabel" name="m_pDimL"> <property name="text"> <string>Dim</string> </property> </widget> </item> <item> <widget class="QLabel" name="m_pSizeL"> <property name="text"> <string>Size</string> </property> </widget> </item> <item> <spacer> <property name="orientation"> <enum>Qt::Vertical</enum> </property> <property name="sizeHint" stdset="0"> <size> <width>49</width> <height>3</height> </size> </property> </spacer> </item> <item> <widget class="QToolButton" name="m_pFullB"> <property name="minimumSize"> <size> <width>28</width> <height>28</height> </size> </property> <property name="maximumSize"> <size> <width>28</width> <height>28</height> </size> </property> <property name="toolTip"> <string>View full-size image</string> </property> <property name="text"> <string>...</string> </property> <property name="icon"> <iconset resource="Mp3Diags.qrc"> <normaloff>:/images/zoom-in.png</normaloff>:/images/zoom-in.png</iconset> </property> <property name="iconSize"> <size> <width>22</width> <height>22</height> </size> </property> <property name="autoRaise"> <bool>true</bool> </property> </widget> </item> <item> <widget class="QWidget" name="widget" native="true"> <property name="minimumSize"> <size> <width>0</width> <height>30</height> </size> </property> <layout class="QHBoxLayout"> <property name="spacing"> <number>0</number> </property> <property name="margin"> <number>0</number> </property> <item> <widget class="QToolButton" name="m_pAssignB"> <property name="minimumSize"> <size> <width>28</width> <height>28</height> </size> </property> <property name="maximumSize"> <size> <width>28</width> <height>28</height> </size> </property> <property name="toolTip"> <string>Assign picture to songs</string> </property> <property name="text"> <string>...</string> </property> <property name="icon"> <iconset resource="Mp3Diags.qrc"> <normaloff>:/images/assign.png</normaloff>:/images/assign.png</iconset> </property> <property name="iconSize"> <size> <width>22</width> <height>22</height> </size> </property> <property name="autoRaise"> <bool>true</bool> </property> </widget> </item> <item> <widget class="QToolButton" name="m_pEraseB"> <property name="minimumSize"> <size> <width>28</width> <height>28</height> </size> </property> <property name="maximumSize"> <size> <width>28</width> <height>28</height> </size> </property> <property name="toolTip"> <string>Erase local file</string> </property> <property name="text"> <string>...</string> </property> <property name="icon"> <iconset resource="Mp3Diags.qrc"> <normaloff>:/images/remove_file.png</normaloff>:/images/remove_file.png</iconset> </property> <property name="iconSize"> <size> <width>22</width> <height>22</height> </size> </property> <property name="autoRaise"> <bool>true</bool> </property> </widget> </item> <item> <spacer> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> <property name="sizeType"> <enum>QSizePolicy::MinimumExpanding</enum> </property> <property name="sizeHint" stdset="0"> <size> <width>1</width> <height>20</height> </size> </property> </spacer> </item> </layout> </widget> </item> </layout> </widget> </item> </layout> </widget> <resources> <include location="Mp3Diags.qrc"/> </resources> <connections/> </ui> ��������������������������������������������������������������������������������MP3Diags-1.2.02/src/PaletteDlgImpl.h����������������������������������������������������������������0000644�0001750�0000144�00000005103�11230111215�015740� 0����������������������������������������������������������������������������������������������������ustar �ciobi���������������������������users������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef PaletteDlgImplH #define PaletteDlgImplH #include <vector> #include <QDialog> #include "ui_Palette.h" #include "CommonData.h" class PaletteDlgImpl : public QDialog, private Ui::PaletteDlg { Q_OBJECT std::vector<QToolButton*> m_vpColButtons; void setBtnColor(int n); void onButtonClicked(int n); CommonData* m_pCommonData; public: PaletteDlgImpl(CommonData* pCommonData, QWidget* pParent); ~PaletteDlgImpl(); /*$PUBLIC_FUNCTIONS$*/ public slots: /*$PUBLIC_SLOTS$*/ protected: /*$PROTECTED_FUNCTIONS$*/ protected slots: /*$PROTECTED_SLOTS$*/ void on_m_pOkB_clicked(); void on_m_pCol0B_clicked() { onButtonClicked(0); } void on_m_pCol1B_clicked() { onButtonClicked(1); } void on_m_pCol2B_clicked() { onButtonClicked(2); } void on_m_pCol3B_clicked() { onButtonClicked(3); } void on_m_pCol4B_clicked() { onButtonClicked(4); } void on_m_pCol5B_clicked() { onButtonClicked(5); } void on_m_pCol6B_clicked() { onButtonClicked(6); } void onHelp(); }; #endif // #ifndef PaletteDlgImplH �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������MP3Diags-1.2.02/src/SerSupport.cpp������������������������������������������������������������������0000644�0001750�0000144�00000023475�11724662240�015607� 0����������������������������������������������������������������������������������������������������ustar �ciobi���������������������������users������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "CommonData.h" #include "SerSupport.h" //#include <boost/archive/binary_oarchive.hpp> //#include <boost/archive/binary_iarchive.hpp> //#include <boost/archive/xml_oarchive.hpp> #include <boost/serialization/vector.hpp> //#include "fstream_unicode.h" #include <boost/serialization/deque.hpp> //#include <boost/serialization/split_member.hpp> //#include <boost/serialization/split_free.hpp> // headers needed only for serialization: #include "Id3V2Stream.h" #include "Id3V230Stream.h" #include "Id3V240Stream.h" #include "ApeStream.h" #include "MpegStream.h" #include "LyricsStream.h" // serialization for std::streampos // ttt2 perhaps put this in SerSupport.h, but it doesn't matter much; it needs to be visible only when instantiating the templates namespace boost { namespace serialization { //ttt2 see if a newer version of Boost handles std::streampos template<class Archive> inline void save(Archive& ar, const std::streampos& pos, const unsigned int /*nVersion*/) { long long n (pos); // ttt2 this conversion isn't quite right, but it's OK for MP3 files ar << n; } template<class Archive> inline void load(Archive& ar, std::streampos& pos, const unsigned int /*nVersion*/) { long long n; ar >> n; pos = n; } } // namespace serialization } // namespace boost BOOST_SERIALIZATION_SPLIT_FREE(std::streampos); using namespace std; using namespace pearl; #if 0 #if 1 // serialization for std::streampos namespace boost { namespace serialization { //ttt2 see if a newer version of Boost handles std::streampos template<class Archive> inline void save(Archive& ar, const std::streampos& pos, const unsigned int /*nVersion*/) { long long n (pos); // ttt2 this conversion isn't quite right, but it's OK for MP3 files ar << n; } template<class Archive> inline void load(Archive& ar, std::streampos& pos, const unsigned int /*nVersion*/) { long long n; ar >> n; pos = n; } } // namespace serialization } // namespace boost BOOST_SERIALIZATION_SPLIT_FREE(std::streampos); //BOOST_SERIALIZATION_SPLIT_FREE(std::fpos<__mbstate_t>); #endif struct MyClass { int a; }; struct MyClass2 { int b; template<class Archive> void save(Archive& ar, const unsigned int ) const { // note, version is always the latest when saving ar & b; } template<class Archive> void load(Archive& ar, const unsigned int ) { ar & b; } BOOST_SERIALIZATION_SPLIT_MEMBER(); }; namespace boost { namespace serialization { template<class Archive> inline void save(Archive& ar, const MyClass& x, const unsigned int /*nVersion*/) { long long n (x.a); // ttt2 this conversion isn't quite right, but it's OK for MP3 files ar << n; } template<class Archive> inline void load(Archive& ar, MyClass& x, const unsigned int /*nVersion*/) { long long n; ar >> n; x.a = n; } //BOOST_SERIALIZATION_SPLIT_FREE(std::streampos); } // namespace serialization } // namespace boost BOOST_SERIALIZATION_SPLIT_FREE(MyClass); namespace boost { namespace serialization { /*template<class Archive> void serialize1(Archive& ar, MyClass& x, const unsigned int version) { ar & x.a; }*/ template<class Archive> void serialize1(Archive& ar, MyClass& x, const unsigned int version) { split_free(ar, x, version); } BOOST_SERIALIZATION_SPLIT_FREE(MyClass); } // namespace serialization } #if 1 #include "fstream_unicode.h" struct BoostTstBase01 { BoostTstBase01(int n1) : m_n1(n1) {} virtual ~BoostTstBase01() { } int m_n1; std::streampos m_pos; MyClass m; MyClass2 m2; protected: BoostTstBase01() {} private: friend class boost::serialization::access; template<class Archive> void serialize(Archive& ar, const unsigned int /*nVersion*/) { ar & m_n1; ar & m_pos; ar & m; ar & m2; } }; void qwrqwrqsrq() { ifstream_utf8 in ("qqq"); boost::archive::text_iarchive iar (in); //boost::archive::text_iarchive iar (in); //iar >> m_vpAllHandlers; BoostTstBase01* p; iar >> p; } #endif #endif #define REGISTER_SER_TYPES0(ar) #define REGISTER_SER_TYPES(ar) \ { \ ar.register_type<Id3V230Frame>(); \ ar.register_type<Id3V240Frame>(); \ ar.register_type<ApeItem>(); \ \ ar.register_type<ApeStream>(); \ ar.register_type<Id3V1Stream>(); \ ar.register_type<Id3V230Stream>(); \ ar.register_type<Id3V240Stream>(); \ ar.register_type<LyricsStream>(); \ ar.register_type<MpegStream>(); \ ar.register_type<VbriStream>(); \ ar.register_type<XingStreamBase>(); \ ar.register_type<LameStream>(); \ ar.register_type<XingStream>(); \ ar.register_type<NullDataStream>(); \ ar.register_type<BrokenDataStream>(); \ ar.register_type<TruncatedMpegDataStream>(); \ ar.register_type<UnknownDataStream>(); \ ar.register_type<UnsupportedDataStream>(); \ \ ar.register_type<Mp3Handler>(); \ } /* */ /* ar.register_type<Id3V2Frame>(); \ ar.register_type<DataStream>(); \ ar.register_type<MpegStreamBase>(); \ ar.register_type<Id3V2StreamBase>(); \ ar.register_type<SimpleDataStream>(); \ ar.register_type<UnknownDataStreamBase>(); \ */ template<class Archive> void CommonData::save(Archive& ar, const unsigned int nVersion) const { if (nVersion > 1) { throw std::runtime_error("invalid version of serialized file"); } int n1 (10); ar << n1; ar << getCrtName(); int n2 (20); ar << n2; ar << (const deque<const Mp3Handler*>&)m_vpAllHandlers; ar << (const Filter&)m_filter; ar << m_eViewMode; //qDebug("save name %s", getCrtName().c_str()); } template<class Archive> void CommonData::load(Archive& ar, const unsigned int nVersion) { if (nVersion > 1) { throw std::runtime_error("invalid version of serialized file"); } int n1 (100); ar >> n1; //string strCrtName; ar >> m_strLoadCrtName; int n2 (100); ar >> n2; deque<Mp3Handler*> v; ar >> v; CB_ASSERT (m_vpAllHandlers.empty()); for (int i = 0; i < cSize(v); ++i) { v[i]->sortNotes(); // !!! ususally not needed, but there's an exception: when loading an older version, referencing notes that have been removed; they get replaced by Notes::getMissingNote(), which comes first in sorting by CmpNotePtrById; probably not worth the trouble of figuring if it's really needed or not; } m_vpAllHandlers.insert(m_vpAllHandlers.end(), v.begin(), v.end()); m_vpFltHandlers = m_vpAllHandlers; m_vpViewHandlers = m_vpAllHandlers; //qDebug("load name %s", strCrtName.c_str()); ar >> m_filter; if (nVersion >= 1) { ar >> m_eViewMode; } //updateWidgets(m_strLoadCrtName); } BOOST_CLASS_VERSION(CommonData, 1); // returns an error message (or empty string if there's no error) string CommonData::save(const string& strFile) const { try { ofstream_utf8 out (strFile.c_str()); if (!out) { return "Cannot open file \"" + strFile + "\""; } //ttt1 maybe translate, but it makes less sense to do the same in load() //boost::archive::binary_oarchive oar (out); boost::archive::text_oarchive oar (out); //boost::archive::xml_oarchive oar (out); // ttt3 would be nice to have this as an option, but doesn't seem possible unless we switch everything to XML, because while binary_oarchive can be used as a replacement for text_oarchive, xml_oarchive can't; the serialized classes need to be modified: http://www.boost.org/doc/libs/1_40_0/libs/serialization/doc/wrappers.html#nvp REGISTER_SER_TYPES(oar); oar << *this; } catch (const std::exception& ex) //ttt2 not sure if this is the way to catch errors { return ex.what(); } return ""; } //#include <iostream> // returns an error message (or empty string if there's no error) string CommonData::load(const string& strFile) { try { ifstream_utf8 in (strFile.c_str()); if (!in) { return ""; } // !!! "no data" is not an error //boost::archive::binary_iarchive iar (in); boost::archive::text_iarchive iar (in); REGISTER_SER_TYPES(iar); iar >> *this; } //catch (const boost::archive::archive_exception&) catch (const std::exception& ex) { return ex.what(); } catch (...) { return "err"; } Notes::clearDestroyList(); return ""; } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������MP3Diags-1.2.02/src/DiscogsDownloader.cpp�����������������������������������������������������������0000444�0001750�0000144�00000072716�12361673305�017075� 0����������������������������������������������������������������������������������������������������ustar �ciobi���������������������������users������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include <map> #include <QHttp> #include "DiscogsDownloader.h" #include "Helpers.h" #include "SimpleSaxHandler.h" #include "StoredSettings.h" ////#include <iostream> //ttt remove using namespace std; using namespace pearl; using namespace Discogs; /* * 2014.07.17 * * Old search endpoint: http://api.discogs.com/search?q=Rammstein&f=xml * - deprecated * - supposed to be removed on August 22, 2014 according to https://www.discogs.com/developers/resources/database/search-endpoint.html but on 2014.07.17 just returned 404 * * New search endpoint: http://api.discogs.com/database/search?q=Rammstein * - doesn't support XML, never will; * - accepts "f=xml" param but ignores it * * http://www.discogs.com/forum/thread/52150d6f94697336111ca17d * I think you're confusing /search with /database/search. * The former is the old search endpoint. It works as it always has and will continue to do so for the foreseeable future. * While it is deprecated, we haven't announced a shutoff date for it -- the "deprecated" flag just means there are better ways to get the data and this endpoint may someday go away. * The latter is the new search endpoint. It has more accurate search results, better sorting and filtering, better pagination, contains IDs and API URLs for each object in the results, and only supports JSON. */ ostream& operator<<(ostream& out, const DiscogsAlbumInfo& inf) { out << "id: \"" << inf.m_strId << "\", artist: \"" << inf.m_strArtist << "\", title: \"" << inf.m_strTitle << "\", composer: \"" << inf.m_strComposer << "\", format: \"" << inf.m_strFormat << "\", genre: \"" << inf.m_strGenre << "\", style: \"" << inf.m_strStyle << "\", released: \"" << inf.m_strReleased << "\"\n\nnotes: " << inf.m_strNotes << "\n\nimages:\n"; for (int i = 0, n = cSize(inf.m_vstrImageNames); i < n; ++i) { out << inf.m_vstrImageNames[i] << endl; } out << "\ntracks:" << endl; for (int i = 0, n = cSize(inf.m_vTracks); i < n; ++i) { out << "pos: \"" << inf.m_vTracks[i].m_strPos << "\", artist: \"" << inf.m_vTracks[i].m_strArtist << "\", title: \"" << inf.m_vTracks[i].m_strTitle << "\", composer: \"" << inf.m_vTracks[i].m_strComposer << "\"" << endl; } return out; } namespace Discogs { /* resp exactresults result uri searchresults result uri */ struct SearchXmlHandler : public SimpleSaxHandler<SearchXmlHandler> { SearchXmlHandler(DiscogsDownloader& dlg) : SimpleSaxHandler<SearchXmlHandler>("resp"), m_dlg(dlg), m_bIsRelease(false) { Node& resp (getRoot()); Node& exactResults (makeNode(resp, "exactresults")); Node& excResult (makeNode(exactResults, "result")); excResult.onStart = &SearchXmlHandler::onResultStart; Node& excUri (makeNode(excResult, "uri")); excUri.onChar = &SearchXmlHandler::onUriChar; Node& searchResults (makeNode(resp, "searchresults")); searchResults.onStart = &SearchXmlHandler::onSearchResultsStart; Node& srchResult (makeNode(searchResults, "result")); srchResult.onStart = &SearchXmlHandler::onResultStart; Node& srchUri (makeNode(srchResult, "uri")); srchUri.onChar = &SearchXmlHandler::onUriChar; } private: DiscogsDownloader& m_dlg; bool m_bIsRelease; void onSearchResultsStart(const QXmlAttributes& attrs) { int nStart (attrs.value("start").toInt()); int nEnd (attrs.value("end").toInt()); int nCount (attrs.value("numResults").toInt()); if (nStart == 1) { m_dlg.m_nPageSize = nEnd; } CB_ASSERT (m_dlg.m_nPageSize > 0); CB_ASSERT (0 == nEnd % m_dlg.m_nPageSize || nEnd == nCount); //ttt2 see if this needs to be more flexible int nPageCnt ((nCount + m_dlg.m_nPageSize - 1) / m_dlg.m_nPageSize); int nCrtPage ((nStart - 1)/m_dlg.m_nPageSize); if (0 == nPageCnt) { nCrtPage = -1; } // !!! needed for consistency m_dlg.m_nTotalPages = nPageCnt; m_dlg.m_nLastLoadedPage = nCrtPage; //cout << "total pages: " << nPageCnt << ", crt: " << nCrtPage << endl; } void onResultStart(const QXmlAttributes& attrs) { m_bIsRelease = "release" == attrs.value("type"); if (m_bIsRelease) { m_dlg.m_vAlbums.push_back(DiscogsAlbumInfo(&m_dlg.m_eStyleOption)); } } void onUriChar(const string& s) { if (m_bIsRelease) { string::size_type n (s.rfind("/")); CB_ASSERT (string::npos != n); //string s1 (convStr(qstrCh.right(qstrCh.size() - n - 1))); string s1 (s.substr(n + 1)); m_dlg.m_vAlbums.back().m_strId = s1; //cout << s << endl; } } }; /* resp release images image artists artist name title extraartists artist name role tracks formats format genres genre styles style released notes tracklist track position artists artist name title extraartists artist name role */ struct AlbumXmlHandler : public SimpleSaxHandler<AlbumXmlHandler> { AlbumXmlHandler(DiscogsAlbumInfo& albumInfo) : SimpleSaxHandler<AlbumXmlHandler>("resp"), m_albumInfo(albumInfo) { Node& resp (getRoot()); resp.onEnd = &AlbumXmlHandler::onRespEnd; Node& rel (makeNode(resp, "release")); rel.onStart = &AlbumXmlHandler::onReleaseStart; Node& images (makeNode(rel, "images")); Node& image (makeNode(images, "image")); image.onStart = &AlbumXmlHandler::onImageStart; Node& albArtists (makeNode(rel, "artists")); Node& albArtist (makeNode(albArtists, "artist")); Node& albArtistName (makeNode(albArtist, "name")); albArtistName.onChar = &AlbumXmlHandler::onAlbArtistNameChar; Node& albTitle (makeNode(rel, "title")); albTitle.onChar = &AlbumXmlHandler::onAlbTitleChar; Node& albExtraArtists (makeNode(rel, "extraartists")); Node& albExtraArtist (makeNode(albExtraArtists, "artist")); albExtraArtist.onStart = &AlbumXmlHandler::onAlbExtraArtistStart; albExtraArtist.onEnd = &AlbumXmlHandler::onAlbExtraArtistEnd; Node& albExtraArtistName (makeNode(albExtraArtist, "name")); albExtraArtistName.onChar = &AlbumXmlHandler::onExtraArtistNameChar; Node& albExtraArtistRole (makeNode(albExtraArtist, "role")); albExtraArtistRole.onChar = &AlbumXmlHandler::onExtraArtistRoleChar; Node& albExtraArtistTracks (makeNode(albExtraArtist, "tracks")); albExtraArtistTracks.onChar = &AlbumXmlHandler::onExtraArtistTracksChar; Node& formats (makeNode(rel, "formats")); Node& format (makeNode(formats, "format")); format.onStart = &AlbumXmlHandler::onFormatStart; Node& genres (makeNode(rel, "genres")); Node& genre (makeNode(genres, "genre")); genre.onChar = &AlbumXmlHandler::onGenreChar; Node& styles (makeNode(rel, "styles")); Node& style (makeNode(styles, "style")); style.onChar = &AlbumXmlHandler::onStyleChar; Node& released (makeNode(rel, "released")); released.onChar = &AlbumXmlHandler::onReleasedChar; Node& notes (makeNode(rel, "notes")); notes.onChar = &AlbumXmlHandler::onNotesChar; Node& tracklist (makeNode(rel, "tracklist")); Node& track (makeNode(tracklist, "track")); track.onStart = &AlbumXmlHandler::onTrackStart; Node& position (makeNode(track, "position")); position.onChar = &AlbumXmlHandler::onPositionChar; Node& trkArtists (makeNode(track, "artists")); Node& trkArtist (makeNode(trkArtists, "artist")); Node& trkArtistName (makeNode(trkArtist, "name")); trkArtistName.onChar = &AlbumXmlHandler::onTrkArtistNameChar; Node& trackTitle (makeNode(track, "title")); trackTitle.onChar = &AlbumXmlHandler::onTrackTitleChar; Node& trkExtraArtists (makeNode(track, "extraartists")); Node& trkExtraArtist (makeNode(trkExtraArtists, "artist")); trkExtraArtist.onEnd = &AlbumXmlHandler::onTrkExtraArtistEnd; Node& trkExtraArtistName (makeNode(trkExtraArtist, "name")); trkExtraArtistName.onChar = &AlbumXmlHandler::onExtraArtistNameChar; Node& trkExtraArtistRole (makeNode(trkExtraArtist, "role")); trkExtraArtistRole.onChar = &AlbumXmlHandler::onExtraArtistRoleChar; } private: DiscogsAlbumInfo& m_albumInfo; map<string, string> m_mComposers; // track-composer string m_strExtraArtistName; string m_strExtraArtistRole; string m_strExtraArtistTracks; static bool isNumber (const string& s) { int n (cSize(s)); if (0 == n) { return false; } for (int i = 0; i < n; ++i) { if (!isdigit(s[i])) { return false; } } return true; } // since roles of "Composed By" and "Written-By" were found, it seems that these don't follow a strict syntax; so probably non-letters are best ignored; case might be better ignored too; static string transfRole(const string& s) { string res; for (int i = 0, n = cSize(s); i < n; ++i) { char c (s[i]); if (isalnum(c)) { res += tolower(c); } } return res; } static bool isComposerRole(string s) { //qDebug(" orig role %s", s.c_str()); s = transfRole(s); //qDebug(" transf role %s", s.c_str()); return "composedby" == s || "writtenby" == s || "musicby" == s || "lyricsby" == s; // ttt2 review whether all these should be added to "composer"; see if some others should be put here } void onReleaseStart(const QXmlAttributes& attrs) { CB_ASSERT (m_albumInfo.m_strId == convStr(attrs.value("id"))); m_mComposers.clear(); } void onImageStart(const QXmlAttributes& attrs) { string strUri (convStr(attrs.value("uri"))); string::size_type n (strUri.rfind('/')); CB_ASSERT (string::npos != n); strUri.erase(0, n + 1); m_albumInfo.m_vstrImageNames.push_back(strUri); //cout << strUri << endl; } void onAlbExtraArtistStart(const QXmlAttributes& /*attrs*/) { m_strExtraArtistName.clear(); m_strExtraArtistRole.clear(); m_strExtraArtistTracks.clear(); } void onFormatStart(const QXmlAttributes& attrs) { string strFmt (convStr(attrs.value("name"))); //cout << strFmt << endl; addIfMissing(m_albumInfo.m_strFormat, strFmt); } void onTrackStart(const QXmlAttributes& /*attrs*/) { m_albumInfo.m_vTracks.push_back(TrackInfo()); } // called by onRespEnd(); assigns composers to tracks based on the content of m_mComposers void onRespEnd() { for (int i = 0, n = cSize(m_albumInfo.m_vTracks); i < n; ++i) { TrackInfo& t (m_albumInfo.m_vTracks[i]); addList(t.m_strComposer, m_mComposers[t.m_strPos]); addList(t.m_strComposer, m_albumInfo.m_strComposer); addList(t.m_strArtist, m_albumInfo.m_strArtist); } m_albumInfo.m_vpImages.resize(m_albumInfo.m_vstrImageNames.size()); m_albumInfo.m_vstrImageInfo.resize(m_albumInfo.m_vstrImageNames.size()); } // takes data from m_strExtraArtistName, m_strExtraArtistRole and m_strExtraArtistTracks and either adds it to m_mComposers (if some tracks were specified) or to m_albumInfo.m_strComposer (if no tracks are given) void onAlbExtraArtistEnd() { if (isComposerRole(m_strExtraArtistRole)) { vector<string> v; if (m_strExtraArtistTracks.empty()) { addIfMissing(m_albumInfo.m_strComposer, m_strExtraArtistName); } else { split(m_strExtraArtistTracks, ",", v); for (int i = 0, n = cSize(v); i < n; ++i) { string t (v[i]); string::size_type p (t.find("to")); if (string::npos != p) { addIfMissing(m_mComposers[t], m_strExtraArtistName); } else { string s1 (t.substr(0, p)); trim(s1); string s2 (t.substr(p + 2)); trim(s2); if (isNumber(s1) && isNumber(s2)) { int n1 (atoi(s1.c_str())); int n2 (atoi(s2.c_str())); char a [10]; const char* szFmt ('0' == s1[0] ? "%02d" : "%d"); for (int i = n1; i <= n2; ++i) { sprintf(a, szFmt, i); addIfMissing(m_mComposers[a], m_strExtraArtistName); } } else { qDebug("track range not supported for non-numeric positions"); // ttt2 maybe support } } } } } } void onTrkExtraArtistEnd() { if (isComposerRole(m_strExtraArtistRole)) // ttt2 see if "Music By" should be compared too { addIfMissing(m_albumInfo.m_vTracks.back().m_strComposer, m_strExtraArtistName); //inspect(m_strExtraArtistName); } } void onAlbArtistNameChar(const string& s) { addIfMissing(m_albumInfo.m_strArtist, s); } void onExtraArtistNameChar(const string& s) { m_strExtraArtistName = s; //addIfMissing(m_strExtraArtistName, s); // ttt2 review if this makes sense, given that for artists there is the option to join, but not sure about extraartists: /* <track> <position>5</position> <artists> <artist> <name>Chloë Agnew</name> <join>&</join> </artist> <artist> <name>Lisa Kelly</name> <join>&</join> </artist> <artist> <name>Órla Fallon</name> <join>&</join> </artist> <artist> <name>Méav Ní Mhaolchatha</name> </artist> </artists> <title>One World 3:49 */ } void onExtraArtistRoleChar(const string& s) { //addIfMissing(m_strExtraArtistRole, s); m_strExtraArtistRole = s; } void onExtraArtistTracksChar(const string& s) { //addIfMissing(m_albumInfo.m_strArtist, s); m_strExtraArtistTracks = s; } void onAlbTitleChar(const string& s) { m_albumInfo.m_strTitle = s; } void onGenreChar(const string& s) { addIfMissing(m_albumInfo.m_strGenre, s); } void onStyleChar(const string& s) { addIfMissing(m_albumInfo.m_strStyle, s); } void onReleasedChar(const string& s) { m_albumInfo.m_strReleased = s; } void onNotesChar(const string& s) { m_albumInfo.m_strNotes = s; } void onPositionChar(const string& s1) { string s (s1); //m_albumInfo.m_vTracks.back().m_nPos = qstrCh.toInt(); if (endsWith(s, ".")) { s.erase(s.size() - 1, 1); } m_albumInfo.m_vTracks.back().m_strPos = s; } void onTrkArtistNameChar(const string& s) { addIfMissing(m_albumInfo.m_vTracks.back().m_strArtist, s); } void onTrackTitleChar(const string& s) { m_albumInfo.m_vTracks.back().m_strTitle = s; } }; /*override*/ void DiscogsAlbumInfo::copyTo(AlbumInfo& dest) { //cout << *this; dest.m_strTitle = m_strTitle; //dest.m_strArtist = m_strArtist; //dest.m_strComposer = m_strComposer; //dest.m_strFormat = m_strFormat; // CD, tape, ... dest.m_strGenre = getGenre(); dest.m_strReleased = m_strReleased; dest.m_strNotes = m_strNotes; dest.m_vTracks = m_vTracks; //dest.m_eVarArtists // !!! missing dest.m_strSourceName = DiscogsDownloader::SOURCE_NAME; // Discogs, MusicBrainz, ... ; needed by MainFormDlgImpl; //dest.m_imageInfo; // !!! not set } std::string DiscogsAlbumInfo::getGenre() const // combination of m_strGenre and m_strStyle { switch (*m_peStyleOption) { case GENRE_ONLY: return m_strGenre; case GENRE_COMMA_STYLE: return m_strGenre.empty() || m_strStyle.empty() ? m_strGenre + m_strStyle : m_strGenre + ", " + m_strStyle; case GENRE_PAR_STYLE: { if (m_strStyle.empty()) { return m_strGenre; } if (m_strGenre.empty()) { return "(" + m_strStyle + ")"; } // ttt2 perhaps without "(/)" return m_strGenre + " (" + m_strStyle + ")"; } case STYLE_ONLY: return m_strStyle; } CB_ASSERT (false); } }; // namespace Discogs //============================================================================================================================= //============================================================================================================================= //============================================================================================================================= /*override*/ void DiscogsDownloader::saveSize() { m_settings.saveDiscogsSettings(width(), height(), m_pStyleCbB->currentIndex()); } /*static*/ const char* DiscogsDownloader::SOURCE_NAME ("Discogs"); // !!! non-translatable DiscogsDownloader::DiscogsDownloader(QWidget* pParent, SessionSettings& settings, bool bSaveResults) : AlbumInfoDownloaderDlgImpl(pParent, settings, bSaveResults), m_nPageSize(200) //ttt0 review "200" - really should be initialized in SearchXmlHandler::onSearchResultsStart() and here just get an invalid value { setWindowTitle(tr("Download album data from Discogs.com")); int nWidth, nHeight, nStyleOption; m_settings.loadDiscogsSettings(nWidth, nHeight, nStyleOption); if (nStyleOption < 0 || nStyleOption > Discogs::DiscogsAlbumInfo::STYLE_ONLY) { nStyleOption = Discogs::DiscogsAlbumInfo::GENRE_ONLY; } m_eStyleOption = Discogs::DiscogsAlbumInfo::StyleOption(nStyleOption); if (nWidth > 400 && nHeight > 400) { resize(nWidth, nHeight); } delete m_pSrchArtistL; delete m_pSrchAlbumL; delete m_pSrchAlbumE; delete m_pSpacer01L; //delete m_pViewAtAmazonE; delete m_pMatchCountCkB; m_pViewAtAmazonL->hide(); { QStringList l; //l << "< don't use >" << "Genre1, Genre2, ... , Style1, Style2, ..." << "Genre1, Genre2, ... (Style1, Style2, ...)" << "Style1, Style2, ..."; l << tr("Genres") << tr("Genres, Styles") << tr("Genres (Styles)") << tr("Styles"); m_pStyleCbB->addItems(l); m_pStyleCbB->setCurrentIndex(nStyleOption); } m_pQHttp->setHost("api.discogs.com"); m_pModel = new WebDwnldModel(*this, *m_pTrackListG); // !!! in a way these would make sense to be in the base constructor, but that would cause calls to pure virtual methods m_pTrackListG->setModel(m_pModel); connect(m_pSearchB, SIGNAL(clicked()), this, SLOT(on_m_pSearchB_clicked())); connect(m_pStyleCbB, SIGNAL(currentIndexChanged(int)), this, SLOT(on_m_pStyleCbB_currentIndexChanged(int))); } DiscogsDownloader::~DiscogsDownloader() { resetNavigation(); // !!! not in base class, because it calls virtual method resetNavigation() clear(); } void DiscogsDownloader::clear() { LAST_STEP("DiscogsDownloader::clear"); clearPtrContainer(m_vpImages); m_vAlbums.clear(); } /*override*/ bool DiscogsDownloader::initSearch(const std::string& strArtist, const std::string& strAlbum) { LAST_STEP("DiscogsDownloader::initSearch"); string s (!strArtist.empty() && !strAlbum.empty() ? strArtist + " " + strAlbum : strArtist + strAlbum); s = removeParentheses(s); m_pSrchArtistE->setText(convStr(s)); return !s.empty(); } // "/search?type=all&q=Beatles+Abbey+Road&f=xml&api_key=f51e9c8f6c" /*override*/ std::string DiscogsDownloader::createQuery() { LAST_STEP("DiscogsDownloader::createQuery"); //string s (strArtist + "+" + strAlbum); string s ("/database/search?q=" + replaceSymbols(convStr(m_pSrchArtistE->text())) + "&f=xml"); for (string::size_type i = 0; i < s.size(); ++i) { if (' ' == s[i]) { s[i] = '+'; } } return s; } //========================================================================================================================== //========================================================================================================================== //========================================================================================================================== void DiscogsDownloader::on_m_pSearchB_clicked() { LAST_STEP("DiscogsDownloader::on_m_pSearchB_clicked"); clear(); search(); } //========================================================================================================================== //========================================================================================================================== //========================================================================================================================== void DiscogsDownloader::loadNextPage() { LAST_STEP("DiscogsDownloader::loadNextPage"); CB_ASSERT (!m_pQHttp->hasPendingRequests()); //ttt0 2010/05/08 - reported again in 0.99.05.033 for Ubuntu 9.10 ++m_nLastLoadedPage; CB_ASSERT (m_nLastLoadedPage <= m_nTotalPages - 1); //m_eState = NEXT; setWaiting(SEARCH); char a [20]; sprintf(a, "&page=%d", m_nLastLoadedPage + 1); string s (m_strQuery + a); QHttpRequestHeader header ("GET", convStr(s)); // ttt1 QHttpRequestHeader and QHttp are deprecated, to be replaced by QNetworkAccessManager header.setValue("Host", "api.discogs.com"); // ttt1 Discogs API v1 is deprecated; switch to v2: http://api.discogs.com/search?q=abba&f=xml vs http://www.discogs.com/search?type=all&q=abba&f=xml&api_key=f51e9c8f6c // see http://www.discogs.com/help/forums/topic/234138 //header.setValue("Host", "api.discogs.com"); header.setValue("Accept-Encoding", "gzip"); //header.setValue("User-Agent" , "Mozilla Firefox"); header.setValue("User-Agent" , "Mp3Diags"); m_pQHttp->request(header); //cout << "sent search " << m_pQHttp->request(header) << " for page " << (m_nLastLoadedPage + 1) << endl; } /* http://www.discogs.com/help/api http://www.discogs.com/developers/ http://www.discogs.com/developers/accessing.html http://www.discogs.com/developers/resources/database/index.html http://www.discogs.com/developers/resources/database/artist.html http://api.discogs.com/release/1 http://www.discogs.com/developers/resources/database/image.html http://www.discogs.com/developers/resources/database/search-endpoint.html http://api.discogs.com/database/search?artist=coldplay http://qtwiki.org/Parsing_JSON_with_QT_using_standard_QT_library http://nilier.blogspot.com/2010/08/json-parser-class-for-qt.html?spref=tw http://ereilin.tumblr.com/post/6857765046/lightweight-json-parser-serializer-class-for-qt https://github.com/ereilin/qt-json http://www.discogs.com/help/forums/topic/326725 - future of XML, JSON-only features */ void DiscogsDownloader::reloadGui() { LAST_STEP("DiscogsDownloader::reloadGui"); AlbumInfoDownloaderDlgImpl::reloadGui(); const DiscogsAlbumInfo& album (m_vAlbums[m_nCrtAlbum]); m_pAlbumNotesM->setText(convStr(album.m_strNotes)); m_pGenreE->setText(convStr(album.getGenre())); } //http://api.discogs.com/release/1565272?f=xml vs http://www.discogs.com/release/1565272?f=xml&api_key=f51e9c8f6c void DiscogsDownloader::requestAlbum(int nAlbum) { LAST_STEP("DiscogsDownloader::requestAlbum"); CB_ASSERT (!m_pQHttp->hasPendingRequests()); //ttt1 triggered according to mail (Nov 2, 2009, 2:31 PM - Qt 4.5.2; there is something in the 4.5.3 change log at http://qt.nokia.com/developer/changes/changes-4.5.3 about duplicate HTTP requests, but according to http://www.qtcentre.org/forum/f-qt-programming-2/t-qhttp-response-content-25255-post121265.html it only affected QNetworkAccessManager, which is a replacement for QHttp, so this is probably not related) m_nLoadingAlbum = nAlbum; setWaiting(ALBUM); string s ("/release/" + m_vAlbums[nAlbum].m_strId + "?f=xml"); QHttpRequestHeader header ("GET", convStr(s)); header.setValue("Host", "api.discogs.com"); header.setValue("Accept-Encoding", "gzip"); header.setValue("User-Agent" , "Mp3Diags"); m_pQHttp->request(header); //cout << "sent album " << m_vAlbums[nAlbum].m_strId << " - " << m_pQHttp->request(header) << endl; addNote(AlbumInfoDownloaderDlgImpl::tr("getting album info ...")); } //http://api.discogs.com/image/R-1565272-1228883740.jpeg vs http://www.discogs.com/image/R-1565272-1228883740.jpeg?api_key=f51e9c8f6c void DiscogsDownloader::requestImage(int nAlbum, int nImage) { LAST_STEP("DiscogsDownloader::requestImage"); CB_ASSERT (!m_pQHttp->hasPendingRequests()); m_nLoadingAlbum = nAlbum; m_nLoadingImage = nImage; setWaiting(IMAGE); const string& strName (m_vAlbums[nAlbum].m_vstrImageNames[nImage]); setImageType(strName); string s ("/image/" + strName); //cout << " get img " << s << endl; QHttpRequestHeader header ("GET", convStr(s)); header.setValue("Host", "api.discogs.com"); //header.setValue("Accept-Encoding", "gzip"); header.setValue("User-Agent" , "Mp3Diags"); m_pQHttp->request(header); //cout << "sent img " << m_vAlbums[nAlbum].m_vstrImageNames[nImage] << " - " << m_pQHttp->request(header) << endl; addNote(AlbumInfoDownloaderDlgImpl::tr("getting image ...")); } void DiscogsDownloader::on_m_pStyleCbB_currentIndexChanged(int k) { LAST_STEP("DiscogsDownloader::on_m_pStyleCbB_currentIndexChanged"); if (m_nCrtAlbum < 0 || m_nCrtAlbum >= cSize(m_vAlbums)) { return; } DiscogsAlbumInfo& album (m_vAlbums[m_nCrtAlbum]); m_eStyleOption = Discogs::DiscogsAlbumInfo::StyleOption(k); m_pGenreE->setText(convStr(album.getGenre())); } //========================================================================================================================== //========================================================================================================================== //========================================================================================================================== /*override*/ QHttp* DiscogsDownloader::getWaitingHttp() { LAST_STEP("DiscogsDownloader::getWaitingHttp"); return m_pQHttp; } /*override*/ WebAlbumInfoBase& DiscogsDownloader::album(int i) { return m_vAlbums.at(i); } /*override*/ int DiscogsDownloader::getAlbumCount() const { return cSize(m_vAlbums); } /*override*/ QXmlDefaultHandler* DiscogsDownloader::getSearchXmlHandler() { return new SearchXmlHandler(*this); } /*override*/ QXmlDefaultHandler* DiscogsDownloader::getAlbumXmlHandler(int nAlbum) { return new AlbumXmlHandler(m_vAlbums.at(nAlbum)); } /*override*/ const WebAlbumInfoBase* DiscogsDownloader::getCrtAlbum() const // returns 0 if there's no album { if (m_nCrtAlbum < 0 || m_nCrtAlbum >= cSize(m_vAlbums)) { return 0; } return &m_vAlbums[m_nCrtAlbum]; } MP3Diags-1.2.02/src/Id3V240Stream.cpp0000644000175000001440000005246111724634135015627 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "Id3V240Stream.h" #include "Helpers.h" using namespace std; using namespace pearl; //============================================================================================================ //============================================================================================================ //============================================================================================================ // !!! Note that the exceptions thrown by Id3V240Frame::Id3V240Frame() pass through Id3V240Stream, so they should have a type suited for this situation. // Also note that by the time Id3V240Frame::Id3V240Frame() gets executed, it was already decided that we deal with an Id3V240Stream. Now we should see if it is valid or broken. // since broken applications may use all 8 bits for size, although only 7 should be used, this tries to figure out if the size is correct bool Id3V240Frame::checkSize(istream& in, streampos posNext) { streampos pos (m_pos); pos += m_nDiskDataSize + m_nDiskHdrSize; if (pos > posNext) { return false; } if (pos == posNext) { return true; } in.seekg(pos); char bfr [ID3_FRAME_HDR_SIZE]; int nHdrBytesSkipped (0); int nRead (readID3V2(m_bHasUnsynch, in, bfr, ID3_FRAME_HDR_SIZE, posNext, nHdrBytesSkipped)); in.clear(); if (nRead < ID3_FRAME_HDR_SIZE) { // should be padding, which is always 0 for (int i = 0; i < nRead; ++i) { if (0 != bfr[i]) { return false; } } return true; } // check for frames: uppercase letters and digits for (int i = 0; i < 4; ++i) { char c (bfr[i]); if ((c < 'A' || c > 'Z') && (c < '0' || c > '9')) //ttt3 ASCII-specific { if (0 != c) { return false; } goto tryPadding; } } return true; // another frame seems to follow tryPadding: in.seekg(pos); int nSize (posNext - pos); char* a (new char[nSize]); ArrayPtrRelease rel (a); nRead = readID3V2(m_bHasUnsynch, in, a, nSize, posNext, nHdrBytesSkipped); in.clear(); if (nRead != nSize) { return false; } for (int i = 0; i < nRead; ++i) { if (0 != a[i]) { return false; } } return true; } /*override*/ int Id3V240Frame::getOffset() const { return 0 != (m_cFlag2 & 0x01) ? 4 : 0; // not quite OK if the flag is set but the frame is only several bytes, but that was invalid to begin with; anyway, it doesn't mess up loading from disk, since none is needed for such short frames } void Id3V240Frame::load(NoteColl& notes, istream& in, streampos posNext, bool bHasUnsynch) { StreamStateRestorer rst (in); if (m_nDiskDataSize < 0) { return; } if (m_nDiskDataSize > 5000000) { return; } // there are probably no frames over 5MB vector v (m_nDiskDataSize); m_vcData.clear(); //int nContentBytesSkipped (0); int nRead (read(in, &v[0], m_nDiskDataSize)); //readID3V2(bHasUnsynch, in, &v[0], m_nMemDataSize1 w, posNext, nContentBytesSkipped)); //m_nDiskDataSize1 = q m_nMemDataSize1 + nContentBytesSkipped; if (m_nDiskDataSize != nRead || !checkSize(in, posNext)) { return; } rst.setOk(); if (bHasUnsynch && !v.empty()) { for (int i = 0; i < cSize(v); ++i) { m_vcData.push_back(v[i]); if (char(0xff) == v[i] && i < cSize(v) - 1 && 0 == v[i + 1]) // !!! it's OK to run this on the Data length indicator, because it doesn't contain any 0xff { ++i; } } } else { v.swap(m_vcData); } m_nMemDataSize = cSize(m_vcData); if (getOffset() > 0 && m_nMemDataSize > 3) { // Data length indicator unsigned char* p (reinterpret_cast (&m_vcData[0])); //inspect(p, 50); int nDli ((p[0] << 21) + (p[1] << 14) + (p[2] << 7) + (p[3] << 0)); if (nDli != m_nMemDataSize - 4) { MP3_NOTE (m_pos, id3v240IncorrectDli); //ttt2 probably adjust m_nMemDataSize anyway; after all, the Data length indicator flag is still there } else { m_nMemDataSize -= 4; m_vcData.erase(m_vcData.begin(), m_vcData.begin() + 4); } } } void Id3V240Frame::load(NoteColl& notes, istream& in, streampos posNext) { load(notes, in, posNext, m_bHasUnsynch); if (-1 == m_nMemDataSize) { load(notes, in, posNext, !m_bHasUnsynch); if (-1 != m_nMemDataSize) { m_bHasUnsynch = !m_bHasUnsynch; MP3_NOTE (m_pos, id3v240IncorrectFrameSynch); } } } Id3V240Frame::Id3V240Frame(NoteColl& notes, istream& in, streampos pos, bool bHasUnsynch, streampos posNext, StringWrp* pFileName) : Id3V2Frame(pos, bHasUnsynch, pFileName) { in.seekg(pos); char bfr [ID3_FRAME_HDR_SIZE]; int nHdrBytesSkipped (0); int nRead (readID3V2(bHasUnsynch/*false*/, in, bfr, ID3_FRAME_HDR_SIZE, posNext, nHdrBytesSkipped)); MP3_CHECK (ID3_FRAME_HDR_SIZE == nRead || (nRead >= 1 && 0 == bfr[0]), pos, id3v2FrameTooShort, StreamIsBroken(Id3V240Stream::getClassDisplayName(), tr("Truncated ID3V2.4.0 tag."))); // in 2.4.0 bHasUnsynch shouldn't matter, because the frame header doesn't need unsynch; but unconforming apps use 8 bits for size, as opposed to 7, as they are supposed to unsigned char* p (reinterpret_cast (bfr)); if (0 == bfr[0]) { // padding //m_nMemDataSize = -1; // !!! not needed; the constructor makes it -1 //m_nDiskDataSize = -1; // !!! not needed; the constructor makes it -1 m_szName[0] = 0; return; } m_nDiskHdrSize = ID3_FRAME_HDR_SIZE + nHdrBytesSkipped; //inspect(bfr, ID3_FRAME_HDR_SIZE); strncpy(m_szName, bfr, 4); m_szName[4] = 0; { char c (m_szName[0]); MP3_CHECK (c >= 'A' && c <= 'Z', pos, id3v2InvalidName, StreamIsBroken(Id3V240Stream::getClassDisplayName(), tr("ID3V2.4.0 tag containing a frame with an invalid name: %1.").arg(convStr(getReadableName())))); for (int i = 1; i < 4; ++i) { char c (m_szName[i]); MP3_CHECK ((c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'), pos, id3v2InvalidName, StreamIsBroken(Id3V240Stream::getClassDisplayName(), tr("ID3V2.4.0 tag containing a frame with an invalid name: %1.").arg(convStr(getReadableName())))); //ttt3 ASCII-specific } } m_cFlag1 = bfr[8]; m_cFlag2 = bfr[9]; //inspect(bfr); MP3_CHECK (0 == (m_cFlag1 & ~0x60), pos, id3v2UnsuppFlags1, StreamIsUnsupported(Id3V240Stream::getClassDisplayName(), tr("ID3V2.4.0 tag containing a frame with an unsupported flag."))); // ignores "Tag alter preservation" and "File alter preservation" // ttt2 use them MP3_CHECK (0 == (m_cFlag2 & ~0x03), pos, id3v2UnsuppFlags2, StreamIsUnsupported(Id3V240Stream::getClassDisplayName(), tr("ID3V2.4.0 tag containing a frame with an unsupported flag."))); m_bHasUnsynch = (0 != (m_cFlag2 & ~0x02)); //m_bHasUnsynch = false; if (0 != (m_cFlag1 & 0x8f)) { MP3_NOTE (pos, id3v2IncorrectFlg1); } if (0 != (m_cFlag2 & 0xb0)) { MP3_NOTE (pos, id3v2IncorrectFlg2); } if (0 == (p[4] & 0x80) && 0 == (p[5] & 0x80) && 0 == (p[6] & 0x80) && 0 == (p[7] & 0x80)) { // by the specs it should be 7 bit unsynch m_nDiskDataSize = (p[4] << 21) + (p[5] << 14) + (p[6] << 7) + (p[7] << 0); load(notes, in, posNext); } if (-1 == m_nMemDataSize) { // failed to load as 7bit unsynch, so try 8bit m_nDiskDataSize = (p[4] << 24) + (p[5] << 16) + (p[6] << 8) + (p[7] << 0); load(notes, in, posNext); if (-1 == m_nMemDataSize) { MP3_THROW (pos, id3v240CantReadFrame, StreamIsBroken(Id3V240Stream::getClassDisplayName(), tr("Broken ID3V2.4.0 tag."))); } else { MP3_NOTE (pos, id3v240IncorrectSynch); } } try { try { getUtf8String(); if ('T' == m_szName[0]) { if (!isTxxx() && 0 == m_nMemDataSize) { // this is really invalid; text frames must have at least a byte; however, by doing these we make sure that an empty (i.e. having a single byte, for text encoding) frame gets copied if the tag is edited; m_vcData.clear(); m_vcData.push_back(0); m_nMemDataSize = cSize(m_vcData); MP3_NOTE_D (pos, id3v2EmptyTextFrame, tr("%1 (Frame: %2)").arg(noteTr(id3v2EmptyTextFrame)).arg(m_szName)); } else if (isTxxx() && m_nMemDataSize <= 1) { // this is really invalid; text frames must have at least a byte; however, by doing these we make sure that an empty (i.e. having a single byte, for text encoding) frame gets copied if the tag is edited; m_vcData.clear(); string strInvalid (convStr(tr("INVALID"))); const char* szInvalid (strInvalid.c_str()); m_vcData.push_back(0); // latin1 m_vcData.insert(m_vcData.end(), szInvalid, szInvalid + strlen(szInvalid)); // description m_vcData.push_back(0); // terminator m_vcData.insert(m_vcData.end(), szInvalid, szInvalid + strlen(szInvalid)); // value m_nMemDataSize = cSize(m_vcData); MP3_NOTE_D (pos, id3v2EmptyTextFrame, tr("%1 (Frame: %2)").arg(noteTr(id3v2EmptyTextFrame)).arg(m_szName)); // ttt2 actually TXXX is not text } //ttt2 add check for terminating 0 for TXXX, to split the value into descr and val } } catch (const NotId3V2Frame&) { MP3_THROW (pos, id3v2TextError, StreamIsBroken(Id3V240Stream::getClassDisplayName(), tr("ID3V2.4.0 tag containing a broken text frame named %1.").arg(convStr(getReadableName())))); } catch (const UnsupportedId3V2Frame&) { MP3_THROW (pos, id3v240UnsuppText, StreamIsUnsupported(Id3V240Stream::getClassDisplayName(), tr("ID3V2.4.0 tag containing a text frame named %1 using unsupported characters or unsupported text encoding.").arg(convStr(getReadableName())))); //ttt2 not specific enough; this might need reviewing in the future } } catch (const std::bad_alloc&) { throw; } catch (...) { throw; } if (m_nMemDataSize > 150) // ttt2 perhaps make configurable { // if the frame needs a lot of space, erase the data from memory; it will be retrieved from the disk when needed; vector().swap(m_vcData); } } // may return multiple null characters; it's the job of getUtf8String() to deal with them; // chars after the first null are considered comments (or after the second null, for TXXX), so the nulls are replaced with commas, except for those at the end of the string (which are removed) and the first null in TXXX; string Id3V240Frame::getUtf8StringImpl() const { if ('T' != m_szName[0]) { return convStr(TagReader::tr("")); } // have a "pos" to pass here and ability to log; could also be used for large tags, to not store them in memory yet have them available // also use pos below // or perhaps forget these and throw exceptions that have error messages, catch them and log/show dialogs // 2008.07.12 - actually all these proposals don't seem to work well: the callers don't catch the exceptions thrown here and wouldn't know what to do with them; there's no good place to display the errors (the end user isn't supposed to look at logs); it seems better to not throw, but return a string describing the problem; // 2008.07.12 - on a second thought - throw but call this on the constructor, where it can be logged properly; if it worked on the constructor it should work later too CB_CHECK1 (m_nMemDataSize > 0, NotId3V2Frame()); Id3V2FrameDataLoader wrp (*this); const char* pData (wrp.getData()); //ttt2 from http://www.id3.org/id3v2.4.0-frames - All text information frames supports multiple strings, stored as a null separated list string s; if (0 == pData[0]) { // Latin-1 for (int i = 1; i < m_nMemDataSize; ++i) { unsigned char c (pData[i]); if (c < 128) { // !!! 0 is OK s += char(c); } else { m_bHasLatin1NonAscii = true; unsigned char c1 (0xc0 | (c >> 6)); unsigned char c2 (0x80 | (c & 0x3f)); s += char(c1); s += char(c2); } } } else if (1 == pData[0]) { s = utf8FromBomUtf16(pData + 1, m_nMemDataSize - 1); } else if (3 == pData[0]) { s = string(pData + 1, m_nMemDataSize - 1); } else { if (2 == pData[0]) { CB_THROW1 (UnsupportedId3V2Frame()); //ttt2 add support for UTF-16BE (2 = "UTF-16BE [UTF-16] encoded Unicode [UNICODE] without BOM"); } CB_THROW1 (NotId3V2Frame()); } { // deal with nulls int k (0); if (isTxxx()) { for (; k < cSize(s) && 0 != s[k]; ++k) {} ++k; } int i (cSize(s) - 1); for (; i >= k && 0 == s[i]; --i) { s.erase(i); } for (; i >= k; --i) { if (0 == s[i]) { //s.replace(i, 1, i > k ? ", " : ""); s.replace(i, 1, ", "); } } } return s; } /*override*/ bool Id3V240Frame::discardOnChange() const { return 0 != (m_cFlag1 & 0x60); } //============================================================================================================ //============================================================================================================ //============================================================================================================ Id3V240Stream::Id3V240Stream(int nIndex, NoteColl& notes, istream& in, StringWrp* pFileName, bool bAcceptBroken /* = false*/) : Id3V2StreamBase(nIndex, in, pFileName) { StreamStateRestorer rst (in); streampos pos (m_pos); char bfr [ID3_HDR_SIZE]; MP3_CHECK_T (ID3_HDR_SIZE == read(in, bfr, ID3_HDR_SIZE), pos, "Invalid ID3V2.4.0 tag. File too small.", NotId3V2()); MP3_CHECK_T ('I' == bfr[0] && 'D' == bfr[1] && '3' == bfr[2], pos, "Invalid ID3V2.4.0 tag. Invalid ID3V2 header.", NotId3V2()); MP3_CHECK_T (4 == bfr[3] && 0 == bfr[4], pos, "Invalid ID3V2.4.0 tag. Invalid ID3V2.4.0 header.", NotId3V2()); m_nTotalSize = getId3V2Size (bfr); m_cFlags = bfr[5]; MP3_CHECK (0 == (m_cFlags & 0x7f), pos, id3v2UnsuppFlag, StreamIsUnsupported(Id3V240Stream::getClassDisplayName(), tr("ID3V2 tag with unsupported flag."))); //ttt2 review, support streampos posNext (pos); posNext += m_nTotalSize; pos += ID3_HDR_SIZE; bool bHasLatin1NonAscii (false); // if it has a text frame that uses Latin1 encoding and has chars between 128 and 255 try { for (;;) { long long nDiff (pos - posNext); if (nDiff >= 0) { break; } Id3V240Frame* p (new Id3V240Frame(notes, in, pos, hasUnsynch(), posNext, m_pFileName)); bHasLatin1NonAscii = bHasLatin1NonAscii || p->m_bHasLatin1NonAscii; if (-1 == p->m_nMemDataSize) { // it encountered zeroes, which signals the beginning of padding //ttt2 should check that there's no garbage after the first zero m_nPaddingSize = posNext - pos; delete p; break; } m_vpFrames.push_back(p); pos += p->m_nDiskHdrSize + p->m_nDiskDataSize; } } catch (const std::bad_alloc&) { throw; } catch (...) { if (bAcceptBroken) { preparePicture(notes); return; } clearPtrContainer(m_vpFrames); throw; /*if (!bAcceptBroken) //ttt2 2011.12.01 - see if this would make more sense - rather than exit immediately, continue all the processing; probably not: if bAcceptBroken is true, we don't care about notes, just want to recover whatever is available { clearPtrContainer(m_vpFrames); throw; }*/ } checkDuplicates(notes); checkFrames(notes); if (bHasLatin1NonAscii) { MP3_NOTE (m_pos, id3v2HasLatin1NonAscii); } if (0 != findFrame(KnownFrames::LBL_TIME_YEAR_230())) { if (0 != findFrame(KnownFrames::LBL_TIME_240())) { MP3_NOTE (m_pos, id3v240DeprTyerAndTdrc); // ttt2 check consistency among TYER and TDRC } else { MP3_NOTE (m_pos, id3v240DeprTyer); } } if (0 != findFrame(KnownFrames::LBL_TIME_DATE_230())) { if (0 != findFrame(KnownFrames::LBL_TIME_240())) { MP3_NOTE (m_pos, id3v240DeprTdatAndTdrc); // ttt2 check consistency among TDAT and TDRC } else { MP3_NOTE (m_pos, id3v240DeprTdat); } } preparePicture(notes); switch (m_eImageStatus) { case ImageInfo::NO_PICTURE_FOUND: MP3_NOTE (m_pos, id3v2NoApic); break; case ImageInfo::ERROR_LOADING: MP3_NOTE (m_pos, id3v2CouldntLoadPic); break; //case ImageInfo::USES_LINK: MP3_NOTE (m_pos, id3v2LinkNotSupported); break; // !!! already reported by id3v2LinkInApic, so no need for another note; case ImageInfo::LOADED_NOT_COVER: MP3_NOTE (m_pos, id3v2NotCoverPicture); break; default:; } if (m_vpFrames.empty()) { MP3_NOTE (m_pos, id3v2EmptyTag); } MP3_TRACE (m_pos, "Id3V240Stream built."); { pos = m_pos; pos += m_nTotalSize - 1; in.seekg(pos); char c; MP3_CHECK (1 == read(in, &c, 1), m_pos, id3v240CantReadFrame, NotId3V2()); } rst.setOk(); } /*override*/ TagReader::SuportLevel Id3V240Stream::getSupport(Feature eFeature) const { switch (eFeature) { case TITLE: case ARTIST: case TRACK_NUMBER: case TIME: case GENRE: case IMAGE: case ALBUM: case RATING: case COMPOSER: case VARIOUS_ARTISTS: return READ_ONLY; default: return NOT_SUPPORTED; } /*{ , , , , , , , }; enum SuportLevel { NOT_SUPPORTED, , READ_WRITE };*/ } /*override*/ TagTimestamp Id3V240Stream::getTime(bool* pbFrameExists /* = 0*/) const { //const Id3V240Frame* p (findFrame(KnownFrames::LBL_TIME_YEAR_230())); const Id3V2Frame* p (findFrame(KnownFrames::LBL_TIME_240())); if (0 != p) { try { TagTimestamp t (p->getUtf8String()); if (0 != pbFrameExists) { *pbFrameExists = true; } return t; } catch (const TagTimestamp::InvalidTime&) { // !!! nothing } } return get230TrackTime(pbFrameExists); } /* http://www.id3.org/id3v2.4.0-structure : The timestamp fields are based on a subset of ISO 8601. When being as precise as possible the format of a time string is yyyy-MM-ddTHH:mm:ss (year, "-", month, "-", day, "T", hour (out of 24), ":", minutes, ":", seconds), but the precision may be reduced by removing as many time indicators as wanted. Hence valid timestamps are yyyy, yyyy-MM, yyyy-MM-dd, yyyy-MM-ddTHH, yyyy-MM-ddTHH:mm and yyyy-MM-ddTHH:mm:ss. All time stamps are UTC. For durations, use the slash character as described in 8601, and for multiple non- contiguous dates, use multiple strings, if allowed by the frame definition. */ //============================================================================================================ //============================================================================================================ //============================================================================================================ //ttt2 Banana%20Erectors%20-%20Disagreeable%20Sensation.mp3 - unsupported flag MP3Diags-1.2.02/src/SessionsDlgImpl.cpp0000644000175000001440000004312411727066025016533 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include #include "SessionsDlgImpl.h" #include "Helpers.h" #include "StoredSettings.h" #include "CheckedDir.h" #include "SessionEditorDlgImpl.h" #include "OsFile.h" #include "Widgets.h" #include "Translation.h" using namespace std; //======================================================================================================================== //======================================================================================================================== //======================================================================================================================== SessionsModel::SessionsModel(std::vector& vstrSessions) : m_vstrSessions(vstrSessions) { } /*override*/ int SessionsModel::rowCount(const QModelIndex&) const { return cSize(m_vstrSessions); } /*override*/ QVariant SessionsModel::data(const QModelIndex& index, int nRole /* = Qt::DisplayRole*/) const { LAST_STEP("SessionsModel::data()"); if (!index.isValid()) { return QVariant(); } int i (index.row()); //int j (index.column()); // ttt2 perhaps Qt::ToolTipRole if (Qt::DisplayRole != nRole) { return QVariant(); } return toNativeSeparators(convStr(m_vstrSessions.at(i))); } /*override*/ QVariant SessionsModel::headerData(int nSection, Qt::Orientation eOrientation, int nRole /* = Qt::DisplayRole*/) const { if (nRole != Qt::DisplayRole) { return QVariant(); } if (Qt::Horizontal == eOrientation) { return tr("File name"); } return nSection + 1; } //======================================================================================================================== //======================================================================================================================== //======================================================================================================================== extern int CELL_HEIGHT; const QFont& getDefaultFont(); SessionsDlgImpl::SessionsDlgImpl(QWidget* pParent) : QDialog(pParent, getMainWndFlags()), Ui::SessionsDlg(), m_sessionsModel(m_vstrSessions) { { QApplication::setFont(getDefaultFont()); CELL_HEIGHT = QApplication::fontMetrics().height() + 3; //ttt2 hard-coded } setupUi(this); //vector vstrSess; string strLast; //m_pSettings = SessionSettings::getGlobalSettings(); GlobalSettings st; bool bOpenLast; st.loadSessions(m_vstrSessions, strLast, bOpenLast, m_strTempSessTempl, m_strDirSessTempl, m_strTranslation); m_pOpenLastCkB->setChecked(bOpenLast); m_pSessionsG->verticalHeader()->setResizeMode(QHeaderView::Interactive); m_pSessionsG->verticalHeader()->setMinimumSectionSize(CELL_HEIGHT + 1); // ttt0 is this initialized before creating sessions? should it be? m_pSessionsG->verticalHeader()->setDefaultSectionSize(CELL_HEIGHT + 1);//*/ m_pSessionsG->setModel(&m_sessionsModel); m_pSessionsG->horizontalHeader()->setResizeMode(0, QHeaderView::Stretch); m_pSessionsG->horizontalHeader()->setResizeMode(0, QHeaderView::Stretch); m_pSessionsG->horizontalHeader()->hide(); m_pCheckedDirModel = new CheckedDirModel(this, CheckedDirModel::NOT_USER_CHECKABLE); m_pDirectoriesT->header()->hide(); m_pDirectoriesT->setModel(m_pCheckedDirModel); m_pCheckedDirModel->setFilter(QDir::AllDirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::Drives); m_pCheckedDirModel->setSorting(QDir::IgnoreCase); m_pDirectoriesT->header()->setStretchLastSection(false); m_pDirectoriesT->header()->setResizeMode(0, QHeaderView::ResizeToContents); QPalette grayPalette (m_pDirectoriesT->palette()); grayPalette.setColor(QPalette::Base, grayPalette.color(QPalette::Disabled, QPalette::Window)); m_pDirectoriesT->setPalette(grayPalette); loadTemplates(); //m_sessionsModel.emitLayoutChanged(); connect(m_pSessionsG->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), this, SLOT(onCrtSessChanged())); connect(m_pSessionsG, SIGNAL(doubleClicked(const QModelIndex&)), this, SLOT(onSessDoubleClicked(const QModelIndex&))); //m_pSessionsG->setCurrentIndex(m_sessionsModel.index(nLast, 0)); if (strLast.empty() && !m_vstrSessions.empty()) { strLast = m_vstrSessions.back(); } if (!strLast.empty()) { selectSession(strLast); } { int nWidth, nHeight; st.loadSessionsDlgSize(nWidth, nHeight); if (nWidth > 400 && nHeight > 400) { resize(nWidth, nHeight); } } { // language int nCrt (0); const vector& vstrTranslations (TranslatorHandler::getGlobalTranslator().getTranslations()); string strTmpTranslation (m_strTranslation); // !!! needed because on_m_pTranslationCbB_currentIndexChanged() will get triggered and change m_strTranslation for (int i = 0; i < cSize(vstrTranslations); ++i) { m_pTranslationCbB->addItem(convStr(TranslatorHandler::getLanguageInfo(vstrTranslations[i]))); if (strTmpTranslation == vstrTranslations[i]) { nCrt = i; } } m_strTranslation = strTmpTranslation; m_pTranslationCbB->setCurrentIndex(nCrt); } { QAction* p (new QAction(this)); p->setShortcut(QKeySequence("F1")); connect(p, SIGNAL(triggered()), this, SLOT(onHelp())); addAction(p); } QTimer::singleShot(1, this, SLOT(onShow())); } SessionsDlgImpl::~SessionsDlgImpl() { GlobalSettings st; st.saveSessionsDlgSize(width(), height()); { /*string strLast; //m_pSettings = SessionSettings::getGlobalSettings(); //GlobalSettings st; vector v; bool bOpenLast; string strTempSessTempl; string strDirSessTempl; string strTranslation; st.loadSessions(v, strLast, bOpenLast, strTempSessTempl, strDirSessTempl, strTranslation); //ttt0 what's the point of this? no vars are used*/ saveTemplates(); st.saveSessions(m_vstrSessions, getCrtSession(), m_pOpenLastCkB->isChecked(), m_strTempSessTempl, m_strDirSessTempl, m_strTranslation, GlobalSettings::LOAD_EXTERNAL_CHANGES); } } // ttt2 generic inconsistency in what is saved depending on the user clicking the "x" button, pressing ESC, clicking other button ... // sets up the combo boxes with temp/folder session templates based on m_vstrSessions, m_strTempSessTempl, and m_strDirSessTempl void SessionsDlgImpl::loadTemplates() { m_pTempSessionCbB->clear(); m_pTempSessionCbB->addItem(tr("")); m_pDirSessionCbB->clear(); m_pDirSessionCbB->addItem(tr("")); for (int i = 0; i < cSize(m_vstrSessions); ++i) { m_pTempSessionCbB->addItem(toNativeSeparators(convStr(m_vstrSessions[i]))); if (m_strTempSessTempl == m_vstrSessions[i]) { m_pTempSessionCbB->setCurrentIndex(m_pTempSessionCbB->count() - 1); } m_pDirSessionCbB->addItem(toNativeSeparators(convStr(m_vstrSessions[i]))); if (m_strDirSessTempl == m_vstrSessions[i]) { m_pDirSessionCbB->setCurrentIndex(m_pDirSessionCbB->count() - 1); } } } // sets c and m_strDirSessTempl based on the current items in the combo boxes void SessionsDlgImpl::saveTemplates() { m_strTempSessTempl.clear(); if (m_pTempSessionCbB->currentIndex() > 0) { m_strTempSessTempl = fromNativeSeparators(convStr(m_pTempSessionCbB->currentText())); } m_strDirSessTempl.clear(); if (m_pDirSessionCbB->currentIndex() > 0) { m_strDirSessTempl = fromNativeSeparators(convStr(m_pDirSessionCbB->currentText())); } } void SessionsDlgImpl::onShow() { m_pCheckedDirModel->expandNodes(m_pDirectoriesT); } string SessionsDlgImpl::run() { if (QDialog::Accepted != exec()) { return ""; } return getCrtSession(); } void GlobalSettings::saveSessionsDlgSize(int nWidth, int nHeight) { m_pSettings->setValue("sessions/width", nWidth); m_pSettings->setValue("sessions/height", nHeight); } void GlobalSettings::loadSessionsDlgSize(int& nWidth, int& nHeight) const { nWidth = m_pSettings->value("sessions/width").toInt(); nHeight = m_pSettings->value("sessions/height").toInt(); } void GlobalSettings::saveSessionEdtSize(int nWidth, int nHeight) { m_pSettings->setValue("sessionEditor/width", nWidth); m_pSettings->setValue("sessionEditor/height", nHeight); } void GlobalSettings::loadSessionEdtSize(int& nWidth, int& nHeight) const { nWidth = m_pSettings->value("sessionEditor/width").toInt(); nHeight = m_pSettings->value("sessionEditor/height").toInt(); } void SessionsDlgImpl::onCrtSessChanged() { int i (m_pSessionsG->selectionModel()->currentIndex().row()); int n (cSize(m_vstrSessions)); if (i < 0 || i >= n) { return; } SessionSettings st (m_vstrSessions[i]); vector vstrCheckedDirs, vstrUncheckedDirs; bool bOk (st.loadDirs(vstrCheckedDirs, vstrUncheckedDirs)); if (!bOk) { //ttt2 some warning } m_pCheckedDirModel->setDirs(vstrCheckedDirs, vstrUncheckedDirs, m_pDirectoriesT); } void SessionsDlgImpl::selectSession(const string& strLast) { vector::iterator it (std::find(m_vstrSessions.begin(), m_vstrSessions.end(), strLast)); CB_ASSERT (m_vstrSessions.end() != it); int k (it - m_vstrSessions.begin()); m_sessionsModel.emitLayoutChanged(); m_pSessionsG->setCurrentIndex(m_sessionsModel.index(k, 0)); onCrtSessChanged(); } string SessionsDlgImpl::getCrtSession() const { int i (m_pSessionsG->currentIndex().row()); if (i < 0 || i >= cSize(m_vstrSessions)) { return ""; } return m_vstrSessions[i]; } string SessionsDlgImpl::getCrtSessionDir() const { string s (getCrtSession()); if (!s.empty()) { s = getParent(s); } return s; } void SessionsDlgImpl::addSession(const std::string& strSession) { int n (cSize(m_vstrSessions)); for (int i = 0; i < n; ++i) { if (m_vstrSessions[i] == strSession) //ttt2 ignore case on windows { m_vstrSessions.erase(m_vstrSessions.begin() + i); --n; break; } } m_vstrSessions.push_back(strSession); selectSession(strSession); GlobalSettings st; loadTemplates(); saveTemplates(); st.saveSessions(m_vstrSessions, strSession, m_pOpenLastCkB->isChecked(), m_strTempSessTempl, m_strDirSessTempl, m_strTranslation, GlobalSettings::LOAD_EXTERNAL_CHANGES); } void SessionsDlgImpl::on_m_pNewB_clicked() { string strSession; { SessionEditorDlgImpl dlg (this, getCrtSessionDir(), SessionEditorDlgImpl::NOT_FIRST_TIME, m_strTranslation); strSession = dlg.run(); if (strSession.empty()) { return; } } addSession(strSession); } void SessionsDlgImpl::on_m_pEditB_clicked() { string strSession (getCrtSession()); if (strSession.empty()) { return; } SessionEditorDlgImpl dlg (this, strSession); dlg.run(); //m_sessionsModel.emitLayoutChanged(); onCrtSessChanged(); } void SessionsDlgImpl::removeCrtSession() { int k (m_pSessionsG->currentIndex().row()); int n (cSize(m_vstrSessions)); if (k < 0 || k >= n) { return; } m_vstrSessions.erase(m_vstrSessions.begin() + k); //m_sessionsModel.emitLayoutChanged(); if (k == n - 1) { --k; } string strCrtSess; if (k >= 0) { strCrtSess = m_vstrSessions[k]; selectSession(strCrtSess); } else { vector v; m_pCheckedDirModel->setDirs(v, v, m_pDirectoriesT); // to uncheck all dirs } GlobalSettings st; loadTemplates(); saveTemplates(); st.saveSessions(m_vstrSessions, strCrtSess, m_pOpenLastCkB->isChecked(), m_strTempSessTempl, m_strDirSessTempl, m_strTranslation, GlobalSettings::IGNORE_EXTERNAL_CHANGES); } void SessionsDlgImpl::on_m_pEraseB_clicked() { if (m_vstrSessions.empty()) { return; } if (0 != showMessage(this, QMessageBox::Question, 1, 1, tr("Confirm"), tr("Do you really want to erase the current session?"), tr("Erase"), tr("Cancel"))) { return; } string s (getCrtSession()); removeCrtSession(); try { //ttt0 use eraseFiles() deleteFile(s); deleteFile(SessionEditorDlgImpl::getDataFileName(s)); if (fileExists(SessionEditorDlgImpl::getLogFileName(s))) { deleteFile(SessionEditorDlgImpl::getLogFileName(s)); } } catch (const std::bad_alloc&) { throw; } catch (...) //ttt2 use specific exceptions { showCritical(this, tr("Error"), tr("Failed to remove the data files associated with this session")); // maybe the files were already deleted ... return; } } //ttt2 perhaps: when choosing dirs show them in the title bar or a label void SessionsDlgImpl::on_m_pSaveAsB_clicked() { if (m_vstrSessions.empty()) { return; } //string s (getCrtSession()); QFileDialog dlg (this, tr("Save session as ..."), convStr(getCrtSessionDir()), SessionEditorDlgImpl::tr("MP3 Diags session files (*%1)").arg(SessionEditorDlgImpl::SESS_EXT)); dlg.setAcceptMode(QFileDialog::AcceptSave); if (QDialog::Accepted != dlg.exec()) { return; } QStringList fileNames (dlg.selectedFiles()); if (1 != fileNames.size()) { return; } string s (convStr(fileNames.first())); if (!endsWith(s, SessionEditorDlgImpl::SESS_EXT)) { s += SessionEditorDlgImpl::SESS_EXT; } string strCrt (getCrtSession()); if (s == strCrt) { return; } try { copyFile2(strCrt, s); copyFile2(SessionEditorDlgImpl::getDataFileName(strCrt), SessionEditorDlgImpl::getDataFileName(s)); if (fileExists(SessionEditorDlgImpl::getLogFileName(strCrt))) { copyFile2(SessionEditorDlgImpl::getLogFileName(strCrt), SessionEditorDlgImpl::getLogFileName(s)); } } catch (const std::bad_alloc&) { throw; } catch (...) { //ttt2 show errors } addSession(s); } void SessionsDlgImpl::on_m_pHideB_clicked() { if (m_vstrSessions.empty()) { return; } if (0 != showMessage(this, QMessageBox::Question, 1, 1, tr("Confirm"), tr("Do you really want to hide the current session?"), tr("Hide"), tr("Cancel"))) { return; } removeCrtSession(); } void SessionsDlgImpl::on_m_pLoadB_clicked() { QFileDialog dlg (this, tr("Choose a session file"), convStr(getCrtSessionDir()), SessionEditorDlgImpl::tr("MP3 Diags session files (*%1)").arg(SessionEditorDlgImpl::SESS_EXT)); //ttt0 add ".ini", for import from older versions dlg.setAcceptMode(QFileDialog::AcceptOpen); if (QDialog::Accepted != dlg.exec()) { return; } QStringList fileNames (dlg.selectedFiles()); if (1 != fileNames.size()) { return; } string s (convStr(fileNames.first())); CB_ASSERT (endsWith(s, SessionEditorDlgImpl::SESS_EXT)); for (int i = 0, n = cSize(m_vstrSessions); i < n; ++i) { if (m_vstrSessions[i] == s) { showCritical(this, tr("Error"), tr("The session named \"%1\" is already part of the session list").arg(toNativeSeparators(convStr(s)))); return; } } addSession(s); } void SessionsDlgImpl::on_m_pOpenB_clicked() { if (getCrtSession().empty()) { showCritical(this, tr("Error"), tr("The session list is empty. You must create a new session or load an existing one.")); return; } accept(); } void SessionsDlgImpl::on_m_pCloseB_clicked() // ttt0 redo screenshots Cancel->Close { reject(); } void SessionsDlgImpl::on_m_pTranslationCbB_currentIndexChanged(int) { const vector& vstrTranslations (TranslatorHandler::getGlobalTranslator().getTranslations()); m_strTranslation = vstrTranslations[m_pTranslationCbB->currentIndex()]; TranslatorHandler::getGlobalTranslator().setTranslation(m_strTranslation); retranslateUi(this); loadTemplates(); // !!! to retranslate "" } void SessionsDlgImpl::onSessDoubleClicked(const QModelIndex& index) { selectSession(m_vstrSessions.at(index.row())); on_m_pOpenB_clicked(); } void SessionsDlgImpl::onHelp() { openHelp("310_advanced.html"); } MP3Diags-1.2.02/src/MpegStream.h0000644000175000001440000002735711720256551015175 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef MpegStreamH #define MpegStreamH #include "DataStream.h" #include "MpegFrame.h" #include "Helpers.h" // reads a single frame class MpegStreamBase : public DataStream { StreamStateRestorer* m_pRst; protected: MpegStreamBase() : m_pRst(0) {} // serialization-only constructor MpegFrame m_firstFrame; std::streampos m_pos; MpegStreamBase(int nIndex, NoteColl& notes, std::istream& in); void setRstOk() { m_pRst->setOk(); releasePtr(m_pRst); // destroy and set to 0 } public: ~MpegStreamBase(); /*override*/ void copy(std::istream& in, std::ostream& out); // copies "m_firstFrame"; derived classes should decide if it's good enough for them /*override*/ std::streampos getPos() const { return m_pos; } /*override*/ std::streamoff getSize() const { return m_firstFrame.getSize(); } const MpegFrame& getFirstFrame() const { return m_firstFrame; } private: friend class boost::serialization::access; template void serialize(Archive& ar, const unsigned int nVersion) { if (nVersion > 0) { throw std::runtime_error("invalid version of serialized file"); } ar & boost::serialization::base_object(*this); ar & m_firstFrame; ar & m_pos; //ar & m_pRst; // 2009.04.24 - shouldn't be needed, because it's only used by the "real" constructor } }; class MpegStream : public MpegStreamBase { std::streamoff m_nSize; int m_nBitrate; bool m_bVbr; int m_nFrameCount; std::streampos m_posLastFrame; MpegFrame m_lastFrame; long long m_nTotalBps; bool m_bRemoveLastFrameCalled; enum { MIN_FRAME_COUNT = 10 }; public: MpegStream(int nIndex, NoteColl& notes, std::istream& in); /*override*/ void copy(std::istream& in, std::ostream& out); DECL_NAME(QT_TRANSLATE_NOOP("DataStream", "MPEG Audio")) /*override*/ std::string getInfo() const; /*override*/ std::streampos getPos() const { return m_pos; } /*override*/ std::streamoff getSize() const { return m_nSize; } int getBitrate() const { return m_nBitrate; } bool isVbr() const { return m_bVbr; } using MpegStreamBase::getFirstFrame; std::streampos getLastFramePos() const { return m_posLastFrame; } void removeLastFrame(); // this can only be called once; the second call will throw (it's harder and quite pointless to allow more than one call) bool isCompatible(const MpegFrameBase& frame); int getFrameCount() const { return m_nFrameCount; } MpegFrame::ChannelMode getChannelMode() const { return m_firstFrame.getChannelMode(); } bool findNextCompatFrame(std::istream& in, std::streampos posMax); // moves the read pointer to the first frame compatible with the stream; returns "false" if no such frame is found std::string getDuration() const; struct StreamTooShort // exception thrown if a stream has less than 10 frames { std::string m_strInfo; int m_nFrameCount; StreamTooShort(const std::string& strInfo, int nFrameCount) : m_strInfo(strInfo), m_nFrameCount(nFrameCount) {} }; struct UnknownHeader {}; // exception thrown if the first frame seems to be part of a (Lame) header, having a different framerate in an otherwise CBR stream void createXing(std::ostream& out); private: friend class boost::serialization::access; MpegStream() {} // serialization-only constructor template void serialize(Archive& ar, const unsigned int nVersion) { if (nVersion > 0) { throw std::runtime_error("invalid version of serialized file"); } ar & boost::serialization::base_object(*this); ar & m_nSize; ar & m_nBitrate; ar & m_bVbr; ar & m_nFrameCount; ar & m_posLastFrame; ar & m_lastFrame; ar & m_nTotalBps; ar & m_bRemoveLastFrameCalled; } }; void createXing(std::ostream& out, const MpegFrame& frame, int nFrameCount, std::streamoff nStreamSize); // throws if it can't write to the disk class XingStreamBase : public MpegStreamBase { unsigned char m_cFlags; int m_nFrameCount; int m_nByteCount; char m_toc [100]; int m_nQuality; protected: void getXingInfo(std::ostream&) const; XingStreamBase() {} // serialization-only constructor public: XingStreamBase(int nIndex, NoteColl& notes, std::istream& in); // /*override*/ void copy(std::istream& in, std::ostream& out); DECL_NAME(QT_TRANSLATE_NOOP("DataStream", "Xing Header")) /*override*/ std::string getInfo() const; std::string getInfoForXml() const; bool matchesStructure(const MpegStream&) const; // checks that there is a metch for version, layer, frequency bool matches(const MpegStream&) const; // checks that there is a metch for version, layer, frequency and frame count bool matches(const DataStream* pNext) const; // checks that pNext is MpegStream* in addition to matches(const MpegStream&) bool isBrokenByMp3Fixer(const DataStream* pNext, const DataStream* pAfterNext) const; const MpegFrame& getFrame() const { return m_firstFrame; } int getFrameCount() const { return m_nFrameCount; } using MpegStreamBase::getFirstFrame; struct NotXingStream {}; private: friend class boost::serialization::access; template void serialize(Archive& ar, const unsigned int nVersion) { if (nVersion > 0) { throw std::runtime_error("invalid version of serialized file"); } ar & boost::serialization::base_object(*this); ar & m_cFlags; ar & m_nFrameCount; ar & m_nByteCount; ar & m_toc; ar & m_nQuality; } }; class XingStream : public XingStreamBase { public: XingStream(int nIndex, NoteColl& notes, std::istream& in); private: friend class boost::serialization::access; XingStream() {} // serialization-only constructor template void serialize(Archive& ar, const unsigned int nVersion) { if (nVersion > 0) { throw std::runtime_error("invalid version of serialized file"); } ar & boost::serialization::base_object(*this); } }; class LameStream : public XingStreamBase //ttt2 read & interpret data from Lame tag { public: LameStream(int nIndex, NoteColl& notes, std::istream& in); // /*override*/ void copy(std::istream& in, std::ostream& out); DECL_NAME(QT_TRANSLATE_NOOP("DataStream", "Lame Header")) /*override*/ std::string getInfo() const; struct NotLameStream {}; private: friend class boost::serialization::access; LameStream() {} // serialization-only constructor template void serialize(Archive& ar, const unsigned int nVersion) { if (nVersion > 0) { throw std::runtime_error("invalid version of serialized file"); } ar & boost::serialization::base_object(*this); } }; class VbriStream : public MpegStreamBase // Amarok doesn't seem to care about this, so the Xing header must be used anyway for VBR to work { public: VbriStream(int nIndex, NoteColl& notes, std::istream& in); // /*override*/ void copy(std::istream& in, std::ostream& out); DECL_NAME(QT_TRANSLATE_NOOP("DataStream", "VBRI Header")) /*override*/ std::string getInfo() const; const MpegFrame& getFrame() const { return m_firstFrame; } struct NotVbriStream {}; private: friend class boost::serialization::access; VbriStream() {} // serialization-only constructor template void serialize(Archive& ar, const unsigned int nVersion) { if (nVersion > 0) { throw std::runtime_error("invalid version of serialized file"); } ar & boost::serialization::base_object(*this); } }; //------------------------------------------------------------------------------------------------------- class Id3V1Stream : public DataStream, public TagReader { char m_data [128]; std::streampos m_pos; // needed only to display info enum TestResult { BAD, ZERO_PADDED, SPACE_PADDED, MIXED_PADDED, NOT_PADDED }; static TestResult checkId3V1String(const char* p, int nSize); // makes sure that a valid string is stored at the address given, meaning no chars smaller than 32; well, except for 0: 0 isn't really valid, as the fields are supposed to be padded with spaces at the right, but some tools use 0 anyway static bool isLegal(char c); std::string getStr(int nAddr, int nMaxSize) const; // returns the string located at nAddr, removing trailing spaces; the result is in UTF8 format enum Version { V10, V11, V11b }; Version m_eVersion; int getCommSize() const; public: Id3V1Stream(int nIndex, NoteColl& notes, std::istream& in); /*override*/ void copy(std::istream& in, std::ostream& out); DECL_RD_NAME("ID3V1") /*override*/ std::string getInfo() const; /*override*/ std::streampos getPos() const { return m_pos; } /*override*/ std::streamoff getSize() const { return 128; } const char* getVersion() const; struct NotId3V1Stream {}; // ================================ TagReader ========================================= /*override*/ std::string getTitle(bool* pbFrameExists = 0) const; /*override*/ std::string getArtist(bool* pbFrameExists = 0) const; /*override*/ std::string getTrackNumber(bool* pbFrameExists = 0) const; /*override*/ TagTimestamp getTime(bool* pbFrameExists = 0) const; /*override*/ std::string getGenre(bool* pbFrameExists = 0) const; /*override*/ ImageInfo getImage(bool* /*pbFrameExists*/ = 0) const { throw NotSupportedOp(); } /*override*/ std::string getAlbumName(bool* pbFrameExists = 0) const; /*override*/ std::string getOtherInfo() const; /*override*/ SuportLevel getSupport(Feature) const; private: friend class boost::serialization::access; Id3V1Stream() {} // serialization-only constructor template void serialize(Archive& ar, const unsigned int nVersion) { if (nVersion > 0) { throw std::runtime_error("invalid version of serialized file"); } ar & boost::serialization::base_object(*this); ar & m_data; ar & m_pos; ar & m_eVersion; } }; const char* getId3V1Genre(int n); // doesn't throw; for invalid params returns "" #endif // ifndef MpegStreamH MP3Diags-1.2.02/src/Id3V230Stream.h0000644000175000001440000001625411720256551015271 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef Id3V230StreamH #define Id3V230StreamH #include "Id3V2Stream.h" // Frame of an ID3V2.3.0 tag struct Id3V230Frame : public Id3V2Frame { Q_DECLARE_TR_FUNCTIONS(Id3V230Frame) public: Id3V230Frame(NoteColl& notes, std::istream& in, std::streampos pos, bool bHasUnsynch, std::streampos posNext, StringWrp* pFileName); Id3V230Frame(const std::string& strName, std::vector& vcData); // needed by Id3V230StreamWriter::addBinaryFrame(), so objects created with this constructor don't get serialized; destroys vcData by doing a swap for its own representation /*override*/ ~Id3V230Frame(); /*override*/ bool discardOnChange() const; private: // may return multiple null characters; it's the job of getUtf8String() to deal with them; // chars after the first null are considered comments (or after the second null, for TXXX); /*override*/ std::string getUtf8StringImpl() const; friend class boost::serialization::access; Id3V230Frame() {} // serialization-only constructor template void serialize(Archive& ar, const unsigned int nVersion) { if (nVersion > 0) { throw std::runtime_error("invalid version of serialized file"); } ar & boost::serialization::base_object(*this); } }; class Id3V230Stream : public Id3V2StreamBase { Q_DECLARE_TR_FUNCTIONS(Id3V230Stream) public: Id3V230Stream(int nIndex, NoteColl& notes, std::istream& in, StringWrp* pFileName, bool bAcceptBroken = false); //typedef typename Id3V2Stream::NotId3V2 NotId3V230; DECL_RD_NAME("ID3V2.3.0") /*override*/ TagTimestamp getTime(bool* pbFrameExists = 0) const; /*override*/ void setTrackTime(const TagTimestamp&) { throw NotSupportedOp(); } /*override*/ SuportLevel getSupport(Feature) const; private: friend class boost::serialization::access; Id3V230Stream() {} // serialization-only constructor template void serialize(Archive& ar, const unsigned int nVersion) { if (nVersion > 0) { throw std::runtime_error("invalid version of serialized file"); } ar & boost::serialization::base_object(*this); } }; class Id3V240Stream; class CommonData; class Id3V230StreamWriter { std::vector m_vpOwnFrames; std::vector m_vpAllFrames; bool m_bKeepOneValidImg; bool m_bFastSave; const std::string m_strDebugFileName; // used only to show debug info from asserts public: enum { KEEP_ALL_IMG, KEEP_ONE_VALID_IMG }; Id3V230StreamWriter(bool bKeepOneValidImg, bool bFastSave, Id3V2StreamBase*, const std::string& strDebugFileName); // if bKeepOneValidImg is true, at most an APIC frame is kept, and it has to be valid or at least link; Id3V2StreamBase* may be 0; ~Id3V230StreamWriter(); void removeFrames(const std::string& strName, int nPictureType = -1); // if multiple frames with the same name exist, they are all removed; asserts that nPictureType is -1 for non-APIC frames; if nPictureType is -1 and strName is APIC, it removes all APIC frames //void removeApicFrames(const std::vector& vcData, int nPictureType); // an APIC frame is removed iff it has the image in vcData or the type nPictureType (or both) void addTextFrame(const std::string& strName, const std::string& strVal); // strVal is UTF8; the frame will use ASCII if possible and UTF16 otherwise (so if there's a char with a code above 127, UTF16 gets used, to avoid codepage issues for codes between 128 and 255); nothing is added if strVal is empty; all zeroes are saved and not considered terminators; void addBinaryFrame(const std::string& strName, std::vector& vcData); // destroys vcData by doing a swap for its own representation; asserts that strName is not APIC // the image type is ignored; images are always added as cover; // if there is an APIC frame with the same image, it is removed (it doesn't matter if it has different type, description ...); // if cover image already exists it is removed; void addImg(std::vector& vcData); void addNonOwnedFrame(const Id3V2Frame* p); void setRecTime(const TagTimestamp& time); // this only changes the frames that correspond to the active settings in the configuration; // if WMP handling is disabled, TPE2 is left untouched; if WMP handling is enabled, TPE2 is either removed or set to "Various Artists", based on "b" // if iTunes handling is disabled, TCON is left untouched; if WMP handling is enabled, TCON is either removed or set to "1", based on "b" void setVariousArtists(bool b); // throws WriteError if it cannot write, including the case when nTotalSize is too small; // if nTotalSize is >0, the padding will be be whatever is left; // if nTotalSize is <0 and m_bFastSave is true, there will be a padding of around ImageInfo::MAX_IMAGE_SIZE+Id3V2Expander::EXTRA_SPACE; // if (nTotalSize is <0 and m_bFastSave is false) or if nTotalSize is 0 (regardless of m_bFastSave), there will be a padding of between DEFAULT_EXTRA_SPACE and DEFAULT_EXTRA_SPACE + 511; // (0 discards extra padding regardless of m_bFastSave) void write(std::ostream& out, int nTotalSize = -1) const; //bool equalTo(Id3V2StreamBase* pId3V2Stream) const; // returns true if all of these happen: pId3V2Stream is ID3V2.3.0, no unsynch is used, the frames are identical except for their order; padding is ignored; bool contentEqualTo(Id3V2StreamBase* pId3V2Stream) const; // returns true if the frames are identical except for their order; padding, unsynch and version is ignored; bool isEmpty() const { return m_vpAllFrames.empty(); } static const int DEFAULT_EXTRA_SPACE; }; #endif // ifndef Id3V230StreamH MP3Diags-1.2.02/src/ConfigDlgImpl.cpp0000644000175000001440000021225411734661304016133 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include "ConfigDlgImpl.h" #include "StructuralTransformation.h" #include "Transformation.h" #include "OsFile.h" #include "Helpers.h" #include "Id3Transf.h" #include "Id3V230Stream.h" #include "MpegStream.h" #include "CommonData.h" #include "StoredSettings.h" #include "ColumnResizer.h" #include "Widgets.h" #include "Translation.h" ////#include //ttt remove using namespace std; using namespace pearl; //ttt2 simple / detailed files tab, with "simple" only allowing to "create backups in ..." //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== class TransfListElem : public ListElem { /*override*/ std::string getText(int nCol) const; const Transformation* m_pTransformation; // doesn't own the pointer //CommonData* m_pCommonData; public: TransfListElem(const Transformation* pTransformation/*, CommonData* pCommonData*/) : m_pTransformation(pTransformation)/*, m_pCommonData(pCommonData)*/ {} const Transformation* getTransformation() const { return m_pTransformation; } }; /*override*/ std::string TransfListElem::getText(int nCol) const { if (0 == nCol) { return convStr(Transformation::tr(m_pTransformation->getActionName())); } return convStr(Transformation::tr(m_pTransformation->getDescription())); } class CustomTransfListPainter : public ListPainter { Q_DECLARE_TR_FUNCTIONS(CustomTransfListPainter) /*override*/ int getColCount() const { return 2; } /*override*/ std::string getColTitle(int nCol) const { return 0 == nCol ? convStr(tr("Action")) : convStr(tr("Description")); } /*override*/ void getColor(int /*nIndex*/, int /*nColumn*/, bool /*bSubList*/, QColor& /*bckgColor*/, QColor& /*penColor*/, double& /*dGradStart*/, double& /*dGradEnd*/) const { } /*override*/ int getColWidth(int /*nCol*/) const { return -1; } // positive values are used for fixed widths, while negative ones are for "stretched" /*override*/ int getHdrHeight() const { return CELL_HEIGHT; } /*override*/ Qt::Alignment getAlignment(int /*nCol*/) const { return Qt::AlignTop | Qt::AlignLeft; } /*override*/ std::string getTooltip(TooltipKey eTooltipKey) const; /*override*/ void reset(); const SubList& m_vDefaultSel; // to be used by reset() public: CustomTransfListPainter(const CommonData* pCommonData, const SubList& vOrigSel, const SubList& vSel, const SubList& vDefaultSel); ~CustomTransfListPainter(); }; CustomTransfListPainter::CustomTransfListPainter(const CommonData* pCommonData, const SubList& vOrigSel, const SubList& vSel, const SubList& vDefaultSel) : ListPainter(""), m_vDefaultSel(vDefaultSel) { //qDebug("init CustomTransfListPainter with origsel %d and sel %d", cSize(vOrigSel), cSize(vSel)); for (int i = 0, n = cSize(pCommonData->getAllTransf()); i < n; ++i) { m_vpOrigAll.push_back(new TransfListElem(pCommonData->getAllTransf()[i])); } m_vpResetAll = m_vpOrigAll; // !!! no new pointers m_vOrigSel = vOrigSel; m_vSel = vSel; } CustomTransfListPainter::~CustomTransfListPainter() { clearPtrContainer(m_vpOrigAll); } /*override*/ string CustomTransfListPainter::getTooltip(TooltipKey eTooltipKey) const { switch (eTooltipKey) { case SELECTED_G: return "";//"Notes to be included"; case AVAILABLE_G: return "";//"Available notes"; case ADD_B: return convStr(tr("Add selected transformation(s)")); case DELETE_B: return convStr(tr("Remove selected transformation(s)")); case ADD_ALL_B: return "";//"Add all transformations"; case DELETE_ALL_B: return "";//"Remove all transformations"; case RESTORE_DEFAULT_B: return convStr(tr("Restore current list to its default value")); case RESTORE_OPEN_B: return convStr(tr("Restore current list to the configuration it had when the window was open")); default: CB_ASSERT(false); } } /*override*/ void CustomTransfListPainter::reset() { m_vSel = m_vDefaultSel; } //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== class VisibleTransfPainter : public ListPainter { Q_DECLARE_TR_FUNCTIONS(VisibleTransfPainter) /*override*/ int getColCount() const { return 2; } /*override*/ std::string getColTitle(int nCol) const { return 0 == nCol ? convStr(tr("Action")) : convStr(tr("Description")); } /*override*/ void getColor(int /*nIndex*/, int /*nColumn*/, bool /*bSubList*/, QColor& /*bckgColor*/, QColor& /*penColor*/, double& /*dGradStart*/, double& /*dGradEnd*/) const { } /*override*/ int getColWidth(int /*nCol*/) const { return -1; } // positive values are used for fixed widths, while negative ones are for "stretched" /*override*/ int getHdrHeight() const { return CELL_HEIGHT; } /*override*/ Qt::Alignment getAlignment(int /*nCol*/) const { return Qt::AlignTop | Qt::AlignLeft; } /*override*/ std::string getTooltip(TooltipKey eTooltipKey) const; /*override*/ void reset(); const SubList& m_vDefaultSel; // to be used by reset() public: VisibleTransfPainter(const CommonData* pCommonData, const SubList& vOrigSel, const SubList& vSel, const SubList& vDefaultSel); ~VisibleTransfPainter(); }; VisibleTransfPainter::VisibleTransfPainter(const CommonData* pCommonData, const SubList& vOrigSel, const SubList& vSel, const SubList& vDefaultSel) : ListPainter(""), m_vDefaultSel(vDefaultSel) { const vector& v (pCommonData->getAllTransf()); for (int i = 0, n = cSize(v); i < n; ++i) { const Transformation* p (v[i]); m_vpOrigAll.push_back(new TransfListElem(p)); } m_vpResetAll = m_vpOrigAll; // !!! no new pointers m_vOrigSel = vOrigSel; m_vSel = vSel; } VisibleTransfPainter::~VisibleTransfPainter() { clearPtrContainer(m_vpOrigAll); } /*override*/ string VisibleTransfPainter::getTooltip(TooltipKey eTooltipKey) const { switch (eTooltipKey) { case SELECTED_G: return "";//"Notes to be included"; case AVAILABLE_G: return "";//"Available notes"; case ADD_B: return convStr(tr("Add selected transformation(s)")); case DELETE_B: return convStr(tr("Remove selected transformation(s)")); case ADD_ALL_B: return "";//"Add all transformations"; case DELETE_ALL_B: return "";//"Remove all transformations"; case RESTORE_DEFAULT_B: return convStr(tr("Restore current list to its default value")); case RESTORE_OPEN_B: return convStr(tr("Restore current list to the configuration it had when the window was open")); default: CB_ASSERT(false); } } /*override*/ void VisibleTransfPainter::reset() { m_vSel = m_vDefaultSel; } //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== namespace BpsInfo { int s_aValidBps[] = { 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 288, 320, 10000 }; //int s_aValidBps[] = { 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 10000 }; int getIndex(int nVal) { if (nVal >= 10000) { return 0; } int i (0); for (; s_aValidBps[i] < nVal; ++i) {} if (10000 == s_aValidBps[i]) { return i - 1; } return i; } const QStringList& getValueList() { static QStringList lst; if (lst.isEmpty()) { for (int i = 0; ; ++i) { if (10000 == s_aValidBps[i]) { break; } lst << QString::number(s_aValidBps[i]); } } return lst; } } // namespace BpsInfo //===================================================================================================================== /* class EventFilter : public QObject { bool eventFilter(QObject* pObj, QEvent* pEvent) { if (QEvent::Paint != pEvent->type()) { qDebug("%s %d", pObj->objectName().toUtf8().constData(), (int)pEvent->type()); } static bool b (true); if (QEvent::Hide == pEvent->type()) { b = !b; if (b) { qDebug("not allowed"); pEvent->ignore(); return true; } } return QObject::eventFilter(pObj, pEvent); } public: EventFilter(QObject* pParent) : QObject(pParent) {} }; */ ConfigDlgImpl::ConfigDlgImpl(TransfConfig& transfCfg, CommonData* pCommonData, QWidget* pParent, bool bFull) : QDialog(pParent, getDialogWndFlags()), Ui::ConfigDlg(), NoteListPainterBase(pCommonData, convStr(tr(""))), m_transfCfg(transfCfg), m_pCommonData(pCommonData), m_bFull(bFull), m_pCustomTransfListPainter(0), m_pCustomTransfDoubleList(0), m_vvnCustomTransf(pCommonData->getCustomTransf()), m_nCurrentTransf(-1), m_vvnDefaultCustomTransf(CUSTOM_TRANSF_CNT), m_pVisibleTransfPainter(0), m_vnVisibleTransf(pCommonData->getVisibleTransf()), m_bExtToolChanged(false), m_vExternalToolInfos(pCommonData->m_vExternalToolInfos) { setupUi(this); #ifdef DISABLE_CHECK_FOR_UPDATES m_pCheckForUpdatesCkB->hide(); #endif if (!bFull) { m_pMainTabWidget->removeTab(8); m_pMainTabWidget->removeTab(7); m_pMainTabWidget->removeTab(6); m_pMainTabWidget->removeTab(5); m_pMainTabWidget->removeTab(4); m_pMainTabWidget->removeTab(2); m_pMainTabWidget->removeTab(1); m_pLocaleGrp->hide(); m_pCaseGrp->hide(); //m_pRenameW->hide(); m_pOthersMiscGrp->hide(); //m_pScanAtStartupCkB->hide(); //m_pShowDebugCkB->hide(); //m_pShowSessCkB->hide(); //m_pIconConfW->hide(); //m_pFontsW->hide(); m_pNormalizeGrp->hide(); m_pRenamerGrp->hide(); } m_pSourceDirF->hide(); { m_pExternalToolsModel = new ExternalToolsModel(this); m_pExternalToolsG->setModel(m_pExternalToolsModel); m_pExternalToolsG->verticalHeader()->setMinimumSectionSize(CELL_HEIGHT); m_pExternalToolsG->verticalHeader()->setDefaultSectionSize(CELL_HEIGHT); connect(m_pExternalToolsG->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), this, SLOT(onExternalToolsGCurrentChanged())); connect(m_pExternalToolsG->selectionModel(), SIGNAL(currentChanged(const QModelIndex&, const QModelIndex &)), this, SLOT(onExternalToolsGCurrentChanged())); } int nWidth, nHeight; m_pCommonData->m_settings.loadConfigSize(nWidth, nHeight); if (nWidth > 400 && nHeight > 400) { resize(nWidth, nHeight); } else { defaultResize(*this); } { // set text for ID3V2 codepage test QString qstr; const Mp3Handler* p (m_pCommonData->getCrtMp3Handler()); if (0 != p) { const vector& vpStreams (p->getStreams()); for (int i = 0, n = cSize(vpStreams); i < n; ++i) { DataStream* p (vpStreams[i]); Id3V2StreamBase* pId3V2 (dynamic_cast(p)); if (0 != pId3V2) { QString q; const vector& vpFrames (pId3V2->getFrames()); for (int i = 0, n = cSize(vpFrames); i < n; ++i) { const Id3V2Frame* pFrm (vpFrames[i]); if ('T' == pFrm->m_szName[0] && pFrm->m_bHasLatin1NonAscii) { q += convStr(pFrm->getUtf8String()) + "\n"; } } if (!q.isEmpty()) { qstr += "*************************************************************************\nID3V2\n" + q + "\n\n"; } } Id3V1Stream* pId3V1 (dynamic_cast(p)); if (0 != pId3V1) { vector v; QString q; v.push_back(pId3V1->getTitle()); v.push_back(pId3V1->getArtist()); v.push_back(pId3V1->getAlbumName()); v.push_back(pId3V1->getOtherInfo()); for (int i = 0; i < cSize(v); ++i) { for (int j = 0; j < cSize(v[i]); ++j) { unsigned char c (v[i][j]); if (c >= 128) { q += convStr(v[i]) + "\n"; break; } } } if (!q.isEmpty()) { qstr += "*************************************************************************\nID3V1\n" + q + "\n\n"; } } } } if (qstr.isEmpty()) { qstr = tr("If you don't know exactly what codepage you want, it's better to make current a file having an ID3V2 tag that contains text frames using the Latin-1 encoding and having non-ASCII characters. Then the content of those frames will replace this text, allowing you to decide which codepage is a match for your file. ID3V1 tags are supported as well, if you want to copy data from them to ID3V2."); } m_codepageTestText = qstr.toLatin1(); } /*EventFilter* pEventFilter (new EventFilter(this)); tab->installEventFilter(pEventFilter); //tab_3->installEventFilter(pEventFilter); */ { { delete m_pRemovableL; delete m_pFileSettingsW->layout(); m_pFileSettingsLayout = new QStackedLayout(m_pFileSettingsW); //m_pFileSettingsLayout->setContentsMargins(0, 50, 50, 0); //m_pFileSettingsW->setLayout(m_pFileSettingsLayout); m_pFileSettingsLayout->addWidget(m_pSimpleViewTab); m_pFileSettingsLayout->addWidget(m_pFullViewTab); delete m_pDetailsTabWidget; } m_pPODestE->setText(toNativeSeparators(convStr(getSepTerminatedDir(transfCfg.getProcOrigDir())))); m_pPODest2E->setText(m_pPODestE->text()); m_pUODestE->setText(toNativeSeparators(convStr(getSepTerminatedDir(transfCfg.getUnprocOrigDir())))); m_pProcDestE->setText(toNativeSeparators(convStr(getSepTerminatedDir(transfCfg.getProcessedDir())))); m_pTempDestE->setText(toNativeSeparators(convStr(getSepTerminatedDir(transfCfg.getTempDir())))); m_pCompDestE->setText(toNativeSeparators(convStr(getSepTerminatedDir(transfCfg.getCompDir())))); m_pSrcDirE->setText(toNativeSeparators(convStr(getSepTerminatedDir(transfCfg.getSrcDir())))); //setSimpleViewOpt(transfCfg.m_options); //!!! pointless; doesn't work if keepTime is true; anyway, the on_m_pSimpleViewB_clicked() call below will select the right radiobutton setFullViewOpt(transfCfg.m_options); // !!! the buttons should be set for both views setSimpleViewOpt(transfCfg.m_options); TransfConfig::Options opt (transfCfg.m_options); if (opt.asBackup() == opt || opt.asNonBackup() == opt) { m_pSimpleViewB->setChecked(true); on_m_pSimpleViewB_clicked(); } else { m_pFullViewB->setChecked(true); on_m_pFullViewB_clicked(); } m_pKeepOrigTimeCkB->setChecked(transfCfg.m_options.m_bKeepOrigTime); } // -------------------------------------------- ignored ------------------------------------------------------ m_pIgnoredNotesListHldr->setLayout(new QHBoxLayout()); const vector& vpAllNotes (Notes::getAllNotes()); for (int i = 0, n = cSize(vpAllNotes); i < n; ++i) { m_vpOrigAll.push_back(new NoteListElem(vpAllNotes[i], m_pCommonData)); } m_vOrigSel = pCommonData->getIgnoredNotes(); //cout << "constr:\n"; printContainer(pCommonData->m_vnIgnoredNotes, cout); m_vSel = m_vOrigSel; m_pDoubleList = new DoubleList( *this, DoubleList::ADD_ALL | DoubleList::DEL_ALL | DoubleList::RESTORE_OPEN | DoubleList::RESTORE_DEFAULT, DoubleList::SINGLE_UNSORTABLE, convStr(tr("Other notes")), convStr(tr("Ignore notes")), this); m_pIgnoredNotesListHldr->layout()->addWidget(m_pDoubleList); m_pIgnoredNotesListHldr->layout()->setContentsMargins(0, 0, 0, 0); // -------------------------------------------- custom transformations ------------------------------------------------------ m_pTransfListHldr->setLayout(new QHBoxLayout()); m_pTransfListHldr->layout()->setContentsMargins(0, 0, 0, 0); m_vpTransfButtons.push_back(m_pCustomTransform1B); m_vpTransfButtons.push_back(m_pCustomTransform2B); m_vpTransfButtons.push_back(m_pCustomTransform3B); m_vpTransfButtons.push_back(m_pCustomTransform4B); // CUSTOM_TRANSF_CNT m_defaultPalette = m_pCustomTransform1T->palette(); m_wndPalette = m_pCustomTransform1T->palette(); m_wndPalette.setColor(QPalette::Base, m_wndPalette.color(QPalette::Disabled, QPalette::Window)); m_vpTransfLabels.push_back(m_pCustomTransform1T); m_vpTransfLabels.push_back(m_pCustomTransform2T); m_vpTransfLabels.push_back(m_pCustomTransform3T); m_vpTransfLabels.push_back(m_pCustomTransform4T); // CUSTOM_TRANSF_CNT for (int i = 0; i < CUSTOM_TRANSF_CNT; ++i) { m_vpTransfLabels[i]->setPalette(m_wndPalette); refreshTransfText(i); initDefaultCustomTransf(i, m_vvnDefaultCustomTransf, m_pCommonData); } selectCustomTransf(0); const QStringList& freqLst (BpsInfo::getValueList()); { m_pQualStCbrCbB->insertItems(0, freqLst); m_pQualStCbrCbB->setCurrentIndex(BpsInfo::getIndex(m_pCommonData->getQualThresholds().m_nStereoCbr/1000)); } { m_pQualJtStCbrCbB->insertItems(0, freqLst); m_pQualJtStCbrCbB->setCurrentIndex(BpsInfo::getIndex(m_pCommonData->getQualThresholds().m_nJointStereoCbr/1000)); } { m_pQualDlChCbrCbB->insertItems(0, freqLst); m_pQualDlChCbrCbB->setCurrentIndex(BpsInfo::getIndex(m_pCommonData->getQualThresholds().m_nDoubleChannelCbr/1000)); } // ttt2 try and create some custom widget based on QSpinBox for CBR, so only valid frequencies can be chosen; m_pQualStVbrSB->setValue(m_pCommonData->getQualThresholds().m_nStereoVbr/1000); m_pQualJtStVbrSB->setValue(m_pCommonData->getQualThresholds().m_nJointStereoVbr/1000); m_pQualDlChVbrSB->setValue(m_pCommonData->getQualThresholds().m_nDoubleChannelVbr/1000); { // locale QStringList lNames; set s; //set s1; QList l (QTextCodec::availableCodecs()); for (int i = 0, n = l.size(); i < n; ++i) { //lNames << QString::fromLatin1(l[i]); //lNames << QTextCodec::codecForName(l[i])->name(); s.insert(QTextCodec::codecForName(l[i])->name()); // a codec is known by several names; by doing this we eliminate redundant names and make the list a lot smaller //s.insert(QTextCodec::codecForName(l[i])->name() + QString::fromLatin1(l[i])); //qDebug("%s", (QString::fromLatin1(l[i]) + " / " + QTextCodec::codecForName(l[i])->name()).toUtf8().data()); //s1.insert(QTextCodec::codecForName(l[i])->name() + " / " + QString::fromLatin1(l[i])); } for (set::const_iterator it = s.begin(); it != s.end(); ++it) { lNames << *it; } //for (set::const_iterator it = s1.begin(); it != s1.end(); ++it) { qDebug("%s", it->toUtf8().data()); } //lNames.sort(); m_pId3LocaleCbB->addItems(lNames); int n (m_pId3LocaleCbB->findText(m_pCommonData->m_locale)); if (-1 == n) { n = 0; } m_pId3LocaleCbB->setCurrentIndex(n); } { // case QStringList lNames; /*lNames << "Lower case: first part. second part."; lNames << "Upper case: FIRST PART. SECOND PART."; lNames << "Title case: First Part. Second Part."; lNames << "Sentence case: First part. Second part.";*/ // ttt2 perhaps put this back; as of 2009.10.15, "." is no longer supported as a sentence ending lNames << tr("lower case"); lNames << tr("UPPER CASE"); lNames << tr("Title Case"); lNames << tr("Sentence case"); m_pArtistsCaseCbB->addItems(lNames); m_pOthersCaseCbB->addItems(lNames); m_pArtistsCaseCbB->setCurrentIndex((int)m_pCommonData->m_eCaseForArtists); m_pOthersCaseCbB->setCurrentIndex((int)m_pCommonData->m_eCaseForOthers); } { // colors m_vpColButtons.push_back(m_pCol0B); m_vpColButtons.push_back(m_pCol1B); m_vpColButtons.push_back(m_pCol2B); m_vpColButtons.push_back(m_pCol3B); m_vpColButtons.push_back(m_pCol4B); m_vpColButtons.push_back(m_pCol5B); m_vpColButtons.push_back(m_pCol6B); m_vpColButtons.push_back(m_pCol7B); m_vpColButtons.push_back(m_pCol8B); m_vpColButtons.push_back(m_pCol9B); m_vpColButtons.push_back(m_pCol10B); m_vpColButtons.push_back(m_pCol11B); m_vpColButtons.push_back(m_pCol12B); m_vpColButtons.push_back(m_pCol13B); m_vNoteCategColors = m_pCommonData->m_vNoteCategColors; for (int i = 0; i < cSize(m_vpColButtons); ++i) { setBtnColor(i); } } { // tag editor m_pWarnOnNonSeqTracksCkB->setChecked(m_pCommonData->m_bWarnOnNonSeqTracks); m_pWarnOnPasteToNonSeqTracksCkB->setChecked(m_pCommonData->m_bWarnPastingToNonSeqTracks); } { //misc m_pScanAtStartupCkB->setChecked(m_pCommonData->m_bScanAtStartup); m_pFastSaveCkB->setChecked(m_pCommonData->useFastSave()); m_pShowExportCkB->setChecked(m_pCommonData->m_bShowExport); m_pShowDebugCkB->setChecked(m_pCommonData->m_bShowDebug); m_pShowSessCkB->setChecked(m_pCommonData->m_bShowSessions); m_pShowCustomCloseButtonsCkB->setChecked(m_pCommonData->m_bShowCustomCloseButtons); m_pNormalizerE->setText(convStr(m_pCommonData->m_strNormalizeCmd)); m_pKeepNormOpenCkB->setChecked(m_pCommonData->m_bKeepNormWndOpen); switch (m_pCommonData->m_eAssignSave) { case CommonData::SAVE: m_pAssgnSaveRB->setChecked(true); break; case CommonData::DISCARD: m_pAssgnDiscardRB->setChecked(true); break; case CommonData::ASK: m_pAssgnAskRB->setChecked(true); break; default: CB_ASSERT (false); } switch (m_pCommonData->m_eNonId3v2Save) { case CommonData::SAVE: m_pNonId3v2SaveRB->setChecked(true); break; case CommonData::DISCARD: m_pNonId3v2DiscardRB->setChecked(true); break; case CommonData::ASK: m_pNonId3v2AskRB->setChecked(true); break; default: CB_ASSERT (false); } m_pIconSizeSB->setValue(m_pCommonData->m_nMainWndIconSize); m_pAutoSizeIconsCkB->setChecked(m_pCommonData->m_bAutoSizeIcons); m_pKeepOneValidImgCkB->setChecked(m_pCommonData->m_bKeepOneValidImg); m_pWmpCkB->setChecked(m_pCommonData->m_bWmpVarArtists); m_pItunesCkB->setChecked(m_pCommonData->m_bItunesVarArtists); m_pMaxImgSizeSB->setValue(ImageInfo::MAX_IMAGE_SIZE/1024); m_pTraceToFileCkB->setChecked(m_pCommonData->isTraceToFileEnabled()); m_pInvalidCharsE->setText(convStr(m_pCommonData->m_strRenamerInvalidChars)); m_pInvalidReplacementE->setText(convStr(m_pCommonData->m_strRenamerReplacementString)); m_pCheckForUpdatesCkB->setChecked("yes" == m_pCommonData->m_strCheckForNewVersions); m_generalFont = m_pCommonData->getNewGeneralFont(); m_pDecrLabelFontSB->setValue(m_pCommonData->getLabelFontSizeDecr()); m_fixedFont = m_pCommonData->getNewFixedFont(); setFontLabels(); { // language m_pFlagIconL->setMaximumSize(CELL_HEIGHT*5, CELL_HEIGHT*3); int nCrt (0); const vector& vstrTranslations (TranslatorHandler::getGlobalTranslator().getTranslations()); string m_strTranslation (m_pCommonData->m_strTranslation); //ttt0 make member; ttt0 retranslate dynamically on on_m_pTranslationCbB_currentIndexChanged() string strTmpTranslation (m_strTranslation); // !!! needed because on_m_pTranslationCbB_currentIndexChanged() will get triggered and change m_strTranslation for (int i = 0; i < cSize(vstrTranslations); ++i) { m_pTranslationCbB->addItem(convStr(TranslatorHandler::getLanguageInfo(vstrTranslations[i]))); if (strTmpTranslation == vstrTranslations[i]) { nCrt = i; } } m_strTranslation = strTmpTranslation; m_pTranslationCbB->setCurrentIndex(nCrt); } } { initDefaultVisibleTransf(m_vnDefaultVisibleTransf, m_pCommonData); m_pVisibleTransfPainter = new VisibleTransfPainter(m_pCommonData, m_pCommonData->getVisibleTransf(), m_vnVisibleTransf, m_vnDefaultVisibleTransf); m_pVisibleTransfDoubleList = new DoubleList( *m_pVisibleTransfPainter, DoubleList::RESTORE_OPEN | DoubleList::RESTORE_DEFAULT, DoubleList::SINGLE_SORTABLE, convStr(tr("Invisible transformations")), convStr(tr("Visible transformations")), this); m_pVisibleTransformsHndlr->setLayout(new QHBoxLayout()); m_pVisibleTransformsHndlr->layout()->setContentsMargins(0, 0, 0, 0); m_pVisibleTransformsHndlr->layout()->addWidget(m_pVisibleTransfDoubleList); } m_pSrcDirE->setFocus(); { QAction* p (new QAction(this)); p->setShortcut(QKeySequence("F1")); connect(p, SIGNAL(triggered()), this, SLOT(onHelp())); addAction(p); } m_pInvalidCharsE->setToolTip(tr("Characters in this list get replaced with the string below, in \"Replace with\"\n\n" "An underlined font is used to allow spaces to be seen")); m_pInvalidReplacementE->setToolTip(tr("This string replaces invalid characters in the file renamer\"\n\n" "An underlined font is used to allow spaces to be seen")); if (m_bFull) { m_pShellTempSessCkB->setChecked(ShellIntegration::isTempSessionEnabled()); m_pShellVisibleSessCkB->setChecked(ShellIntegration::isVisibleSessionEnabled()); m_pShellHiddenSessCkB->setChecked(ShellIntegration::isHiddenSessionEnabled()); bool b (ShellIntegration::isShellIntegrationEditable()); m_pShellTempSessCkB->setEnabled(b); m_pShellVisibleSessCkB->setEnabled(b); m_pShellHiddenSessCkB->setEnabled(b); string strShellErr (ShellIntegration::getShellIntegrationError()); m_pShellErrorL->setText(convStr(strShellErr)); QTimer::singleShot(1, this, SLOT(onResizeDelayed())); } #if 0 installEventFilter(this); //tab_8->installEventFilter(this); //m_pExtToolNameE->installEventFilter(this); #endif } void ConfigDlgImpl::setBtnColor(int n) { // QPalette pal (m_vpColButtons[n]->palette()); //QPalette pal (m_pCol0B->palette()); /* pal.setBrush(QPalette::Button, m_vNoteCategColors[n]); pal.setBrush(QPalette::Window, m_vNoteCategColors[n]); pal.setBrush(QPalette::Midlight, QColor(255, 0, 0)); pal.setBrush(QPalette::Dark, QColor(255, 0, 0)); pal.setBrush(QPalette::Mid, QColor(255, 0, 0)); pal.setBrush(QPalette::Shadow, QColor(255, 0, 0));*/ //m_vpColButtons[n]->setPalette(pal); int f (QApplication::style()->pixelMetric(QStyle::PM_DefaultFrameWidth, 0, m_vpColButtons.at(n)) + 2); //ttt2 hard-coded "2" int w (m_vpColButtons[n]->width() - f), h (m_vpColButtons[n]->height() - f); QPixmap pic (w, h); QPainter pntr (&pic); pntr.fillRect(0, 0, w, h, m_vNoteCategColors.at(n)); m_vpColButtons[n]->setIcon(pic); m_vpColButtons[n]->setIconSize(QSize(w, h)); } void ConfigDlgImpl::onButtonClicked(int n) { QColor c (QColorDialog::getColor(m_vNoteCategColors.at(n), this)); if (!c.isValid()) { return; } m_vNoteCategColors[n] = c; setBtnColor(n); } void ConfigDlgImpl::on_m_pResetColorsB_clicked() { QColor c (getDefaultBkgCol()); for (int i = 0; i < cSize(m_vNoteCategColors) - 1; ++i) // !!! "-1" because there is no configuration for CUSTOM colors { m_vNoteCategColors[i] = c; setBtnColor(i); } } void SessionSettings::saveTransfConfig(const TransfConfig& transfConfig) { m_pSettings->remove("transformation"); m_pSettings->setValue("transformation/srcDir", convStr(transfConfig.getSrcDir())); m_pSettings->setValue("transformation/procOrigDir", convStr(transfConfig.getProcOrigDir())); m_pSettings->setValue("transformation/unprocOrigDir", convStr(transfConfig.getUnprocOrigDir())); m_pSettings->setValue("transformation/processedDir", convStr(transfConfig.getProcessedDir())); m_pSettings->setValue("transformation/tempDir", convStr(transfConfig.getTempDir())); m_pSettings->setValue("transformation/compDir", convStr(transfConfig.getCompDir())); m_pSettings->setValue("transformation/options", transfConfig.getOptions()); /*if (transfConfig.getOptions() != 14349) //ttt remove { qDebug("cfg changed: %x", transfConfig.getOptions()); }*/ } // returns false if there was some error while loading (so the user can be told about defaults being used and those defaults could get saved) bool SessionSettings::loadTransfConfig(TransfConfig& transfConfig) const { try { //TransfConfig tc ("/r/temp/1", "/r/temp/1", "/r/temp/1/proc", "/r/temp/1/temp_mp3"); TransfConfig tc ( convStr(m_pSettings->value("transformation/srcDir", "*").toString()), convStr(m_pSettings->value("transformation/procOrigDir", "*").toString()), convStr(m_pSettings->value("transformation/unprocOrigDir", "*").toString()), convStr(m_pSettings->value("transformation/processedDir", "*").toString()), convStr(m_pSettings->value("transformation/tempDir", "*").toString()), convStr(m_pSettings->value("transformation/compDir", "*").toString()), m_pSettings->value("transformation/options", -1).toInt() ); transfConfig = tc; return !tc.hadInitError(); } catch (const IncorrectDirName&) { TransfConfig tc ("*", "*", "*", "*", "*", "*", -1); transfConfig = tc; return false; } } bool ConfigDlgImpl::run() { TRACER("ConfigDlgImpl::run()"); if (QDialog::Accepted != exec()) { return false; } m_pCommonData->m_settings.saveConfigSize(width(), height()); return true; } void ConfigDlgImpl::on_m_pId3LocaleCbB_currentIndexChanged(int) { QTextCodec* pCodec (QTextCodec::codecForName(m_pId3LocaleCbB->currentText().toLatin1())); CB_ASSERT (0 != pCodec); QString qstrTxt (pCodec->toUnicode(m_codepageTestText)); m_pCodepageTestM->setText(qstrTxt); } //ttt2 treat "orig" and "proc" files as "orig" when processing them void initDefaultCustomTransf(int k, vector >& vv, CommonData* pCommonData) { vector& v (vv[k]); switch (k) { case 0: v.push_back(pCommonData->getTransfPos(SingleBitRepairer::getClassName())); v.push_back(pCommonData->getTransfPos(InnerNonAudioRemover::getClassName())); v.push_back(pCommonData->getTransfPos(TruncatedMpegDataStreamRemover::getClassName())); v.push_back(pCommonData->getTransfPos(VbrRepairer::getClassName())); v.push_back(pCommonData->getTransfPos(MismatchedXingRemover::getClassName())); // !!! takes care of broken Xing headers for CBR files (VbrRepairer only deals with VBR files) break; case 1: v.push_back(pCommonData->getTransfPos(Id3V2Rescuer::getClassName())); v.push_back(pCommonData->getTransfPos(UnsupportedId3V2Remover::getClassName())); //v.push_back(pCommonData->getTransfPos(MultipleId3StreamRemover::getClassName())); //v.push_back(pCommonData->getTransfPos(Id3V1ToId3V2Copier::getClassName())); break; case 2: v.push_back(pCommonData->getTransfPos(InnerNonAudioRemover::getClassName())); v.push_back(pCommonData->getTransfPos(UnknownDataStreamRemover::getClassName())); v.push_back(pCommonData->getTransfPos(BrokenDataStreamRemover::getClassName())); v.push_back(pCommonData->getTransfPos(UnsupportedDataStreamRemover::getClassName())); v.push_back(pCommonData->getTransfPos(TruncatedMpegDataStreamRemover::getClassName())); v.push_back(pCommonData->getTransfPos(NullStreamRemover::getClassName())); //v.push_back(pCommonData->getTransfPos(MultipleId3StreamRemover::getClassName())); v.push_back(pCommonData->getTransfPos(MismatchedXingRemover::getClassName())); break; case 3: // CUSTOM_TRANSF_CNT v.push_back(pCommonData->getTransfPos(SingleBitRepairer::getClassName())); v.push_back(pCommonData->getTransfPos(InnerNonAudioRemover::getClassName())); v.push_back(pCommonData->getTransfPos(TruncatedMpegDataStreamRemover::getClassName())); v.push_back(pCommonData->getTransfPos(VbrRepairer::getClassName())); v.push_back(pCommonData->getTransfPos(MismatchedXingRemover::getClassName())); // !!! takes care of broken Xing headers for CBR files (VbrRepairer only deals with VBR files) v.push_back(pCommonData->getTransfPos(Id3V2Rescuer::getClassName())); v.push_back(pCommonData->getTransfPos(UnsupportedId3V2Remover::getClassName())); v.push_back(pCommonData->getTransfPos(MultipleId3StreamRemover::getClassName())); //v.push_back(pCommonData->getTransfPos(Id3V1ToId3V2Copier::getClassName())); //v.push_back(pCommonData->getTransfPos(InnerNonAudioRemover::getClassName())); v.push_back(pCommonData->getTransfPos(UnknownDataStreamRemover::getClassName())); v.push_back(pCommonData->getTransfPos(BrokenDataStreamRemover::getClassName())); v.push_back(pCommonData->getTransfPos(UnsupportedDataStreamRemover::getClassName())); v.push_back(pCommonData->getTransfPos(TruncatedMpegDataStreamRemover::getClassName())); v.push_back(pCommonData->getTransfPos(NullStreamRemover::getClassName())); //v.push_back(pCommonData->getTransfPos(MultipleId3StreamRemover::getClassName())); //v.push_back(pCommonData->getTransfPos(MismatchedXingRemover::getClassName())); break; default: CB_ASSERT (false); } } void initDefaultVisibleTransf(vector& v, CommonData* pCommonData) { v.push_back(pCommonData->getTransfPos(SingleBitRepairer::getClassName())); v.push_back(pCommonData->getTransfPos(InnerNonAudioRemover::getClassName())); v.push_back(pCommonData->getTransfPos(UnknownDataStreamRemover::getClassName())); v.push_back(pCommonData->getTransfPos(BrokenDataStreamRemover::getClassName())); v.push_back(pCommonData->getTransfPos(UnsupportedDataStreamRemover::getClassName())); v.push_back(pCommonData->getTransfPos(TruncatedMpegDataStreamRemover::getClassName())); v.push_back(pCommonData->getTransfPos(NullStreamRemover::getClassName())); v.push_back(pCommonData->getTransfPos(BrokenId3V2Remover::getClassName())); v.push_back(pCommonData->getTransfPos(UnsupportedId3V2Remover::getClassName())); //v.push_back(pCommonData->getTransfPos(IdentityTransformation::getClassName())); v.push_back(pCommonData->getTransfPos(MultipleId3StreamRemover::getClassName())); v.push_back(pCommonData->getTransfPos(MismatchedXingRemover::getClassName())); v.push_back(pCommonData->getTransfPos(TruncatedAudioPadder::getClassName())); v.push_back(pCommonData->getTransfPos(VbrRepairer::getClassName())); v.push_back(pCommonData->getTransfPos(VbrRebuilder::getClassName())); v.push_back(pCommonData->getTransfPos(Id3V2Cleaner::getClassName())); v.push_back(pCommonData->getTransfPos(Id3V2Rescuer::getClassName())); v.push_back(pCommonData->getTransfPos(Id3V2UnicodeTransformer::getClassName())); //v.push_back(pCommonData->getTransfPos(Id3V2CaseTransformer::getClassName())); //v.push_back(pCommonData->getTransfPos(Id3V2ComposerAdder::getClassName())); //v.push_back(pCommonData->getTransfPos(Id3V2ComposerRemover::getClassName())); //v.push_back(pCommonData->getTransfPos(Id3V2ComposerCopier::getClassName())); //v.push_back(pCommonData->getTransfPos(SmallerImageRemover::getClassName())); //v.push_back(pCommonData->getTransfPos(Id3V1ToId3V2Copier::getClassName())); //v.push_back(pCommonData->getTransfPos(Id3V1Remover::getClassName())); //v.push_back(pCommonData->getTransfPos(Id3V2Compactor::getClassName())); //v.push_back(pCommonData->getTransfPos(Id3V2Expander::getClassName())); } ConfigDlgImpl::~ConfigDlgImpl() { clearPtrContainer(m_vpOrigAll); clearPtrContainer(m_vpResetAll); // doesn't matter if it was used or not, or if m_bResultInReset is true or false delete m_pVisibleTransfPainter; delete m_pCustomTransfListPainter; delete m_pExternalToolsModel; } void ConfigDlgImpl::refreshTransfText(int k) { QString s; for (int i = 0, n = cSize(m_vvnCustomTransf[k]); i < n; ++i) { s += m_pCommonData->getAllTransf()[m_vvnCustomTransf[k][i]]->getVisibleActionName(); if (i < n - 1) { s += "\n"; } } m_vpTransfLabels[k]->setText(s); } void ConfigDlgImpl::selectCustomTransf(int k) // 0 <= k <= 2 { getTransfData(); delete m_pCustomTransfDoubleList; delete m_pCustomTransfListPainter; m_pCustomTransfListPainter = new CustomTransfListPainter(m_pCommonData, m_pCommonData->getCustomTransf()[k], m_vvnCustomTransf[k], m_vvnDefaultCustomTransf[k]); m_pCustomTransfDoubleList = new DoubleList( *m_pCustomTransfListPainter, DoubleList::RESTORE_OPEN | DoubleList::RESTORE_DEFAULT, DoubleList::MULTIPLE, convStr(tr("All transformations")), convStr(tr("Used transformations")), this); m_pTransfListHldr->layout()->addWidget(m_pCustomTransfDoubleList); //m_pTransfListHldr->layout()->activate(); //?->300x358//m_pCustomTransfDoubleList->layout()->activate(); //m_pCustomTransfDoubleList->resizeOnShow(); //m_pDetailsTabWidget->setCurrentIndex(0); //m_pDetailsTabWidget->setCurrentIndex(2); for (int i = 0; i < CUSTOM_TRANSF_CNT; ++i) { m_vpTransfButtons[i]->setChecked(false); m_vpTransfLabels[i]->setPalette(m_wndPalette); } m_vpTransfButtons[k]->setChecked(true); m_vpTransfLabels[k]->setPalette(m_defaultPalette); m_nCurrentTransf = k; connect(m_pCustomTransfDoubleList, SIGNAL(dataChanged()), this, SLOT(onTransfDataChanged())); } void ConfigDlgImpl::onTransfDataChanged() { getTransfData(); } void ConfigDlgImpl::getTransfData() { if (-1 == m_nCurrentTransf) { return; } m_vvnCustomTransf[m_nCurrentTransf] = m_pCustomTransfListPainter->getSel(); //qDebug("transf %d at %d", cSize(m_vvnCustomTransf[m_nCurrentTransf]), m_nCurrentTransf); refreshTransfText(m_nCurrentTransf); } void ConfigDlgImpl::on_m_pOkB_clicked() { if (m_bExtToolChanged) { int nOpt (showMessage(this, QMessageBox::Question, 1, 1, tr("Confirm"), tr("You modified the external tool information but you didn't save your changes. Discard the changes or cancel closing of the options window?"), tr("&Discard"), tr("&Cancel"))); if (nOpt == 1) { return; } } { string strInv (convStr(m_pInvalidCharsE->text())); string strRepl (convStr(m_pInvalidReplacementE->text())); if (!strInv.empty()) { string::size_type n (strRepl.find_first_of(strInv)); if (string::npos != n) { showCritical(this, tr("Error"), tr("You can't have '%1' in both the list of invalid characters and the string that invalid characters are replaced with.").arg(strRepl[n])); return; } } } //logState("on_m_pOkB_clicked 1"); try { TransfConfig::Options opt (getOpt()); TransfConfig cfg ( getNonSepTerminatedDir(convStr(fromNativeSeparators(m_pSrcDirE->text()))), getNonSepTerminatedDir(convStr(fromNativeSeparators((m_pSimpleViewB->isChecked() ? m_pPODest2E : m_pPODestE)->text()))), getNonSepTerminatedDir(convStr(fromNativeSeparators(m_pUODestE->text()))), getNonSepTerminatedDir(convStr(fromNativeSeparators(m_pProcDestE->text()))), getNonSepTerminatedDir(convStr(fromNativeSeparators(m_pTempDestE->text()))), getNonSepTerminatedDir(convStr(fromNativeSeparators(m_pCompDestE->text()))), opt.getVal() ); m_transfCfg = cfg; { // ignored m_pCommonData->setIgnoredNotes(m_vSel); //cout << "OK:\n"; printContainer(m_pCommonData->m_vnIgnoredNotes, cout); } { // custom transformations getTransfData(); m_pCommonData->setCustomTransf(m_vvnCustomTransf); } { //m_pCommonData->setVisibleTransf(m_vnVisibleTransf); m_pCommonData->setVisibleTransf(m_pVisibleTransfPainter->getSel()); } { QualThresholds q; q.m_nStereoCbr = BpsInfo::s_aValidBps[m_pQualStCbrCbB->currentIndex()]*1000; q.m_nJointStereoCbr = BpsInfo::s_aValidBps[m_pQualJtStCbrCbB->currentIndex()]*1000; q.m_nDoubleChannelCbr = BpsInfo::s_aValidBps[m_pQualDlChCbrCbB->currentIndex()]*1000; q.m_nStereoVbr = m_pQualStVbrSB->value()*1000; q.m_nJointStereoVbr = m_pQualJtStVbrSB->value()*1000; q.m_nDoubleChannelVbr = m_pQualDlChVbrSB->value()*1000; m_pCommonData->setQualThresholds(q); } { m_pCommonData->m_locale = m_pId3LocaleCbB->currentText().toLatin1(); m_pCommonData->m_pCodec = QTextCodec::codecForName(m_pCommonData->m_locale); CB_ASSERT (0 != m_pCommonData->m_pCodec); } { // case m_pCommonData->m_eCaseForArtists = (TextCaseOptions)m_pArtistsCaseCbB->currentIndex(); //ttt2 perhaps allow NONE m_pCommonData->m_eCaseForOthers = (TextCaseOptions)m_pOthersCaseCbB->currentIndex(); } { // colors m_pCommonData->m_vNoteCategColors = m_vNoteCategColors; } { // tag editor m_pCommonData->m_bWarnOnNonSeqTracks = m_pWarnOnNonSeqTracksCkB->isChecked(); m_pCommonData->m_bWarnPastingToNonSeqTracks = m_pWarnOnPasteToNonSeqTracksCkB->isChecked(); } { // misc m_pCommonData->m_bScanAtStartup = m_pScanAtStartupCkB->isChecked(); m_pCommonData->setFastSave(m_pFastSaveCkB->isChecked(), CommonData::UPDATE_TRANSFORMS); m_pCommonData->m_bShowExport = m_pShowExportCkB->isChecked(); m_pCommonData->m_bShowDebug = m_pShowDebugCkB->isChecked(); m_pCommonData->m_bShowSessions = m_pShowSessCkB->isChecked(); m_pCommonData->m_bShowCustomCloseButtons = m_pShowCustomCloseButtonsCkB->isChecked(); m_pCommonData->m_strNormalizeCmd = convStr(m_pNormalizerE->text()); m_pCommonData->m_bKeepNormWndOpen = m_pKeepNormOpenCkB->isChecked(); m_pCommonData->m_eAssignSave = m_pAssgnSaveRB->isChecked() ? CommonData::SAVE : m_pAssgnDiscardRB->isChecked() ? CommonData::DISCARD : CommonData::ASK; m_pCommonData->m_eNonId3v2Save = m_pNonId3v2SaveRB->isChecked() ? CommonData::SAVE : m_pNonId3v2DiscardRB->isChecked() ? CommonData::DISCARD : CommonData::ASK; m_pCommonData->m_nMainWndIconSize = m_pIconSizeSB->value(); m_pCommonData->m_bAutoSizeIcons = m_pAutoSizeIconsCkB->isChecked(); m_pCommonData->m_bKeepOneValidImg = m_pKeepOneValidImgCkB->isChecked(); m_pCommonData->m_bWmpVarArtists = m_pWmpCkB->isChecked(); m_pCommonData->m_bItunesVarArtists = m_pItunesCkB->isChecked(); ImageInfo::MAX_IMAGE_SIZE = m_pMaxImgSizeSB->value()*1024; //ttt2 inconsistent to keep this in static var and the others in CommonData; perhaps switch to a global CommonData that anybody can access, without passing it in params m_pCommonData->setTraceToFile(m_pTraceToFileCkB->isChecked()); m_pCommonData->setFontInfo(convStr(m_generalFont.family()), m_generalFont.pointSize(), m_pDecrLabelFontSB->value(), convStr(m_fixedFont.family()), m_fixedFont.pointSize()); m_pCommonData->m_strRenamerInvalidChars = convStr(m_pInvalidCharsE->text()); m_pCommonData->m_strRenamerReplacementString = convStr(m_pInvalidReplacementE->text()); m_pCommonData->m_strCheckForNewVersions = m_pCheckForUpdatesCkB->isChecked() ? "yes" : "no"; string strTranslation (TranslatorHandler::getGlobalTranslator().getTranslations()[m_pTranslationCbB->currentIndex()]); if (m_pCommonData->m_strTranslation != strTranslation) { showWarning(this, tr("Info"), tr("You need to restart the program to use the new language.")); m_pCommonData->m_strTranslation = strTranslation; } } if (m_bFull) { // shell integration ShellIntegration::enableHiddenSession(m_pShellHiddenSessCkB->isChecked()); ShellIntegration::enableVisibleSession(m_pShellVisibleSessCkB->isChecked()); ShellIntegration::enableTempSession(m_pShellTempSessCkB->isChecked()); } m_pCommonData->m_vExternalToolInfos = m_vExternalToolInfos; accept(); } catch (const IncorrectDirName&) { showCritical(this, tr("Invalid folder name"), tr("A folder name is incorrect.")); //ttt2 say which name } } void ConfigDlgImpl::on_m_pCancelB_clicked() { reject(); } void ConfigDlgImpl::on_m_pChangeGenFontB_clicked() { bool bOk; QFont font = QFontDialog::getFont(&bOk, m_generalFont, this); //ttt2 see if possible to remove "What's this" button if (!bOk) { return; } m_generalFont = font; setFontLabels(); } void ConfigDlgImpl::on_m_pChangeFixedFontB_clicked() { bool bOk; QFont font = QFontDialog::getFont(&bOk, m_fixedFont, this); if (!bOk) { return; } m_fixedFont = font; setFontLabels(); } void ConfigDlgImpl::setFontLabels() { m_pGeneralFontL->setText(QString("%1, %2pt").arg(m_generalFont.family()).arg(m_generalFont.pointSize())); m_pGeneralFontL->setFont(m_generalFont); m_pFixedFontL->setText(QString("%1, %2pt").arg(m_fixedFont.family()).arg(m_fixedFont.pointSize())); m_pFixedFontL->setFont(m_fixedFont); QFont f (m_fixedFont); f.setUnderline(true); m_pInvalidReplacementE->setFont(f); m_pInvalidCharsE->setFont(f); } void ConfigDlgImpl::logState(const char* /*szPlace*/) const { /*cout << szPlace << ": m_filter.m_vSelDirs=" << m_pCommonData->m_filter.m_vSelDirs.size() << " m_availableDirs.m_vstrDirs=" << m_availableDirs.m_vstrDirs.size() << " m_selectedDirs.m_vSelDirs=" << m_selectedDirs.m_vstrDirs.size() << endl;*/ } //--------------------------------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------------------------------- /*override*/ string ConfigDlgImpl::getTooltip(TooltipKey eTooltipKey) const { switch (eTooltipKey) { case SELECTED_G: return "";//"Notes to be included"; case AVAILABLE_G: return "";//"Available notes"; case ADD_B: return convStr(tr("Add selected note(s)")); case DELETE_B: return convStr(tr("Remove selected note(s)")); case ADD_ALL_B: return convStr(tr("Add all notes")); case DELETE_ALL_B: return convStr(tr("Remove all notes")); case RESTORE_DEFAULT_B: return convStr(tr("Restore lists to their default value")); case RESTORE_OPEN_B: return convStr(tr("Restore lists to the configuration they had when the window was open")); default: CB_ASSERT(false); } } /*override*/ void ConfigDlgImpl::reset() { if (m_vpResetAll.empty()) { const vector& v (Notes::getAllNotes()); for (int i = 0, n = cSize(v); i < n; ++i) { const Note* p (v[i]); m_vpResetAll.push_back(new NoteListElem(p, m_pCommonData)); } } m_vSel = Notes::getDefaultIgnoredNoteIds(); } void ConfigDlgImpl::selectDir(QLineEdit* pEdt) { QString qstrStart (fromNativeSeparators(pEdt->text())); qstrStart = convStr(getExistingDir(convStr(qstrStart))); if (qstrStart.isEmpty()) { qstrStart = getTempDir(); } QFileDialog dlg (this, tr("Select folder"), qstrStart, tr("All files") + " (*)"/*, 0, QFileDialog::DontUseNativeDialog*/); //ttt0 test on Qt 4.8, for implications of "native" standard dialogs (file, color, ...) probably best is to force non-native on Qt 4.8. The thing is the native dialogs, which seem to be introduced in 4.8, are not translatable; OTOH from http://stackoverflow.com/questions/6405234/qfiledialogdontusenativedialog-is-not-working and http://stackoverflow.com/questions/4259994/qfiledialog-alternative-that-uses-default-file-dialog-defined-by-os it seems that only the static methods of QFileDialog create native dialogs; see also TranslatorHandler::TranslatorHandler() for translations and perhaps do an ifdef for 4.8 & later dlg.setFileMode(QFileDialog::Directory); if (QDialog::Accepted != dlg.exec()) { return; } QStringList fileNames (dlg.selectedFiles()); if (1 != fileNames.size()) { return; } QString s (fileNames.first()); QString s1 (getPathSep()); if ("//" == s) { s = "/"; } if (!s.endsWith(s1)) { s += s1; } pEdt->setText(toNativeSeparators(s)); } void ConfigDlgImpl::onHelp() { switch(m_pMainTabWidget->currentIndex()) { case 0: openHelp("250_config_files.html"); break; case 1: openHelp("260_config_ignored_notes.html"); break; case 2: openHelp("270_config_custom_transf.html"); break; case 3: openHelp("290_config_transf_params.html"); break; case 4: openHelp("292_config_visible_transf.html"); break; case 5: openHelp("295_config_quality.html"); break; case 6: openHelp("297_config_colors.html"); break; case 7: openHelp("298_config_shell.html"); break; case 8: openHelp("299_ext_tools.html"); break; case 9: openHelp("300_config_others.html"); break; //tttr revise as needed default: /*openHelp("index.html");*/ break; } } void ConfigDlgImpl::on_m_pFastSaveCkB_stateChanged() { if (0 == m_pVisibleTransfPainter) { return; } if (m_pFastSaveCkB->isChecked()) { const vector& vnAvail (m_pVisibleTransfPainter->getAvailable()); const vector& vpAll (m_pVisibleTransfPainter->getAll()); set s; for (int i = 0; i < cSize(vnAvail); ++i) { const TransfListElem* p (dynamic_cast(vpAll.at(vnAvail.at(i)))); CB_ASSERT (0 != p); if (p->getTransformation()->getActionName() == string(Id3V2Compactor::getClassName()) || p->getTransformation()->getActionName() == string(Id3V2Expander::getClassName())) { s.insert(i); } } m_pVisibleTransfDoubleList->add(s); } else { const vector& vnSel (m_pVisibleTransfPainter->getSel()); const vector& vpAll (m_pVisibleTransfPainter->getAll()); set s; for (int i = 0; i < cSize(vnSel); ++i) { const TransfListElem* p (dynamic_cast(vpAll.at(vnSel.at(i)))); CB_ASSERT (0 != p); if (p->getTransformation()->getActionName() == string(Id3V2Compactor::getClassName()) || p->getTransformation()->getActionName() == string(Id3V2Expander::getClassName())) { s.insert(i); } } m_pVisibleTransfDoubleList->remove(s); } } TransfConfig::Options ConfigDlgImpl::getSimpleViewOpt() // doesn't set m_bKeepOrigTime { TransfConfig::Options opt (getFullViewOpt()); if (m_pBackupCustomRB->isChecked()) { return opt; } if (m_pDontCreateBackupRB->isChecked()) { return opt.asNonBackup(); } return opt.asBackup(); } TransfConfig::Options ConfigDlgImpl::getFullViewOpt() // doesn't set m_bKeepOrigTime { TransfConfig::Options opt; opt.m_eProcOrigChange = m_pPODontChangeRB->isChecked() ? TransfConfig::Options::PO_DONT_CHG : m_pPOEraseRB->isChecked() ? TransfConfig::Options::PO_ERASE : m_pPOMoveAlwaysChangeRB->isChecked() ? TransfConfig::Options::PO_MOVE_ALWAYS_RENAME : m_pPOMoveChangeIfNeededRB->isChecked() ? TransfConfig::Options::PO_MOVE_RENAME_IF_USED : m_pPOChangeNameRB->isChecked() ? TransfConfig::Options::PO_RENAME_SAME_DIR : m_pPOMoveOrEraseRB->isChecked() ? TransfConfig::Options::PO_MOVE_OR_ERASE : TransfConfig::Options::ProcOrig(7); CB_ASSERT (7 != opt.m_eProcOrigChange); opt.m_bProcOrigUseLabel = m_pPOUseLabelRB->isChecked(); opt.m_bProcOrigAlwayUseCounter = m_pPOAlwaysUseCounterRB->isChecked(); opt.m_eUnprocOrigChange = m_pUODontChangeRB->isChecked() ? TransfConfig::Options::UPO_DONT_CHG : m_pUOEraseRB->isChecked() ? TransfConfig::Options::UPO_ERASE : m_pUOMoveAlwaysChangeRB->isChecked() ? TransfConfig::Options::UPO_MOVE_ALWAYS_RENAME : m_pUOMoveChangeIfNeededRB->isChecked() ? TransfConfig::Options::UPO_MOVE_RENAME_IF_USED : m_pUOChangeNameRB->isChecked() ? TransfConfig::Options::UPO_RENAME_SAME_DIR : TransfConfig::Options::UnprocOrig(7); CB_ASSERT (7 != opt.m_eUnprocOrigChange); opt.m_bUnprocOrigUseLabel = m_pUOUseLabelRB->isChecked(); opt.m_bUnprocOrigAlwayUseCounter = m_pUOAlwaysUseCounterRB->isChecked(); opt.m_eProcessedCreate = m_pProcDontCreateRB->isChecked() ? TransfConfig::Options::PR_DONT_CREATE : m_pProcCreateAlwaysChangeRB->isChecked() ? TransfConfig::Options::PR_CREATE_ALWAYS_RENAME : m_pProcCreateChangeIfNeededRB->isChecked() ? TransfConfig::Options::PR_CREATE_RENAME_IF_USED : TransfConfig::Options::Processed(3); CB_ASSERT (3 != opt.m_eProcessedCreate); opt.m_bProcessedUseLabel = m_pProcUseLabelRB->isChecked(); opt.m_bProcessedAlwayUseCounter = m_pProcAlwaysUseCounterRB->isChecked(); opt.m_bProcessedUseSeparateDir = m_pProcUseSeparateDirRB->isChecked(); opt.m_bTempCreate = m_pTempCreateRB->isChecked(); opt.m_bCompCreate = m_pCompCreateRB->isChecked(); return opt; } TransfConfig::Options ConfigDlgImpl::getOpt() // has the correct m_bKeepOrigTime { TransfConfig::Options opt (m_pSimpleViewB->isChecked() ? getSimpleViewOpt() : getFullViewOpt()); opt.m_bKeepOrigTime = m_pKeepOrigTimeCkB->isChecked(); return opt; } void ConfigDlgImpl::setSimpleViewOpt(const TransfConfig::Options& opt) // m_bKeepOrigTime shouldn't be set { if (opt == opt.asNonBackup()) { m_pDontCreateBackupRB->setChecked(true); } else if (opt == opt.asBackup()) { m_pCreateBackupRB->setChecked(true); } else { m_pBackupCustomRB->setChecked(true); } } void ConfigDlgImpl::setFullViewOpt(const TransfConfig::Options& opt) // m_bKeepOrigTime is ignored { { // ProcOrig switch (opt.m_eProcOrigChange) { case TransfConfig::Options::PO_DONT_CHG: m_pPODontChangeRB->setChecked(true); break; case TransfConfig::Options::PO_ERASE: m_pPOEraseRB->setChecked(true); break; case TransfConfig::Options::PO_MOVE_ALWAYS_RENAME: m_pPOMoveAlwaysChangeRB->setChecked(true); break; case TransfConfig::Options::PO_MOVE_RENAME_IF_USED: m_pPOMoveChangeIfNeededRB->setChecked(true); break; case TransfConfig::Options::PO_RENAME_SAME_DIR: m_pPOChangeNameRB->setChecked(true); break; case TransfConfig::Options::PO_MOVE_OR_ERASE: m_pPOMoveOrEraseRB->setChecked(true); break; default: CB_ASSERT(false); // the constructor of TransfConfig should have detected it } if (opt.m_bProcOrigUseLabel) { m_pPOUseLabelRB->setChecked(true); } else { m_pPODontUseLabelRB->setChecked(true); } if (opt.m_bProcOrigAlwayUseCounter) { m_pPOAlwaysUseCounterRB->setChecked(true); } else { m_pPOUseCounterIfNeededRB->setChecked(true); } } { // UnprocOrig switch (opt.m_eUnprocOrigChange) { case TransfConfig::Options::UPO_DONT_CHG: m_pUODontChangeRB->setChecked(true); break; case TransfConfig::Options::UPO_ERASE: m_pUOEraseRB->setChecked(true); break; case TransfConfig::Options::UPO_MOVE_ALWAYS_RENAME: m_pUOMoveAlwaysChangeRB->setChecked(true); break; case TransfConfig::Options::UPO_MOVE_RENAME_IF_USED: m_pUOMoveChangeIfNeededRB->setChecked(true); break; case TransfConfig::Options::UPO_RENAME_SAME_DIR: m_pUOChangeNameRB->setChecked(true); break; default: CB_ASSERT(false); // the constructor of TransfConfig should have detected it } if (opt.m_bUnprocOrigUseLabel) { m_pUOUseLabelRB->setChecked(true); } else { m_pUODontUseLabelRB->setChecked(true); } if (opt.m_bUnprocOrigAlwayUseCounter) { m_pUOAlwaysUseCounterRB->setChecked(true); } else { m_pUOUseCounterIfNeededRB->setChecked(true); } } { // Processed switch (opt.m_eProcessedCreate) { case TransfConfig::Options::PR_DONT_CREATE: m_pProcDontCreateRB->setChecked(true); break; case TransfConfig::Options::PR_CREATE_ALWAYS_RENAME: m_pProcCreateAlwaysChangeRB->setChecked(true); break; case TransfConfig::Options::PR_CREATE_RENAME_IF_USED: m_pProcCreateChangeIfNeededRB->setChecked(true); break; default: CB_ASSERT(false); // the constructor of TransfConfig should have detected it } if (opt.m_bProcessedUseLabel) { m_pProcUseLabelRB->setChecked(true); } else { m_pProcDontUseLabelRB->setChecked(true); } if (opt.m_bProcessedAlwayUseCounter) { m_pProcAlwaysUseCounterRB->setChecked(true); } else { m_pProcUseCounterIfNeededRB->setChecked(true); } if (opt.m_bProcessedUseSeparateDir) { m_pProcUseSeparateDirRB->setChecked(true); } else { m_pProcUseSrcRB->setChecked(true); } } { // Temp if (opt.m_bTempCreate) { m_pTempCreateRB->setChecked(true); } else { m_pTempDontCreateRB->setChecked(true); } } { // Comp if (opt.m_bCompCreate) { m_pCompCreateRB->setChecked(true); } else { m_pCompDontCreateRB->setChecked(true); } } } void ConfigDlgImpl::on_m_pSimpleViewB_clicked() { m_pFileSettingsLayout->setCurrentWidget(m_pSimpleViewTab); m_pFullViewB->setChecked(false); m_pPODest2E->setText(m_pPODestE->text()); setSimpleViewOpt(getFullViewOpt()); } void ConfigDlgImpl::on_m_pFullViewB_clicked() { m_pFileSettingsLayout->setCurrentWidget(m_pFullViewTab); m_pSimpleViewB->setChecked(false); m_pPODestE->setText(m_pPODest2E->text()); setFullViewOpt(getSimpleViewOpt()); } //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== void ConfigDlgImpl::resizeWidgets() { SimpleQTableViewWidthInterface intf1 (*m_pExternalToolsG); ColumnResizer rsz1 (intf1, 100, ColumnResizer::FILL, ColumnResizer::CONSISTENT_RESULTS); for (int i = 0; i < cSize(m_vExternalToolInfos); ++i) // needed on Qt 4.3.1, not needed on 4.7.1 { m_pExternalToolsG->verticalHeader()->resizeSection(i, CELL_HEIGHT); } } /*override*/ void ConfigDlgImpl::resizeEvent(QResizeEvent* pEvent) { resizeWidgets(); QDialog::resizeEvent(pEvent); } #if 0 /*override*/ void ConfigDlgImpl::closeEvent(QCloseEvent* pEvent) { pEvent->ignore(); QCoreApplication::postEvent(this, new QKeyEvent(QEvent::KeyPress, Qt::Key_Escape, Qt::NoModifier)); // ttt2 not sure if a KeyRelease pair is expected } /*override*/ bool ConfigDlgImpl::eventFilter(QObject* pObj, QEvent* pEvent) { //throw 1; //qDebug("pppp"); QKeyEvent* pKeyEvent (dynamic_cast(pEvent)); int nKey (0 == pKeyEvent ? 0 : pKeyEvent->key()); /*static int s_nCnt (0); if (0 != pKeyEvent //&& Qt::Key_Escape == nKey ) { qDebug("%d. %s %d", s_nCnt++, pObj->objectName().toUtf8().constData(), (int)pEvent->type()); //return QDialog::eventFilter(pObj, pEvent); }//*/ if (0 != pKeyEvent && Qt::Key_Escape == nKey && this == pObj && QEvent::KeyPress == pEvent->type()) { bool bMayClose (true); if (m_bExtToolChanged) { int nOpt (showMessage(this, QMessageBox::Question, 1, 1, "Confirm", "You modified the external tool information but you didn't save your changes. Discard the changes or cancel the closing?", "&Discard", "&Cancel")); if (nOpt == 1) { bMayClose = false; } } if (!bMayClose) { return true; } } return QDialog::eventFilter(pObj, pEvent); } #endif void ConfigDlgImpl::tableToEdit() { QModelIndex ndx (m_pExternalToolsG->currentIndex()); //ttt2 perhaps don't allow the current element to be changed as long as m_bExtToolChanged==true; or better: get rid of the other controls and just use edits and combo boxes in the table, then allow dragging of the cells in the vertical header to rearrange the tools int i (ndx.row()); if (ndx.isValid() && i < cSize(m_vExternalToolInfos)) { m_pExtToolNameE->setText(convStr(m_vExternalToolInfos[i].m_strName)); m_pExtToolCmdE->setText(convStr(m_vExternalToolInfos[i].m_strCommand)); switch (m_vExternalToolInfos[i].m_eLaunchOption) { case ExternalToolInfo::DONT_WAIT: m_pExtToolDontWaitRB->click(); break; case ExternalToolInfo::WAIT_AND_KEEP_WINDOW_OPEN: m_pExtToolWaitKeepOpenRB->click(); break; case ExternalToolInfo::WAIT_THEN_CLOSE_WINDOW: m_pExtToolWaitCloseRB->click(); break; default: CB_ASSERT (false); } m_pExtToolConfirmLaunchCkB->setChecked(m_vExternalToolInfos[i].m_bConfirmLaunch); } else { m_pExtToolNameE->setText(""); m_pExtToolCmdE->setText(""); m_pExtToolConfirmLaunchCkB->setChecked(true); m_pExtToolDontWaitRB->setChecked(true); } m_bExtToolChanged = false; } void ConfigDlgImpl::editToTable() { QModelIndex ndx (m_pExternalToolsG->currentIndex()); if (!ndx.isValid()) { return; } int i (ndx.row()); if (i >= cSize(m_vExternalToolInfos)) { return; } m_vExternalToolInfos[i] = externalToolInfoFromEdit(); m_bExtToolChanged = false; m_pExternalToolsModel->emitLayoutChanged(); resizeWidgets(); } ExternalToolInfo ConfigDlgImpl::externalToolInfoFromEdit() { return ExternalToolInfo( convStr(m_pExtToolNameE->text()), convStr(m_pExtToolCmdE->text()), m_pExtToolDontWaitRB->isChecked() ? ExternalToolInfo::DONT_WAIT : m_pExtToolWaitKeepOpenRB->isChecked() ? ExternalToolInfo::WAIT_AND_KEEP_WINDOW_OPEN : ExternalToolInfo::WAIT_THEN_CLOSE_WINDOW, m_pExtToolConfirmLaunchCkB->isChecked()); } void ConfigDlgImpl::on_m_pExtToolAddB_clicked() { m_vExternalToolInfos.push_back(externalToolInfoFromEdit()); m_pExternalToolsModel->emitLayoutChanged(); m_pExternalToolsG->setCurrentIndex(m_pExternalToolsModel->index(cSize(m_vExternalToolInfos) - 1, 0)); resizeWidgets(); //editToTable(); //m_pExternalToolsG->re } void ConfigDlgImpl::on_m_pExtToolUpdateB_clicked() { editToTable(); } void ConfigDlgImpl::on_m_pExtToolDeleteB_clicked() { QModelIndex ndx (m_pExternalToolsG->currentIndex()); int i (ndx.row()); if (!ndx.isValid() || i >= cSize(m_vExternalToolInfos)) { return; } m_vExternalToolInfos.erase(m_vExternalToolInfos.begin() + i); m_pExternalToolsModel->emitLayoutChanged(); if (!m_vExternalToolInfos.empty()) { m_pExternalToolsG->setCurrentIndex(m_pExternalToolsModel->index(i < cSize(m_vExternalToolInfos) ? i : i - 1, 0)); } tableToEdit(); } void ConfigDlgImpl::on_m_pExtToolDiscardB_clicked() { tableToEdit(); } void ConfigDlgImpl::on_m_pMainTabWidget_currentChanged(int /*nIndex*/) { resizeWidgets(); // !!! really needed only the first time the external tools tab is shown QModelIndex ndx (m_pExternalToolsG->currentIndex()); int i (ndx.row()); if (!ndx.isValid() || i >= cSize(m_vExternalToolInfos)) { if (!m_vExternalToolInfos.empty()) { m_pExternalToolsG->setCurrentIndex(m_pExternalToolsModel->index(0, 0)); // this will trigger tableToEdit(); } else { tableToEdit(); } } } //===================================================================================================================== //===================================================================================================================== ExternalToolsModel::ExternalToolsModel(const ConfigDlgImpl* pConfigDlgImpl) : m_pConfigDlgImpl(pConfigDlgImpl)//, m_pCommonData(pConfigDlgImpl->getCommonData()) { } /*override*/ int ExternalToolsModel::rowCount(const QModelIndex&) const { return cSize(m_pConfigDlgImpl->m_vExternalToolInfos); } /*override*/ int ExternalToolsModel::columnCount(const QModelIndex&) const { return 4; } /*override*/ QVariant ExternalToolsModel::data(const QModelIndex& index, int nRole) const { LAST_STEP("ExternalToolsModel::data()"); if (!index.isValid()) { return QVariant(); } int i (index.row()); int j (index.column()); //qDebug("ndx %d %d", i, j); if (Qt::DisplayRole != nRole && Qt::ToolTipRole != nRole && Qt::EditRole != nRole) { return QVariant(); } QString s; const ExternalToolInfo& info (m_pConfigDlgImpl->m_vExternalToolInfos[i]); switch (j) { case 0: return convStr(info.m_strName); case 1: return convStr(info.m_strCommand); case 2: return ExternalToolInfo::launchOptionAsTranslatedString(info.m_eLaunchOption); case 3: return GlobalTranslHlp::tr(boolAsYesNo(info.m_bConfirmLaunch)); default: CB_ASSERT (false); } } /*override*/ QVariant ExternalToolsModel::headerData(int nSection, Qt::Orientation eOrientation, int nRole /* = Qt::DisplayRole*/) const { LAST_STEP("ExternalToolsModel::headerData"); if (nRole != Qt::DisplayRole) { return QVariant(); } if (Qt::Horizontal == eOrientation) { switch (nSection) { case 0: return tr("Name"); case 1: return tr("Command"); case 2: return tr("Wait"); case 3: return tr("Confirm launch"); default: CB_ASSERT (false); } } return nSection + 1; } //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== //ttt2 dir config: perhaps something simpler, with a "more options" button //ttt2 Font style is ignored (see DejaVu Sans / Light on machines with antialiased fonts) //ttt2 proxy: QNetworkProxyFactory::systemProxyForQuery; QNetworkProxy; http://www.dbits.be/index.php/pc-problems/65-vistaproxycfg https://sourceforge.net/projects/mp3diags/forums/forum/947207/topic/3415940 MP3Diags-1.2.02/src/LyricsStream.h0000644000175000001440000001101611720256551015533 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef LyricsStreamH #define LyricsStreamH #include #include #include "DataStream.h" class LyricsStream : public DataStream, public TagReader { std::streampos m_pos; std::streamoff m_nSize; public: LyricsStream(int nIndex, NoteColl& notes, std::istream& in, const std::string& strFileName); /*override*/ void copy(std::istream& in, std::ostream& out); DECL_RD_NAME("Lyrics3 V2.00") /*override*/ std::string getInfo() const; /*override*/ std::streampos getPos() const { return m_pos; } /*override*/ std::streamoff getSize() const { return m_nSize; } struct NotLyricsStream {}; std::string m_strTitle; std::string m_strArtist; std::string m_strGenre; std::string m_strImageFiles; std::string m_strAlbum; std::string m_strAuthor; // composer std::string m_strLyrics; std::string m_strOther; // these are lost during transfer //ttt2 or maybe not, though not sure if transferring them to some "comment" field would be a good idea std::string m_strInd; std::string m_strFileName; bool m_bHasTitle; bool m_bHasArtist; bool m_bHasGenre; bool m_bHasImage; bool m_bHasAlbum; ImageInfo readImage(const QString& strRelName) const; private: friend class boost::serialization::access; LyricsStream(); // serialization-only constructor // ================================ TagReader ========================================= /*override*/ std::string getTitle(bool* pbFrameExists = 0) const; /*override*/ std::string getArtist(bool* pbFrameExists = 0) const; /*override*/ std::string getTrackNumber(bool* /*pbFrameExists*/ = 0) const { throw NotSupportedOp(); } /*override*/ TagTimestamp getTime(bool* /*pbFrameExists*/ = 0) const { throw NotSupportedOp(); } /*override*/ std::string getGenre(bool* pbFrameExists = 0) const; /*override*/ ImageInfo getImage(bool* pbFrameExists = 0) const; /*override*/ std::string getAlbumName(bool* pbFrameExists = 0) const; /*override*/ std::string getOtherInfo() const; /*override*/ SuportLevel getSupport(Feature) const; /*override*/ std::vector getImages() const; template void serialize(Archive& ar, const unsigned int nVersion) { if (nVersion > 1) { throw std::runtime_error("invalid version of serialized file"); } ar & boost::serialization::base_object(*this); ar & m_pos; ar & m_nSize; if (nVersion > 0) { ar & m_strTitle; ar & m_strArtist; ar & m_strGenre; ar & m_strImageFiles; ar & m_strAlbum; ar & m_strAuthor; ar & m_strLyrics; ar & m_strOther; ar & m_strInd; ar & m_strFileName; ar & m_bHasTitle; ar & m_bHasArtist; ar & m_bHasGenre; ar & m_bHasImage; ar & m_bHasAlbum; } } }; BOOST_CLASS_VERSION(LyricsStream, 1); #endif // ifndef LyricsStreamH MP3Diags-1.2.02/src/Config.ui0000644000175000001440000033403311716016575014521 0ustar ciobiusers ConfigDlg 0 0 991 685 630 440 Configuration true -1 Qt::NoFocus 0 Files 0 Qt::NoFocus Simple view true true Qt::NoFocus Full view true Qt::Horizontal 40 20 0 10 0 Removable TextLabel 0 Tab 1 1 0 14 0 0 Don't create backup for modified files 9 Create backup for modified files in ... false Custom settings (go to full view to make changes) Qt::Vertical 20 241 Tab 2 0 QTabWidget::North QTabWidget::Rounded 0 Original file that would be changed 0 Don't touch Erase Move and change the name Move but change the name only if a name collision would occur Change name Move if the destination doesn't already exist; erase if it does Name change params 0 Identifying label 0 0 0 Don't use an identifying label Use "orig" as an identifying label Counter 0 0 0 Always use a counter Only use a counter when a name collision would occur Destination 0 ... Qt::Vertical 421 37 Original file that would not be changed 0 Don't touch Erase Move and change the name Move but change the name only if a name collision would occur Change name Name change params 0 Identifying label 0 0 0 Don't use an identifying label Use "orig" as an identifying label Counter 0 0 0 Always use a counter Only use a counter when a name collision would occur Destination 0 ... Qt::Vertical 421 37 Changed file 0 Don't create Create and change the name Create but change the name only if a name collision would occur Name change params 0 Identifying label 0 0 0 Don't use an identifying label Use "proc" as an identifying label Counter 0 0 0 Always use a counter Only use a counter when a name collision would occur Destination 0 0 Use the source dir Use 0 ... Qt::Vertical 591 53 Temporary files QFrame::NoFrame QFrame::Raised Source directory 0 ... Temporary files 0 0 Don't create Create in 0 ... Compare files 0 0 Don't create Create in 0 ... Qt::Vertical 20 81 Ignored notes Custom transformation lists 16777215 150 20 0 0 40 0 40 16777215 0 0 40 40 40 40 Qt::NoFocus ... :/images/transform1.svg:/images/transform1.svg 36 36 true true Qt::Vertical 20 40 true true 0 40 0 40 16777215 0 0 40 40 40 40 Qt::NoFocus ... :/images/transform2.svg:/images/transform2.svg 36 36 true true Qt::Vertical 20 40 true true 0 40 0 40 16777215 0 0 40 40 40 40 Qt::NoFocus ... :/images/transform3.svg:/images/transform3.svg 36 36 true true Qt::Vertical 20 40 true true 0 40 0 40 16777215 0 0 40 40 40 40 Qt::NoFocus ... :/images/transform4.svg:/images/transform4.svg 36 36 true true Qt::Vertical 20 40 true true 0 0 0 100 Transformation params Locale for text conversion 0 200 0 30 Qt::Horizontal 181 23 Case transformation 0 Artists 150 0 Qt::Horizontal QSizePolicy::Fixed 31 20 Others 150 0 Qt::Horizontal 40 20 Keep original modification time when changing a file Keep a single image for a file Qt::Vertical 787 31 Visible Transformations Quality thresholds If the audio bitrate falls below these limits, warnings are generated. For VBR a low bitrate is less of an indicatior of poor quality, because if the signal is simple there's no need for higher bitrates. Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop true The bitrate is not the best quality indicator. Values from a "Lame header" (if present) are generally better, but these headers are not processed in the current version. Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop true Note that for mono streams, half of the value for "Dual Channel" is used. Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop true Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop true 0 0 0 Stereo CBR 15 Joint Stereo CBR 15 Dual Channel CBR 15 Stereo VBR 70 0 Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 8 448 Joint Stereo VBR 70 0 Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 8 448 Dual Channel VBR 70 0 Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 8 448 Qt::Vertical 108 41 Qt::Horizontal 381 20 Colors 0 60 40 60 40 Qt::NoFocus Audio 60 40 60 40 Qt::NoFocus Xing and LAME 60 40 60 40 Qt::NoFocus VBRI 60 40 60 40 Qt::NoFocus ID3V2 60 40 60 40 Qt::NoFocus APIC 60 40 60 40 Qt::NoFocus ID3V2.3.0 60 40 60 40 Qt::NoFocus ID3V2.4.0 Qt::Horizontal QSizePolicy::Fixed 100 20 0 60 40 60 40 Qt::NoFocus ID3V1 60 40 60 40 Qt::NoFocus Broken streams 60 40 60 40 Qt::NoFocus Truncated streams 60 40 60 40 Qt::NoFocus Unknown streams 60 40 60 40 Qt::NoFocus Lyrics 60 40 60 40 Qt::NoFocus Ape 60 40 60 40 Qt::NoFocus Others Qt::Horizontal 171 20 0 Reset to default colors Qt::Horizontal 40 20 Qt::Vertical 20 131 Shell Enable temporary session per folder Enable hidden session per folder Enable visible session per folder Qt::Vertical QSizePolicy::Preferred 20 41 191 0 0 191 0 0 116 115 111 12 ErrorShell true Qt::Vertical 20 486 External tools 0 10 0 1 QLayout::SetMinimumSize Name Command Wait Don't wait Wait for external tool to finish, then keep launch window open Wait for external tool to finish, then close launch window Qt::Vertical 20 40 Confirm launch true Confirm Add Update Delete Discard changes Qt::Vertical 20 40 Others Tag editor 0 0 0 Warn when the tag editor enters albums with non-sequential track numbers Warn when pasting track information in the tag editor for albums with non-sequential track numbers Use "fast save" in the tag editor 0 Maximum image size, in kB If an image size is above this value, it will be recompressed before storing it inside MP3 files 5 1000 100 Qt::Horizontal 40 20 0 200 70 Handle "various artists" for 0 0 iTunes Windows Media Player Qt::Horizontal QSizePolicy::Minimum 30 20 Qt::Vertical 20 2 0 If a field is marked as "assigned" when exiting an album 0 0 Save automatically Discard Ask If a field's value is different from that in the ID3V2 tag when exiting an album 0 0 Save automatically Discard Ask Misc 0 0 0 0 0 10 0 Scan for new, modified, and deleted files at startup Show "Export" button Show "Debug" button Show "Sessions" button Show Gnome 3 close buttons Log program state to "_trace" and "_step" files Check for new version at startup 30 0 0 20 0 0 99 60 :/images/preferences-desktop-locale.svg true Language 0 0 0 General font: 0 0 TextLabel Change ... Label font smaller by: 5 Fixed font: 0 0 TextLabel Change ... Qt::Vertical 20 40 0 Icon size 20 64 40 Auto-size icons based on the width of the main window Qt::Horizontal 40 20 0 7 0 Normalizer 0 0 Command (file names will get added at the end) Keep the window open after completion 4 0 File renamer 0 Invalid characters Replace with Qt::Vertical 161 66 0 0 QFrame::NoFrame QFrame::Raised 0 Qt::Horizontal 40 20 O&K Cancel m_pOkB m_pCancelB m_pCustomTransform1T m_pCustomTransform2T m_pCustomTransform3T m_pCustomTransform4T m_pQualStCbrCbB m_pQualJtStCbrCbB m_pQualDlChCbrCbB m_pQualStVbrSB m_pQualJtStVbrSB m_pQualDlChVbrSB m_pId3LocaleCbB m_pCodepageTestM m_pArtistsCaseCbB m_pOthersCaseCbB m_pKeepOrigTimeCkB m_pAssgnSaveRB m_pAssgnDiscardRB m_pAssgnAskRB m_pNonId3v2SaveRB m_pNonId3v2DiscardRB m_pNonId3v2AskRB m_pIconSizeSB MP3Diags-1.2.02/src/SongInfoParser.cpp0000644000175000001440000012112611717741227016355 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include "SongInfoParser.h" #include "Helpers.h" #include "DataStream.h" using namespace std; using namespace pearl; namespace SongInfoParser { //ttt2 perhaps null namespace (some classes are used in the header, though) // helper for TrackTextParser; // // base class for reading fields (artist, track#, ...) from a string containing the name of a file; // the string is passed in init(), which can be called repeatedly, without a need for new Reader objects to be created; // subclasses: BoundReader, UnboundReader, SequenceReader and OptionalReader struct Reader { enum Optional { OPT, NON_OPT }; enum Bound { BOUND, UNBOUND }; enum State { UNASSIGNED, ASSIGNED, FAILED }; enum { RIGHT, LEFT }; Optional m_eOptional; Bound getBound(bool bLeft) const { return bLeft ? m_eLeftBound : m_eRightBound; } Bound getLeftBound() const { return m_eLeftBound; } Bound getRightBound() const { return m_eRightBound; } State m_eState; const char* m_pcLeft; // set during the call to init() if it returns true const char* m_pcRight; // set during the call to init() if it returns true Reader(Optional eOptional, Bound eLeftBound, Bound eRightBound) : m_eOptional(eOptional), m_eState(UNASSIGNED), m_pcLeft(0), m_pcRight(0), m_eLeftBound(eLeftBound), m_eRightBound(eRightBound) { } virtual ~Reader() {} enum { RIGHT_FIRST, LEFT_FIRST }; enum { MOBILE_LEFT, FIXED_LEFT }; enum { MOBILE_RIGHT, FIXED_RIGHT }; virtual bool init(const char* pcLeft, const char* pcRight, bool bLeftFirst, bool bFixedLeft, bool bFixedRight) = 0; // sets m_pcLeft and m_pcRight such that the field is matched; returns false if the assignment couldn't be made virtual void clearState() { m_eState = UNASSIGNED; } virtual void set(vector&) {} protected: Bound m_eLeftBound, m_eRightBound; // rating, year, track - know when to stop bool failure() { m_eState = FAILED; return false; } bool success() { m_eState = ASSIGNED; return true; } private: Reader(const Reader&); Reader& operator=(const Reader&); }; struct BoundReader : public Reader { BoundReader(Optional eOptional) : Reader(eOptional, BOUND, BOUND) {} /*override*/ bool init(const char* pcLeft, const char* pcRight, bool bLeftFirst, bool bFixedLeft, bool bFixedRight); private: virtual int checkMatch(const char* pcLeft, const char* pcRight, const char* pcStart) = 0; // returns the size of the matching string, starting at pcStart; returns -1 if no match is found }; /*override*/ bool BoundReader::init(const char* pcLeft, const char* pcRight, bool bLeftFirst, bool bFixedLeft, bool bFixedRight) { const char* p (bLeftFirst ? pcLeft : pcRight - 1); int nInc (bLeftFirst ? 1 : -1); for (; p < pcRight && p >= pcLeft; p += nInc) { int nMatch (checkMatch(pcLeft, pcRight, p)); if (-1 != nMatch) { m_pcLeft = p; m_pcRight = p + nMatch; if ((bFixedLeft && m_pcLeft != pcLeft) || (bFixedRight && m_pcRight != pcRight)) { return failure(); } return success(); } //if (bFixedLeft && m_pcLeft != pcLeft) || (bFixedRight && m_pcRight != pcRight)) // ttt2 suboptimal; /*if (bFixedLeft || bFixedRight) { // if either end was supposed to be fixed and the first step wasn't a match, there's no point in going to another step return failure(); // !!! incorrect; the reason is the "-1" in the first line; for example a static reader "ab" fixed at right would fail on "xxab", because the first test would be for "b", and only the second test for "ab"; if we fail after first test there's no chance for the second; replacing "-1" with "-2" would fix the problem in this case, but that doesn't quite work, because static readers aren't the only bound readers, and the other ones may have variable length }*/ if (bLeftFirst && bFixedLeft) { // if the left end was supposed to be fixed and the first step wasn't a match, there's no point in going to another step; OTOH something similar cannot be done for the right side return failure(); } } return failure(); } struct StaticReader : public BoundReader { StaticReader() : BoundReader(NON_OPT) {} string m_str; private: /*override*/ int checkMatch(const char* /*pcLeft*/, const char* pcRight, const char* pcStart) { int nSize (cSize(m_str)); if (pcRight - pcStart < nSize) { return -1; } string s (pcStart, nSize); if (s == m_str) { return nSize; } return -1; } }; struct RatingReader : public BoundReader { RatingReader() : BoundReader(NON_OPT) {} /*override*/ void set(vector& v) { v[TagReader::RATING].assign(m_pcLeft, 1); } private: /*override*/ int checkMatch(const char* pcLeft, const char* pcRight, const char* pcStart) { if (pcStart > pcLeft && isalnum(*(pcStart - 1))) { return -1; } if (pcStart < pcRight - 1 && !isdigit(*(pcStart + 1))) { return -1; } char c (*pcStart); if (c < 'a' || c > 'z') { return -1; } return 1; } }; struct TrackNoReader : public BoundReader { TrackNoReader() : BoundReader(NON_OPT) {} /*override*/ void set(vector& v) { //v[TagReader::TRACK_NUMBER].assign(m_pcLeft, m_pcRight - m_pcLeft); string s; s.assign(m_pcLeft, m_pcRight - m_pcLeft); if ("00" == s) { s.clear(); } v[TagReader::TRACK_NUMBER] = s; } private: enum { DIGIT_LIMIT = 2 }; //ttt2 maybe make configurable /*override*/ int checkMatch(const char* pcLeft, const char* pcRight, const char* pcStart) { if (!isdigit(*(pcStart)) || (pcStart > pcLeft && isdigit(*(pcStart - 1)))) { return -1; } const char* p (pcStart); for (; p < pcRight && isdigit(*p); ++p) { } int nCnt (p - pcStart); if (nCnt > DIGIT_LIMIT) { return -1; } return nCnt; } }; struct YearReader : public BoundReader { YearReader() : BoundReader(NON_OPT) {} /*override*/ void set(vector& v) { v[TagReader::TIME].assign(m_pcLeft, 4); } private: /*override*/ int checkMatch(const char* pcLeft, const char* pcRight, const char* pcStart) { if (!isdigit(*(pcStart)) || (pcStart > pcLeft && isdigit(*(pcStart - 1)))) { return -1; } const char* p (pcStart); for (; p < pcRight && isdigit(*p); ++p) { } int nCnt (p - pcStart); if (4 != nCnt) { return -1; } char q [5]; strncpy(q, pcStart, 4); q[4] = 0; int nYear (atoi(q)); if (nYear < 1900 || nYear > 2100) { return -1; } return nCnt; } }; struct UnboundReader : public Reader { UnboundReader(int nIndex) : Reader(NON_OPT, UNBOUND, UNBOUND), m_nIndex(nIndex) {} /*override*/ bool init(const char* pcLeft, const char* pcRight, bool /*bLeftFirst*/, bool /*bFixedLeft*/, bool /*bFixedRight*/) { m_pcLeft = pcLeft; m_pcRight = pcRight; m_eState = m_pcLeft != m_pcRight ? ASSIGNED : FAILED; //ttt2 see about the test; not sure if allowing an empty string is ok; //ttt2 perhaps at least %i should be allowed to be empty; maybe add constructor param to tell if empty is OK return ASSIGNED == m_eState; } /*override*/ void set(vector& v) { v[m_nIndex].assign(m_pcLeft, m_pcRight - m_pcLeft); } private: int m_nIndex; }; // handles a sequence of other Readers struct SequenceReader : public Reader { struct ReaderInfo { Reader* m_pReader; bool m_bFromLeft; int m_nOrigPos; ReaderInfo(Reader* pReader, bool bFromLeft, int nOrigPos) : m_pReader(pReader), m_bFromLeft(bFromLeft), m_nOrigPos(nOrigPos) {} }; vector m_vpReaders; SequenceReader() : Reader(NON_OPT, BOUND, BOUND) {} bool setBounds() // to be called after all elems have been added; returns false if there's something wrong with the elements (e.g. touching unbounded) { int nCnt (cSize(m_vpReaders)); //CB_ASSERT (0 != nCnt); // empty sequence are not allowed; if (0 == nCnt) { return false; } /*m_eLeftBound = m_eRightBound = UNBOUND; if (NON_OPT == m_vpReaders[0]->m_eOptional && BOUND == m_vpReaders[0]->getLeftBound()) { m_eLeftBound = BOUND; } if (NON_OPT == m_vpReaders[nCnt - 1]->m_eOptional && BOUND == m_vpReaders[nCnt - 1]->getRightBound()) { m_eRightBound = BOUND; }*/ m_eLeftBound = m_eRightBound = BOUND; // skip all opt that are bound; if any unbound found, set unbound for (int i = 0; i < nCnt; ++i) { if (NON_OPT == m_vpReaders[i]->m_eOptional && BOUND == m_vpReaders[i]->getLeftBound()) { break; } if (UNBOUND == m_vpReaders[i]->getLeftBound()) { m_eLeftBound = UNBOUND; break; } } // skip all opt that are bound; if any unbound found, set unbound for (int i = nCnt - 1; i >= 0; --i) { if (NON_OPT == m_vpReaders[i]->m_eOptional && BOUND == m_vpReaders[i]->getRightBound()) { break; } if (UNBOUND == m_vpReaders[i]->getRightBound()) { m_eRightBound = UNBOUND; break; } } // don't allow unbound ends to touch one another; optionals make it more complicated: a bound optional is ignored, while an unbound optional is considered non-optional bool bPrevRightBound (true); for (int i = 0; i < nCnt; ++i) { Reader* p (m_vpReaders[i]); if (!bPrevRightBound && UNBOUND == p->getLeftBound()) { return false; } if (NON_OPT == p->m_eOptional) { // non-optional bPrevRightBound = BOUND == p->getRightBound(); } else { // optional bPrevRightBound = bPrevRightBound && (BOUND == p->getRightBound()); } } return true; } /*override*/ void clearState() { m_eState = UNASSIGNED; for (int i = 0, n = cSize(m_vpReaders); i < n; ++i) { m_vpReaders[i]->clearState(); } } /*override*/ bool init(const char* pcLeft, const char* pcRight, bool bLeftFirst, bool bFixedLeft, bool bFixedRight) { CB_ASSERT (UNASSIGNED == m_eState); if (m_vpReaders.empty()) { if (pcLeft == pcRight) { return success(); } else { return failure(); } } m_eState = FAILED; m_pcLeft = pcLeft; m_pcRight = pcRight; // needed to allow findLimits() to work const int nCnt (cSize(m_vpReaders)); vector v; // ttt2 perhaps move this to setBounds(), to avoid building it every time; it seems that both a "left" and a "right" vector would be needed, since it's possible for bLeftFirst to come with either value, depending on other Readers failing or not (to be further analyzed) { int l (0); for (; l < nCnt; ++l) // bound at the beginning, on the left (or rather on the side indicated by bLeftFirst, but to understand the comments in this block it's easier to assume that bLeftFirst is true) { int f (bLeftFirst ? l : nCnt - 1 - l); Reader* p (m_vpReaders[f]); if (BOUND != p->getBound(!bLeftFirst)) { break; } // !!! keep adding from the left as long as there is a RIGHT bound v.push_back(ReaderInfo(p, bLeftFirst, f)); } int r (nCnt - 1); for (; r > l; --r) // bound at the end, on the right { int f (bLeftFirst ? r : nCnt - 1 - r); Reader* p (m_vpReaders[f]); if (BOUND != p->getBound(bLeftFirst)) { break; } v.push_back(ReaderInfo(p, !bLeftFirst, f)); } for (int k = l; k <= r; ++k) // all remaining statics, from the left { int f (bLeftFirst ? k : nCnt - 1 - k); Reader* p (m_vpReaders[f]); if (0 != dynamic_cast(p)) // !!! StaticReader is always bound at both sides { v.push_back(ReaderInfo(p, bLeftFirst, f)); } } for (int k = l; k <= r; ++k) // all remaining bound at both sides, from the left { int f (bLeftFirst ? k : nCnt - 1 - k); Reader* p (m_vpReaders[f]); if (BOUND == p->getBound(LEFT) && BOUND == p->getBound(RIGHT) && 0 == dynamic_cast(p)) { v.push_back(ReaderInfo(p, bLeftFirst, f)); } } // now what is left is sourrounded by bound readers at their unbounded side(s) (or are at a margin) for (int k = l; k <= r; ++k) // all remaining, from the left { int f (bLeftFirst ? k : nCnt - 1 - k); Reader* p (m_vpReaders[f]); if (UNBOUND == p->getBound(LEFT) || UNBOUND == p->getBound(RIGHT)) { v.push_back(ReaderInfo(p, bLeftFirst, f)); } } } CB_ASSERT (cSize(v) == cSize(m_vpReaders)); for (int i = 0; i < nCnt; ++i) { Reader* pCrt (v[i].m_pReader); bool bCrtDir (v[i].m_bFromLeft); const char* pcCrtLeft; const char* pcCrtRight; bool bCrtFixedLeft, bCrtFixedRight; findLimits(v[i].m_nOrigPos, pcCrtLeft, pcCrtRight, bFixedLeft, bFixedRight, bCrtFixedLeft, bCrtFixedRight); pCrt->init(pcCrtLeft, pcCrtRight, bCrtDir, bCrtFixedLeft, bCrtFixedRight); CB_ASSERT (pCrt->m_eState != UNASSIGNED); bool bFail (pCrt->m_eState != ASSIGNED); if (!bFail) { // make sure no unusable space is left to the left int k (v[i].m_nOrigPos - 1); for (; k >= 0 && m_vpReaders[k]->m_eState == FAILED; --k) {} if (k >= 0) { // found something if (ASSIGNED == m_vpReaders[k]->m_eState && m_vpReaders[k]->m_pcRight != pCrt->m_pcLeft) { bFail = true; } } } if (!bFail) { // make sure no unusable space is left to the right int k (v[i].m_nOrigPos + 1); for (; k < nCnt && m_vpReaders[k]->m_eState == FAILED; ++k) {} if (k < nCnt) { // found something if (ASSIGNED == m_vpReaders[k]->m_eState && m_vpReaders[k]->m_pcLeft != pCrt->m_pcRight) { bFail = true; } } } if (bFail) { if (OPT != pCrt->m_eOptional) { // failure on non-optional return false; } pCrt->m_eState = FAILED; } } m_pcLeft = 0; m_pcRight = 0; { // assign m_pcLeft and m_pcRight + assert there are no holes; const char* pLastRight (pcLeft); Reader* pLastRd (0); for (int i = 0; i < nCnt; ++i) { Reader* p (m_vpReaders[i]); CB_ASSERT (UNASSIGNED != p->m_eState); if (ASSIGNED == p->m_eState) { //CB_ASSERT (pLastRight == p->m_pcLeft || (!m_bTopLevel && 0 == pLastRd)); if (pLastRight != p->m_pcLeft && 0 != pLastRd) { // there is a gap between the previous and current reader return false; } //CB_ASSERT (0 == pLastRd || BOUND == pLastRd->m_eBound || BOUND == p->m_eBound); // 2 unbound can't be neighbors, because then there's no way to split something between them; tttx 2008.12.17 - this doesn't allow handling of "%t[ - %a]" but allowing it here is not enough: actually "[ - %a]" is "bound to the left", and should have priority vs "%t"; 2009.01.13 - seems no longer relevant pLastRd = p; pLastRight = p->m_pcRight; if (0 == m_pcLeft) { m_pcLeft = p->m_pcLeft; } m_pcRight = p->m_pcRight; } } if (0 == pLastRd && 0 == m_pcLeft && 0 == m_pcRight) { // sequence of optionals, all failed; return false; // !!! otherwise there would be issues matching it's left/right to the neighbours; also, the only place a seq with all failed is legal is inside an Optional } CB_ASSERT (0 != pLastRd); // must be true, because !m_vpReaders.empty() /*if (pLastRight != pcRight && m_bTopLevel) { // there is a match for the sequence, but some charachters remain to the left; that's invalid in the top level reader return false; }*/ //CB_ASSERT (pLastRight == pcRight || !m_bTopLevel); CB_ASSERT (0 != m_pcLeft); CB_ASSERT (0 != m_pcRight); } if ((bFixedLeft && m_pcLeft != pcLeft) || (bFixedRight && m_pcRight != pcRight)) { return failure(); } return success(); } /*override*/ void set(vector& v) { if (ASSIGNED != m_eState) { return; } for (int i = 0, n = cSize(m_vpReaders); i < n; ++i) { Reader* p (m_vpReaders[i]); if (ASSIGNED == p->m_eState) { p->set(v); } } } private: void findLimits(int nPos, const char*& pcLeft, const char*& pcRight, bool bSeqFixedLeft, bool bSeqFixedRight, bool& bFixedLeft, bool& bFixedRight); // finds limits for m_vpReader[nPos], based on the status of the other elements //bool m_bTopLevel; }; void SequenceReader::findLimits(int nPos, const char*& pcLeft, const char*& pcRight, bool bSeqFixedLeft, bool bSeqFixedRight, bool& bFixedLeft, bool& bFixedRight) // finds limits for m_vpReader[nPos], based on the status of the other elements { pcLeft = m_pcLeft; for (int k = nPos - 1; k >= 0; --k) { Reader* p (m_vpReaders[k]); if (ASSIGNED == p->m_eState) { pcLeft = p->m_pcRight; break; } } int n (cSize(m_vpReaders)); pcRight = m_pcRight; for (int k = nPos + 1; k < n; ++k) { Reader* p (m_vpReaders[k]); if (ASSIGNED == p->m_eState) { pcRight = p->m_pcLeft; break; } } int nLeft (nPos - 1); // the reader to the left of that on nPos, skipping those that failed; -1 if there's no such thing for (; nLeft >= 0 && FAILED == m_vpReaders[nLeft]->m_eState; --nLeft) {} bFixedLeft = (-1 == nLeft && bSeqFixedLeft) || (-1 != nLeft && ASSIGNED == m_vpReaders.at(nLeft)->m_eState); //bFixedRight = (n - 1 == nPos && bSeqFixedRight) || (n - 1 != nPos && ASSIGNED == m_vpReaders[nPos + 1]->m_eState); int nRight (nPos + 1); // the reader to the right of that on nPos, skipping those that failed; n if there's no such thing for (; nRight <= n - 1 && FAILED == m_vpReaders[nRight]->m_eState; ++nRight) {} bFixedRight = (n == nRight && bSeqFixedRight) || (n != nRight && ASSIGNED == m_vpReaders.at(nRight)->m_eState); } struct OptionalReader : public Reader { SequenceReader* m_pSequenceReader; OptionalReader(SequenceReader* pSequenceReader) : Reader(OPT, pSequenceReader->getLeftBound(), pSequenceReader->getRightBound()), m_pSequenceReader(pSequenceReader) {} /*override*/ bool init(const char* pcLeft, const char* pcRight, bool bLeftFirst, bool bFixedLeft, bool bFixedRight) { m_pSequenceReader->init(pcLeft, pcRight, bLeftFirst, bFixedLeft, bFixedRight); m_eState = m_pSequenceReader->m_eState; m_pcLeft = m_pSequenceReader->m_pcLeft; m_pcRight = m_pSequenceReader->m_pcRight; return ASSIGNED == m_eState; } /*override*/ void clearState() { m_eState = UNASSIGNED; m_pSequenceReader->clearState(); } /*override*/ void set(vector& v) { if (ASSIGNED == m_eState) { m_pSequenceReader->set(v); } } }; TrackTextParser::TrackTextParser(const string& strPattern) : m_strPattern(strPattern) { if (strPattern.empty()) { throw InvalidPattern(0); } string::size_type n (0); char cSep (getPathSep()); enum FoundSep { NO, ESC, FREE }; FoundSep e (NO); for (;;) { // don't allow mixing of free and escaped patterns n = strPattern.find(cSep, n); if (n == string::npos) { break; } if (0 == n || '%' != strPattern[n - 1]) { if (ESC == e) { throw InvalidPattern(n); } else { e = FREE; } } else { if (FREE == e) { throw InvalidPattern(n); } else { e = ESC; } } ++n; } m_bSplitAtPathSep = (FREE == e); construct(strPattern); // so we can debug } void TrackTextParser::construct(const std::string& strPattern) { const char* pBegin (strPattern.c_str()); char cSep (getPathSep()); if (cSep == *pBegin) { CB_ASSERT (m_bSplitAtPathSep); ++pBegin; } const char* p (pBegin); int nErrPos (-1); StaticReader* pCrtStatic (0); stack stack; stack.push(new SequenceReader()); m_vpAllReaders.push_back(stack.top()); m_vpSeqReaders.push_back(stack.top()); for (;; ++p) { char c (*p); if (0 == c) { break; } bool bRegularChar (c != '[' && c != ']' && c != '%' && c != cSep); if ('%' == c) { char c1 (*(p + 1)); if ('%' == c1 || '[' == c1 || ']' == c1 || cSep == c1) { c = c1; ++p; bRegularChar = true; } } if (bRegularChar) { if (0 == pCrtStatic) { pCrtStatic = new StaticReader(); m_vpAllReaders.push_back(pCrtStatic); stack.top()->m_vpReaders.push_back(pCrtStatic); } pCrtStatic->m_str += c; } else { pCrtStatic = 0; if (cSep == c) { CB_ASSERT (m_bSplitAtPathSep); if (1 != cSize(stack)) { nErrPos = p - pBegin; goto err; } if (!stack.top()->setBounds()) { nErrPos = p - pBegin; goto err; } stack.pop(); stack.push(new SequenceReader()); m_vpAllReaders.push_back(stack.top()); m_vpSeqReaders.push_back(stack.top()); } else { switch (c) { case '[': { stack.push(new SequenceReader()); m_vpAllReaders.push_back(stack.top()); break; // !!! there's no point in creating an OptionalReader now; it will be created at the end of the optional sequence } case ']': { if (1 == cSize(stack)) { nErrPos = p - pBegin; goto err; } if (!stack.top()->setBounds()) { nErrPos = p - pBegin; goto err; } OptionalReader* o (new OptionalReader(stack.top())); m_vpAllReaders.push_back(o); stack.pop(); stack.top()->m_vpReaders.push_back(o); break; } case '%': { c = *++p; switch (c) { case 'n': { stack.top()->m_vpReaders.push_back(new TrackNoReader()); break; } case 'a': { stack.top()->m_vpReaders.push_back(new UnboundReader(TagReader::ARTIST)); break; } case 't': { stack.top()->m_vpReaders.push_back(new UnboundReader(TagReader::TITLE)); break; } case 'b': { stack.top()->m_vpReaders.push_back(new UnboundReader(TagReader::ALBUM)); break; } case 'y': { stack.top()->m_vpReaders.push_back(new YearReader()); break; } case 'g': { stack.top()->m_vpReaders.push_back(new UnboundReader(TagReader::GENRE)); break; } case 'r': { stack.top()->m_vpReaders.push_back(new RatingReader()); break; } case 'c': { stack.top()->m_vpReaders.push_back(new UnboundReader(TagReader::COMPOSER)); break; } //ttt2 perhaps add something for "various artists" case 'i': { stack.top()->m_vpReaders.push_back(new UnboundReader(TagReader::LIST_END)); break; } // !!! LIST_END for ignored items default: { nErrPos = p - pBegin; goto err; } } break; } default: CB_ASSERT(false); } } } } if (1 == cSize(stack) && stack.top()->setBounds()) { return; } nErrPos = p - pBegin; err: clearPtrContainer(m_vpAllReaders); throw InvalidPattern(nErrPos); } TrackTextParser::~TrackTextParser() { clearPtrContainer(m_vpAllReaders); } void TrackTextParser::assign(const string& s, vector& v) { CB_ASSERT (TagReader::LIST_END + 1 == cSize(v)); // !!! "LIST_END + 1" instead of just "LIST_END", so the last entry can be used for "%i" (i.e. "ignored") for (int i = cSize(m_vpSeqReaders) - 1; i >= 0; --i) { m_vpSeqReaders[i]->clearState(); } string::size_type nPrev (s.size()); // last position to search (inclusive) if (m_bSplitAtPathSep) { if (nPrev <= 4) { return; } nPrev -= 5; // discard ".mp3" extension } else { --nPrev; // needed because a "1" will be added below } const char* szName (s.c_str()); int n (cSize(m_vpSeqReaders)); CB_ASSERT (1 == n || m_bSplitAtPathSep); for (int i = n - 1; i >= 0; --i) { string::size_type nCrt (m_bSplitAtPathSep ? s.rfind(getPathSep(), nPrev) : 0); if (string::npos == nCrt) { break; } if (!m_bSplitAtPathSep) { --nCrt; // now it's -1 (or rather npos), to get it ready for the addition of 1 } SequenceReader& rd (*m_vpSeqReaders[i]); rd.init(szName + nCrt + 1, szName + nPrev + 1, Reader::LEFT_FIRST, Reader::FIXED_LEFT, Reader::FIXED_RIGHT); if (SequenceReader::ASSIGNED == rd.m_eState) { CB_ASSERT(rd.m_pcRight - rd.m_pcLeft == (int)nPrev - (int)nCrt); rd.set(v); /*if (rd.m_pcRight - rd.m_pcLeft == (int)nPrev - (int)nCrt) // have to test this for top-level SeqReaders, which must cover the whole in this case { rd.set(v); }*/ } nPrev = nCrt; if (0 == nPrev) { break; } --nPrev; } for (int i = 0; i < TagReader::LIST_END; ++i) { trim(v[i]); } } // returns an empty string if the pattern is valid and an error message (including the column) if it's invalid string testPattern(const string& strPattern) { try { TrackTextParser rd (strPattern); return ""; } catch (const TrackTextParser::InvalidPattern& ex) { return convStr(TrackTextParser::tr("\"%1\" is not a valid pattern. Error in column %2.").arg(toNativeSeparators(convStr(strPattern))).arg(ex.m_nPos)); } } } // namespace SongInfoParser #ifdef LPPPLPJOJAOIIIOJ #include struct TestTrackTextParser { string f (string s) { for (;;) { string::size_type n (s.find(' ')); if (string::npos == n) { return " " + s + "\n"; } s[n] = '*'; } } TestTrackTextParser() { /* { cout << "-------------------------------\n"; SongInfoParser::TrackTextParser fr ("/%n[%r]"); vector v (TagReader::LIST_END + 1); fr.init("/r/temp/1/tmp2/c pic/b/q/12ab.mp3", v); for (int i = 0; i < cSize(v); ++i) { cout << i << " " << v[i] << endl; } } { cout << "-------------------------------\n"; SongInfoParser::TrackTextParser fr ("/[%r]%n"); vector v (TagReader::LIST_END + 1); fr.init("/r/temp/1/tmp2/c pic/b/q/ab12.mp3", v); for (int i = 0; i < cSize(v); ++i) { cout << i << " " << v[i] << endl; } } { cout << "-------------------------------\n"; SongInfoParser::TrackTextParser fr ("/%a/%b/[%r]%n [-[ ]]%t"); vector v (TagReader::LIST_END + 1); fr.init("/r/temp/1/tmp2/c pic/b/q/wa04 Snow Come Down.mp3", v); for (int i = 0; i < cSize(v); ++i) { cout << i << " " << v[i] << endl; } } */ /*{ cout << "-------------------------------\n"; SongInfoParser::TrackTextParser fr ("/%a/%t/[%r]%n"); vector v (TagReader::LIST_END + 1); fr.init("/r/temp/1/tmp2/c pic/e/q/ab12.mp3", v); for (int i = 0; i < cSize(v); ++i) { cout << i << " " << v[i] << endl; } } { cout << "-------------------------------\n"; SongInfoParser::TrackTextParser fr ("/%a/%t/[%r]%n"); vector v (TagReader::LIST_END + 1); fr.init("/r/temp/1/tmp2/c pic/e/q/b12.mp3", v); for (int i = 0; i < cSize(v); ++i) { cout << i << " " << v[i] << endl; } }*/ /*{ cout << "-------------------------------\n"; SongInfoParser::TrackTextParser fr ("/[.%a.]%b"); vector v (TagReader::LIST_END + 1); fr.init("/r/temp/1/tmp2/c pic/e/q/.aaaa.bbbb.mp3", v); for (int i = 0; i < cSize(v); ++i) { cout << i << " " << v[i] << endl; } }*/ /*{ cout << "-------------------------------\n"; SongInfoParser::TrackTextParser fr ("/[.]%b"); vector v (TagReader::LIST_END + 1); fr.init("/r/temp/1/tmp2/c pic/e/q/aaaa.bbbb.mp3", v); for (int i = 0; i < cSize(v); ++i) { cout << i << " " << v[i] << endl; } } { cout << "-------------------------------\n"; SongInfoParser::TrackTextParser fr ("/[.]%b"); vector v (TagReader::LIST_END + 1); fr.init("/r/temp/1/tmp2/c pic/e/q/aaaa,bbbb.mp3", v); for (int i = 0; i < cSize(v); ++i) { cout << i << " " << v[i] << endl; } } try { //SongInfoParser::TrackTextParser fr ("/%a%b"); //SongInfoParser::TrackTextParser fr ("/%a[%b]"); //%a/%b/[%r]%n [-[ ]]%t SongInfoParser::TrackTextParser fr ("/[-[ ]]%t"); } catch (...) { cout << "err\n"; }*/ /*{ //ttt2 make these work: SongInfoParser::TrackTextParser fr ("/[ [ ]][-[ [ ]]]%t"); //SongInfoParser::TrackTextParser fr ("/%t"); const char* s ("-------------------------------\n"); vector v; vector (TagReader::LIST_END + 1).swap(v); fr.init(".../ tst01.mp3", v); cout << "2 " << s; for (int i = 0; i < cSize(v); ++i) { cout << i << " " << v[i] << endl; } }//*/ /*{ //ttt2 make these work: SongInfoParser::TrackTextParser fr ("/[[%r]%n][[ ] ][-[[ ] ]]%t"); //SongInfoParser::TrackTextParser fr ("/%t"); const char* s ("-------------------------------\n"); vector v; vector (TagReader::LIST_END + 1).swap(v); fr.init(".../b06 -tst01.mp3", v); cout << "2 " << s; for (int i = 0; i < cSize(v); ++i) { cout << i << " " << v[i] << endl; } }*/ /*{ SongInfoParser::TrackTextParser fr ("/[[%r]%n][ [ ]][-[ [ ]]]%t"); const char* s ("-------------------------------\n"); vector v; vector (TagReader::LIST_END + 1).swap(v); fr.assign(".../b06tst01.mp3", v); cout << "1 " << s; for (int i = 0; i < cSize(v); ++i) { cout << i << f(v[i]); } vector (TagReader::LIST_END + 1).swap(v); fr.assign(".../b06 tst01.mp3", v); cout << "2 " << s; for (int i = 0; i < cSize(v); ++i) { cout << i << f(v[i]); } vector (TagReader::LIST_END + 1).swap(v); fr.assign(".../b06 tst01.mp3", v); cout << "3 " << s; for (int i = 0; i < cSize(v); ++i) { cout << i << f(v[i]); } vector (TagReader::LIST_END + 1).swap(v); fr.assign(".../b06-tst01.mp3", v); cout << "4 " << s; for (int i = 0; i < cSize(v); ++i) { cout << i << f(v[i]); } vector (TagReader::LIST_END + 1).swap(v); fr.assign(".../b06- tst01.mp3", v); cout << "5 " << s; for (int i = 0; i < cSize(v); ++i) { cout << i << f(v[i]); } vector (TagReader::LIST_END + 1).swap(v); fr.assign(".../b06 -tst01.mp3", v); cout << "6 " << s; for (int i = 0; i < cSize(v); ++i) { cout << i << f(v[i]); } vector (TagReader::LIST_END + 1).swap(v); fr.assign(".../b06 - tst01.mp3", v); cout << "7 " << s; for (int i = 0; i < cSize(v); ++i) { cout << i << f(v[i]); } vector (TagReader::LIST_END + 1).swap(v); fr.assign(".../b06 - tst01.mp3", v); cout << "8 " << s; for (int i = 0; i < cSize(v); ++i) { cout << i << f(v[i]); } vector (TagReader::LIST_END + 1).swap(v); fr.assign(".../b06 - tst01.mp3", v); cout << "9 " << s; for (int i = 0; i < cSize(v); ++i) { cout << i << f(v[i]); } vector (TagReader::LIST_END + 1).swap(v); fr.assign(".../b06 - tst01.mp3", v); cout << "10 " << s; for (int i = 0; i < cSize(v); ++i) { cout << i << f(v[i]); } }//*/ /*{ SongInfoParser::TrackTextParser fr ("[[%r]%n][ [ ]][-[ [ ]]]%t"); const char* s ("-------------------------------\n"); vector v; vector (TagReader::LIST_END + 1).swap(v); fr.assign("b06tst01.mp3", v); cout << "1 " << s; for (int i = 0; i < cSize(v); ++i) { cout << i << f(v[i]); } vector (TagReader::LIST_END + 1).swap(v); fr.assign("b06 tst01.mp3", v); cout << "2 " << s; for (int i = 0; i < cSize(v); ++i) { cout << i << f(v[i]); } vector (TagReader::LIST_END + 1).swap(v); fr.assign("b06 tst01.mp3", v); cout << "3 " << s; for (int i = 0; i < cSize(v); ++i) { cout << i << f(v[i]); } vector (TagReader::LIST_END + 1).swap(v); fr.assign("b06-tst01.mp3", v); cout << "4 " << s; for (int i = 0; i < cSize(v); ++i) { cout << i << f(v[i]); } vector (TagReader::LIST_END + 1).swap(v); fr.assign("b06- tst01.mp3", v); cout << "5 " << s; for (int i = 0; i < cSize(v); ++i) { cout << i << f(v[i]); } vector (TagReader::LIST_END + 1).swap(v); fr.assign("b06 -tst01.mp3", v); cout << "6 " << s; for (int i = 0; i < cSize(v); ++i) { cout << i << f(v[i]); } vector (TagReader::LIST_END + 1).swap(v); fr.assign("b06 - tst01.mp3", v); cout << "7 " << s; for (int i = 0; i < cSize(v); ++i) { cout << i << f(v[i]); } vector (TagReader::LIST_END + 1).swap(v); fr.assign("b06 - tst01.mp3", v); cout << "8 " << s; for (int i = 0; i < cSize(v); ++i) { cout << i << f(v[i]); } vector (TagReader::LIST_END + 1).swap(v); fr.assign("b06 - tst01.mp3", v); cout << "9 " << s; for (int i = 0; i < cSize(v); ++i) { cout << i << f(v[i]); } vector (TagReader::LIST_END + 1).swap(v); fr.assign("b06 - tst01.mp3", v); cout << "10 " << s; for (int i = 0; i < cSize(v); ++i) { cout << i << f(v[i]); } }//*/ { SongInfoParser::TrackTextParser fr ("%t-%an"); const char* s ("-------------------------------\n"); vector v; vector (TagReader::LIST_END + 1).swap(v); fr.assign("HHHHHHHHHHHHHH - QQQQQQQ Listen", v); cout << "1 " << s; for (int i = 0; i < cSize(v); ++i) { cout << i << f(v[i]); } vector (TagReader::LIST_END + 1).swap(v); fr.assign("H-Listen", v); cout << "2 " << s; for (int i = 0; i < cSize(v); ++i) { cout << i << f(v[i]); } }//*/ { SongInfoParser::TrackTextParser fr ("%t-%aen"); const char* s ("-------------------------------\n"); vector v; vector (TagReader::LIST_END + 1).swap(v); fr.assign("HHHHHHHHHHHHHH - QQQQQQQ Listen", v); cout << "3 " << s; for (int i = 0; i < cSize(v); ++i) { cout << i << f(v[i]); } vector (TagReader::LIST_END + 1).swap(v); fr.assign("H-Len", v); cout << "4 " << s; for (int i = 0; i < cSize(v); ++i) { cout << i << f(v[i]); } }//*/ /* patterns\value0000=%a/%b/[%r]%n [-[ ]]%t patterns\value0001=%t patterns\value0002=%i %t[go] patterns\value0003=[%a][%b] patterns\value0004=%a-%b patterns\value0005=%a[-%b] patterns\value0006=[%a-]%b patterns\value0007=[%a]%b { cout << "-------------------------------\n"; SongInfoParser::TrackTextParser fr ("/%a/%b/[%r]%n [-[ ]]%t"); vector v (TagReader::LIST_END + 1); fr.init("/r/temp/1/tmp2/c pic/b/q/wa04 Snow Come Down.mp3", v); for (int i = 0; i < cSize(v); ++i) { cout << i << " " << v[i] << endl; } } { cout << "-------------------------------\n"; SongInfoParser::TrackTextParser fr ("/%a/%b/[%r]%n [-[ ]]%t"); vector v (TagReader::LIST_END + 1); fr.init("/r/temp/1/tmp2/c pic/b/q/a04 Snow Come Down.mp3", v); for (int i = 0; i < cSize(v); ++i) { cout << i << " " << v[i] << endl; } }*/ } }; /* --------------------- cp "tst01.mp3" "d1/b06tst01.mp3" cp "tst01.mp3" "d1/b06 tst01.mp3" cp "tst01.mp3" "d1/b06 tst01.mp3" cp "tst01.mp3" "d1/b06-tst01.mp3" cp "tst01.mp3" "d1/b06- tst01.mp3" cp "tst01.mp3" "d1/b06 -tst01.mp3" cp "tst01.mp3" "d1/b06 - tst01.mp3" cp "tst01.mp3" "d1/b06 - tst01.mp3" cp "tst01.mp3" "d1/b06 - tst01.mp3" cp "tst01.mp3" "d1/b06 - tst01.mp3" cp "tst01.mp3" "d1/06tst01.mp3" cp "tst01.mp3" "d1/06 tst01.mp3" cp "tst01.mp3" "d1/06 tst01.mp3" cp "tst01.mp3" "d1/06-tst01.mp3" cp "tst01.mp3" "d1/06- tst01.mp3" cp "tst01.mp3" "d1/06 -tst01.mp3" cp "tst01.mp3" "d1/06 - tst01.mp3" cp "tst01.mp3" "d1/06 - tst01.mp3" cp "tst01.mp3" "d1/06 - tst01.mp3" cp "tst01.mp3" "d1/06 - tst01.mp3" cp "tst01.mp3" "d1/btst01.mp3" cp "tst01.mp3" "d1/b tst01.mp3" cp "tst01.mp3" "d1/b tst01.mp3" cp "tst01.mp3" "d1/b-tst01.mp3" cp "tst01.mp3" "d1/b- tst01.mp3" cp "tst01.mp3" "d1/b -tst01.mp3" cp "tst01.mp3" "d1/b - tst01.mp3" cp "tst01.mp3" "d1/b - tst01.mp3" cp "tst01.mp3" "d1/b - tst01.mp3" cp "tst01.mp3" "d1/b - tst01.mp3" */ TestTrackTextParser qqwtrh; #endif MP3Diags-1.2.02/src/FileEnum.cpp0000644000175000001440000003205211265050316015147 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ //#include #include #include #include #include "FileEnum.h" #include "OsFile.h" using namespace std; // All directory names in DirTreeEnumeratorImpl end with the path separator struct DirTreeEnumerator::DirTreeEnumeratorImpl { DirTreeEnumeratorImpl(const vector& vstrIncludeDirs, const vector& vstrExcludeDirs); void reset(); void reset(const vector& vstrIncludeDirs, const vector& vstrExcludeDirs); set m_sstrIncludeDirs, m_sstrExcludeDirs; set m_sstrProcDirs; // selected dirs (or their descendants) that have no ancestors in m_sstrProcDirs (for the first step they should have no ancestors in m_sstrIncludeDirs) set m_sstrWaitingDirs; // selected dirs (or their descendants) that have ancestors in m_sstrProcDirs; //void getClosestAncestor(string strDir, string& strAncestor, bool& bIsChecked); enum ClosestAncestorState { NO_ANCESTOR, INCLUDED, EXCLUDED }; ClosestAncestorState getClosestAncestorState(const string& strDir) const; bool hasAncestorInProcDirs(const string& strDir) const; // "strict" ancestor, doesn't matter if the argument itself is in ProcDirs void addDirs(set& s, const vector& v) { for (int i = 0, n = cSize(v); i < n; ++i) { string str (v[i]); CB_ASSERT (string::npos == str.find("//")); // put to see if handling of "//" is needed; // ttt2 see also QFileInfo::absoluteFilePath(), for Windows meaning if (!endsWith(str, "/")) { str += "/"; } s.insert(str); } } vector m_vstrCrtDirList; // contains files only string m_strCrtDir; // used because QFileInfo::absoluteFilePath() has a performance warning; not sure if it helps; see also QFileInfo::canonicalFilePath int m_nCrtDirNdx; // index in m_vstrCrtDirList string next(); // returns an empty string if there's nothing next bool isIncluded(const string& strFile); }; DirTreeEnumerator::~DirTreeEnumerator() { delete m_pImpl; } DirTreeEnumerator::DirTreeEnumerator(const vector& vstrIncludeDirs, const vector& vstrExcludeDirs) : m_pImpl(new DirTreeEnumeratorImpl(vstrIncludeDirs, vstrExcludeDirs)), m_noDefaults(0) { } DirTreeEnumerator::DirTreeEnumerator() : m_pImpl(new DirTreeEnumeratorImpl(vector(), vector())), m_noDefaults(0) { } void DirTreeEnumerator::reset(const vector& vstrIncludeDirs, const vector& vstrExcludeDirs) { m_pImpl->reset(vstrIncludeDirs, vstrExcludeDirs); } void DirTreeEnumerator::reset() { m_pImpl->reset(); } string DirTreeEnumerator::next() // returns an empty string if there's nothing next { return m_pImpl->next(); } bool DirTreeEnumerator::isIncluded(const std::string& strFile) { return m_pImpl->isIncluded(strFile); } DirTreeEnumerator::DirTreeEnumeratorImpl::DirTreeEnumeratorImpl(const vector& vstrIncludeDirs, const vector& vstrExcludeDirs) { reset(vstrIncludeDirs, vstrExcludeDirs); } void DirTreeEnumerator::DirTreeEnumeratorImpl::reset(const vector& vstrIncludeDirs, const vector& vstrExcludeDirs) { m_sstrIncludeDirs.clear(); m_sstrExcludeDirs.clear(); addDirs(m_sstrIncludeDirs, vstrIncludeDirs); addDirs(m_sstrExcludeDirs, vstrExcludeDirs); vector v; set_intersection(m_sstrIncludeDirs.begin(), m_sstrIncludeDirs.end(), m_sstrExcludeDirs.begin(), m_sstrExcludeDirs.end(), back_inserter(v)); CB_CHECK1 (vstrIncludeDirs.size() + vstrExcludeDirs.size() == m_sstrIncludeDirs.size() + m_sstrExcludeDirs.size() && v.empty(), DirTreeEnumerator::InvalidDirs()); for (set::iterator it = m_sstrIncludeDirs.begin(), end = m_sstrIncludeDirs.end(); it != end; ++it) { ClosestAncestorState e (getClosestAncestorState(*it)); CB_CHECK1 (NO_ANCESTOR == e || EXCLUDED == e, DirTreeEnumerator::InvalidDirs()); } for (set::iterator it = m_sstrExcludeDirs.begin(), end = m_sstrExcludeDirs.end(); it != end; ++it) { ClosestAncestorState e (getClosestAncestorState(*it)); CB_CHECK1 (INCLUDED == e, DirTreeEnumerator::InvalidDirs()); } reset(); } void DirTreeEnumerator::DirTreeEnumeratorImpl::reset() { m_nCrtDirNdx = -1; } DirTreeEnumerator::DirTreeEnumeratorImpl::ClosestAncestorState DirTreeEnumerator::DirTreeEnumeratorImpl::getClosestAncestorState(const string& strDir) const { string s (strDir); //bIsChecked = false; strAncestor.clear(); if (endsWith(s, "/")) { s.erase(s.size() - 1); } // it's messier to work with an s that ends with '/', because then s=="/" is a special case for (;;) { string::size_type n (s.rfind("/")); if (string::npos == n) { return NO_ANCESTOR; } s.erase(n + 1); bool bI (m_sstrIncludeDirs.count(s) > 0); bool bE (m_sstrExcludeDirs.count(s) > 0); CB_ASSERT (!(bE && bI)); if (bI) { return INCLUDED; } if (bE) { return EXCLUDED; } s.erase(s.size() - 1); } } bool DirTreeEnumerator::DirTreeEnumeratorImpl::hasAncestorInProcDirs(const string& strDir) const // "strict" ancestor, doesn't matter if the argument itself is in ProcDirs { string s (strDir); if (endsWith(s, "/")) { s.erase(s.size() - 1); } // it's messier to work with an s that ends with '/', because then s=="/" is a special case for (;;) { string::size_type n (s.rfind("/")); if (string::npos == n) { return false; } s.erase(n + 1); if (m_sstrProcDirs.count(s) > 0) { return true; } s.erase(s.size() - 1); } } bool DirTreeEnumerator::DirTreeEnumeratorImpl::isIncluded(const string& strFile) { //return INCLUDED == getClosestAncestorState(getParent(strFile)); return INCLUDED == getClosestAncestorState(strFile); } /* Algorithm: - split m_sstrIncludeDirs in 2, putting those that have no ancestors in m_sstrProcDirs and the others in m_sstrWaitingDirs; - when next() is called: - go to the next file in the "current dir", putting all the found directories in m_sstrIncludeDirs (well, unless they are in m_sstrExcludeDirs); - if there are files left in the "current dir", return the next file; - if there are no files left in the "current dir", take the first entry from m_sstrIncludeDirs and turn it into the current dir, then move to m_sstrIncludeDirs all entries from m_sstrWaitingDirs that don't have an ancestor in m_sstrIncludeDirs - if m_sstrIncludeDirs is empty, just return ""; */ string DirTreeEnumerator::DirTreeEnumeratorImpl::next() // returns an empty string if there's nothing next { if (-1 == m_nCrtDirNdx) { m_nCrtDirNdx = 0; m_vstrCrtDirList.clear(); set sstrNewProc; m_sstrWaitingDirs.clear(); m_sstrProcDirs = m_sstrIncludeDirs; // !!! needed for hasAncestorInProcDirs() for (set::iterator it = m_sstrIncludeDirs.begin(), end = m_sstrIncludeDirs.end(); it != end; ++it) { if (!hasAncestorInProcDirs(*it)) { sstrNewProc.insert(*it); } else { m_sstrWaitingDirs.insert(*it); } } m_sstrProcDirs.swap(sstrNewProc); } for (;;) { if (m_nCrtDirNdx < cSize(m_vstrCrtDirList)) { return m_vstrCrtDirList[m_nCrtDirNdx++]; } if (m_sstrProcDirs.empty()) { if (m_sstrWaitingDirs.empty()) { return ""; } set sstrNewProc, sstrNewWt; m_sstrProcDirs = m_sstrWaitingDirs; // !!! needed for hasAncestorInProcDirs() for (set::iterator it = m_sstrWaitingDirs.begin(), end = m_sstrWaitingDirs.end(); it != end; ++it) { if (!hasAncestorInProcDirs(*it)) { sstrNewProc.insert(*it); } else { sstrNewWt.insert(*it); } } sstrNewWt.swap(m_sstrWaitingDirs); sstrNewProc.swap(m_sstrProcDirs); } m_vstrCrtDirList.clear(); m_nCrtDirNdx = 0; m_strCrtDir = *m_sstrProcDirs.begin(); m_sstrProcDirs.erase(m_sstrProcDirs.begin()); for (set::iterator it = m_sstrWaitingDirs.begin(), end = m_sstrWaitingDirs.end(); it != end; ++it) { if (!hasAncestorInProcDirs(*it)) { m_sstrWaitingDirs.insert(*it); } } QDir dir (convStr(m_strCrtDir)); QFileInfoList lst (dir.entryInfoList(QDir::AllEntries | QDir::NoDotAndDotDot)); for (QFileInfoList::iterator it = lst.begin(), end = lst.end(); it != end; ++it) { const QFileInfo& inf (*it); string s (convStr(inf.fileName())); s = m_strCrtDir + s; // !!! note that inf.isSymLink() may return true in addition to isFile() or isDir() returning true, so no "special case" processing is needed unless we want to exclude symlinks if (inf.isFile()) { m_vstrCrtDirList.push_back(s); } else if (inf.isDir()) { if (!endsWith(s, "/")) { s += "/"; } if (0 == m_sstrExcludeDirs.count(s)) { m_sstrProcDirs.insert(s); } } } } } /* struct AAAlp { AAAlp() { vector vIncl, vExcl; //vIncl.push_back("/r/temp/1/tmp2/a rep/a"); vIncl.push_back("/r/temp/1/tmp2/a rep/"); vExcl.push_back("/r/temp/1/tmp2/a rep/b"); DirTreeEnumerator en (vIncl, vExcl); for (;;) { string s (en.next()); if (s.empty()) { throw 1; } qDebug("%s", s.c_str()); } } }; AAAlp lprecerce; */ //======================================================================================================================== //======================================================================================================================== //======================================================================================================================== ListEnumerator::ListEnumerator(const string& strDir) : m_noDefaults(0), m_nCrt(0) { QDir dir (convStr(strDir)); string s1 (strDir); if (!endsWith(s1, "/")) { s1 += "/"; } QFileInfoList lst (dir.entryInfoList(QDir::Files | QDir::NoDotAndDotDot)); for (QFileInfoList::iterator it = lst.begin(), end = lst.end(); it != end; ++it) { const QFileInfo& inf (*it); string s (convStr(inf.fileName())); s = s1 + s; // !!! note that inf.isSymLink() may return true in addition to isFile() or isDir() returning true, so no "special case" processing is needed unless we want to exclude symlinks if (inf.isFile()) { m_vstrFiles.push_back(s); } } } ListEnumerator::ListEnumerator(const std::vector& vstrFiles) : m_noDefaults(0), m_vstrFiles(vstrFiles), m_nCrt(0) { } // returns an empty string if there's nothing next /*override*/ std::string ListEnumerator::next() { if (m_nCrt >= cSize(m_vstrFiles)) { return ""; } return m_vstrFiles[m_nCrt++]; } //======================================================================================================================== //======================================================================================================================== //======================================================================================================================== MP3Diags-1.2.02/src/AboutDlgImpl.cpp0000644000175000001440000002512612265745454016011 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include "AboutDlgImpl.h" #include "Helpers.h" #include "Version.h" using namespace Version; /* " + QString(::AboutDlgImpl::tr("QQQ")).arg("QQQ").arg("QQQ").arg("QQQ") + " " + QString(::AboutDlgImpl::tr("QQQ")).arg("QQQ").arg("QQQ") + " " + QString(::AboutDlgImpl::tr("QQQ")).arg("QQQ") + " " + ::AboutDlgImpl::tr("QQQ").arg("QQQ").arg("QQQ").arg("QQQ") + " " + ::AboutDlgImpl::tr("QQQ").arg("QQQ").arg("QQQ") + " " + ::AboutDlgImpl::tr("QQQ").arg("QQQ") + " */ AboutDlgImpl::AboutDlgImpl(QWidget* pParent /* = 0*/) : QDialog(pParent, getDialogWndFlags()), Ui::AboutDlg() { setupUi(this); QPalette pal (m_pMainTextM->palette()); pal.setColor(QPalette::Base, pal.color(QPalette::Disabled, QPalette::Window)); m_pMainTextM->setPalette(pal); /* NNNNNNNNNNNNNN */ m_pMainTextM->setHtml( "

" + AboutDlgImpl::tr("Written by %1, %2").arg("Marian Ciobanu (Ciobi)").arg("2008 - 2013") + "

" "

" + AboutDlgImpl::tr("Command-line mode by %1, %2").arg("Michael Elsdörfer").arg("2011") + "

" "

" + AboutDlgImpl::tr("%1 translation by %2, %3").arg(AboutDlgImpl::tr("Czech")).arg("Pavel Fric").arg("2012") + "

" "

" + AboutDlgImpl::tr("%1 translation by %2, %3").arg(AboutDlgImpl::tr("German")).arg("Marco Krause").arg("2012") + "

" "

" + AboutDlgImpl::tr("%1 translation by %2, %3").arg(AboutDlgImpl::tr("French")).arg("Gwilherm Baudic").arg("2012") + "

" "

" + AboutDlgImpl::tr("Distributed under %1").arg("GPL V2") + "

" "

" + AboutDlgImpl::tr("Using %1, released under %2").arg("Qt").arg("LGPL 2.1") + "

" "

" + AboutDlgImpl::tr("Using %1, released under the %2zlib License%3").arg("zlib").arg("").arg("") + "

" "

" + AboutDlgImpl::tr("Using %1 and %2, distributed under the %3Boost Software License%4").arg("Boost Serialization").arg("Boost Program Options").arg("").arg("") + "

" "

" + AboutDlgImpl::tr("Using original and modified icons from the %1 for %2, distributed under %3LGPL V3%4").arg("Oxygen Project").arg("KDE 4").arg("").arg("") + "

" "

" + AboutDlgImpl::tr("Using web services provided by %1 to retrieve album data").arg("Discogs") + "

" "

" + AboutDlgImpl::tr("Using web services provided by %1 to retrieve album data").arg("MusicBrainz") + "

" "

" + AboutDlgImpl::tr("Home page and documentation: %1").arg("http://mp3diags.sourceforge.net" + QString(getWebBranch()) + "/") + "

" "

" + AboutDlgImpl::tr("Feedback and support: %1 or %2 at SourceForge").arg("Open Discussion Forum").arg("Help Forum") + "

" "

" + AboutDlgImpl::tr("Bug reports and feature requests: %1 at SourceForge").arg("MantisBT Issue Tracker") + "

" "

" + AboutDlgImpl::tr("Change log for the latest version: %1").arg("http://mp3diags.sourceforge.net" + QString(getWebBranch()) + "/015_changelog.html") + "

" ); m_pVersionL->setText(QString(getAppName()) + " " + getAppVer()); //ttt1 write "unstable" in red initText(m_pGplV2M, ":/licences/gplv2.txt"); initText(m_pGplV3M, ":/licences/gplv3.txt"); initText(m_pLgplV3M, ":/licences/lgplv3.txt"); initText(m_pLgplV21M, ":/licences/lgpl-2.1.txt"); initText(m_pBoostM, ":/licences/boost.txt"); initText(m_pZlibM, ":/licences/zlib.txt"); m_pSysInfoM->setText(getSystemInfo()); m_pMainTextM->setFocus(); //{ QAction* p (new QAction(this)); p->setShortcut(QKeySequence("Ctrl+N")); connect(p, SIGNAL(triggered()), this, SLOT(accept())); addAction(p); } { QAction* p (new QAction(this)); p->setShortcut(QKeySequence("F1")); connect(p, SIGNAL(triggered()), this, SLOT(onHelp())); addAction(p); } } void AboutDlgImpl::initText(QTextBrowser* p, const char* szFileName) { QFile f (szFileName); //QFile::FileError err (f.error()); //qDebug("file: %d", (int)err); //qDebug("size : %d", (int)f.size()); //QByteArray b (f.readAll()); f.open(QIODevice::ReadOnly); //qDebug("read size : %d", (int)b.size()); p->setText(QString::fromUtf8(f.readAll())); } AboutDlgImpl::~AboutDlgImpl() { } void AboutDlgImpl::onHelp() { openHelp("index.html"); } // exist: mp3 insight, mp3 doctor, mp3 butcher, mp3 toolbox, mp3 mechanic, mp3 workshop; //ttt melt ? ice ? ? sorcerer ? exorcist ? healer ? ? MP3 Spy //"mp3 workshop", "mp3 atelier" //workshop synonyms: foundry, laboratory, mill, plant, studio, works // deep understanding /* //bjam --toolset=gcc //PATH=D:\Qt\2009.02\mingw\bin;%PATH% bjam serialization toolset=gcc bjam toolset=gcc serialization threading=multi release http://stackoverflow.com/questions/718447/adding-external-library-into-qt-creator-project http://stackoverflow.com/questions/199092/compiling-a-qt-program-in-windows-xp-with-mingws-g */ /* Finds problems in MP3 files and helps the user to fix many of them using included tools. Looks at both the audio part (VBR info, quality, normalization) and the tags containing track information (ID3.) Also includes a tag editor and a file renamer. */ //PATH=D:\Qt\2009.02\qt\bin;%PATH% //ttt2 perhaps "Scan images in the current folder", checked by default //ttt2 perhaps something to remove image files after assigning them, or at least show them in a different color; it was suggested to add a "-" button to remove images, below the "v" for "assigning them", but not sure it's such great idea; perhaps some option to delete local images that were assigned (but perhaps the unassigned CD scan should go as well); // perhaps "-" works, though; should be enabled/visible only for local files //ttt2 some standard means to log only uncaught exceptions // backport jaunty 9.04 : https://bugs.launchpad.net/jaunty-backports/+bug/423560 /* //ttt2 w7 8.3 names C:\Windows\system32>fsutil behavior query disable8dot3 c: The volume state for Disable8dot3 is 0 (8dot3 name creation is enabled). The registry state of NtfsDisable8dot3NameCreation is 2, the default (Volume level setting). Based on the above two settings, 8dot3 name creation is enabled on c:. */ //ttt2 Perhaps linking Boost statically and Qt dynamically would solve most dependency issues - http://pages.cs.wisc.edu/~thomas/X/static-linking.html : "surround the libraries you wish to link statically with -static and -dynamic (in that order)"; OTOH "-dynamic" is not in man, and that page is from 1997; things have changed, and -static seems to be a global option, so it doesn't matter if you put it first or last; what should work is specifying the file name: -l:libboost_serialization.a rather than -lboost_serialization (!!! note the ":") //ttt2 mutt rips: https://sourceforge.net/projects/mp3diags/forums/forum/947206/topic/3441516 ; also, check for missing tracks and other album-related issues; //ttt0 might have to remove the program before switching packages; - in the fake mp3diags //ttt1 01 - The Privateer.mp3 - go to tag editor, change track, save; new image doesn't show, but there is the original "other" bmp and the new "cover" jpg //ttt2 maybe support for saving images to .directory files - point 7 at https://sourceforge.net/projects/mp3diags/forums/forum/947206/topic/3389395 MP3Diags-1.2.02/src/ThreadRunnerDlgImpl.cpp0000644000175000001440000002400311724676023017323 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include "ThreadRunnerDlgImpl.h" #include "Helpers.h" PausableThread::PausableThread(/*QObject* pParent = 0*/) : /*QThread(pParent),*/ m_bPaused(false), m_bAborted(false) { static bool s_bRegistered (false); if (!s_bRegistered) { s_bRegistered = true; qRegisterMetaType("StrList"); } } /*virtual*/ PausableThread::~PausableThread() { //qDebug("thread destroyed"); } void PausableThread::pause() { m_bPaused = true; // !!! no synch needed } void PausableThread::resume() { QMutexLocker lck(&m_mutex); m_bPaused = false; m_waitCondition.wakeAll(); } void PausableThread::abort() { QMutexLocker lck(&m_mutex); m_bAborted = true; m_waitCondition.wakeAll(); } void PausableThread::checkPause() // if m_bPaused is set, it waits until resume() or abort() get called; otherwise it returns immediately { if (!m_bPaused) { return; } QMutexLocker lck(&m_mutex); if (!m_bPaused) { return; } // !!! it was tested 2 lines above, but might have changed after that; now it's a different story, because it's protected by the mutex; if (m_bAborted) { return; } m_waitCondition.wait(&m_mutex); } //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== ThreadRunnerDlgImpl::ThreadRunnerDlgImpl(QWidget* pParent, Qt::WFlags flags, PausableThread* pThread, bool bShowCounter, TruncatePos eTruncatePos, bool bShowPauseAbort /* = true*/) : QDialog(pParent, flags), Ui::ThreadRunnerDlg(), m_pThread(pThread), m_nCounter(0), m_bShowCounter(bShowCounter), //m_nLastKey(0), m_tRealBegin(time(0)), m_tRunningBegin(time(0)), m_bShowPauseAbort(bShowPauseAbort), m_bFirstTime(true), m_eTruncatePos(eTruncatePos) { setupUi(this); if (!bShowPauseAbort) { m_pPauseResumeB->hide(); m_pAbortB->hide(); } pThread->setParent(this); connect(m_pThread, SIGNAL(stepChanged(const StrList&, int)), this, SLOT(onStepChanged(const StrList&, int))); connect(m_pThread, SIGNAL(completed(bool)), this, SLOT(onThreadCompleted(bool))); connect(&m_closeTimer, SIGNAL(timeout()), this, SLOT(onCloseTimer())); connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(onUpdateTimer())); { QAction* p (new QAction(this)); p->setShortcut(QKeySequence(Qt::Key_Escape)); connect(p, SIGNAL(triggered()), this, SLOT(on_m_pAbortB_clicked())); addAction(p); } // !!! make ESC call on_m_pAbortB_clicked() instead of closing the dialog m_pCurrentL->setText(""); pThread->start(); m_updateTimer.start(500); // 0.5 sec { QAction* p (new QAction(this)); p->setShortcut(QKeySequence("F1")); connect(p, SIGNAL(triggered()), this, SLOT(onHelp())); addAction(p); } } ThreadRunnerDlgImpl::~ThreadRunnerDlgImpl() { } void ThreadRunnerDlgImpl::onThreadCompleted(bool bSuccess) { m_bSuccess = bSuccess; // !!! can't just exit; should wait for the thread's "run()" metod to finish m_pCurrentL->setText(tr("Completed")); m_pPauseResumeB->setEnabled(false); m_pAbortB->setEnabled(false); m_closeTimer.start(100); // 0.1 sec onCloseTimer(); // this may return immediately or not, depending on which thread gets executed after "PausableThread::notifComplete()" } void ThreadRunnerDlgImpl::on_m_pPauseResumeB_clicked() { if (!m_pThread->m_bPaused) { // currently running m_pPauseResumeB->setText(tr("&Resume")); m_pThread->pause(); m_tPauseBegin = time(0); } else { // currently paused m_pPauseResumeB->setText(tr("&Pause")); time_t t (time(0)); m_tRunningBegin = m_tRunningBegin + t - m_tPauseBegin; m_pThread->resume(); } } void ThreadRunnerDlgImpl::on_m_pAbortB_clicked() { m_pPauseResumeB->setEnabled(false); m_pAbortB->setEnabled(false); m_pThread->abort(); } QString ThreadRunnerDlgImpl::truncateLarge(const QString& s, int nKeepFirst /* = 0*/) // truncates strings that are too wide to display without resizing { QFontMetrics fontMetrics (m_pCurrentL->font()); const int MARGIN (8); // normally this should be 0 but in other cases Qt missed a few pixels when estimating how much space it needed, so it seems better to lose some pixels // ttt2 2009.04.30 - actually this is probably related to spacing; if this is true, the hard-coded value should be replaced by some query to QApplication::style()->pixelMetric() if (fontMetrics.width(s) < m_pCurrentL->width() - MARGIN) { return s; } int nSize (s.size() - 1); QString res (s); switch (m_eTruncatePos) { case TRUNCATE_BEGIN: { res.insert (nKeepFirst, "... "); nKeepFirst += 4; // size of "... " nSize -= nKeepFirst; while (nSize > 0 && fontMetrics.width(res) >= m_pCurrentL->width() - MARGIN) { res.remove(nKeepFirst, 1); --nSize; } return res; } case TRUNCATE_END: { while (nSize > 0 && fontMetrics.width(res + " ...") >= m_pCurrentL->width() - MARGIN) { res.truncate(nSize); --nSize; } return res + " ..."; } default: //CB_ASSERT (false); //ttt2 add support for TRUNCATE_MIDDLE throw false; } } //void ThreadRunnerDlgImpl::onStepChanged(const QString& qstrLabel1, const QString& qstrLabel2 /* = ""*/, const QString& qstrLabel3 /* = ""*/, const QString& qstrLabel4 /* = ""*/) void ThreadRunnerDlgImpl::onStepChanged(const StrList& v, int nStep) { //qDebug("step %s", qstrLabel.toStdString().c_str()); if (-1 == nStep) { ++m_nCounter; } else { m_nCounter = nStep; } m_vStepInfo = v; if (m_bFirstTime) { QTimer::singleShot(1, this, SLOT(onUpdateTimer())); m_bFirstTime = false; } } static QString getTimeFmt(int n) // n in seconds { int h (n / 3600); n -= h*3600; int m (n / 60); n -= m*60; QString q; q.sprintf("%d:%02d:%02d", h, m, n); return q; } void ThreadRunnerDlgImpl::onCloseTimer() { //qDebug("ThreadRunnerDlgImpl::onTimer()"); if (!m_pThread->isFinished()) { //qDebug("waithing for thread"); return; } m_closeTimer.stop(); m_updateTimer.stop(); if (m_bSuccess) { accept(); } else { reject(); } } void ThreadRunnerDlgImpl::onUpdateTimer() { QString s; if (m_bShowCounter) { static QLocale loc ("C"); s = loc.toString(m_nCounter) + ". "; } int n ((int)m_vStepInfo.size()); if (n >= 1) { s = truncateLarge(s + m_vStepInfo[0], s.size()); for (int i = 1; i < n; ++i) { s += "\n" + truncateLarge(m_vStepInfo[i]); } } time_t t (time(0)); if (m_bShowPauseAbort) { s += tr("\n\nTotal time: %1" // ttt1 show Total time only if different from Running time "\nRunning time: %2").arg(getTimeFmt(t - m_tRealBegin)).arg(getTimeFmt((m_pThread->m_bPaused ? m_tPauseBegin : t) - m_tRunningBegin)); } else { s += "\n\n" + tr("Time: %1").arg(getTimeFmt(t - m_tRealBegin)); } m_pCurrentL->setText(s); } /*override*/ void ThreadRunnerDlgImpl::closeEvent(QCloseEvent* pEvent) { pEvent->ignore(); on_m_pAbortB_clicked(); } void ThreadRunnerDlgImpl::onHelp() { // openHelp("index.html"); //ttt2 see if anything more specific can be done } #if 0 /* Not sure if this should work: from the doc for QDialog: Escape Key If the user presses the Esc key in a dialog, QDialog::reject() will be called. This will cause the window to close: The close event cannot be ignored. ttt2 see Qt::Key_Escape in MainFormDlgImpl for a different approach, decide which is better */ /*override*/ void ThreadRunnerDlgImpl::keyPressEvent(QKeyEvent* pEvent) { //qDebug("key prs %d", pEvent->key()); m_nLastKey = pEvent->key(); pEvent->ignore(); } /*override*/ void ThreadRunnerDlgImpl::keyReleaseEvent(QKeyEvent* pEvent) { //qDebug("key rel %d", pEvent->key()); if (Qt::Key_Escape == pEvent->key()) { on_m_pAbortB_clicked(); } pEvent->ignore(); // ttt2 not sure this is the way to do it, but the point is to disable the ESC key } #endif MP3Diags-1.2.02/src/TagReadPanel.h0000644000175000001440000000433411260643304015401 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include "CommonTypes.h" struct TagReader; class QToolButton; class QLabel; class ImageInfoPanel : public QFrame { Q_OBJECT ImageInfo m_imageInfo; QToolButton* m_pBtn; QLabel* m_pInfoLabel; void createButton(int nSize); public: ImageInfoPanel(QWidget* pParent, const ImageInfo& imageInfo); void resize(int nSize); // removes the label and makes the button smaller protected slots: void onShowFull(); }; class TagReadPanel : public QFrame { Q_OBJECT QWidget* m_pImgWidget; std::vector m_vpImgPanels; public: TagReadPanel(QWidget* pParent, TagReader* pTagReader); protected slots: void onCheckSize(); }; MP3Diags-1.2.02/src/Mp3Manip.h0000644000175000001440000002214411576173660014552 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef Mp3HandlerH #define Mp3HandlerH #include "fstream_unicode.h" #include #include "SerSupport.h" #include #include "Notes.h" #include "ThreadRunnerDlgImpl.h" class Id3V2StreamBase; class Id3V230Stream; class Id3V240Stream; class Id3V1Stream; class LameStream; class XingStreamBase; class VbriStream; class MpegStream; class ApeStream; class LyricsStream; class NullDataStream; class UnknownDataStream; class BrokenDataStream; class UnsupportedDataStream; class TruncatedMpegDataStream; class DataStream; PausableThread* getSerThread(); //ttt2 global function struct QualThresholds { int m_nStereoCbr; // below this bitrate for stereo CBR a warning is generated; value is in bps int m_nJointStereoCbr; // below this bitrate for joint stereo CBR a warning is generated int m_nDoubleChannelCbr; // below this bitrate for dual channel CBR a warning is generated; half of this value is used for mono streams int m_nStereoVbr; // below this bitrate for stereo VBR a warning is generated int m_nJointStereoVbr; // below this bitrate for joint stereo VBR a warning is generated int m_nDoubleChannelVbr; // below this bitrate for dual channel VBR a warning is generated; half of this value is used for mono streams // ttt2 add Lame header info analysis, which might give a better idea about quality than simple bitrate static const QualThresholds& getDefaultQualThresholds(); }; // the details for an MP3 file class Mp3Handler { StringWrp* m_pFileName; // couldn't get Boost ser to allow an object, rather than a pointer; tried BOOST_CLASS_TRACKING; the reason seems to be that when using an object, it is also saved by the frames, as a pointer; so they all should be pointers; Id3V230Stream* m_pId3V230Stream; Id3V240Stream* m_pId3V240Stream; Id3V1Stream* m_pId3V1Stream; LameStream* m_pLameStream; XingStreamBase* m_pXingStream; VbriStream* m_pVbriStream; MpegStream* m_pMpegStream; ApeStream* m_pApeStream; LyricsStream* m_pLyricsStream; std::vector m_vpNullStreams; std::vector m_vpUnknownStreams; std::vector m_vpBrokenStreams; std::vector m_vpUnsupportedStreams; std::vector m_vpTruncatedMpegStreams; std::vector m_vpAllStreams; ifstream_utf8* m_pIn; // this isn't used after the constructor completes; however, many streams have a StreamStateRestorer member, which does this on its destructor: it restores the stream position if the stream's constructor fails and clears the errors otherwise, assuming that the stream pointer is non-0; m_pIn is set to 0 after Mp3Handler's constructor completes, so the restorers of the successfuly built streams don't do anything when the streams get destroyed, because they see a null pointer; //bool m_bHasId3V2; //MpegFrame m_firstFrame; //const char* szFileName; //bool m_bVbr; //int m_nBitrate; std::streampos m_posEnd; long long m_nSize, m_nTime; NoteColl m_notes; // owned pointers void parse(ifstream_utf8&); void analyze(const QualThresholds& qualThresholds); // checks the streams for issues (missing ID3V2, Unknown streams, inconsistencies, ...) //void checkMpegAudio(); //void checkEqualFrames(std::streampos pos); //void findFirstFrame(std::streampos& pos); void checkLastFrameInMpegStream(ifstream_utf8& in); // what looks like the last frame in an MPEG stream may actually be truncated and somewhere inside it an ID3V1 or Ape tag may actually begin; if that's the case, that "frame" is removed from the stream; then most likely an "Unknown" stream will be detected, followed by an ID3V1 or Ape stream void reloadId3V2Hlp(); public: Mp3Handler(const std::string& strFileName, bool bStoreTraceNotes, const QualThresholds& qualThresholds); ~Mp3Handler(); //void copyMpeg(std::istream& in, std::ostream& out); //void logStreamInfo() const; //void logStreamInfo(std::ostream& out) const; const NoteColl& getNotes() const { return m_notes; } const std::vector& getStreams() const { return m_vpAllStreams; } const std::string& getName() const { return m_pFileName->s; } QString getUiName() const; // uses native separators long long getSize() const; std::string getShortName() const; std::string getDir() const; const Id3V2StreamBase* getId3V2Stream() const; // if the underlying file seems changed (or removed); looks at time and size, as well as FastSaveWarn and Notes::getMissingNote(); a difference in size always makes this return true; if bUseFastSave is true, both m_nFastSaveTime and m_nTime are tested, while m_notes.hasFastSaveWarn() and Notes::getMissingNote() are ignored; if it is false, m_nFastSaveTime is ignored, while m_nTime, m_notes.hasFastSaveWarn(), and Notes::getMissingNote() are tested bool needsReload(bool bUseFastSave) const; void sortNotes() { m_notes.sort(); } // this is needed when loading from the disk, if unknown (most likely obsolete) notes are found // removes the ID3V2 tag and the notes associated with it and scans the file again, but only the new ID3V2 tag; // asserts that there was an existing ID3V2 tag at the beginning and it had the same size as the new one; // this isn't really const, but it seems better to have a const_cast in the only place where it is needed rather than remove the const restriction from many places void reloadId3V2() const; mutable long long m_nFastSaveTime; enum { DONT_USE_FAST_SAVE, USE_FAST_SAVE }; struct FileNotFound {}; private: friend class boost::serialization::access; Mp3Handler() {} // serialization-only constructor template void serialize(Archive& ar, const unsigned int nVersion) { if (nVersion > 0) { throw std::runtime_error("invalid version of serialized file"); } ar & m_pFileName; ar & m_pId3V230Stream; ar & m_pId3V240Stream; ar & m_pId3V1Stream; ar & m_pLameStream; ar & m_pXingStream; ar & m_pVbriStream; ar & m_pMpegStream; ar & m_pApeStream; ar & m_pLyricsStream; ar & m_vpNullStreams; ar & m_vpUnknownStreams; ar & m_vpBrokenStreams; ar & m_vpUnsupportedStreams; ar & m_vpTruncatedMpegStreams; ar & m_vpAllStreams; ar & m_posEnd; ar & m_nSize; ar & m_nTime; ar & m_notes; PausableThread* pThread (getSerThread()); if (0 != pThread) { StrList l; l.push_back(QString::fromUtf8(m_pFileName->s.c_str())); pThread->emitStepChanged(l); } } }; struct CmpMp3HandlerPtrByName { /*bool operator()(const std::string& strName1, const std::string& strName2) const { return cmp(strName1, strName2); } bool operator()(const std::string& strName1, const Mp3Handler* p2) const { return cmp(strName1, p2->getName()); }*/ bool operator()(const Mp3Handler* p1, const std::string& strName2) const; bool operator()(const std::string& strName1, const Mp3Handler* p2) const; bool operator()(const Mp3Handler* p1, const Mp3Handler* p2) const; private: bool cmp(const std::string& strName1, const std::string& strName2) const; }; // finds the position where the next ID3V2, MPEG, Xing, Lame or Ape frame begins; "pos" is not considered; the search starts at pos+1; throws if no frame is found std::streampos getNextStream(std::istream& in, std::streampos pos); #endif // #ifndef Mp3HandlerH MP3Diags-1.2.02/src/ExportDlgImpl.cpp0000644000175000001440000005671111724620314016206 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include "ExportDlgImpl.h" #include "Helpers.h" #include "CommonData.h" #include "Widgets.h" #include "StoredSettings.h" #include "DataStream.h" #include "fstream_unicode.h" #include "Id3V2Stream.h" #include "MpegStream.h" #include "ApeStream.h" #include "LyricsStream.h" using namespace std; ExportDlgImpl::ExportDlgImpl(QWidget* pParent) : QDialog(pParent, getDialogWndFlags()), Ui::ExportDlg() { setupUi(this); int nWidth, nHeight; bool bSortByShortNames; string strFile; bool bUseVisible; string strM3uRoot; string strLocale; getCommonData()->m_settings.loadExportSettings(nWidth, nHeight, bSortByShortNames, strFile, bUseVisible, strM3uRoot, strLocale); if (nWidth > 400 && nHeight > 300) { resize(nWidth, nHeight); } else { //defaultResize(*this); } m_pSortByShortNamesCkB->setChecked(bSortByShortNames); m_pFileNameE->setText(toNativeSeparators(convStr(strFile))); (bUseVisible ? m_pVisibleRB : m_pSelectedRB)->setChecked(true); m_pM3uRootE->setText(toNativeSeparators(convStr(strM3uRoot))); { // locale QStringList lNames; set s; QList l (QTextCodec::availableCodecs()); for (int i = 0, n = l.size(); i < n; ++i) { s.insert(QTextCodec::codecForName(l[i])->name()); // a codec is known by several names; by doing this we eliminate redundant names and make the list a lot smaller } for (set::const_iterator it = s.begin(); it != s.end(); ++it) { lNames << *it; } m_pLocaleCbB->addItems(lNames); int n (m_pLocaleCbB->findText(strLocale.c_str())); if (-1 == n) { n = 0; } m_pLocaleCbB->setCurrentIndex(n); } m_pM3uRB->setChecked(true); setFormatBtn(); } ExportDlgImpl::~ExportDlgImpl() { } void ExportDlgImpl::run() { if (QDialog::Accepted != exec()) { return; } getCommonData()->m_settings.saveExportSettings(width(), height(), m_pSortByShortNamesCkB->isChecked(), convStr(fromNativeSeparators(m_pFileNameE->text())), m_pVisibleRB->isChecked(), convStr(fromNativeSeparators(m_pM3uRootE->text())), m_pLocaleCbB->currentText().toUtf8().constData()); } QString ExportDlgImpl::getFileName() { QString qs (fromNativeSeparators(m_pFileNameE->text())); if (qs.isEmpty()) { showCritical(this, tr("Error"), tr("The file name cannot be empty. Exiting ...")); return ""; } if (QFileInfo(qs).isAbsolute()) { return qs; } if (!m_pM3uRB->isChecked()) { showCritical(this, tr("Error"), tr("You need to specify an absolute file name when exporting to formats other than .m3u. Exiting ...")); return ""; } QString qstrRoot (fromNativeSeparators(m_pM3uRootE->text())); if (qstrRoot.isEmpty()) { showCritical(this, tr("Error"), tr("The root cannot be empty if the file name is relative. Exiting ...")); return ""; } if (!QFileInfo(qstrRoot).isAbsolute()) { showCritical(this, tr("Error"), tr("The root must be an absolute directory name. Exiting ...")); return ""; } if (!qstrRoot.endsWith(getPathSep())) { qstrRoot += getPathSep(); } if (!QFileInfo(qstrRoot).isDir()) { showCritical(this, tr("Error"), tr("The root doesn't exist. Exiting ...")); return ""; } return qstrRoot + qs; } void ExportDlgImpl::on_m_pExportB_clicked() { QString qs (getFileName()); if (qs.isEmpty()) { return; } if (QFileInfo(qs).isFile()) { if (0 != HtmlMsg::msg(this, 1, 1, 0, 0, tr("Warning"), tr("A file called \"%1\" already exists. Do you want to overwrite it?").arg(toNativeSeparators(qs)), 600, 200, tr("&Overwrite"), tr("Cancel"))) { return; } } bool b (false); { CursorOverrider crs; string s (convStr(qs)); if (m_pTextRB->isChecked()) { b = exportAsText(s); } else if (m_pM3uRB->isChecked()) { b = exportAsM3u(s); } else if (m_pXmlRB->isChecked()) { b = exportAsXml(s); } else { CB_ASSERT(false); } } if (b) { HtmlMsg::msg(this, 0, 0, 0, 0, tr("Info"), tr("Successfully created file \"%1\"").arg(toNativeSeparators(qs)), 600, 200, tr("O&K")); } else { HtmlMsg::msg(this, 0, 0, 0, 0, tr("Error"), tr("There was an error writing to the file \"%1\"").arg(toNativeSeparators(qs)), 600, 200, tr("O&K")); } } void ExportDlgImpl::on_m_pChooseFileB_clicked() { QString qstrDir (QFileInfo(fromNativeSeparators(m_pFileNameE->text())).path()); QFileDialog dlg (this, tr("Choose destination file"), qstrDir, tr("XML files (*.xml);;Text files (*.txt);;M3U files (*.m3u)")); /*QStringList filters; filters << "Text files (*.txt)" << "M3U files (*.m3u)"; dlg.setFilters(filters);*/ dlg.setFileMode(QFileDialog::AnyFile); if (QDialog::Accepted != dlg.exec()) { return; } QStringList fileNames (dlg.selectedFiles()); if (1 != fileNames.size()) { return; } QString s (fileNames.first()); QString flt (dlg.selectedFilter()); if (flt.endsWith("xml)") && !s.endsWith(".xml")) { s += ".xml"; } else if (flt.endsWith("txt)") && !s.endsWith(".txt")) { s += ".txt"; } else if (flt.endsWith("m3u)") && !s.endsWith(".m3u")) { s += ".m3u"; } m_pFileNameE->setText(toNativeSeparators(s)); setFormatBtn(); } void ExportDlgImpl::setFormatBtn() { QString qs (m_pFileNameE->text()); if (qs.endsWith(".xml")) { m_pXmlRB->setChecked(true); } else if (qs.endsWith(".txt")) { m_pTextRB->setChecked(true); } else if (qs.endsWith(".m3u")) { m_pM3uRB->setChecked(true); } } void ExportDlgImpl::setExt(const char* szExt) { CB_ASSERT (3 == strlen(szExt)); QString s (m_pFileNameE->text()); int n (s.size() - 4); if (n > 0 && '.' == s[n]) { QString s1 (s); s1.remove(n + 1, 3); s1 += szExt; if (s1 != s) { m_pFileNameE->setText(s1); } } } /*$SPECIALIZATION$*/ namespace { struct CmpMp3HandlerByShortNameAndSize { bool operator()(const Mp3Handler* p1, const Mp3Handler* p2) { if (p1->getShortName() < p2->getShortName()) { return true; } if (p2->getShortName() < p1->getShortName()) { return false; } if (p1->getSize() < p2->getSize()) { return true; } if (p2->getSize() < p1->getSize()) { return false; } return p1->getName() < p2->getName(); } }; } void ExportDlgImpl::getHandlers(vector& v) { v.clear(); const deque& vpHndl (m_pVisibleRB->isChecked() ? getCommonData()->getViewHandlers() : getCommonData()->getSelHandlers()); v.insert(v.end(), vpHndl.begin(), vpHndl.end()); if (m_pSortByShortNamesCkB->isChecked()) { sort(v.begin(), v.end(), CmpMp3HandlerByShortNameAndSize()); } } bool ExportDlgImpl::exportAsText(const string& strFileName) { vector v; getHandlers(v); ofstream_utf8 out (strFileName.c_str()); //const char* aSeverity = "EWST"; QString qstrSeverity = tr("EWST", "the letters are the initials of the 4 severity levels: Error, Warning, Support, Trace"); for (int i = 0, n = cSize(v); i < n; ++i) { const Mp3Handler* p (v[i]); out << toNativeSeparators(p->getName()) << " "; out << p->getSize() << endl; const vector& vpStreams (p->getStreams()); for (int i = 0, n = cSize(vpStreams); i < n; ++i) { DataStream* p (vpStreams[i]); out << " " << hex << p->getPos() << "-" << (p->getPos() + (p->getSize() - 1)) << dec << " (" << p->getSize() << ") " << convStr(p->getTranslatedDisplayName()); const string& s (p->getInfo()); if (!s.empty()) { out << ": " << s; } out << endl; } const NoteColl& notes (p->getNotes()); out << " --------------------------------------------\n"; vector vpNotes (notes.getList().begin(), notes.getList().end()); sort(vpNotes.begin(), vpNotes.end(), CmpNotePtrByPosAndId()); for (int i = 0, n = cSize(vpNotes); i < n; ++i) { const Note* p (vpNotes[i]); if (getCommonData()->m_bUseAllNotes || getCommonData()->findPos(p) >= 0) // !!! "ignored" notes shouldn't be exported unless UseAllNotes is checked, so there is consistency between what is shown on the screen and what is saved { out << " " << convStr(QString(qstrSeverity[p->getSeverity()])) << " "; const string& q (p->getPosHex()); if (!q.empty()) { out << q << " "; } const string& s (p->getDetail()); if (s.empty()) // ttt2 perhaps show descr anyway { out << p->getDescription(); } else { out << s; } out << endl; } } out << "\n\n"; } return out; } bool ExportDlgImpl::exportAsM3u(const std::string& strFileName) { QTextCodec* pCodec (QTextCodec::codecForName(m_pLocaleCbB->currentText().toUtf8())); CB_ASSERT (0 != pCodec); vector v; getHandlers(v); string strRoot (convStr(fromNativeSeparators(m_pM3uRootE->text()))); if (!strRoot.empty() && !endsWith(strRoot, getPathSepAsStr())) { strRoot += getPathSepAsStr(); } int nRootSize (cSize(strRoot)); ofstream_utf8 out (strFileName.c_str()); for (int i = 0, n = cSize(v); i < n; ++i) { const Mp3Handler* p (v[i]); string s (toNativeSeparators(p->getName())); if (nRootSize > 0) { if (beginsWith(s, strRoot)) { s.erase(0, nRootSize); } else { CursorOverrider crs (Qt::ArrowCursor); showCritical(this, tr("Error"), tr("The file named \"%1\" isn't inside the specified root. Exiting ...").arg(convStr(s))); return false; } } QString qs (convStr(s)); if (!pCodec->canEncode(qs)) { CursorOverrider crs (Qt::ArrowCursor); showCritical(this, tr("Error"), tr("The file named \"%1\" cannot be encoded in the selected locale. Exiting ...").arg(qs)); return false; } out << pCodec->fromUnicode(qs).constData() << endl; } return out; } namespace { string escapeXml(const string& s) { QString qs (Qt::escape(convStr(s))); qs.replace(QString("\""), """); qs.replace(QString("#"), "#"); return convStr(qs); } void printDataStream(ostream& out, DataStream* p) { out << " type=\"" << p->getDisplayName() << "\" begin=\"0x" << hex << p->getPos() << "\" end=\"0x" << (p->getPos() + (p->getSize() - 1)) << "\" size=\"" << dec << p->getSize() << "\""; } void printTagReader(ostream& out, TagReader* p) { bool b; string s; if (TagReader::NOT_SUPPORTED != p->getSupport(TagReader::TITLE)) { s = p->getTitle(&b); if (b) { out << " title=\"" << escapeXml(s) << "\""; } } if (TagReader::NOT_SUPPORTED != p->getSupport(TagReader::ARTIST)) { s = p->getArtist(&b); if (b) { out << " artist=\"" << escapeXml(s) << "\""; } } if (TagReader::NOT_SUPPORTED != p->getSupport(TagReader::TRACK_NUMBER)) { s = p->getTrackNumber(&b); if (b) { out << " trackNo=\"" << escapeXml(s) << "\""; } } if (TagReader::NOT_SUPPORTED != p->getSupport(TagReader::TIME)) { p->getTime(&b); if (b) { out << " time=\"" << p->getValue(TagReader::TIME) << "\""; } } if (TagReader::NOT_SUPPORTED != p->getSupport(TagReader::GENRE)) { s = p->getGenre(&b); if (b) { out << " genre=\"" << escapeXml(s) << "\""; } } if (TagReader::NOT_SUPPORTED != p->getSupport(TagReader::IMAGE)) { s = p->getImageData(&b); if (b) { out << " image=\"" << escapeXml(s) << "\""; } } if (TagReader::NOT_SUPPORTED != p->getSupport(TagReader::ALBUM)) { s = p->getAlbumName(&b); if (b) { out << " album=\"" << escapeXml(s) << "\""; } } if (TagReader::NOT_SUPPORTED != p->getSupport(TagReader::RATING)) { double d (p->getRating(&b)); if (b) { out << " rating=\"" << setprecision(1) << d << "\""; } } if (TagReader::NOT_SUPPORTED != p->getSupport(TagReader::COMPOSER)) { s = p->getComposer(&b); if (b) { out << " composer=\"" << escapeXml(s) << "\""; } } if (TagReader::NOT_SUPPORTED != p->getSupport(TagReader::VARIOUS_ARTISTS)) { p->getVariousArtists(&b); if (b) { out << " variousArtists=\"" << p->getValue(TagReader::VARIOUS_ARTISTS) << "\""; } } } enum { DONT_PRINT_BPS, PRINT_BPS }; void printMpegInfo(ostream& out, const MpegFrame& frm, bool bPrintBps) { out << " version=\"" << frm.getSzVersion() << "\"" << " layer=\"" << frm.getSzLayer() << "\"" << " channelMode=\"" << frm.getSzChannelMode() << "\"" << " frequency=\"" << frm.getFrequency() << "\""; if (bPrintBps) { out << " bps=\"" << frm.getBitrate() << "\""; } out << " crc=\"" << boolAsYesNo(frm.getCrcUsage()) << "\""; } } // namespace bool ExportDlgImpl::exportAsXml(const std::string& strFileName) //ttt1 XML is not translated because: 1) not sure it's a good idea; 2) it's significant work and not sure it's worth it { vector v; getHandlers(v); ofstream_utf8 out (strFileName.c_str()); out << "\n\n"; /*{ string x; for (int i = 32; i < 128; ++i) x += char(i); out << "\n" << escapeXml(x) << "\n\n"; }*/ const char* aszSeverity[] = { "error", "warning", "support", "trace" }; for (int i = 0, n = cSize(v); i < n; ++i) { const Mp3Handler* p (v[i]); out << " getName())) << "\" size=\"" << p->getSize() << "\">\n"; out << " \n"; const vector& vpStreams (p->getStreams()); for (int i = 0, n = cSize(vpStreams); i < n; ++i) { DataStream* p (vpStreams[i]); if (0 != dynamic_cast(p)) { Id3V2StreamBase* p1 (dynamic_cast(p)); out << " \n"; vector vpFrames (p1->getFrames()); for (int i = 0, n = cSize(vpFrames); i < n; ++i) { Id3V2Frame* p (vpFrames[i]); if (Id3V2Frame::NO_APIC != p->m_eApicStatus) { out << " getReadableName() << "\" size=\"" << p->m_nDiskDataSize << "\" width=\"" << p->m_nWidth << "\" height=\"" << p->m_nHeight << "\" type=\"" << p->getImageType() << "\" status=\"" << p->getImageStatus() << "\" compr=\"" << ImageInfo::getComprStr(p->m_eCompr) << "\"/>\n"; //ttt1 somehow redundant, as there are image details both here and several lines above, in the call to printTagReader(); well, it looks like this has a few more details, while the other is an attribute blob; probably keep it as is, as it might be used } else if ('T' == p->m_szName[0]) // ttt2 not quite right for TXXX { out << " getReadableName() << "\" size=\"" << p->m_nDiskDataSize << "\">\n"; out << " " << escapeXml(p->getUtf8String()) << "\n"; out << " \n"; } else if (0 == strcmp(KnownFrames::LBL_RATING(), p->m_szName)) { out << " getReadableName() << "\" size=\"" << p->m_nDiskDataSize << "\" normalizedValue=\"" << setprecision(1) << p->getRating() << "\"/>\n"; } else { out << " getReadableName() << "\" size=\"" << p->m_nDiskDataSize << "\"/>\n"; } } out << " \n"; } else if (0 != dynamic_cast(p)) { Id3V1Stream* p1 (dynamic_cast(p)); out << " getVersion() << "\""; printDataStream(out, p); printTagReader(out, p1); out << "/>\n"; } else if (0 != dynamic_cast(p)) { MpegStream* p1 (dynamic_cast(p)); out << " getDuration() << "\""; printMpegInfo(out, p1->getFirstFrame(), DONT_PRINT_BPS); out << " bitrate=\"" << p1->getBitrate() << "\"" << " vbr=\"" << boolAsYesNo(p1->isVbr()) << "\"" << " frameCount=\"" << p1->getFrameCount() << "\""; // << " bitrate=\"" << p1->getBitrate() << "\"" (m_bRemoveLastFrameCalled ? " removed; it was" : "") << " located at 0x" << hex << m_posLastFrame out << "/>\n"; } else if (0 != dynamic_cast(p)) { XingStream* p1 (dynamic_cast(p)); out << " getFirstFrame(), PRINT_BPS); out << p1->getInfoForXml() << "/>\n"; } else if (0 != dynamic_cast(p)) { LameStream* p1 (dynamic_cast(p)); out << " getFirstFrame(), PRINT_BPS); out << p1->getInfoForXml() << "/>\n"; } else if (0 != dynamic_cast(p)) { VbriStream* p1 (dynamic_cast(p)); out << " getFirstFrame(), PRINT_BPS); out << "/>\n"; } else if (0 != dynamic_cast(p)) { TruncatedMpegDataStream* p1 (dynamic_cast(p)); out << " getBegin(), 0); out << "/>\n"; } else if (0 != dynamic_cast(p)) { ApeStream* p1 (dynamic_cast(p)); out << " \n"; } else if (0 != dynamic_cast(p)) { LyricsStream* p1 (dynamic_cast(p)); out << " \n"; } else { out << " \n"; const string& s (p->getInfo()); if (!s.empty()) { out << " \n"; out << " " << escapeXml(s) << "\n"; out << " \n"; } out << " \n"; } } out << " \n"; const NoteColl& notes (p->getNotes()); out << " \n"; vector vpNotes (notes.getList().begin(), notes.getList().end()); sort(vpNotes.begin(), vpNotes.end(), CmpNotePtrByPosAndId()); for (int i = 0, n = cSize(vpNotes); i < n; ++i) { const Note* p (vpNotes[i]); if (getCommonData()->m_bUseAllNotes || getCommonData()->findPos(p) >= 0) // !!! "ignored" notes shouldn't be exported unless UseAllNotes is checked, so there is consistency between what is shown on the screen and what is saved { out << " getSeverity()] << "\""; const string& q (p->getPosHex()); if (!q.empty()) { out << " pos=\"" << q << "\""; } out << " description=\"" << escapeXml(p->getDescription()) << "\""; const string& s (p->getDetail()); if (!s.empty()) { out << " detail=\"" << escapeXml(s) << "\""; } out << "/>\n"; } } out << " \n"; out << " \n"; } out << "\n"; return out; } MP3Diags-1.2.02/src/SessionEditorDlgImpl.h0000644000175000001440000001014611715721512017156 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef SessionEditorDlgImplH #define SessionEditorDlgImplH #include #include #include "ui_SessionEditor.h" class CheckedDirModel; class QSettings; class SessionEditorDlgImpl : public QDialog, private Ui::SessionEditorDlg { Q_OBJECT CheckedDirModel* m_pDirModel; const std::string m_strDir; std::string m_strSessFile; bool m_bNew; std::string m_strTranslation; bool m_bOpenLastSession; // meaningful only if bFirstTime was true on the constructor; void commonConstr(); // common code for both constructors void setWindowTitle(); public: // if bFirstTime is false it doesn't show the "Open last session" checkbox; // strDir is used as a start directory by on_m_pFileNameB_clicked; enum { NOT_FIRST_TIME, FIRST_TIME }; SessionEditorDlgImpl(QWidget* pParent, const std::string& strDir, bool bFirstTime, const std::string& strTranslation); // used for creating a new session; SessionEditorDlgImpl(QWidget* pParent, const std::string& strSessFile); // used for editing an existing session; ~SessionEditorDlgImpl(); /*$PUBLIC_FUNCTIONS$*/ //std::vector m_vstrCheckedDirs; //std::vector m_vstrUncheckedDirs; // !!! not needed; retrieved from the config file bool shouldOpenLastSession() const { return m_bOpenLastSession; } // meaningful only if bFirstTime was true on the constructor; // returns the name of an INI file for OK and an empty string for Cancel; returns "*" to just go to the sessions dialog; std::string run(); const std::string& getTranslation() const { return m_strTranslation; } static std::string getDataFileName(const std::string& strSessFile); static std::string getLogFileName(const std::string& strSessFile); static std::string getBaseName(const std::string& strSessFile); // removes extension static std::string getTitleName(const std::string& strSessFile); // removes path and extension static void removeSession(const std::string& strSessFile); // removes all files associated with a session: .ini, .mp3ds, .log, .dat, _trace.txt, _step1.txt, _step2.txt static const char* const SESS_EXT; static int SESS_EXT_LEN; public slots: /*$PUBLIC_SLOTS$*/ protected: /*$PROTECTED_FUNCTIONS$*/ protected slots: /*$PROTECTED_SLOTS$*/ void on_m_pOkB_clicked(); void on_m_pCancelB_clicked(); void on_m_pBackupB_clicked(); void on_m_pFileNameB_clicked(); //void on_m_pLoadB_clicked(); void on_m_pOpenSessionsB_clicked(); void on_m_pTranslationCbB_currentIndexChanged(int); void onShow(); void onHelp(); }; #endif MP3Diags-1.2.02/src/TagEdtPatternsDlgImpl.cpp0000644000175000001440000001421011724673226017614 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include "TagEdtPatternsDlgImpl.h" #include "Helpers.h" #include "SongInfoParser.h" #include "StoredSettings.h" #include "Widgets.h" using namespace std; TagEdtPatternsDlgImpl::TagEdtPatternsDlgImpl(QWidget* pParent, SessionSettings& settings, const vector& vstrPredef) : QDialog(pParent, getDialogWndFlags()), Ui::PatternsDlg(), m_settings(settings), m_vstrPredef(vstrPredef), m_nCrtLine(-1), m_nCrtCol(-1) { setupUi(this); QPalette grayPalette (m_infoM->palette()); grayPalette.setColor(QPalette::Base, grayPalette.color(QPalette::Disabled, QPalette::Window)); m_infoM->setPalette(grayPalette); m_infoM->setTabStopWidth(fontMetrics().width("%ww")); #ifndef WIN32 QString qsSep (getPathSep()); #else QString qsSep ("\\"); #endif m_infoM->setText(tr("%n\ttrack number\n%a\tartist\n%t\ttitle\n%b\talbum\n%y\tyear\n%g\tgenre\n%r\trating (a lowercase letter)\n%c\tcomposer\n%i\tignored" "\n\nTo include the special characters \"%\", \"[\", \"]\" and \"%1\", preced them by a \"%\": \"%%\", \"%[\", \"%]\" and \"%%1\"" "\n\nFor a pattern to be considered a \"file pattern\" (as opposed to a \"table pattern\"), it must contain at least a \"%1\", even if you don't care about what's in the file's parent directory (see the fourth predefined pattern for an example.)" "\n\nLeading and trailing spaces are removed automatically from unbound fields after matching, so \"-[ ]%t\" is equivalent to \"-%t\" (but \"-[ ]%n\" is not equivalent to \"-%n\", because %n is a fixed format field). However, all non-optional characters matter in the matching phase, including spaces.").arg(qsSep)); //ttt2 maybe further improve wording - see https://sourceforge.net/projects/mp3diags/forums/forum/947207/topic/3962666: int nWidth, nHeight; m_settings.loadTagEdtPatternsSettings(nWidth, nHeight); if (nWidth > 400 && nHeight > 300) { resize(nWidth, nHeight); } connect(m_pTextM, SIGNAL(cursorPositionChanged()), this, SLOT(onCrtPosChanged())); { QAction* p (new QAction(this)); p->setShortcut(QKeySequence("F1")); connect(p, SIGNAL(triggered()), this, SLOT(onHelp())); addAction(p); } } TagEdtPatternsDlgImpl::~TagEdtPatternsDlgImpl() { } /*$SPECIALIZATION$*/ void TagEdtPatternsDlgImpl::on_m_pCancelB_clicked() { reject(); } void TagEdtPatternsDlgImpl::on_m_pOkB_clicked() { m_vPatterns.clear(); string s (convStr(m_pTextM->toPlainText())); const char* p (s.c_str()); if (0 == *p) { accept(); return; } for (; '\n' == *p; ++p) {} const char* q (p); for (;;) { if ('\n' == *p || 0 == *p) { string s1 (q, p - q); s1 = fromNativeSeparators(s1); string strCheck (SongInfoParser::testPattern(s1)); if (!strCheck.empty()) { showCritical(this, tr("Error"), convStr(strCheck)); return; } m_vPatterns.push_back(s1); for (; '\n' == *p; ++p) {} if (0 == *p) { break; } q = p; } ++p; } m_settings.saveTagEdtPatternsSettings(width(), height()); accept(); } bool TagEdtPatternsDlgImpl::run(vector >& v) { string s; for (int i = 0, n = cSize(v); i < n; ++i) { if (!s.empty()) { s += "\n"; } s += toNativeSeparators(v[i].first); } m_pTextM->setText(convStr(s)); if (QDialog::Accepted != exec()) { return false; } set sPos; vector > v1; for (int i = 0, n = cSize(m_vPatterns); i < n; ++i) { int j (0); int m (cSize(v)); for (; j < m; ++j) { if (m_vPatterns[i] == v[j].first && sPos.end() == sPos.find(j)) { sPos.insert(j); break; } } if (m == j) { j = -1; } v1.push_back(make_pair(m_vPatterns[i], j)); } //v.clear(); v.swap(v1); return true; } void TagEdtPatternsDlgImpl::on_m_pAddPredefB_clicked() { string s (convStr(m_pTextM->toPlainText())); for (unsigned i = 0; i < m_vstrPredef.size(); ++i) { if (!s.empty() && !endsWith(s, "\n")) { s += "\n"; } s += toNativeSeparators(m_vstrPredef[i]); } m_pTextM->setText(convStr(s)); } void TagEdtPatternsDlgImpl::onHelp() { openHelp("220_tag_editor_patterns.html"); } void TagEdtPatternsDlgImpl::onCrtPosChanged() { QTextCursor crs (m_pTextM->textCursor()); m_nCrtLine = crs.blockNumber(); m_nCrtCol = crs.columnNumber(); m_pCrtPosL->setText(tr("Line %1, Col %2").arg(m_nCrtLine + 1).arg(m_nCrtCol + 1)); } MP3Diags-1.2.02/src/SessionsDlgImpl.h0000644000175000001440000000775311715723662016214 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef SessionsDlgImplH #define SessionsDlgImplH #include #include #include "ui_Sessions.h" class QSettings; ////#include class CheckedDirModel; class SessionsModel : public QAbstractTableModel { Q_OBJECT //TagEditorDlgImpl* m_pTagEditorDlgImpl; //TagWriter* m_pTagWriter; //const CommonData* m_pCommonData; std::vector& m_vstrSessions; public: SessionsModel(std::vector& vstrSessions); /*override*/ int rowCount(const QModelIndex&) const; /*override*/ int columnCount(const QModelIndex&) const { return 1; } /*override*/ QVariant data(const QModelIndex&, int nRole = Qt::DisplayRole) const; /*override*/ QVariant headerData(int nSection, Qt::Orientation eOrientation, int nRole = Qt::DisplayRole) const; void emitLayoutChanged() { emit layoutChanged(); } }; class SessionsDlgImpl : public QDialog, private Ui::SessionsDlg { Q_OBJECT std::vector m_vstrSessions; //QSettings& m_settings; SessionsModel m_sessionsModel; //QSettings* m_pSettings; CheckedDirModel* m_pCheckedDirModel; std::string getCrtSession() const; // returns empty str if there's no session std::string getCrtSessionDir() const; //bool m_bOpenLast; void removeCrtSession(); void selectSession(const std::string& strLast); void addSession(const std::string&); std::string m_strTempSessTempl, m_strDirSessTempl; void loadTemplates(); // sets up the combo boxes with temp/folder session templates based on m_vstrSessions, m_strTempSessTempl, and m_strDirSessTempl void saveTemplates(); // sets m_strTempSessTempl and m_strDirSessTempl based on the current items in the combo boxes std::string m_strTranslation; public: SessionsDlgImpl(QWidget* pParent /*, QSettings& settings*/); ~SessionsDlgImpl(); /*$PUBLIC_FUNCTIONS$*/ std::string run(); public slots: /*$PUBLIC_SLOTS$*/ protected: /*$PROTECTED_FUNCTIONS$*/ protected slots: /*$PROTECTED_SLOTS$*/ void onCrtSessChanged(); void on_m_pNewB_clicked(); void on_m_pEditB_clicked(); void on_m_pEraseB_clicked(); void on_m_pSaveAsB_clicked(); void on_m_pHideB_clicked(); void on_m_pLoadB_clicked(); void on_m_pOpenB_clicked(); void on_m_pCloseB_clicked(); void on_m_pTranslationCbB_currentIndexChanged(int); //void on_ _clicked(); void onShow(); void onSessDoubleClicked(const QModelIndex& index); void onHelp(); }; #endif MP3Diags-1.2.02/src/ApeStream.h0000644000175000001440000001165311720256551015002 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef ApeStreamH #define ApeStreamH #include #include #include // for translation #include "DataStream.h" //struct ApeItem; struct ApeItem // !!! needs to be public for serialization { Q_DECLARE_TR_FUNCTIONS(ApeItem) public: ApeItem(NoteColl& notes, std::istream& in); ~ApeItem() {} unsigned char m_cFlags1; unsigned char m_cFlags2; unsigned char m_cFlags3; unsigned char m_cFlags4; std::string m_strName; std::vector m_vcValue; // raw value; may be binary or UTF8 enum Type { UTF8, BINARY }; // ttt2 add UTF8LIST, DATE, ... Type m_eType; std::string getUtf8String() const; int getTotalSize() const { return 4 + 4 + int(m_strName.size()) + 1 + int(m_vcValue.size()); } private: friend class boost::serialization::access; ApeItem() {} // serialization-only constructor template void serialize(Archive& ar, const unsigned int nVersion) { if (nVersion > 0) { throw std::runtime_error("invalid version of serialized file"); } ar & m_cFlags1; ar & m_cFlags2; ar & m_cFlags3; ar & m_cFlags4; ar & m_strName; ar & m_vcValue; ar & m_eType; } }; class ApeStream : public DataStream, public TagReader { Q_DECLARE_TR_FUNCTIONS(ApeStream) int m_nVersion; std::streampos m_pos; std::streamoff m_nSize; std::vector m_vpItems; void readKeys(NoteColl& notes, std::istream& in); ApeItem* findItem(const char* szFrameName); const ApeItem* findItem(const char* szFrameName) const; public: ApeStream(int nIndex, NoteColl& notes, std::istream& in); ~ApeStream(); /*override*/ void copy(std::istream& in, std::ostream& out); DECL_RD_NAME("Ape") /*override*/ std::string getInfo() const; /*override*/ std::streampos getPos() const { return m_pos; } /*override*/ std::streamoff getSize() const { return m_nSize; } //enum Mp3Gain { NONE = 0x00, TRACK = 0x01, ALBUM = 0x02, BOTH = 0x03 }; //Mp3Gain getMp3GainStatus() const; bool hasMp3Gain() const; struct NotApeStream {}; struct NotApeHeader {}; struct NotApeFooter {}; struct HeaderFooterMismatch {}; // ================================ TagReader ========================================= /*override*/ std::string getTitle(bool* pbFrameExists = 0) const; /*override*/ std::string getArtist(bool* pbFrameExists = 0) const; /*override*/ std::string getTrackNumber(bool* pbFrameExists = 0) const; /*override*/ TagTimestamp getTime(bool* pbFrameExists = 0) const; /*override*/ std::string getGenre(bool* pbFrameExists = 0) const; /*override*/ ImageInfo getImage(bool* /*pbFrameExists*/ = 0) const { throw NotSupportedOp(); } /*override*/ std::string getAlbumName(bool* pbFrameExists = 0) const; /*override*/ std::string getOtherInfo() const; /*override*/ SuportLevel getSupport(Feature) const; private: friend class boost::serialization::access; ApeStream() {} // serialization-only constructor template void serialize(Archive& ar, const unsigned int nVersion) { if (nVersion > 0) { throw std::runtime_error("invalid version of serialized file"); } ar & boost::serialization::base_object(*this); ar & m_nVersion; ar & m_pos; ar & m_nSize; ar & m_vpItems; } }; #endif // ifndef ApeStreamH MP3Diags-1.2.02/src/SerSupport.h0000644000175000001440000000430611200235743015235 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef SerSupportH #define SerSupportH //#include //#include #include #include #include // !!! the archive headers must be included before serialization; otherwise compiler errors are triggered; this looks like a Boost 1.33 bug /* #include #include #include #include #include #include */ #endif MP3Diags-1.2.02/src/Mp3Manip.cpp0000644000175000001440000012141112063367560015076 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include "Mp3Manip.h" #include "Helpers.h" #include "DataStream.h" #include "MpegStream.h" #include "ApeStream.h" #include "LyricsStream.h" #include "Id3V230Stream.h" #include "Id3V240Stream.h" #include "OsFile.h" using namespace std; using namespace pearl; //============================================================================================================ //============================================================================================================ //============================================================================================================ //#define VERBOSE //#define TRACE_FRAME_INFO //============================================================================================================ //============================================================================================================ //============================================================================================================ /*static */ const QualThresholds& QualThresholds::getDefaultQualThresholds() { static QualThresholds defaultQualThresholds = { 192000, // m_nStereoCbr 192000, // m_nJointStereoCbr 192000, // m_nDoubleChannelCbr 170000, // m_nStereoVbr 160000, // m_nJointStereoVbr 180000 // m_nDoubleChannelVbr }; return defaultQualThresholds; }; //============================================================================================================ //============================================================================================================ //============================================================================================================ static string s_strCrtMp3Handler; static string s_strPrevMp3Handler; string getGlobalMp3HandlerName() // a hack to get the name of the current file from inside various streams without storing the name there //ttt2 review { return s_strCrtMp3Handler + " (" + s_strPrevMp3Handler + ")"; } Mp3Handler::Mp3Handler(const string& strFileName, bool bStoreTraceNotes, const QualThresholds& qualThresholds) : m_pFileName(new StringWrp(strFileName)), m_pId3V230Stream(0), m_pId3V240Stream(0), m_pId3V1Stream(0), m_pLameStream(0), m_pXingStream(0), m_pVbriStream(0), m_pMpegStream(0), m_pApeStream(0), m_pLyricsStream(0), m_notes(1000), //ttt2 hard-coded m_nFastSaveTime(0) { s_strPrevMp3Handler = s_strCrtMp3Handler; s_strCrtMp3Handler = strFileName; //TRACER1A("Mp3Handler constr ", 1); TRACER("Mp3Handler constr: " + strFileName); ifstream_utf8 in (m_pFileName->s.c_str(), ios::binary); if (!in) { qDebug("Couldn't open file \"%s\"", strFileName.c_str()); //inspect(strFileName.c_str(), cSize(strFileName) + 1); trace("Couldn't open file: " + strFileName); CB_THROW1(FileNotFound()); } //TRACER1A("Mp3Handler constr ", 2); ostringstream out; time_t t (time(0)); out << "************************* " << strFileName << " ************************* memory: " << getMemUsage() << "; time: " << ctime(&t); string s (out.str()); s.erase(s.size() - 1); // needed because ctime() uses a terminating '\n' trace(""); trace(s); // cout << s << endl; //TRACER1A("Mp3Handler constr ", 3); parse(in); //TRACER1A("Mp3Handler constr ", 4); m_notes.resetCounter(); //TRACER1A("Mp3Handler constr ", 5); analyze(qualThresholds); //TRACER1A("Mp3Handler constr ", 6); if (!bStoreTraceNotes) { //TRACER1A("Mp3Handler constr ", 7); m_notes.removeTraceNotes(); } //TRACER1A("Mp3Handler constr ", 8); getFileInfo(strFileName, m_nTime, m_nSize); //TRACER1A("Mp3Handler constr ", 9); } Mp3Handler::~Mp3Handler() { TRACER("Mp3Handler destr: " + m_pFileName->s); //qDebug("begin destroying Mp3Handler at %p", this); /*delete m_pId3V230Stream; delete m_pId3V240Stream; delete m_pId3V1Stream; delete m_pLameStream; delete m_pXingStream; delete m_pMpegStream; delete m_pApeStream; clearPtrContainer(m_vpNullStreams); clearPtrContainer(m_vpUnknownStreams);*/ clearPtrContainer(m_vpAllStreams); //TRACER1A("Mp3Handler destr ", 1); delete m_pFileName; //qDebug("done destroying Mp3Handler at %p", this); //clearPtrContainer(m_notes); } const Id3V2StreamBase* Mp3Handler::getId3V2Stream() const { if (0 != m_pId3V230Stream) { return m_pId3V230Stream; } return m_pId3V240Stream; } // what looks like the last frame in an MPEG stream may actually be truncated and somewhere inside it an ID3V1 or Ape tag may actually begin; if that's the case, that "frame" is removed from the stream; then most likely an "Unknown" stream will be detected, followed by an ID3V1 or Ape stream //ttt2 make sure that that is the case; a possibility is that the standard allows the last frame to be shorter than the calculated size, if some condition is met; this seems unlikely, though void Mp3Handler::checkLastFrameInMpegStream(ifstream_utf8& in) { STRM_ASSERT (!m_vpAllStreams.empty()); MpegStream* pStream (dynamic_cast(m_vpAllStreams.back())); if (0 == pStream) { return; } streampos pos (pStream->getLastFramePos()); streampos posNext (pStream->getPos()); posNext += pStream->getSize(); NoteColl notes (0); for (;;) { if (posNext - pos <= 0) { //clearPtrContainer(vpNotes); return; } in.clear(); in.seekg(pos); try { DataStream* p (new Id3V240Stream(0, notes, in, m_pFileName)); delete p; break; } catch (const std::bad_alloc&) { throw; } catch (...) //ttt2 replace "..." with something app-specific, to avoid catching system exceptions { in.clear(); in.seekg(pos); } in.seekg(pos); try { DataStream* p (new ApeStream(0, notes, in)); delete p; break; } catch (const std::bad_alloc&) { throw; } catch (...) //ttt2 replace "..." with something app-specific, to avoid catching system exceptions { in.clear(); in.seekg(pos); } in.seekg(pos); try { DataStream* p (new Id3V1Stream(0, notes, in)); delete p; break; } catch (const std::bad_alloc&) { throw; } catch (...) //ttt2 replace "..." with something app-specific, to avoid catching system exceptions { in.clear(); in.seekg(pos); } in.seekg(pos); try { DataStream* p (new LyricsStream(0, notes, in, m_pFileName->s)); delete p; break; } catch (const std::bad_alloc&) { throw; } catch (...) //ttt2 replace "..." with something app-specific, to avoid catching system exceptions { in.clear(); in.seekg(pos); } try { pos = getNextStream(in, pos); } catch (const EndOfFile&) { return; } } //clearPtrContainer(notes); pStream->removeLastFrame(); } void Mp3Handler::parse(ifstream_utf8& in) // ttt2 this function is a mess; needs rethinking { in.seekg(0, ios::end); m_posEnd = in.tellg(); in.seekg(0, ios::beg); STRM_ASSERT (in); int nIndex (0); //NoteColl& notes (m_notes); streampos pos (0); while (pos < m_posEnd) { in.clear(); in.seekg(pos); //if (m_vpAllStreams.size() > 48) if (cSize(m_vpAllStreams) > 100) //ttt2 perhaps make this configurable { { m_notes.resetCounter(); NoteColl& notes (m_notes); MP3_NOTE (pos, tooManyStreams); } UnknownDataStream* p (new UnknownDataStream(nIndex, m_notes, in, m_posEnd - pos)); UnknownDataStream* pPrev (m_vpAllStreams.empty() ? 0 : dynamic_cast(m_vpAllStreams.back())); if (0 != pPrev) { // last stream is "unknown" too pPrev->append(*p); delete p; } else { m_vpAllStreams.push_back(p); ++nIndex; } pos = m_posEnd; break; } bool bBrokenMpegFrameFound (false); int nBrokenMpegFrameCount (0); string strBrokenInfo; const char* szBrokenName (0); string strUnsupportedInfo; const char* szUnsupportedName (0); try { m_vpAllStreams.push_back(new Id3V230Stream(nIndex, m_notes, in, m_pFileName)); pos += m_vpAllStreams.back()->getSize(); ++nIndex; continue; } catch (const StreamIsBroken& ex) { if (0 == szBrokenName) { szBrokenName = ex.getStreamName(); strBrokenInfo = ex.getInfo(); } } catch (const StreamIsUnsupported& ex) { if (0 == szUnsupportedName) { szUnsupportedName = ex.getStreamName(); strUnsupportedInfo = ex.getInfo(); } } catch (const std::bad_alloc&) { throw; } catch (...) {} //ttt2 replace "..." with something app-specific, to avoid catching system exceptions in.clear(); in.seekg(pos); try { m_vpAllStreams.push_back(new Id3V240Stream(nIndex, m_notes, in, m_pFileName)); pos += m_vpAllStreams.back()->getSize(); ++nIndex; continue; } catch (const StreamIsBroken& ex) { if (0 == szBrokenName) { szBrokenName = ex.getStreamName(); strBrokenInfo = ex.getInfo(); } } catch (const StreamIsUnsupported& ex) { if (0 == szUnsupportedName) { szUnsupportedName = ex.getStreamName(); strUnsupportedInfo = ex.getInfo(); } } catch (const std::bad_alloc&) { throw; } catch (...) {} //ttt2 replace "..." with something app-specific, to avoid catching system exceptions in.clear(); in.seekg(pos); try { m_vpAllStreams.push_back(new LameStream(nIndex, m_notes, in)); pos += m_vpAllStreams.back()->getSize(); ++nIndex; continue; } catch (const MpegFrame::PrematurelyEndedMpegFrame& ex) { // this could as well be in the Xing, Vbri or MPEG block and the program would perform pretty much the same (the only difference is that more exceptions would be thrown and caught as "...", with no side effect, if one of the other blocks is used); the exception means that what began as an MPEG frame should be longer than what is left in the file bBrokenMpegFrameFound = true; szBrokenName = MpegStream::getClassDisplayName(); strBrokenInfo = ex.m_strInfo; goto e1; } catch (const StreamIsBroken& ex) { if (0 == szBrokenName) { szBrokenName = ex.getStreamName(); strBrokenInfo = ex.getInfo(); } } catch (const StreamIsUnsupported& ex) { if (0 == szUnsupportedName) { szUnsupportedName = ex.getStreamName(); strUnsupportedInfo = ex.getInfo(); } } catch (const std::bad_alloc&) { throw; } catch (...) {} //ttt2 replace "..." with something app-specific, to avoid catching system exceptions in.clear(); in.seekg(pos); try { m_vpAllStreams.push_back(new XingStream(nIndex, m_notes, in)); pos += m_vpAllStreams.back()->getSize(); ++nIndex; continue; } catch (const StreamIsBroken& ex) { if (0 == szBrokenName) { szBrokenName = ex.getStreamName(); strBrokenInfo = ex.getInfo(); } } catch (const StreamIsUnsupported& ex) { if (0 == szUnsupportedName) { szUnsupportedName = ex.getStreamName(); strUnsupportedInfo = ex.getInfo(); } } catch (const std::bad_alloc&) { throw; } catch (...) {} //ttt2 replace "..." with something app-specific, to avoid catching system exceptions in.clear(); in.seekg(pos); try { m_vpAllStreams.push_back(new VbriStream(nIndex, m_notes, in)); pos += m_vpAllStreams.back()->getSize(); ++nIndex; continue; } catch (const StreamIsBroken& ex) { if (0 == szBrokenName) { szBrokenName = ex.getStreamName(); strBrokenInfo = ex.getInfo(); } } catch (const StreamIsUnsupported& ex) { if (0 == szUnsupportedName) { szUnsupportedName = ex.getStreamName(); strUnsupportedInfo = ex.getInfo(); } } catch (const std::bad_alloc&) { throw; } catch (...) {} //ttt2 replace "..." with something app-specific, to avoid catching system exceptions in.clear(); in.seekg(pos); try { m_vpAllStreams.push_back(new MpegStream(nIndex, m_notes, in)); trace("enter checkLastFrameInMpegStream()"); checkLastFrameInMpegStream(in); trace("exit checkLastFrameInMpegStream()"); pos += m_vpAllStreams.back()->getSize(); ++nIndex; continue; } catch (const MpegStream::StreamTooShort& ex) { // not 100% correct, but most likely it gets here after in the previous step it managed to read an MPEG stream which had a truncated last frame, which was removed from that stream and now it has thrown this exception; quite similar to what PrematurelyEndedMpegFrame as caught in the LameStream block above is doing, the difference here being that there are enough bytes left in the file to read a full "frame", while there it's EOF; both cases are likely to have some other streams inside them bBrokenMpegFrameFound = true; szBrokenName = MpegStream::getClassDisplayName(); strBrokenInfo = ex.m_strInfo; nBrokenMpegFrameCount = ex.m_nFrameCount; goto e1; } catch (const StreamIsBroken& ex) { if (0 == szBrokenName) { szBrokenName = ex.getStreamName(); strBrokenInfo = ex.getInfo(); } } catch (const StreamIsUnsupported& ex) { if (0 == szUnsupportedName) { szUnsupportedName = ex.getStreamName(); strUnsupportedInfo = ex.getInfo(); } } catch (const std::bad_alloc&) { throw; } catch (...) {} //ttt2 replace "..." with something app-specific, to avoid catching system exceptions in.clear(); in.seekg(pos); try { m_vpAllStreams.push_back(new ApeStream(nIndex, m_notes, in)); pos += m_vpAllStreams.back()->getSize(); ++nIndex; continue; } catch (const StreamIsBroken& ex) { if (0 == szBrokenName) { szBrokenName = ex.getStreamName(); strBrokenInfo = ex.getInfo(); } } catch (const StreamIsUnsupported& ex) { if (0 == szUnsupportedName) { szUnsupportedName = ex.getStreamName(); strUnsupportedInfo = ex.getInfo(); } } catch (const std::bad_alloc&) { throw; } catch (...) {} //ttt2 replace "..." with something app-specific, to avoid catching system exceptions in.clear(); in.seekg(pos); try { m_vpAllStreams.push_back(new Id3V1Stream(nIndex, m_notes, in)); pos += m_vpAllStreams.back()->getSize(); ++nIndex; continue; } catch (const StreamIsBroken& ex) { if (0 == szBrokenName) { szBrokenName = ex.getStreamName(); strBrokenInfo = ex.getInfo(); } } catch (const StreamIsUnsupported& ex) { if (0 == szUnsupportedName) { szUnsupportedName = ex.getStreamName(); strUnsupportedInfo = ex.getInfo(); } } catch (const std::bad_alloc&) { throw; } catch (...) {} //ttt2 replace "..." with something app-specific, to avoid catching system exceptions in.clear(); in.seekg(pos); try { m_vpAllStreams.push_back(new LyricsStream(nIndex, m_notes, in, m_pFileName->s)); pos += m_vpAllStreams.back()->getSize(); ++nIndex; continue; } catch (const StreamIsBroken& ex) { if (0 == szBrokenName) { szBrokenName = ex.getStreamName(); strBrokenInfo = ex.getInfo(); } } catch (const StreamIsUnsupported& ex) { if (0 == szUnsupportedName) { szUnsupportedName = ex.getStreamName(); strUnsupportedInfo = ex.getInfo(); } } catch (const std::bad_alloc&) { throw; } catch (...) {} //ttt2 replace "..." with something app-specific, to avoid catching system exceptions in.clear(); in.seekg(pos); try { m_vpAllStreams.push_back(new NullDataStream(nIndex, m_notes, in)); pos += m_vpAllStreams.back()->getSize(); ++nIndex; continue; } catch (const StreamIsBroken& ex) { if (0 == szBrokenName) { szBrokenName = ex.getStreamName(); strBrokenInfo = ex.getInfo(); } } catch (const StreamIsUnsupported& ex) { if (0 == szUnsupportedName) { szUnsupportedName = ex.getStreamName(); strUnsupportedInfo = ex.getInfo(); } } catch (const std::bad_alloc&) { throw; } catch (...) {} //ttt2 replace "..." with something app-specific, to avoid catching system exceptions in.clear(); in.seekg(pos); //------------------------------------------------------------------ e1: streampos posNextFrame; try { posNextFrame = getNextStream(in, pos); } catch (const EndOfFile&) { posNextFrame = m_posEnd; in.clear(); } //try { bool bAdded (false); if (bBrokenMpegFrameFound) //ttt2 review this: "bBrokenMpegFrameFound gets set if MpegStream::StreamTooShort is thrown"; it's probably OK { try { MpegStream* pPrev (m_vpAllStreams.empty() ? 0 : dynamic_cast(m_vpAllStreams.back())); m_vpAllStreams.push_back(new TruncatedMpegDataStream(pPrev, nIndex, m_notes, in, posNextFrame - pos)); pos += m_vpAllStreams.back()->getSize(); ++nIndex; bAdded = true; } catch (const TruncatedMpegDataStream::NotTruncatedMpegDataStream&) { in.clear(); in.seekg(pos); /* ttt2 perhaps add limit of 10 broken streams per file keep in mind that a transformation that removes broken streams will have trouble processing a file with "10 broken streams", because after it removes the first, a new one will get created, always having 10 of them */ } } //ttt2 consider this: there's a stream beginning identified at 1000 and the next one is at 8000; if the first begins with a valid MPEG frame and the second is something else, the whole block from 1000 up to 7999 is identified as "broken mpeg"; this isn't quite right: since no other mpeg frame can be found in the block (if it could, it would be the second stream beginning), there's a lot of other data in the stream besides an mpeg frame; this doesn't feel right; if (!bAdded) { streamoff nSize (posNextFrame - pos); if (0 != szUnsupportedName) { m_vpAllStreams.push_back(new UnsupportedDataStream(nIndex, m_notes, in, nSize, szUnsupportedName, strUnsupportedInfo)); //pos += m_vpAllStreams.back()->getSize(); ++nIndex; } else { // either broken or unknown; these have little useful information, so try to append to the previous one, in these cases: both broken and unknown can be appended to truncated and unknown alone can be appended to unknown DataStream* pPrev (m_vpAllStreams.empty() ? 0 : m_vpAllStreams.back()); TruncatedMpegDataStream* pPrevTrunc (dynamic_cast(pPrev)); UnknownDataStream* pPrevUnkn (dynamic_cast(pPrev)); UnsupportedDataStream* pPrevUnsupp (dynamic_cast(pPrev)); if (MpegStream::getClassDisplayName() == szBrokenName && 1 == nBrokenMpegFrameCount) { // a "broken audio" with a single sense doesn't make much sense at this point (but it mattered above, to add a truncated audio stream) szBrokenName = 0; } if (0 != pPrevTrunc && pPrevTrunc->hasSpace(nSize)) { // append to truncated UnknownDataStream* p (new UnknownDataStream(nIndex, m_notes, in, nSize)); pPrevTrunc->append(*p); delete p; } else if (0 != szBrokenName) { // create broken m_vpAllStreams.push_back(new BrokenDataStream(nIndex, m_notes, in, nSize, szBrokenName, strBrokenInfo)); //pos += m_vpAllStreams.back()->getSize(); ++nIndex; } else if (0 != pPrevUnkn) { // append to unknown UnknownDataStream* p (new UnknownDataStream(nIndex, m_notes, in, nSize)); pPrevUnkn->append(*p); delete p; } else if (0 != pPrevUnsupp) { // append to unknown UnknownDataStream* p (new UnknownDataStream(nIndex, m_notes, in, nSize)); pPrevUnsupp->append(*p); delete p; } else { // create unknown UnknownDataStream* p (new UnknownDataStream(nIndex, m_notes, in, nSize)); m_vpAllStreams.push_back(p); ++nIndex; } } pos += nSize; } } /*catch (...) // !!! DON'T catch anything; there's no point; the file is changed or something else pretty bad happened, because creating an Unknown stream shouldn't throw; most likely the existing streams became invalid too; also, there's a chance that the implementation of UnknownDataStream has a bug and an infinite loop will be entered, as all the constructors fail and the position in the file doesn't advance { in.clear(); in.seekg(pos); not ok }*/ } //cout << "=======================\n"; //CB_ASSERT (!m_vpAllStreams.empty()); STRM_ASSERT (pos == m_posEnd); // ttt1 triggered according to mail on 2012.12.16 pos = 0; for (int i = 0; i < cSize(m_vpAllStreams); ++i) { DataStream* p (m_vpAllStreams[i]); //cout << p->getInfo() << endl; STRM_ASSERT (p->getPos() == pos); pos += p->getSize(); } STRM_ASSERT (pos == m_posEnd); } // checks the streams for issues (missing ID3V2, Unknown streams, inconsistencies, ...) void Mp3Handler::analyze(const QualThresholds& qualThresholds) { NoteColl& notes (m_notes); // for MP3_NOTE() for (int i = 0, n = cSize(m_vpAllStreams); i < n; ++i) { DataStream* pDs (m_vpAllStreams[i]); { Id3V230Stream* p (dynamic_cast(pDs)); if (0 != p) { if (0 == m_pId3V230Stream) m_pId3V230Stream = p; else MP3_NOTE (p->getPos(), twoId3V230); } } { Id3V240Stream* p (dynamic_cast(pDs)); if (0 != p) { if (0 == m_pId3V240Stream) m_pId3V240Stream = p; else MP3_NOTE (p->getPos(), twoId3V240); } } { Id3V1Stream* p (dynamic_cast(pDs)); if (0 != p) { if (0 == m_pId3V1Stream) m_pId3V1Stream = p; else MP3_NOTE (p->getPos(), twoId3V1); } } { LameStream* p (dynamic_cast(pDs)); if (0 != p) { if (0 == m_pLameStream) m_pLameStream = p; else MP3_NOTE (p->getPos(), twoLame); } } { XingStreamBase* p (dynamic_cast(pDs)); if (0 != p) { if (0 == m_pXingStream) { m_pXingStream = p; if (i < n - 2 && p->isBrokenByMp3Fixer(m_vpAllStreams[i + 1], m_vpAllStreams[i + 2])) { MP3_NOTE (p->getPos(), xingAddedByMp3Fixer); } if (i < n - 1) { MpegStream* q (dynamic_cast(m_vpAllStreams[i + 1])); if (0 != q && p->getFrameCount() != q->getFrameCount()) { MP3_NOTE (p->getPos(), xingFrameCountMismatch); } } } else MP3_NOTE (p->getPos(), twoXing); } } { VbriStream* p (dynamic_cast(pDs)); if (0 != p) { if (0 == m_pVbriStream) m_pVbriStream = p; else MP3_NOTE (p->getPos(), twoVbri); } } { MpegStream* p (dynamic_cast(pDs)); if (0 != p) { if (0 == m_pMpegStream) m_pMpegStream = p; else MP3_NOTE (p->getPos(), twoAudio); //const MpegFrame& frm (p->getFirstFrame()); if (p->isVbr()) { switch (p->getChannelMode()) { // ttt2 should be possible that changing qual thresholds in config triggers notes being added / removed case MpegFrame::STEREO: if (p->getBitrate() < qualThresholds.m_nStereoVbr) { MP3_NOTE (p->getPos(), lowQualAudio); } break; case MpegFrame::JOINT_STEREO: if (p->getBitrate() < qualThresholds.m_nJointStereoVbr) { MP3_NOTE (p->getPos(), lowQualAudio); } break; case MpegFrame::DUAL_CHANNEL: if (p->getBitrate() < qualThresholds.m_nDoubleChannelVbr) { MP3_NOTE (p->getPos(), lowQualAudio); } break; case MpegFrame::SINGLE_CHANNEL: if (p->getBitrate() < qualThresholds.m_nDoubleChannelVbr / 2) { MP3_NOTE (p->getPos(), lowQualAudio); } break; } } else { switch (p->getChannelMode()) { case MpegFrame::STEREO: if (p->getBitrate() < qualThresholds.m_nStereoCbr) { MP3_NOTE (p->getPos(), lowQualAudio); } break; case MpegFrame::JOINT_STEREO: if (p->getBitrate() < qualThresholds.m_nJointStereoCbr) { MP3_NOTE (p->getPos(), lowQualAudio); } break; case MpegFrame::DUAL_CHANNEL: if (p->getBitrate() < qualThresholds.m_nDoubleChannelCbr) { MP3_NOTE (p->getPos(), lowQualAudio); } break; case MpegFrame::SINGLE_CHANNEL: if (p->getBitrate() < qualThresholds.m_nDoubleChannelCbr / 2) { MP3_NOTE (p->getPos(), lowQualAudio); } break; } } } } { ApeStream* p (dynamic_cast(pDs)); if (0 != p) { if (0 == m_pApeStream) m_pApeStream = p; else MP3_NOTE (p->getPos(), twoApe); } } { LyricsStream* p (dynamic_cast(pDs)); if (0 != p) { if (0 == m_pLyricsStream) m_pLyricsStream = p; else MP3_NOTE (p->getPos(), twoLyr); } } { NullDataStream* p (dynamic_cast(pDs)); if (0 != p) { m_vpNullStreams.push_back(p); } } { UnknownDataStream* p (dynamic_cast(pDs)); if (0 != p) { m_vpUnknownStreams.push_back(p); } } { BrokenDataStream* p (dynamic_cast(pDs)); if (0 != p) { m_vpBrokenStreams.push_back(p); } } { UnsupportedDataStream* p (dynamic_cast(pDs)); if (0 != p) { m_vpUnsupportedStreams.push_back(p); } } { TruncatedMpegDataStream* p (dynamic_cast(pDs)); if (0 != p) { m_vpTruncatedMpegStreams.push_back(p); } } } if (0 == m_pMpegStream) { MP3_NOTE (-1, noAudio); } if (0 == m_pId3V230Stream) { MP3_NOTE (-1, noId3V230); } if (0 != m_pId3V230Stream && 0 != m_pId3V240Stream) { MP3_NOTE (m_pId3V240Stream->getPos(), bothId3V230_V240); } if (0 != m_pVbriStream) { MP3_NOTE (m_pVbriStream->getPos(), vbriFound); } //if (0 != m_pLyricsStream) { MP3_NOTE (m_pLyricsStream->getPos(), lyricsNotSupported); } if (0 != m_pId3V1Stream && 0 == m_pId3V230Stream && 0 == m_pId3V240Stream && 0 == m_pApeStream) { MP3_NOTE (m_pId3V1Stream->getPos(), onlyId3V1); } if (0 == m_pId3V1Stream && 0 == m_pId3V230Stream && 0 == m_pId3V240Stream && 0 == m_pApeStream) { MP3_NOTE (-1, noInfoTag); } if (!m_vpNullStreams.empty()) { MP3_NOTE (m_vpNullStreams[0]->getPos(), foundNull); } if (0 != m_pVbriStream && 0 != m_pXingStream) { MP3_NOTE (m_pVbriStream->getPos(), foundVbriAndXing); } if (0 != m_pXingStream && 0 != m_pMpegStream && m_pXingStream->getIndex() != m_pMpegStream->getIndex() - 1) { MP3_NOTE (m_pXingStream->getPos(), xingNotBeforeAudio); } if (0 != m_pXingStream && 0 != m_pMpegStream && !m_pXingStream->matchesStructure(*m_pMpegStream)) { MP3_NOTE (m_pXingStream->getPos(), incompatXing); } if (0 != m_pId3V1Stream && 0 != m_pMpegStream && m_pId3V1Stream->getIndex() < m_pMpegStream->getIndex()) { MP3_NOTE (m_pId3V1Stream->getPos(), id3v1BeforeAudio); } if (0 != m_pId3V230Stream && 0 != m_pId3V230Stream->getIndex()) { MP3_NOTE (m_pId3V230Stream->getPos(), id3v230AfterAudio); } if (0 == m_pXingStream && 0 != m_pMpegStream && m_pMpegStream->isVbr()) { MP3_NOTE (m_pMpegStream->getPos(), missingXing); } if (0 != m_pMpegStream && m_pMpegStream->isVbr() && (MpegFrame::MPEG1 != m_pMpegStream->getFirstFrame().getVersion() || MpegFrame::LAYER3 != m_pMpegStream->getFirstFrame().getLayer())) { MP3_NOTE (m_pMpegStream->getPos(), vbrUsedForNonMpg1L3); } if ( (0 != m_pMpegStream) && (0 == m_pApeStream || !m_pApeStream->hasMp3Gain()) && (0 == m_pId3V230Stream || !m_pId3V230Stream->hasReplayGain()) && (0 == m_pId3V240Stream || !m_pId3V240Stream->hasReplayGain())) //ttt2 not quite OK when mutliple ID3V2 streams are found { MP3_NOTE (m_pMpegStream->getPos(), noMp3Gain); } if (!m_vpBrokenStreams.empty()) { if (1 == cSize(m_vpBrokenStreams) && m_vpBrokenStreams.back() == m_vpAllStreams.back()) { MP3_NOTE (m_vpBrokenStreams.back()->getPos(), brokenAtTheEnd); } else { MP3_NOTE (m_vpBrokenStreams.back()->getPos(), brokenInTheMiddle); } } if (!m_vpUnknownStreams.empty()) { if (1 == cSize(m_vpUnknownStreams) && m_vpUnknownStreams.back() == m_vpAllStreams.back()) { MP3_NOTE (m_vpUnknownStreams.back()->getPos(), unknownAtTheEnd); } else { MP3_NOTE (m_vpUnknownStreams.back()->getPos(), unknownInTheMiddle); } } if (!m_vpUnsupportedStreams.empty()) { MP3_NOTE (m_vpUnsupportedStreams.back()->getPos(), unsupportedFound); } if (!m_vpTruncatedMpegStreams.empty()) { if (1 == cSize(m_vpTruncatedMpegStreams) && m_vpTruncatedMpegStreams.back() == m_vpAllStreams.back()) { MP3_NOTE (m_vpTruncatedMpegStreams.back()->getPos(), truncAudioWithWholeFile); } else { MP3_NOTE (m_vpTruncatedMpegStreams.back()->getPos(), truncAudio); } } m_notes.sort(); //sort(m_notes.begin(), m_notes.end(), notePtrCmp); } // removes the ID3V2 tag and the notes associated with it and scans the file again, but only the new ID3V2 tag; // asserts that there was an existing ID3V2 tag at the beginning and it had the same size as the new one; // this isn't really const, but it seems better to have a const_cast in the only place where it is needed rather than remove the const restriction from many places void Mp3Handler::reloadId3V2() const { const_cast(this)->reloadId3V2Hlp(); } void Mp3Handler::reloadId3V2Hlp() { STRM_ASSERT (!m_vpAllStreams.empty()); Id3V2StreamBase* pOldId3V2 (dynamic_cast(m_vpAllStreams[0])); STRM_ASSERT (0 != pOldId3V2); m_notes.removeNotes(pOldId3V2->getPos(), pOldId3V2->getPos() + pOldId3V2->getSize()); ifstream_utf8 in (m_pFileName->s.c_str(), ios::binary); STRM_ASSERT (in); // ttt2 not quite right; could have been deleted externally Id3V230Stream* pNewId3V2; try { pNewId3V2 = new Id3V230Stream(0, m_notes, in, m_pFileName); } catch (const std::bad_alloc&) { throw; } catch (...) { STRM_ASSERT (false); } STRM_ASSERT (pOldId3V2->getSize() == pNewId3V2->getSize()); delete pOldId3V2; m_vpAllStreams[0] = pNewId3V2; if (m_pId3V240Stream == pOldId3V2) { m_pId3V240Stream = 0; } m_pId3V230Stream = pNewId3V2; m_notes.addFastSaveWarn(); m_notes.sort(); } static bool isMpegHdr(unsigned char* p) { bool b; decodeMpegFrame(reinterpret_cast(p), " ", &b); return b; } // finds the position where the next ID3V2, MPEG, Xing, Lame or Ape stream begins; "pos" is not considered; the search starts at pos+1; throws if no stream is found streampos getNextStream(istream& in, streampos pos) { StreamStateRestorer rst (in); static const int BFR_SIZE (1024); char bfr [BFR_SIZE]; static const int MPEG_HDR_SIZE (4); static const char* ID3V2_HDR ("ID3"); static const int ID3V2_HDR_SIZE (strlen(ID3V2_HDR)); static const char* ID3V1_HDR ("TAG"); static const int ID3V1_HDR_SIZE (strlen(ID3V1_HDR)); static const char* APE_HDR ("APETAGEX"); static const int APE_HDR_SIZE (strlen(APE_HDR)); static const char* LYRICS_HDR ("LYRICSBEGIN"); static const int LYRICS_HDR_SIZE (strlen(LYRICS_HDR)); static const int MAX_HDR_SIZE (max(MPEG_HDR_SIZE, max(ID3V2_HDR_SIZE, max(ID3V1_HDR_SIZE, max(APE_HDR_SIZE, LYRICS_HDR_SIZE))))); pos += 1; int i (0); in.clear(); for (;;) { in.seekg(pos); int nRead (read(in, bfr, BFR_SIZE)); if (0 == nRead) { throw EndOfFile(); } for (i = 0; i < nRead; ++i) { unsigned char* p (reinterpret_cast(bfr + i)); //if (nRead - i >= MPEG_HDR_SIZE && 0xff == *p && 0xe0 == (0xe0 & *(p + 1))) { goto e1; } if (nRead - i >= MPEG_HDR_SIZE && isMpegHdr(p)) { goto e1; } // MPEG, Xing, Lame // older comment: "ff ff" is not a valid MPEG beginning, so it shouldn't pass // 2008.08.06: actually it's valid: "MPEG1, Layer 1, No CRC"; however, "ff e?", "ff f0", "ff f1", "ff f8" and "ff f9" are invalid, and they are tested by isMpegHdr if (nRead - i >= ID3V2_HDR_SIZE && 0 == strncmp(ID3V2_HDR, bfr + i, ID3V2_HDR_SIZE)) { goto e1; } // ID3V2 (2.3, 2.2, 2.4) if (nRead - i >= ID3V1_HDR_SIZE && 0 == strncmp(ID3V1_HDR, bfr + i, ID3V1_HDR_SIZE)) { goto e1; } // ID3V1 if (nRead - i >= APE_HDR_SIZE && 0 == strncmp(APE_HDR, bfr + i, APE_HDR_SIZE)) { goto e1; } // Ape V2 if (nRead - i >= APE_HDR_SIZE && 0 == strncmp(LYRICS_HDR, bfr + i, LYRICS_HDR_SIZE)) { goto e1; } // Lyrics } pos += BFR_SIZE - MAX_HDR_SIZE + 1; } e1: pos += i; /*if (g_bVerbose)*/ { TRACE("stream found at 0x" << hex << pos << dec); } // !!! no need to call rst.setOk(); we want the read pointer to be restored return pos; } /*void Mp3Handler::logStreamInfo() const { //cout << endl; for (vector::const_iterator it = m_vpAllStreams.begin(), end = m_vpAllStreams.end(); it != end; ++it) { //cout << " " << (*it)->getName(); TRACE((*it)->getInfo()); } //cout << endl; } void Mp3Handler::logStreamInfo(std::ostream& out) const { for (vector::const_iterator it = m_vpAllStreams.begin(), end = m_vpAllStreams.end(); it != end; ++it) { out << (*it)->getInfo() << endl; } } */ long long Mp3Handler::getSize() const { return m_posEnd; //ttt3 doesn't work for large files } QString Mp3Handler::getUiName() const // uses native separators { return toNativeSeparators(convStr(getName())); } string Mp3Handler::getShortName() const { string::size_type n (m_pFileName->s.rfind(getPathSep())); STRM_ASSERT (string::npos != n); return m_pFileName->s.substr(n + 1); } string Mp3Handler::getDir() const { return getParent(m_pFileName->s); } // if the underlying file seems changed (or removed); looks at time and size, as well as FastSaveWarn and Notes::getMissingNote(); a difference in size always makes this return true; if bConsiderFastSave is true, both m_nFastSaveTime and m_nTime are tested, while m_notes.hasFastSaveWarn() and Notes::getMissingNote() are ignored; if it is false, m_nFastSaveTime is ignored, while m_nTime, m_notes.hasFastSaveWarn(), and Notes::getMissingNote() are tested bool Mp3Handler::needsReload(bool bUseFastSave) const { long long nSize, nTime; try { getFileInfo(m_pFileName->s, nTime, nSize); } catch (const NameNotFound&) { return true; } if (nSize != m_nSize) { return true; } if (bUseFastSave) { return nTime != m_nTime && nTime != m_nFastSaveTime; } if (nTime != m_nTime || m_notes.hasFastSaveWarn()) { return true; } const vector& vpNotes (m_notes.getList()); return !vpNotes.empty() && vpNotes[0]->getDescription() == Notes::getMissingNote()->getDescription(); } //============================================================================================================ //============================================================================================================ //============================================================================================================ bool CmpMp3HandlerPtrByName::operator()(const Mp3Handler* p1, const std::string& strName2) const { return cmp(p1->getName(), strName2); } bool CmpMp3HandlerPtrByName::operator()(const std::string& strName1, const Mp3Handler* p2) const { return cmp(strName1, p2->getName()); } bool CmpMp3HandlerPtrByName::operator()(const Mp3Handler* p1, const Mp3Handler* p2) const { return cmp(p1->getName(), p2->getName()); } bool CmpMp3HandlerPtrByName::cmp(const std::string& strName1, const std::string& strName2) const { const std::string strDir1 (getParent(strName1)); const std::string strDir2 (getParent(strName2)); if (strDir1 == strDir2) { return strName1 < strName2; } // ttt3 maybe make comparison locale-dependant return strDir1 < strDir2; } MP3Diags-1.2.02/src/Debug.ui0000644000175000001440000002252711700343534014333 0ustar ciobiusers DebugDlg 0 0 1005 638 Debug true 0 30 10 0 QFrame::StyledPanel QFrame::Raised 9 0 0 0 Enable tracing 40 40 40 40 Qt::NoFocus Save trace messages ... :/images/save_log.svg:/images/save_log.svg 33 36 true QFrame::StyledPanel QFrame::Raised 0 0 0 0 0 80 16777215 40 40 40 40 Qt::NoFocus Decode MPEG Audio frame header dec :/images/decode.svg:/images/decode.svg 36 36 true 40 40 40 40 Qt::NoFocus Test tst :/images/test.svg:/images/test.svg 36 36 true Qt::Horizontal 10 40 0 40 40 40 40 Qt::NoFocus Close ... :/images/dialog-close.svg:/images/dialog-close.svg 36 36 true 0 Use all notes Log transformations Save downloaded data Qt::Horizontal 40 20 Trace messages: MP3Diags-1.2.02/src/Mp3TransformThread.cpp0000644000175000001440000007015712135700415017135 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include "Mp3TransformThread.h" #include "ThreadRunnerDlgImpl.h" #include "Helpers.h" #include "CommonData.h" #include "Transformation.h" #include "OsFile.h" #include "Widgets.h" using namespace std; //using namespace pearl; void logTransformation(const string& strLogFile, const char* szActionName, const string& strMp3File) { time_t t (time(0)); ofstream_utf8 out (strLogFile.c_str(), ios_base::app); out << "<" << strMp3File << "> <" << szActionName << "> - " << ctime(&t); // !!! ctime and a \n } //ttt2 perhaps make "fast-save aware" other transf that operate on id3v3 only (case transf, codepage, discards, ...); OTOH how likely is it to run 2 of these one ofer another? (otherwise you'd have to rescan anyway). still, perhaps allow proceeding in most cases without rescanning ID3V2 would be better, perhaps optional; then everything would be faster with ID3V2 namespace { struct Mp3TransformThread; struct Mp3TransformerGui : public Mp3Transformer { Mp3TransformThread* m_pMp3TransformThread; Mp3TransformerGui( CommonData* pCommonData, const TransfConfig& transfConfig, const deque& vpHndlr, vector& vpDel, vector& vpAdd, vector& vpTransf, Mp3TransformThread* pMp3TransformThread) : Mp3Transformer( pCommonData, transfConfig, vpHndlr, vpDel, vpAdd, vpTransf, 0), m_pMp3TransformThread(pMp3TransformThread) { } /*override*/ bool isAborted(); /*override*/ void checkPause(); /*override*/ void emitStepChanged(const StrList& v, int nStep); }; struct Mp3TransformThread : public PausableThread { Mp3TransformerGui m_mp3TransformerGui; Mp3TransformThread( CommonData* pCommonData, const TransfConfig& transfConfig, const deque& vpHndlr, vector& vpDel, vector& vpAdd, vector& vpTransf) : m_mp3TransformerGui( pCommonData, transfConfig, vpHndlr, vpDel, vpAdd, vpTransf, this) { } /*override*/ void run() { try { CompleteNotif notif(this); notif.setSuccess(m_mp3TransformerGui.transform()); } catch (...) { LAST_STEP("Mp3TransformThread::run()"); CB_ASSERT (false); } } using PausableThread::emitStepChanged; }; /*override*/ bool Mp3TransformerGui::isAborted() { return m_pMp3TransformThread->isAborted(); } /*override*/ void Mp3TransformerGui::checkPause() { m_pMp3TransformThread->checkPause(); } /*override*/ void Mp3TransformerGui::emitStepChanged(const StrList& v, int nStep) { //emit m_pMp3TransformThread->stepChanged(v, nStep); m_pMp3TransformThread->emitStepChanged(v, nStep); } // the idea is to mark a file for deletion but only rename it, so other things can be done as if the file got erased, but if something goes wrong the file can be restored; // the default behavior on the destructor is to restore the file, if the name isn't already used; to prevent the file from being restored, finalize() should be called after the things that could go wrong complete OK class FileEraser { string m_strOrigName; string m_strChangedName; public: void erase(const string& strOrigName) { CB_ASSERT(m_strOrigName.empty()); m_strOrigName = strOrigName; char a [20]; for (int i = 1; i < 1000; ++i) { sprintf(a, ".QQREN%03dREN", i); m_strChangedName = strOrigName + a; if (!fileExists(m_strChangedName)) { a[0] = 0; break; } } CB_ASSERT (0 == a[0]); // not really correct to assert, but quite likely try { renameFile(strOrigName, m_strChangedName); } catch (const CannotDeleteFile&) { revert(); throw; } catch (const CannotRenameFile&) { revert(); throw CannotDeleteFile(); } } ~FileEraser() { if (m_strOrigName.empty() || m_strChangedName.empty()) { return; } try { renameFile(m_strChangedName, m_strOrigName); } catch (...) { //ttt2 perhaps do something } } void finalize() { if (m_strChangedName.empty()) { return; } deleteFile(m_strChangedName); m_strOrigName.clear(); m_strChangedName.clear(); } private: void revert() // to be called on exceptions; assumes that the old file got copied but couldn't be deleted, so now the copy must be deleted { CB_ASSERT (!m_strChangedName.empty()); try { deleteFile(m_strChangedName); } catch (const CannotDeleteFile&) { // nothing } m_strChangedName.clear(); } }; // on destructor erases the file given on constructor, unless release() was called; doesn't throw class TempFileEraser { string m_strName; public: TempFileEraser(const string& strName) : m_strName(strName) { } ~TempFileEraser() { if (m_strName.empty()) { return; } try { deleteFile(m_strName); } catch (...) { //ttt2 perhaps do something } } void release() { m_strName.clear(); } }; void logTransformation(const string& strLogFile, const char* szActionName, const Mp3Handler* pHandler) { ::logTransformation(strLogFile, szActionName, pHandler->getName()); } } // namespace bool Mp3Transformer::transform() { bool bAborted (false); bool bUseFastSave (m_pCommonData->useFastSave()); for (int j = 0, m = cSize(m_vpTransf); j < m; ++j) { if (!m_vpTransf[j]->acceptsFastSave()) { bUseFastSave = false; break; } } try { for (int i = 0, n = cSize(m_vpHndlr); i < n; ++i) { //TRACER1A("transf ", 1); if (isAborted()) { return false; } checkPause(); //TRACER1A("transf ", 2); const Mp3Handler* pOrigHndl (m_vpHndlr[i]); string strOrigName (pOrigHndl->getName()); if (pOrigHndl->needsReload(bUseFastSave)) { m_strErrorFile = strOrigName; m_bWriteError = false; m_bFileChanged = true; return false; } string strTempName; string strPrevTempName; StrList l; l.push_back(toNativeSeparators(convStr(strOrigName))); l.push_back(""); //emit stepChanged(l); auto_ptr pNewHndl (pOrigHndl); //TRACER1A("transf ", 3); try { long long nSize, nOrigTime; getFileInfo(strOrigName, nOrigTime, nSize); for (int j = 0, m = cSize(m_vpTransf); j < m; ++j) { //TRACER1A("transf ", 4); Transformation& t (*m_vpTransf[j]); TRACER("Mp3TransformThread::transform()" + strOrigName + "/" + t.getActionName()); l[1] = t.getVisibleActionName(); //emit stepChanged(l, i + 1); emitStepChanged(l, i + 1); Transformation::Result eTransf; int nRetryCount (0); e1: try { //TRACER1A("transf ", 5); eTransf = t.apply(*pNewHndl, m_transfConfig, strOrigName, strTempName); //TRACER1A("transf ", 6); if (nRetryCount > 0) { TRACER("YYYYYYYYYYYYY Write retry succeeded for the second time to " + strTempName + ". Exiting to capture this ..."); throw WriteError(); //ttt0 remove } } catch (const WriteError&) { //qDebug("disk err"); //TRACER1A("transf ", 7); TRACER1("YYYYYYYYYYYYY Error copying from file " + strOrigName, 1); TRACER1("YYYYYYYYYYYYY Error writing to file " + strTempName, 3); //TRACER1A("transf ", 9); TempFileEraser er (strTempName); //TRACER1A("transf ", 10); if (nRetryCount < 1) { TRACER("Retrying writing to file " + strTempName); ++nRetryCount; PausableThread::msleep(30); strTempName.clear(); goto e1; } else { m_strErrorFile = strTempName; m_bWriteError = true; if (pNewHndl.get() == pOrigHndl) { //TRACER1A("transf ", 8); pNewHndl.release(); } TRACER1("Too many errors trying to write to file " + strTempName + ". Aborting ...", 2); return false; //ttt2 review what happens to pNewHndl } } catch (const EndOfFile&) //ttt2 catch other exceptions, perhaps in the outer loop { //TRACER1A("transf ", 11); m_strErrorFile = strOrigName; m_bWriteError = false; if (pNewHndl.get() == pOrigHndl) { //TRACER1A("transf ", 12); pNewHndl.release(); } TempFileEraser er (strTempName); //TRACER1A("transf ", 13); return false; } //TRACER1A("transf ", 14); //cout << "trying to apply " << t.getActionName() << " to " << pNewHndl.get()->getName() << endl; if (eTransf != Transformation::NOT_CHANGED) { //TRACER1A("transf ", 15); CB_ASSERT (!m_pCommonData->m_strTransfLog.empty()); //ttt0 triggered according to http://sourceforge.net/apps/mantisbt/mp3diags/view.php?id=45 ; however, the code is quite simple and it doesn't seem to be a valid reason for m_strTransfLog to be empty (aside from corrupted memory) ; according to https://sourceforge.net/apps/mantisbt/mp3diags/view.php?id=49 removing the .dat file solved the issue if (0 != m_pLog) { (*m_pLog) << "applied " << t.getActionName() << " to " << pNewHndl.get()->getName() << endl; } if (m_pCommonData->m_bLogTransf) { logTransformation(m_pCommonData->m_strTransfLog, t.getActionName(), pNewHndl.get()); } //TRACER1A("transf ", 16); if (pNewHndl.get() == pOrigHndl) { //TRACER1A("transf ", 17); pNewHndl.release(); CB_ASSERT (strPrevTempName.empty()); //TRACER1A("transf ", 18); } else { //TRACER1A("transf ", 19); CB_ASSERT (!strPrevTempName.empty()); switch (m_transfConfig.getTempAction()) { case TransfConfig::TRANSF_DONT_CREATE: deleteFile(strPrevTempName); break; //ttt2 try ... // or perhaps a try-catch for file errors for the whole block case TransfConfig::TRANSF_CREATE: break; default: CB_ASSERT (false); } //TRACER1A("transf ", 20); } //TRACER1A("transf ", 21); strPrevTempName = strTempName; pNewHndl.reset(new Mp3Handler(strTempName, m_pCommonData->m_bUseAllNotes, m_pCommonData->getQualThresholds())); //ttt2 try..catch checkPause(); //TRACER1A("transf ", 22); if (isAborted()) { //TRACER1A("transf ", 23); bAborted = true; break; // needed because it is possible that a bug in a transformation to make it create "transformations" that are equal to the original, so the loop never exits; // not 100% right, but seems better anyway than just returning; // ttt3 fix: we should delete all the temp and comp files and return false, but for now it can stay as is; } //TRACER1A("transf ", 24); if (Transformation::CHANGED_NO_RECALL != eTransf) { //TRACER1A("transf ", 25); CB_ASSERT (Transformation::CHANGED == eTransf); j = -1; // !!! start again with the first transformation //ttt2 consider: 5 transforms, 1 and 2 are CHANGE_NO_RECALL, 3 is CHANGE, causing 1 and 2 to be called again; probably OK, but review } //TRACER1A("transf ", 26); } //TRACER1A("transf ", 27); } //TRACER1A("transf ", 28); string strNewOrigName; // new name for the orig file; if this is empty, the original file wasn't changed; if it's "*", it was erased; if it's something else, it was renamed; string strProcName; // name for the proc file; if this is empty, a proc file doesn't exist; if it's something else, it's the file name; bool bErrorInTransform (false); FileEraser fileEraser; //TRACER1A("transf ", 29); try { //bool bChanged (true); if (pNewHndl.get() == pOrigHndl) { // nothing changed //TRACER1A("transf ", 30); pNewHndl.release(); //TRACER1A("transf ", 31); switch (m_transfConfig.getUnprocOrigAction()) { case TransfConfig::ORIG_DONT_CHANGE: break; case TransfConfig::ORIG_ERASE: { strNewOrigName = "*"; fileEraser.erase(strOrigName); } break; //ttt2 try ... case TransfConfig::ORIG_MOVE: { m_transfConfig.getUnprocOrigName(strOrigName, strNewOrigName); renameFile(strOrigName, strNewOrigName); } break; default: CB_ASSERT (false); } //TRACER1A("transf ", 32); } else { // at least a processed file exists //TRACER1A("transf ", 33); CB_ASSERT (!strTempName.empty()); //TRACER1A("transf ", 34); TempFileEraser tmpEraser (strTempName); // first we have to handle the original file; //TRACER1A("transf ", 35); switch (m_transfConfig.getProcOrigAction()) { case TransfConfig::ORIG_DONT_CHANGE: break; case TransfConfig::ORIG_ERASE: { strNewOrigName = "*"; fileEraser.erase(strOrigName); } break; //ttt2 try ... case TransfConfig::ORIG_MOVE: { m_transfConfig.getProcOrigName(strOrigName, strNewOrigName); renameFile(strOrigName, strNewOrigName); } break; case TransfConfig::ORIG_MOVE_OR_ERASE: { m_transfConfig.getProcOrigName(strOrigName, strNewOrigName); if (fileExists(strNewOrigName)) { strNewOrigName = "*"; fileEraser.erase(strOrigName); } else { renameFile(strOrigName, strNewOrigName); } } break; default: CB_ASSERT (false); } //TRACER1A("transf ", 36); // the last processed file exists (usualy in the same folder as the source), its name is in strTempName, and we have to see what to do with it (erase, rename, or copy); switch (m_transfConfig.getProcessedAction()) { case TransfConfig::TRANSF_DONT_CREATE: deleteFile(strTempName); break; case TransfConfig::TRANSF_CREATE: {//TRACER1A("transf ", 37); //Tracer t1 (strOrigName); m_transfConfig.getProcessedName(strOrigName, strProcName); //Tracer t2 (strProcName); //TRACER1A("transf ", 371); switch (m_transfConfig.getTempAction()) { case TransfConfig::TRANSF_DONT_CREATE: { /*TRACER1A("transf ", 372);*/ renameFile(strTempName, strProcName); /*TRACER1A("transf ", 373);*/ break; } case TransfConfig::TRANSF_CREATE: { /*TRACER1A("transf ", 374);*/ copyFile(strTempName, strProcName); /*TRACER1A("transf ", 375);*/ break; } default: { /*TRACER1A("transf ", 376);*/ CB_ASSERT (false); } } //TRACER1A("transf ", 377); } break; default: CB_ASSERT (false); } //TRACER1A("transf ", 38); tmpEraser.release(); //TRACER1A("transf ", 39); } //TRACER1A("transf ", 40); fileEraser.finalize(); //TRACER1A("transf ", 41); } catch (const CannotDeleteFile&) { //TRACER1A("transf ", 42); bErrorInTransform = true; } catch (const CannotRenameFile&) //ttt2 perhaps also NameNotFound, AlreadyExists, ... { //TRACER1A("transf ", 43); bErrorInTransform = true; } catch (const CannotCreateDir& ex) { //TRACER1A("transf ", 44); bErrorInTransform = true; m_strErrorDir = ex.m_strDir; } catch (const CannotCopyFile&) { //TRACER1A("transf ", 45); CB_ASSERT(false); //bErrorInTransform = true; } //TRACER1A("transf ", 46); if (bErrorInTransform) { //TRACER1A("transf ", 47); if (!strProcName.empty()) { //TRACER1A("transf ", 48); try { //TRACER1A("transf ", 49); deleteFile(strProcName); //TRACER1A("transf ", 50); } catch (...) { //ttt2 not sure what to do } } m_strErrorFile = strOrigName; m_bWriteError = false; //TRACER1A("transf ", 51); return false; } //ttt2 perhaps do something similar to strNewOrigName //ttt2 review the whole thing //TRACER1A("transf ", 52); if (!strNewOrigName.empty()) { //TRACER1A("transf ", 53); m_vpDel.push_back(pOrigHndl); if ("*" != strNewOrigName && m_pCommonData->m_dirTreeEnum.isIncluded(strNewOrigName)) { //TRACER1A("transf ", 54); m_vpAdd.push_back(new Mp3Handler(strNewOrigName, m_pCommonData->m_bUseAllNotes, m_pCommonData->getQualThresholds())); //TRACER1A("transf ", 55); } } if (!strProcName.empty()) { //TRACER1A("transf ", 56); if (m_transfConfig.m_options.m_bKeepOrigTime) { //TRACER1A("transf ", 57); setFileDate(strProcName, nOrigTime); } //TRACER1A("transf ", 58); if (m_pCommonData->m_dirTreeEnum.isIncluded(strProcName)) { //TRACER1A("transf ", 59); m_vpAdd.push_back(new Mp3Handler(strProcName, m_pCommonData->m_bUseAllNotes, m_pCommonData->getQualThresholds())); // !!! a new Mp3Handler is needed, because pNewHndl has an incorrect file name (but otherwise they should be identical) //TRACER1A("transf ", 60); } //TRACER1A("transf ", 61); } //TRACER1A("transf ", 62); } catch (...) { //TRACER1A("transf ", 63); if (pNewHndl.get() == pOrigHndl) { //TRACER1A("transf ", 64); pNewHndl.release(); //TRACER1A("transf ", 65); } throw; } } } catch (...) { //TRACER1A("transf ", 66); qDebug("Caught unknown exception in Mp3TransformThread::transform()"); traceToFile("Caught unknown exception in Mp3TransformThread::transform()", 0); throw; // !!! needed to restore "erased" files when errors occur, because when an exception is thrown the destructors only get called if that exception is caught; so catching and rethrowing is not a "no-op" } return !bAborted; } //ttt2 try to avoid rescanning the last file in a transform when intermediaries are removed; // if there are errors the user is notified; // returns true iff there were no errors; bool transform(const deque& vpHndlr, vector& vpTransf, const string& strTitle, QWidget* pParent, CommonData* pCommonData, TransfConfig& transfConfig) { vector vpDel; vector vpAdd; string strError; { Mp3TransformThread* pThread (new Mp3TransformThread(pCommonData, transfConfig, vpHndlr, vpDel, vpAdd, vpTransf)); ThreadRunnerDlgImpl dlg (pParent, getNoResizeWndFlags(), pThread, ThreadRunnerDlgImpl::SHOW_COUNTER, ThreadRunnerDlgImpl::TRUNCATE_BEGIN); dlg.setWindowTitle(convStr(strTitle)); dlg.exec(); strError = pThread->m_mp3TransformerGui.getError(); } //if (dlg.exec()) // !!! even if the dialog was aborted, some files might have been changed already, and this is not undoable { pCommonData->mergeHandlerChanges(vpAdd, vpDel, CommonData::SEL | CommonData::CURRENT); } if (!strError.empty()) { showCritical(pParent, Mp3Transformer::tr("Error"), convStr(strError)); } return strError.empty(); } std::string Mp3Transformer::getError() const { if (!m_strErrorFile.empty()) { if (m_bWriteError) { return convStr(tr("There was an error writing to the following file:\n\n%1\n\nMake sure that you have write permissions and that there is enough space on the disk.\n\nProcessing aborted.").arg(convStr(toNativeSeparators(m_strErrorFile)))); } else { if (m_bFileChanged) { return convStr(tr("The file \"%1\" seems to have been modified since the last scan. You need to rescan it before continuing.\n\nProcessing aborted.").arg(convStr(toNativeSeparators(m_strErrorFile)))); } else { if (m_strErrorDir.empty()) { return convStr(tr("There was an error processing the following file:\n\n%1\n\nProbably the file was deleted or modified since the last scan, in which case you should reload / rescan your collection. Or it may be used by another program; if that's the case, you should stop the other program first.\n\nThis may also be caused by access restrictions or a full disk.\n\nProcessing aborted.").arg(convStr(toNativeSeparators(m_strErrorFile)))); } else { return convStr(tr("There was an error processing the following file:\n%1\n\nThe following folder couldn't be created:\n\n%2\n\nProcessing aborted.").arg(convStr(toNativeSeparators(m_strErrorFile))).arg(convStr(toNativeSeparators(m_strErrorDir)))); } } } } return ""; } //ttt1 This is a new crash from when the destination folder was full while saving modifications, in particular adding an album image. /* 17:47:09.836 > TagEditorDlgImpl::run() 17:47:58.031 > Mp3TransformThread::transform()F:/TMP/MP3/MP3DiagsTest/Testfile.mp3/Save ID3V2.3.0 tags 17:47:58.183 > Mp3Handler constr: F:/TMP/MP3/MP3DiagsTest/Testfile.mp3.EQgf73 17:47:58.420 < Mp3Handler constr: F:/TMP/MP3/MP3DiagsTest/Testfile.mp3.EQgf73 17:47:58.422 < Mp3TransformThread::transform()F:/TMP/MP3/MP3DiagsTest/Testfile.mp3/Save ID3V2.3.0 tags 17:47:58.433 > Mp3Handler destr: F:/TMP/MP3/MP3DiagsTest/Testfile.mp3.EQgf73 17:47:58.435 < Mp3Handler destr: F:/TMP/MP3/MP3DiagsTest/Testfile.mp3.EQgf73 17:47:58.435 Caught unknown exception in Mp3TransformThread::transform() 17:47:58.443 Assertion failure in file Mp3TransformThread.cpp, line 98: false. The program will exit. */ MP3Diags-1.2.02/src/RenamerPatternsDlgImpl.h0000644000175000001440000000424411233524007017473 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef RenamerPatternsDlgImplH #define RenamerPatternsDlgImplH #include #include #include #include "ui_Patterns.h" class SessionSettings; class RenamerPatternsDlgImpl : public QDialog, private Ui::PatternsDlg { Q_OBJECT std::vector m_vstrPatterns; SessionSettings& m_settings; int m_nCrtLine, m_nCrtCol; public: RenamerPatternsDlgImpl(QWidget* pParent, SessionSettings& settings); ~RenamerPatternsDlgImpl(); bool run(std::vector&); protected slots: void on_m_pOkB_clicked(); void on_m_pCancelB_clicked(); void onHelp(); void onCrtPosChanged(); }; #endif MP3Diags-1.2.02/src/StoredSettings.h0000644000175000001440000001551711714056071016103 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef StoredSettingsH #define StoredSettingsH #include #include //#include #include // ttt2 what we really want is QByteArray; however, by including QByteArray directly, lots of warnings get displayed; perhaps some defines are needed but don't know which; so we just include QStringList to avoid the warnings (see also Helpers.h) class QSettings; class TransfConfig; class CommonData; class SessionSettings { QSettings* m_pSettings; public: SessionSettings(const std::string& strSessFile); ~SessionSettings(); void saveTransfConfig(const TransfConfig& transfConfig); bool loadTransfConfig(TransfConfig& transfConfig) const; // returns false if there was some error while loading (so the user can be told about defaults being used and those defaults could get saved) void saveDirs(const std::vector& vstrIncludedDirs, const std::vector& vstrExcludedDirs); bool loadDirs(std::vector& vstrIncludedDirs, std::vector& vstrExcludedDirs) const; // returns false if there were inconsistencies in the settings void saveScanAtStartup(bool b); bool loadScanAtStartup() const; void saveNoteFilterSettings(int nWidth, int nHeight); void loadNoteFilterSettings(int& nWidth, int& nHeight) const; void saveDirFilterSettings(int nWidth, int nHeight); void loadDirFilterSettings(int& nWidth, int& nHeight) const; void saveDiscogsSettings(int nWidth, int nHeight, int nStyleOption); void loadDiscogsSettings(int& nWidth, int& nHeight, int& nStyleOption) const; void saveMusicBrainzSettings(int nWidth, int nHeight); void loadMusicBrainzSettings(int& nWidth, int& nHeight) const; void saveConfigSize(int nWidth, int nHeight); void loadConfigSize(int& nWidth, int& nHeight) const; void saveDebugSettings(int nWidth, int nHeight); void loadDebugSettings(int& nWidth, int& nHeight) const; void saveExportSettings(int nWidth, int nHeight, bool bSortByShortNames, const std::string& strFile, bool bUseVisible, const std::string& strM3uRoot, const std::string& strM3uLocale); void loadExportSettings(int& nWidth, int& nHeight, bool& bSortByShortNames, std::string& strFile, bool& bUseVisible, std::string& strM3uRoot, std::string& strM3uLocale) const; void saveRenamerSettings(int nWidth, int nHeight, int nSaButton, int nVaButton, bool bKeepOrig, bool bUnratedAsDuplicate); void loadRenamerSettings(int& nWidth, int& nHeight, int& nSaButton, int& nVaButton, bool& bKeepOrig, bool& bUnratedAsDuplicate) const; void saveMainSettings(int nWidth, int nHeight, int nNotesGW0, int nNotesGW2, int nStrmsGW0, int nStrmsGW1, int nStrmsGW2, int nStrmsGW3, int nUnotesGW0, const QByteArray& stateMainSpl, const QByteArray& stateLwrSpl, int nIconSize, int nScanWidth); void loadMainSettings(int& nWidth, int& nHeight, int& nNotesGW0, int& nNotesGW2, int& nStrmsGW0, int& nStrmsGW1, int& nStrmsGW2, int& nStrmsGW3, int& nUnotesGW0, QByteArray& stateMainSpl, QByteArray& stateLwrSpl, int& nIconSize, int& nScanWidth) const; void saveExternalToolSettings(int nWidth, int nHeight); void loadExternalToolSettings(int& nWidth, int& nHeight) const; void saveTagEdtPatternsSettings(int nWidth, int nHeight); void loadTagEdtPatternsSettings(int& nWidth, int& nHeight) const; void saveRenamerPatternsSettings(int nWidth, int nHeight); void loadRenamerPatternsSettings(int& nWidth, int& nHeight) const; void saveTagEdtSettings(int nWidth, int nHeight, int nArtistCase, int nTitleCase); // Case params are really enum void loadTagEdtSettings(int& nWidth, int& nHeight, int& nArtistsCase, int& nOthersCase) const; void saveVector(const std::string& strPath, const std::vector& v); std::vector loadVector(const std::string& strPath, bool& bErr) const; // allows empty entries, but stops at the first missing entry, in which case sets bErr void saveMiscConfigSettings(const CommonData*); void loadMiscConfigSettings(CommonData*, bool bInitGui) const; enum { DONT_INIT_GUI, INIT_GUI }; void saveDbDirty(bool bDirty); void loadDbDirty(bool& bDirty); void saveCrashedAtStartup(bool bCrashedAtStartup); void loadCrashedAtStartup(bool& bCrashedAtStartup); void saveVersion(const std::string& strVersion); void loadVersion(std::string& strVersion); //ttt2 ??? see about ThreadRunner size; perhaps set width to its parent bool sync(); }; class GlobalSettings { QSettings* m_pSettings; //static QSettings* getGlobalSettings(); public: GlobalSettings(); ~GlobalSettings(); enum { IGNORE_EXTERNAL_CHANGES = 0, LOAD_EXTERNAL_CHANGES = 1 }; void saveSessions(const std::vector& vstrSess, const std::string& strLastSess, bool bOpenLast, const std::string& strTempSessTempl, const std::string& strDirSessTempl, const std::string& strTranslation, bool bLoadExternalChanges); void loadSessions(std::vector& vstrSess, std::string& strLastSess, bool& bOpenLast, std::string& strTempSessTempl, std::string& strDirSessTempl, std::string& strTranslation) const; void saveSessionsDlgSize(int nWidth, int nHeight); void loadSessionsDlgSize(int& nWidth, int& nHeight) const; void saveSessionEdtSize(int nWidth, int nHeight); void loadSessionEdtSize(int& nWidth, int& nHeight) const; }; #endif MP3Diags-1.2.02/src/ExternalTool.ui0000644000175000001440000000436511713446741015735 0ustar ciobiusers ExternalToolDlg 0 0 834 447 External tool true true QTextEdit::NoWrap true 0 Qt::Horizontal 40 20 Keep window open after completion Abort Close true m_pDetailE m_pKeepOpenCkM m_pAbortB m_pCloseB m_pOutM MP3Diags-1.2.02/src/MainFormDlgImpl.cpp0000644000175000001440000040176512135657313016446 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifdef MSVC_QMAKE #pragma warning (disable : 4100) #endif #include #include //#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef WIN32 //#include #else #include #include #endif #include "MainFormDlgImpl.h" #include "DirFilterDlgImpl.h" #include "NoteFilterDlgImpl.h" #include "Helpers.h" #include "OsFile.h" #include "FilesModel.h" #include "NotesModel.h" #include "StreamsModel.h" #include "UniqueNotesModel.h" #include "TagReadPanel.h" #include "ThreadRunnerDlgImpl.h" #include "ConfigDlgImpl.h" #include "AboutDlgImpl.h" #include "Widgets.h" #include "DataStream.h" #include "ExternalToolDlgImpl.h" #include "DebugDlgImpl.h" #include "TagEditorDlgImpl.h" #include "Mp3TransformThread.h" #include "FileRenamerDlgImpl.h" #include "ScanDlgImpl.h" #include "SessionEditorDlgImpl.h" #include "Id3Transf.h" #include "ExportDlgImpl.h" #include "Version.h" #include "Translation.h" using namespace std; using namespace pearl; using namespace Version; //#define LOG_ANYWAY //ttt2 try to switch from QDialog to QWidget, to see if min/max in gnome show up; or add Qt::Dialog flag (didn't seem to work, though) MainFormDlgImpl* getGlobalDlg(); //ttt2 review void trace(const string& s) { MainFormDlgImpl* p (getGlobalDlg()); //p->m_pContentM->append(convStr(s)); //p->m_pCommonData->m_qstrContent += convStr(s); //p->m_pCommonData->m_qstrContent += "\n"; if (0 != p && 0 != p->m_pCommonData) { p->m_pCommonData->trace(s); //ttt if p->m_pCommonData==0 or p==0 use logToGlobalFile(); 2009.09.08 - better not: this is "trace"; it makes sense to log errors before p->m_pCommonData is set up, but this is not the place to do it } } namespace { #ifndef WIN32 class NativeFile { string m_strFileName; public: NativeFile() {} bool open(const string& strFileName) // doesn't truncate the file { m_strFileName = strFileName; ofstream_utf8 out (m_strFileName.c_str(), ios_base::app); return out; } void close() { m_strFileName.clear(); } bool write(const string& s) { ofstream_utf8 out (m_strFileName.c_str(), ios_base::app); out << s; return out; } }; #else // #ifndef WIN32 void CB_LIB_CALL displayOsErrorIfExists(const char* szTitle) { unsigned int nErr (GetLastError()); if (0 == nErr) return; LPVOID lpMsgBuf; FormatMessageA( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, nErr, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language (LPSTR) &lpMsgBuf, 0, NULL ); string strRes ((LPSTR)lpMsgBuf); // Free the buffer. LocalFree(lpMsgBuf); unsigned int nSize ((unsigned int)strRes.size()); if (nSize - 2 == strRes.rfind("\r\n")) { strRes.resize(nSize - 2); } showCritical(0, szTitle, convStr(strRes)); } class NativeFile { HANDLE m_hStepFile; public: NativeFile() : m_hStepFile (INVALID_HANDLE_VALUE) {} bool open(const string& strFileName) { m_hStepFile = CreateFileA(strFileName.c_str(), GENERIC_WRITE, FILE_SHARE_WRITE | FILE_SHARE_READ, 0, OPEN_ALWAYS, //CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY, //FILE_ATTRIBUTE_NORMAL, 0); if (INVALID_HANDLE_VALUE == m_hStepFile) { displayOsErrorIfExists("Error"); return false; } SetFilePointer(m_hStepFile, 0, 0, FILE_END); return true; } void close() { CloseHandle(m_hStepFile); m_hStepFile = INVALID_HANDLE_VALUE; } bool write(const string& s) { DWORD dwWrt; //WriteFile(s_hStepFile, a, strlen(a), &dwWrt, 0); WriteFile(m_hStepFile, s.c_str(), s.size(), &dwWrt, 0); //WriteFile(s_hStepFile, "\r\n", 2, &dwWrt, 0); return (dwWrt == s.size()); } }; #endif // #ifndef WIN32 class FileTracer { string m_strTraceFile; vector m_vstrStepFile; bool m_bEnabled1, m_bEnabled2; // both have to be true for the trace to actually be enabled; they are not symmetrical: changing m_bEnabled2 while m_bEnabled1 is true also removes the files, but doesn't happen the other way; the reason for doing this is that we want trace files from previous run deleted (regardless of how it finished) but we don't want them deleted too early, so the user has a chance to mail them; to complicate the things, there is loading the settings, which may call enable2(true); int m_nStepFile; int m_nCrtStepSize; int m_nPage; int m_nStepLevel; int m_nTraceLevel; NativeFile m_stepFile; void setupFiles(); void removeFiles(); NativeFile m_traceFile; public: FileTracer() : m_bEnabled1(false), m_bEnabled2(false), m_nStepFile(-1), m_nCrtStepSize(-1), m_nPage(-1), m_nStepLevel(-1), m_nTraceLevel(-1) { } //void setupTraceToFile(bool bEnable); void setName(const string& strNameRoot); // also disables void enable1(bool); // used as a master control, by MainFormDlgImpl and assert; if m_bEnabled1 is true, changing m_bEnabled2 triggers the removal of files void enable2(bool); // set based on config //void removeFilesIfDisabled(); void traceToFile(const string& s, int nLevelChange); void traceLastStep(const string& s, int nLevelChange); const string& getTraceFile() const { return m_strTraceFile; } const vector& getStepFiles() const { return m_vstrStepFile; } }; void FileTracer::traceToFile(const string& s, int nLevelChange) { if (!m_bEnabled1 || !m_bEnabled2 || m_strTraceFile.empty()) { return; } static QMutex mutex; QMutexLocker lck (&mutex); m_nTraceLevel += nLevelChange; if (m_nTraceLevel < 0) { m_nTraceLevel = 20; } string s1 (m_nTraceLevel, ' '); // !!! may happen in this case: trace gets enabled by assert, then the Tracer destructor runs, trying to decrease what was never increased s1 += s; QTime t (QTime::currentTime()); char a [15]; sprintf(a, "%02d:%02d:%02d.%03d", t.hour(), t.minute(), t.second(), t.msec()); //ofstream_utf8 out (m_strTraceFile.c_str(), ios_base::app); //out << a << s1 << endl; #ifndef WIN32 m_traceFile.write(a + s1 + "\n"); #else m_traceFile.write(a + s1 + "\r\n"); #endif } void FileTracer::traceLastStep(const string& s, int nLevelChange) { if (!m_bEnabled1 || !m_bEnabled2 || m_strTraceFile.empty()) { return; } static QMutex mutex; QMutexLocker lck (&mutex); m_nStepLevel += nLevelChange; if (m_nStepLevel < 0) { m_nStepLevel = 20; } // !!! may happen in this case: trace gets enabled by assert, then the LastStepTracer destructor runs, trying to decrease what was never increased string s1 (m_nStepLevel, ' '); //string s1; char b [20]; sprintf(b, "%d", s_nLevel); s1 = b; s1 += s; char a [15]; a[0] = 0; //QTime t (QTime::currentTime()); sprintf(a, "%02d:%02d:%02d.%03d ", t.hour(), t.minute(), t.second(), t.msec()); #ifndef WIN32 m_stepFile.write(a + s1 + "\n"); m_nCrtStepSize += s1.size() + 1; #else m_stepFile.write(a + s1 + "\r\n"); m_nCrtStepSize += s1.size() + 2; #endif if (m_nCrtStepSize > 50000) { m_stepFile.close(); m_nCrtStepSize = 0; m_nStepFile = 1 - m_nStepFile; try { deleteFile(m_vstrStepFile[m_nStepFile]); } catch (...) { //ttt2 } { ofstream_utf8 out (m_vstrStepFile[m_nStepFile].c_str(), ios_base::app); out << "page " << ++m_nPage << endl; } m_stepFile.open(m_vstrStepFile[m_nStepFile]); } } void FileTracer::setName(const string& strNameRoot) { m_stepFile.close(); m_traceFile.close(); m_bEnabled1 = m_bEnabled2 = false; CB_ASSERT (!strNameRoot.empty()); m_strTraceFile = strNameRoot + "_trace.txt"; m_vstrStepFile.clear(); m_vstrStepFile.push_back(strNameRoot + "_step1.txt"); m_vstrStepFile.push_back(strNameRoot + "_step2.txt"); } /*void FileTracer::removeFilesIfDisabled() { if (!m_bEnabled1 || !m_bEnabled2) { removeFiles(); } }*/ void FileTracer::removeFiles() { m_nStepFile = 0; m_nCrtStepSize = 0; m_nPage = 0; m_nStepLevel = 0; m_nTraceLevel = 0; try { deleteFile(m_strTraceFile); deleteFile(m_vstrStepFile[0]); deleteFile(m_vstrStepFile[1]); } catch (...) { //ttt2 } } void FileTracer::setupFiles() { if (!m_bEnabled1 || !m_bEnabled2) { if (m_bEnabled1) { removeFiles(); } return; } removeFiles(); m_stepFile.open(m_vstrStepFile[0]); m_traceFile.open(m_strTraceFile); traceToFile(convStr(getSystemInfo()), 0); traceLastStep(convStr(getSystemInfo()), 0); } void FileTracer::enable1(bool b) { if (b == m_bEnabled1 || m_vstrStepFile.empty()) { return; } m_bEnabled1 = b; setupFiles(); } void FileTracer::enable2(bool b) { if (b == m_bEnabled2 || m_vstrStepFile.empty()) { return; } m_bEnabled2 = b; setupFiles(); } FileTracer s_fileTracer; } void traceToFile(const string& s, int nLevelChange) { s_fileTracer.traceToFile(s, nLevelChange); } void setupTraceToFile(bool b) { s_fileTracer.enable2(b); } void traceLastStep(const string& s, int nLevelChange) { s_fileTracer.traceLastStep(s, nLevelChange); } //ttt2 add checkbox to uninst on wnd to remove data and settings; //ttt2 uninst should remove log file as well, including the file created when "debug/log transf" is turned on //static QString s_strAssertTitle ("Assertion failure"); //static QString s_strCrashWarnTitle ("Crash detected"); static QString s_qstrErrorMsg; static bool s_bMainAssertOut; static QString g_qstrCrashMail ("mp3diags@gmail.com"); static QString g_qstrSupportMail ("mp3diags@gmail.com"); static QString g_qstrBugReport ("http://sourceforge.net/apps/mantisbt/mp3diags/"); static void showAssertMsg(QWidget* pParent) { HtmlMsg::msg(pParent, 0, 0, 0, HtmlMsg::SHOW_SYS_INFO, MainFormDlgImpl::tr("Assertion failure"), Qt::escape(s_qstrErrorMsg) + "

" + ( s_fileTracer.getStepFiles().empty() ? MainFormDlgImpl::tr("Plese report this problem to the project's Issue Tracker at %1").arg(g_qstrBugReport) : MainFormDlgImpl::tr("Please restart the application for instructions about how to report this issue") ) + "

", 750, 300, MainFormDlgImpl::tr("Exit")); } void MainFormDlgImpl::showRestartAfterCrashMsg(const QString& qstrText, const QString& qstrCloseBtn) { HtmlMsg::msg(this, 0, 0, 0, HtmlMsg::SHOW_SYS_INFO, tr("Restarting after crash"), qstrText, 750, 450, qstrCloseBtn); } void logAssert(const char* szFile, int nLine, const char* szCond) { //showCritical(0, "Assertion failure", QString("Assertion failure in file %1, line %2: %3").arg(szFile).arg(nLine).arg(szCond), QMessageBox::Close); /*QMessageBox dlg (QMessageBox::Critical, "Assertion failure", QString("Assertion failure in file %1, line %2: %3").arg(szFile).arg(nLine).arg(szCond), QMessageBox::Close, getThreadLocalDlgList().getDlg(), Qt::Dialog | Qt::MSWindowsFixedSizeDialogHint | Qt::WindowStaysOnTopHint);*/ s_qstrErrorMsg = QString("Assertion failure in file %1, line %2: %3. The program will exit.").arg(szFile).arg(nLine).arg(szCond); s_fileTracer.enable1(true); s_fileTracer.enable2(true); traceToFile(convStr(s_qstrErrorMsg), 0); qDebug("Assertion failure in file %s, line %d: %s", szFile, nLine, szCond); s_fileTracer.enable1(false); // !!! to avoid logging irrelevant info about cells drawn after the message was shown (there is some risk of not detecting that messages aren't shown although they should be, but this has never been reported, while trace files full of FilesModel::headerData and the like are common) MainFormDlgImpl* p (getGlobalDlg()); if (0 != p) { s_bMainAssertOut = false; //QTimer::singleShot(1, p, SLOT(onShowAssert())); //ttt2 see why this doesn't work AssertSender s (p); for (;;) { if (s_bMainAssertOut) { break; } //sleep(1); #ifndef WIN32 timespec ts; ts.tv_sec = 0; ts.tv_nsec = 100000000; // 0.1s nanosleep(&ts, 0); #else Sleep(100); #endif } } else { /*QMessageBox dlg (QMessageBox::Critical, s_strAssertTitle, s_strErrorMsg, QMessageBox::Close, 0, Qt::Dialog | Qt::MSWindowsFixedSizeDialogHint | Qt::WindowStaysOnTopHint); // ttt2 this might fail / crash, as it may be called from a secondary thread dlg.exec();*/ showAssertMsg(0); } } void logAssert(const char* szFile, int nLine, const char* szCond, const std::string& strAddtlInfo) { string s (string(szCond) + "; additional info: " + strAddtlInfo); logAssert(szFile, nLine, s.c_str()); } void MainFormDlgImpl::onShowAssert() { /*QMessageBox dlg (QMessageBox::Critical, s_strAssertTitle, s_strErrorMsg, QMessageBox::Close, this, Qt::Dialog | Qt::MSWindowsFixedSizeDialogHint | Qt::WindowStaysOnTopHint); dlg.exec();*/ showAssertMsg(this); s_bMainAssertOut = true; } // resizes a dialog with inexisting/invalid size settings, so it covers an area slightly smaller than MainWnd; however, if the dialog is alrady bigger than that, it doesn't get shrinked void defaultResize(QDialog& dlg) { QSize s (dlg.size()); QDialog& mainDlg (*getGlobalDlg()); s.rwidth() = max(s.rwidth(), mainDlg.width() - 100); //ttt2 doesn't do what it should for the case when working with small fonts and small resolutions s.rheight() = max(s.rheight(), mainDlg.height() - 100); dlg.resize(s.width(), s.height()); } //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== void MainFormDlgImpl::showBackupWarn() { if (m_pCommonData->m_bWarnedAboutBackup) { return; } HtmlMsg::msg(this, 0, 0, &m_pCommonData->m_bWarnedAboutBackup, HtmlMsg::CRITICAL, tr("Warning"), "

" + tr("Because MP3 Diags changes the content of your MP3 files if asked to, it has a significant destructive potential, especially in cases where the user doesn't read the documentation and simply expects the program to do other things than what it was designed to do.") + "

" + tr("Therefore, it is highly advisable to back up your files first.") + "

" + tr("Also, although MP3 Diags is very stable on the developer's computer, who hasn't experienced a crash in a long time and never needed to restore MP3 files from a backup, there are several crash reports that haven't been addressed, as the developer couldn't reproduce the crashes and those who reported the crashes didn't answer the developer's questions that might have helped isolate the problem.") + "

", 520, 300, tr("O&K")); if (!m_pCommonData->m_bWarnedAboutBackup) { return; } m_settings.saveMiscConfigSettings(m_pCommonData); } void MainFormDlgImpl::showSelWarn() { if (m_pCommonData->m_bWarnedAboutSel) { return; } HtmlMsg::msg(this, 0, 0, &m_pCommonData->m_bWarnedAboutSel, HtmlMsg::DEFAULT, tr("Note"), tr("If you simply left-click, all the visible files get processed. However, it is possible to process only the selected files. To do that, either keep SHIFT pressed down while clicking or use the right button, as described at %1").arg("http://mp3diags.sourceforge.net" + QString(getWebBranch()) + "/140_main_window_tools.html"), 520, 300, tr("O&K")); if (!m_pCommonData->m_bWarnedAboutSel) { return; } m_settings.saveMiscConfigSettings(m_pCommonData); } //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== void MainFormDlgImpl::saveIgnored() { const vector& v (m_pCommonData->getIgnoredNotes()); vector u; for (int i = 0, n = cSize(v); i < n; ++i) { u.push_back(Notes::getNote(v[i])->getDescription()); } m_settings.saveVector("ignored/list", u); } void MainFormDlgImpl::loadIgnored() { bool bRes (true); //ttt2 ? use vector v (m_settings.loadVector("ignored/list", bRes)); vector vnIgnored; vector vstrNotFoundNotes; int n (cSize(v)); if (0 == n) { // use default vnIgnored = Notes::getDefaultIgnoredNoteIds(); } else { for (int i = 0; i < n; ++i) { const string& strDescr (v[i]); //qDebug("%s", strDescr.c_str()); const Note* pNote (Notes::getNote(strDescr)); if (0 == pNote) { vstrNotFoundNotes.push_back(strDescr); } else { vnIgnored.push_back(pNote->getNoteId()); } } } { m_pCommonData->setIgnoredNotes(vnIgnored); int n (cSize(vstrNotFoundNotes)); if (n > 0) { QString s; if (1 == n) { s = tr("An unknown note was found in the configuration. This note is unknown:\n\n%1").arg(convStr(vstrNotFoundNotes[0])); } else { QString s1; for (int i = 0; i < n; ++i) { s1 += convStr(vstrNotFoundNotes[i]); if (i < n - 1) { s1 += "\n"; } } s = tr("Unknown notes were found in the configuration. These notes are unknown:\n\n%1").arg(s1); } showWarning(this, tr("Error setting up the \"ignored notes\" list"), s + tr("\n\nYou may want to check again the list and add any notes that you want to ignore.\n\n(If you didn't change the settings file manually, this is probably due to a code enhanement that makes some notes no longer needed, and you can safely ignore this message.)")); //ttt2 use MP3 Diags icon saveIgnored(); } } } static bool s_bToldAboutSupportInCrtRun (false); // to limit to 1 per run the number of times the user is told about support //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== /* Signals: currentFileChanged() - sent by m_pCommonData->m_pFilesModel and received by both MainFormDlgImpl (to create a new m_pTagDetailsW) and CommonData (to update current notes and streams, which calls m_pStreamsModel->emitLayoutChanged() and m_pNotesModel->emitLayoutChanged()) filterChanged() - sent by m_pCommonData->m_filter and received by m_pCommonData; it updates m_pCommonData->m_vpFltHandlers and calls m_pCommonData->updateWidgets(); updateWidgets() calls m_pFilesModel->selectRow(), which triggers currentFileChanged(), which causes the note and stream grids to be updated // ttt2 finish documenting the signal flow (mainly changing of "current" for both main window and tag editor); then check that it is properly implemented; pay attention to not calling signal handlers directly unless there's a very good reason to do so */ static MainFormDlgImpl* s_pGlobalDlg (0); //ttt2 review this MainFormDlgImpl* getGlobalDlg() { return s_pGlobalDlg; } QWidget* getMainForm() { return getGlobalDlg(); } static PausableThread* s_pSerThread; PausableThread* getSerThread() //ttt2 global function { return s_pSerThread; } namespace { struct SerLoadThread : public PausableThread { CommonData* m_pCommonData; const string& m_strSession; string& m_strErr; SerLoadThread(CommonData* pCommonData, const string& strSession, string& strErr) : m_pCommonData(pCommonData), m_strSession(strSession), m_strErr(strErr) {} /*override*/ void run() { try { CompleteNotif notif(this); bool bAborted (!load()); notif.setSuccess(!bAborted); } catch (...) { LAST_STEP("SerLoadThread::run()"); CB_ASSERT (false); } } bool load() { m_strErr = m_pCommonData->load(SessionEditorDlgImpl::getDataFileName(m_strSession)); //m_pCommonData->m_strTransfLog = SessionEditorDlgImpl::getLogFileName(m_strSession); //{ TRACER("001 m_strTransfLog=" + m_pCommonData->m_strTransfLog); } return true; } }; struct SerSaveThread : public PausableThread { CommonData* m_pCommonData; const string& m_strSession; string& m_strErr; SerSaveThread(CommonData* pCommonData, const string& strSession, string& strErr) : m_pCommonData(pCommonData), m_strSession(strSession), m_strErr(strErr) {} /*override*/ void run() { try { CompleteNotif notif(this); bool bAborted (!save()); notif.setSuccess(!bAborted); } catch (...) { LAST_STEP("SerSaveThread::run()"); CB_ASSERT (false); } } bool save() { m_strErr = m_pCommonData->save(SessionEditorDlgImpl::getDataFileName(m_strSession)); return true; } }; //ttt1 "unstable" in html for analytics void listKnownFormats() { for (unsigned i = 0x240; i < 1024; ++i) // everything below 0x240 is invalid { unsigned x (0xffe00000); x += (i & 0x300) << (19 - 8); x += (i & 0x0c0) << (17 - 6); x += (i & 0x03c) << (12 - 2); x += (i & 0x003) << (6 - 0); qDebug("%x: %s", x, decodeMpegFrame(x, ", ").c_str()); } } } // namespace MainFormDlgImpl::MainFormDlgImpl(const string& strSession, bool bDefaultForVisibleSessBtn) : QDialog(0, getMainWndFlags()), m_settings(strSession), m_nLastKey(0)/*, m_settings("Ciobi", "Mp3Diags_v01")*/ /*, m_nPrevTabIndex(-1), m_bTagEdtWasEntered(false)*/, m_pCommonData(0), m_strSession(strSession), m_bShowMaximized(false), m_nScanWidth(0), m_pQHttp(0), m_nGlobalX(0), m_nGlobalY(0) { //int x (2); CB_ASSERT(x > 4); //CB_ASSERT("345" == "ab"); //CB_ASSERT(false); s_fileTracer.setName(SessionEditorDlgImpl::getBaseName(strSession)); // also disables both flags s_pGlobalDlg = 0; setupUi(this); //listKnownFormats(); // ttt2 sizes for many formats seem way too low (e.g. "MPEG-1 Layer I, 44100Hz 32000bps" or "MPEG-2 Layer III, 22050Hz 8000bps") { /*KbdNotifTableView* pStreamsG (new KbdNotifTableView(m_pStreamsG)); connect(pStreamsG, SIGNAL(keyPressed(int)), this, SLOT(onStreamsGKeyPressed(int))); m_pStreamsG = pStreamsG;*/ m_pStreamsG->installEventFilter(this); m_pFilesG->installEventFilter(this); } m_pCommonData = new CommonData(m_settings, m_pFilesG, m_pNotesG, m_pStreamsG, m_pUniqueNotesG, /*m_pCurrentFileG, m_pCurrentAlbumG,*/ /*m_pLogG,*/ /*m_pAssignedB,*/ m_pNoteFilterB, m_pDirFilterB, m_pModeAllB, m_pModeAlbumB, m_pModeSongB, bDefaultForVisibleSessBtn); m_settings.loadMiscConfigSettings(m_pCommonData, SessionSettings::INIT_GUI); m_pCommonData->m_bScanAtStartup = m_settings.loadScanAtStartup(); string strVersion; m_settings.loadVersion(strVersion); if (strVersion == getAppVer()) { bool bDirty; m_settings.loadDbDirty(bDirty); bool bCrashedAtStartup; m_settings.loadCrashedAtStartup(bCrashedAtStartup); if (bDirty || bCrashedAtStartup) { if (m_pCommonData->isTraceToFileEnabled() || fileExists(s_fileTracer.getTraceFile())) // !!! fileExists(s_strTraceFile) allows new asserts to be reported (when m_pCommonData->isTraceToFileEnabled() would still return false) { if (fileExists(s_fileTracer.getTraceFile())) { vector v; if (fileExists(s_fileTracer.getTraceFile())) { v.push_back(s_fileTracer.getTraceFile()); } if (fileExists(s_fileTracer.getStepFiles()[0])) { v.push_back(s_fileTracer.getStepFiles()[0]); } if (fileExists(s_fileTracer.getStepFiles()[1])) { v.push_back(s_fileTracer.getStepFiles()[1]); } CB_ASSERT (!v.empty()); // really v should have at least 2 elements QString qstrFiles ("

" + tr("MP3 Diags is restarting after a crash.")); switch (v.size()) { case 1: qstrFiles += tr("Information in the file %1%5%2 may help identify the cause of the crash so please make it available to the developer by mailing it to %3, by reporting an issue to the project's Issue Tracker at %4 and attaching the files to the report, or by some other means (like putting it on a file sharing site.)", "%1 and %2 are HTML elements") .arg("") .arg("") .arg(g_qstrCrashMail) .arg(g_qstrBugReport) .arg(Qt::escape(toNativeSeparators(convStr(v[0])))); break; case 2: qstrFiles += tr("Information in the files %1%5%2 and %1%6%2 may help identify the cause of the crash so please make them available to the developer by mailing them to %3, by reporting an issue to the project's Issue Tracker at %4 and attaching the files to the report, or by some other means (like putting them on a file sharing site.)", "%1 and %2 are HTML elements") .arg("") .arg("") .arg(g_qstrCrashMail) .arg(g_qstrBugReport) .arg(Qt::escape(toNativeSeparators(convStr(v[0])))) .arg(Qt::escape(toNativeSeparators(convStr(v[1])))); break; case 3: qstrFiles += tr("Information in the files %1%5%2, %1%6%2, and %1%7%2 may help identify the cause of the crash so please make them available to the developer by mailing them to %3, by reporting an issue to the project's Issue Tracker at %4 and attaching the files to the report, or by some other means (like putting them on a file sharing site.)", "%1 and %2 are HTML elements") .arg("") .arg("") .arg(g_qstrCrashMail) .arg(g_qstrBugReport) .arg(Qt::escape(toNativeSeparators(convStr(v[0])))) .arg(Qt::escape(toNativeSeparators(convStr(v[1])))) .arg(Qt::escape(toNativeSeparators(convStr(v[2])))); break; } qstrFiles += "

"; showRestartAfterCrashMsg(qstrFiles + "

" + tr("These are plain text files, which you can review before sending, if you have privacy concerns.") + "

" "

" + tr("After getting the files, the developer will probably want to contact you for more details, so please check back on the status of your report.") + "

" "

" + tr("Note that these files will be removed when you close this window.") + "

" + (m_pCommonData->isTraceToFileEnabled() ? "

" + tr("If there is a name of an MP3 file at the end of %1, that might be a file that consistently causes a crash. Please check if it is so. Then, if confirmed, please make that file available by mailing it to %2 or by putting it on a file sharing site.").arg(Qt::escape(toNativeSeparators(convStr(v[0])))).arg(g_qstrCrashMail) + "

" : "

" + tr("Please also try to repeat the steps that led to the crash before reporting the crash, which will probably result in a new set of files being generated; these files are more likely to contain relevant information than the current set of files, because they will also have information on what happened before the crash, while the current files only tell where the crash occured.") + "

" ) + "

" + tr("You should include in your report any other details that seem relevant (what might have caused the failure, steps to reproduce it, ...)") + "

", tr("Remove these files and continue")); } else { showRestartAfterCrashMsg("

" + tr("MP3 Diags is restarting after a crash. There was supposed to be some information about what led to the crash in the file %1, but that file cannot be found. Please report this issue to the project's Issue Tracker at %2.").arg(Qt::escape(toNativeSeparators(convStr(s_fileTracer.getTraceFile())))).arg(g_qstrBugReport) + "

" + "

" + tr("The developer will probably want to contact you for more details, so please check back on the status of your report.

Make sure to include the data below, as well as any other detail that seems relevant (what might have caused the failure, steps to reproduce it, ...)") + "

", "OK"); } } if (!m_pCommonData->isTraceToFileEnabled()) { showRestartAfterCrashMsg("

" + tr("MP3 Diags is restarting after a crash. To help determine the reason for the crash, the Log program state to _trace and _step files option has been activated. This logs to 3 files what the program is doing, which might make it slightly slower.") + "

" + tr("It is recommended to not process more than several thousand MP3 files while this option is turned on. You can turn it off manually, in the configuration dialog, in the Others tab, but keeping it turned on may provide very useful feedback to the developer, should the program crash again. With this feedback, future versions of MP3 Diags will get closer to being bug free.") + "

", "OK"); m_pCommonData->setTraceToFile(true); m_settings.saveMiscConfigSettings(m_pCommonData); } // !!! don't change "dirty"; 1) there's no point; 2) ser needs it } // !!! nothing to do if not dirty } else { // it's a new version, so we start over //ttt2 perhaps also use some counter, like "20 runs without crash" m_pCommonData->setTraceToFile(false); m_settings.saveMiscConfigSettings(m_pCommonData); } #ifdef LOG_ANYWAY s_fileTracer.enable2(true); #else s_fileTracer.enable2(m_pCommonData->isTraceToFileEnabled()); // this might get called a second time (the first time is from within m_pCommonData->setTraceToFile()), but that's OK #endif s_fileTracer.enable1(true); // !!! after loadMiscConfigSettings(), so the previous files aren't deleted too early m_settings.saveVersion(getAppVer()); TRACER("MainFormDlgImpl constr"); { m_pCommonData->m_pFilesModel = new FilesModel(m_pCommonData); m_pFilesG->setModel(m_pCommonData->m_pFilesModel); FilesGDelegate* pDel (new FilesGDelegate(m_pCommonData, m_pFilesG)); m_pFilesG->setItemDelegate(pDel); m_pFilesG->setHorizontalHeader(new FileHeaderView(m_pCommonData, m_pFilesG)); m_pFilesG->horizontalHeader()->setMinimumSectionSize(CELL_WIDTH); m_pFilesG->verticalHeader()->setMinimumSectionSize(CELL_HEIGHT); m_pFilesG->verticalHeader()->setDefaultSectionSize(CELL_HEIGHT); connect(m_pFilesG->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), m_pCommonData->m_pFilesModel, SLOT(onFilesGSelChanged())); connect(m_pFilesG, SIGNAL(clicked(const QModelIndex &)), m_pCommonData->m_pFilesModel, SLOT(onFilesGSelChanged())); m_pFilesG->horizontalHeader()->setDefaultSectionSize(CELL_WIDTH); m_pFilesG->verticalHeader()->setDefaultSectionSize(CELL_HEIGHT); m_pFilesG->verticalHeader()->setDefaultAlignment(Qt::AlignRight | Qt::AlignVCenter); connect(m_pCommonData->m_pFilesModel, SIGNAL(currentFileChanged()), this, SLOT(onCrtFileChanged())); connect(m_pCommonData->m_pFilesModel, SIGNAL(currentFileChanged()), m_pCommonData, SLOT(onCrtFileChanged())); } { m_pCommonData->m_pNotesModel = new NotesModel(m_pCommonData); m_pNotesG->setModel(m_pCommonData->m_pNotesModel); NotesGDelegate* pNotesGDelegate = new NotesGDelegate(m_pCommonData); m_pNotesG->setItemDelegate(pNotesGDelegate); m_pNotesG->horizontalHeader()->setMinimumSectionSize(CELL_WIDTH + 10); m_pNotesG->verticalHeader()->setMinimumSectionSize(CELL_HEIGHT); m_pNotesG->verticalHeader()->setDefaultSectionSize(CELL_HEIGHT); m_pNotesG->verticalHeader()->setDefaultAlignment(Qt::AlignRight | Qt::AlignVCenter); connect(m_pNotesG->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), m_pCommonData->m_pNotesModel, SLOT(onNotesGSelChanged())); connect(m_pNotesG, SIGNAL(clicked(const QModelIndex &)), m_pCommonData->m_pNotesModel, SLOT(onNotesGSelChanged())); connect(m_pNotesG->horizontalHeader(), SIGNAL(sectionResized(int, int, int)), m_pNotesG, SLOT(resizeRowsToContents())); } { m_pCommonData->m_pStreamsModel = new StreamsModel(m_pCommonData); m_pStreamsG->setModel(m_pCommonData->m_pStreamsModel); StreamsGDelegate* pStreamsGDelegate = new StreamsGDelegate(m_pCommonData); m_pStreamsG->setItemDelegate(pStreamsGDelegate); m_pStreamsG->horizontalHeader()->setMinimumSectionSize(CELL_WIDTH + 10); m_pStreamsG->verticalHeader()->setMinimumSectionSize(CELL_HEIGHT); m_pStreamsG->verticalHeader()->setDefaultSectionSize(CELL_HEIGHT); m_pStreamsG->verticalHeader()->setDefaultAlignment(Qt::AlignRight | Qt::AlignVCenter); connect(m_pStreamsG->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), m_pCommonData->m_pStreamsModel, SLOT(onStreamsGSelChanged())); connect(m_pStreamsG, SIGNAL(clicked(const QModelIndex &)), m_pCommonData->m_pStreamsModel, SLOT(onStreamsGSelChanged())); connect(m_pStreamsG->horizontalHeader(), SIGNAL(sectionResized(int, int, int)), m_pStreamsG, SLOT(resizeRowsToContents())); } { m_pCommonData->m_pUniqueNotesModel = new UniqueNotesModel(m_pCommonData); m_pUniqueNotesG->setModel(m_pCommonData->m_pUniqueNotesModel); UniqueNotesGDelegate* pDel = new UniqueNotesGDelegate(m_pCommonData); m_pUniqueNotesG->setItemDelegate(pDel); m_pUniqueNotesG->horizontalHeader()->setMinimumSectionSize(CELL_WIDTH + 10); m_pUniqueNotesG->verticalHeader()->setMinimumSectionSize(CELL_HEIGHT); m_pUniqueNotesG->verticalHeader()->setDefaultSectionSize(CELL_HEIGHT); m_pUniqueNotesG->verticalHeader()->setDefaultAlignment(Qt::AlignRight | Qt::AlignVCenter); connect(m_pUniqueNotesG->horizontalHeader(), SIGNAL(sectionResized(int, int, int)), m_pUniqueNotesG, SLOT(resizeRowsToContents())); } m_pTagDetailsLayout = new QHBoxLayout(m_pTagDetailsTab); m_pTagDetailsTab->setLayout(m_pTagDetailsLayout); m_pTagDetailsW = new QWidget(m_pTagDetailsTab); m_pTagDetailsLayout->addWidget(m_pTagDetailsW); //m_pTagDetailsLayout->setContentsMargins(1, 1, 1, 1); //m_pTagDetailsLayout->setContentsMargins(6, 6, 6, 6); m_pTagDetailsLayout->setContentsMargins(0, 0, 0, 0); /*connect(m_pFilesG->selectionModel(), SIGNAL(currentChanged(const QModelIndex&, const QModelIndex&)), m_pCommonData->m_pFilesModel, SLOT(onFilesGSelChanged())); //ttt2 see if needed (in addition to selectionChanged); apparently not: this signal is sent "the next time", so first clicking in a grid doesn't send any message, regardless of what was "current" before; then, when the message is sent, the value in the model is not yet updated, so using "m_pCommonData->m_pNotesG->selectionModel()->selection().indexes()" returns the indexes from the last time; perhaps using the first QModelIndex parameter would allow getting to the "current" cell, but not to the whole selection anyway; connect(m_pNotesG->selectionModel(), SIGNAL(currentChanged(const QModelIndex&, const QModelIndex&)), m_pCommonData->m_pNotesModel, SLOT(onNotesGSelChanged())); connect(m_pStreamsG->selectionModel(), SIGNAL(currentChanged(const QModelIndex&, const QModelIndex&)), m_pCommonData->m_pStreamsModel, SLOT(onStreamsGSelChanged()));*/ /* !!! There's this use case: 1) the user selects a file that has multiple instances of the same error 2) the user clicks on one of the duplicates errors in m_pNotesG; the corresponding cell in m_pFilesG gets selected; 3) the user clicks on the selected cell in m_pFilesG; both of the duplicate errors should get selected; If nothing is done, nothing happens, and just one error stays selected; making m_pFilesG's current be (-1,-1) in FilesModel::matchSelToNotes() doesn't work anyaway because this doesn't change the selection either and it messes up keyboard navigation (assuming that it can be done). Instead, m_pFilesG's clicked() signal is connected to onFilesGSelChanged(). */ //cout << convStr(m_settings.fileName()) << endl; { ModifInfoToolButton* p (new ModifInfoToolButton(m_pCustomTransform1B)); m_vpTransfButtons.push_back(p); connect(p, SIGNAL(clicked()), this, SLOT(on_m_pCustomTransform1B_clicked())); m_pCustomTransform1B = p; } { ModifInfoToolButton* p (new ModifInfoToolButton(m_pCustomTransform2B)); m_vpTransfButtons.push_back(p); connect(p, SIGNAL(clicked()), this, SLOT(on_m_pCustomTransform2B_clicked())); m_pCustomTransform2B = p; } { ModifInfoToolButton* p (new ModifInfoToolButton(m_pCustomTransform3B)); m_vpTransfButtons.push_back(p); connect(p, SIGNAL(clicked()), this, SLOT(on_m_pCustomTransform3B_clicked())); m_pCustomTransform3B = p; } { ModifInfoToolButton* p (new ModifInfoToolButton(m_pCustomTransform4B)); m_vpTransfButtons.push_back(p); connect(p, SIGNAL(clicked()), this, SLOT(on_m_pCustomTransform4B_clicked())); m_pCustomTransform4B = p; } // CUSTOM_TRANSF_CNT { m_pModifNormalizeB = new ModifInfoToolButton(m_pNormalizeB); connect(m_pModifNormalizeB, SIGNAL(clicked()), this, SLOT(on_m_pNormalizeB_clicked())); m_pNormalizeB = m_pModifNormalizeB; } { m_pModifReloadB = new ModifInfoToolButton(m_pReloadB); connect(m_pModifReloadB, SIGNAL(clicked()), this, SLOT(on_m_pReloadB_clicked())); m_pReloadB = m_pModifReloadB; } { m_pModifRenameFilesB = new ModifInfoToolButton(m_pRenameFilesB); connect(m_pModifRenameFilesB, SIGNAL(clicked()), this, SLOT(on_m_pRenameFilesB_clicked())); m_pRenameFilesB = m_pModifRenameFilesB; } { QAction* p (new QAction(this)); p->setShortcut(QKeySequence("F1")); connect(p, SIGNAL(triggered()), this, SLOT(onHelp())); addAction(p); } /*{ QAction* p (new QAction(this)); p->setShortcut(QKeySequence("Ctrl+N")); connect(p, SIGNAL(triggered()), this, SLOT(onNext())); addAction(p); } //p->setShortcutContext(Qt::ApplicationShortcut); { QAction* p (new QAction(this)); p->setShortcut(QKeySequence("Ctrl+P")); connect(p, SIGNAL(triggered()), this, SLOT(onPrev())); addAction(p); } { QAction* p (new QAction(this)); p->setShortcut(QKeySequence("Ctrl+V")); connect(p, SIGNAL(triggered()), this, SLOT(onPaste())); addAction(p); } { QAction* p (new QAction(this)); p->setShortcut(QKeySequence("Ctrl+S")); connect(p, SIGNAL(triggered()), this, SLOT(on_m_pScanB_clicked())); addAction(p); }*/ //{ QAction* p (new QAction(this)); p->setShortcut(QKeySequence(Qt::Key_Escape)); connect(p, SIGNAL(triggered()), this, SLOT(emptySlot())); addAction(p); } // !!! 2009.01.13 - no longer usable, because this also prevents edits in QTableView from exiting with ESC; so the m_nLastKey alternative is used; // 2009.03.31 - probably usable again, since the tag editor got moved to a separate window //m_pCurrentAlbumG->setEditTriggers(QAbstractItemView::AllEditTriggers);//EditKeyPressed); loadIgnored(); { if (!m_settings.loadTransfConfig(m_transfConfig)) { m_settings.saveTransfConfig(m_transfConfig); } for (int i = 0; i < CUSTOM_TRANSF_CNT; ++i) { loadCustomTransf(i); } } //ttt2 perhaps have "experimental" transforms, different color (or just have the names begin with "experimental") { loadVisibleTransf(); if (m_pCommonData->getVisibleTransf().empty()) { vector v; initDefaultVisibleTransf(v, m_pCommonData); m_pCommonData->setVisibleTransf(v); } } { loadExternalTools(); } { initializeUi(); } setTransfTooltips(); { delete m_pRemovableL; delete m_pLowerHalfTablesW->layout(); m_pLowerHalfLayout = new QStackedLayout(m_pLowerHalfTablesW); //m_pLowerHalfLayout->setContentsMargins(0, 50, 50, 0); //m_pLowerHalfTablesW->setLayout(m_pLowerHalfLayout); m_pLowerHalfLayout->addWidget(m_pFileInfoTab); m_pLowerHalfLayout->addWidget(m_pAllNotesTab); m_pLowerHalfLayout->addWidget(m_pTagDetailsTab); delete m_pDetailsTabWidget; int nHeight (QApplication::fontMetrics().height() + 7); m_pLowerHalfBtnW->setMinimumHeight(nHeight); m_pLowerHalfBtnW->setMaximumHeight(nHeight); } connect(this, SIGNAL(tagEditorClosed()), m_pCommonData, SLOT(onFilterChanged())); // !!! needed because CommonData::mergeHandlerChanges() adds changed files that shouldn't normally be there in album / filter mode; the reason it does this is to allow comparisons after making changes, but this doesn't make much sense when those changes are done in the tag editor, (saving in the tag editor is different from regular transformations because album navigation is allowed) s_pGlobalDlg = this; { QAction* p (new QAction(this)); p->setShortcut(QKeySequence("Ctrl+A")); connect(p, SIGNAL(triggered()), this, SLOT(emptySlot())); addAction(p); } // !!! needed because it takes a lot of time, during which the app seems frozen (caused by the cells being selected and unselected automatically) // ttt2 see if possible to disable selecting "note" cells with SHIFT pressed QTimer::singleShot(1, this, SLOT(onShow())); } /*override*/ void MainFormDlgImpl::keyPressEvent(QKeyEvent* pEvent) { //qDebug("key prs %x", pEvent->key()); m_nLastKey = pEvent->key(); pEvent->ignore(); } void MainFormDlgImpl::onHelp() { if (m_pViewFileInfoB->isChecked()) { openHelp("130_main_window.html"); } else if (m_pViewAllNotesB->isChecked()) { openHelp("150_main_window_all_notes.html"); } else if (m_pViewTagDetailsB->isChecked()) { openHelp("160_main_window_tag_details.html"); } else { CB_ASSERT(false); } } /*override*/ void MainFormDlgImpl::keyReleaseEvent(QKeyEvent* pEvent) { //qDebug("key rel %d", pEvent->key()); if (Qt::Key_Escape == pEvent->key()) { //on_m_pAbortB_clicked(); pEvent->ignore(); } else { QDialog::keyReleaseEvent(pEvent); } //pEvent->ignore(); // ttt2 not sure this is the way to do it, but the point is to disable the ESC key } void MainFormDlgImpl::initializeUi() { int nWidth, nHeight; int nNotesGW0, nNotesGW2, nStrmsGW0, nStrmsGW1, nStrmsGW2, nStrmsGW3, nUnotesGW0; int nIconSize; QByteArray stateMainSpl, stateLwrSpl; m_settings.loadMainSettings(nWidth, nHeight, nNotesGW0, nNotesGW2, nStrmsGW0, nStrmsGW1, nStrmsGW2, nStrmsGW3, nUnotesGW0, stateMainSpl, stateLwrSpl, nIconSize, m_nScanWidth); if (m_nScanWidth <= 400) { QRect r (QApplication::desktop()->availableGeometry()); m_nScanWidth = min(1600, r.width()); if (m_nScanWidth > 1200) { m_nScanWidth = m_nScanWidth*3/4; } else { m_nScanWidth = m_nScanWidth*4/5; } //qDebug("%d %d %d %d", r.x(), r.y(), r.width(), r.height()); } if (nWidth > 400 && nHeight > 400) { QRect r (QApplication::desktop()->availableGeometry()); const int nApprox (16); #ifndef WIN32 int nTitleHeight(0); #else int nTitleHeight(GetSystemMetrics(SM_CYSIZE)); // ttt2 actually there's a pixel missing but not obvious where to get it from; nApprox should allow enough tolerance, though #endif if (r.width() - nWidth < nApprox && r.height() - nHeight - nTitleHeight < nApprox) { m_bShowMaximized = true; } else { resize(nWidth, nHeight); } } else { //QRect r (QApplication::desktop()->availableGeometry()); //qDebug("%d %d %d %d", r.x(), r.y(), r.width(), r.height()); m_bShowMaximized = true; // ttt2 perhaps implement m_bShowMaximized for all windows } m_pCommonData->m_nMainWndIconSize = nIconSize; { if (nNotesGW0 < CELL_WIDTH + 8) { nNotesGW0 = CELL_WIDTH + 8; } if (nNotesGW2 < 10) { nNotesGW2 = 65; } m_pNotesG->horizontalHeader()->resizeSection(0, nNotesGW0); // ttt2 apparently a call to resizeColumnsToContents() in NotesModel::updateCurrentNotes() should make columns 0 and 2 have the right size, but that's not the case at all; (see further notes there) m_pNotesG->horizontalHeader()->resizeSection(2, nNotesGW2); m_pNotesG->horizontalHeader()->setResizeMode(1, QHeaderView::Stretch); //m_pNotesG->horizontalHeader()->setResizeMode(2, QHeaderView::Stretch); } { if (nStrmsGW0 < 10) { nStrmsGW0 = 80; } // ttt2 "80" hard-coded if (nStrmsGW1 < 10) { nStrmsGW1 = 80; } if (nStrmsGW2 < 10) { nStrmsGW2 = 80; } if (nStrmsGW3 < 10) { nStrmsGW3 = 110; } m_pStreamsG->horizontalHeader()->resizeSection(0, nStrmsGW0); m_pStreamsG->horizontalHeader()->resizeSection(1, nStrmsGW1); m_pStreamsG->horizontalHeader()->resizeSection(2, nStrmsGW2); m_pStreamsG->horizontalHeader()->resizeSection(3, nStrmsGW3); m_pStreamsG->horizontalHeader()->setResizeMode(4, QHeaderView::Stretch); } { if (nUnotesGW0 < CELL_WIDTH + 8) { nUnotesGW0 = CELL_WIDTH + 8; } // ttt2 replace CELL_WIDTH m_pUniqueNotesG->horizontalHeader()->resizeSection(0, nUnotesGW0); m_pUniqueNotesG->horizontalHeader()->setResizeMode(1, QHeaderView::Stretch); } { if (!stateMainSpl.isNull()) { m_pMainSplitter->restoreState(stateMainSpl); } m_pMainSplitter->setOpaqueResize(false); if (!stateLwrSpl.isNull()) { m_pLowerSplitter->restoreState(stateLwrSpl); } } m_pCrtDirE->setFocus(); if (!m_pCommonData->m_bShowExport) { m_pExportB->hide(); } if (!m_pCommonData->m_bShowDebug) { m_pDebugB->hide(); } if (!m_pCommonData->m_bShowSessions) { m_pSessionsB->hide(); if (!m_pCommonData->m_bShowExport) { m_pOptBtn1W->hide(); } } resizeIcons(); } void MainFormDlgImpl::onShow() { TRACER("MainFormDlgImpl::onShow()"); bool bLoadErr (false); bool bCrashedAtStartup; m_pCommonData->m_strTransfLog = SessionEditorDlgImpl::getLogFileName(m_strSession); m_settings.loadCrashedAtStartup(bCrashedAtStartup); if (bCrashedAtStartup) { showCritical(this, tr("Error"), tr("MP3 Diags crashed while reading song data from the disk. The whole collection will be rescanned.")); } else { m_settings.saveCrashedAtStartup(true); string strErr; SerLoadThread* p (new SerLoadThread(m_pCommonData, m_strSession, strErr)); ThreadRunnerDlgImpl dlg (this, getNoResizeWndFlags(), p, ThreadRunnerDlgImpl::SHOW_COUNTER, ThreadRunnerDlgImpl::TRUNCATE_BEGIN, ThreadRunnerDlgImpl::HIDE_PAUSE_ABORT); CB_ASSERT (m_nScanWidth > 400); dlg.resize(m_nScanWidth, dlg.height()); dlg.setWindowIcon(QIcon(":/images/logo.svg")); dlg.setWindowTitle(tr("Loading data")); s_pSerThread = p; dlg.exec(); s_pSerThread = 0; m_nScanWidth = dlg.width(); m_pCommonData->setCrtAtStartup(); if (!strErr.empty()) { bLoadErr = true; showCritical(this, tr("Error"), tr("An error occured while loading the MP3 information. Your files will be rescanned.\n\n").arg(convStr(strErr))); } } m_settings.saveCrashedAtStartup(false); bool bDirty; m_settings.loadDbDirty(bDirty); if (bDirty) { /*s_strErrorMsg = "Rescanning files after crash."; showErrorDlg(this, false);*/ if (m_transfConfig.m_options.m_bKeepOrigTime) { showWarning(this, tr("Warning"), tr("It seems that MP3 Diags is restarting after a crash. Your files will be rescanned.\n\n(Since this may take a long time for large collections, you may want to abort the full rescanning and apply a filter to include only the files that you changed since the last time the program closed correctly, then manually rescan only those files.)")); } else { bDirty = false; // !!! if original time is not kept, any changes will be detected anyway, no need to force a full reload } } m_settings.saveDbDirty(true); if (m_pCommonData->m_bScanAtStartup || bDirty) { fullReload(bDirty || bLoadErr || bCrashedAtStartup ? FORCE : DONT_FORCE); } resizeEvent(0); // !!! without these the the file grid may look bad if it has a horizontal scrollbar and one of the last files is current string strCrt (m_pCommonData->getCrtName()); m_pFilesG->setCurrentIndex(m_pFilesG->model()->index(0, 0)); m_pCommonData->updateWidgets(strCrt); #ifndef DISABLE_CHECK_FOR_UPDATES checkForNewVersion(); #endif } void MainFormDlgImpl::fullReload(bool bForceReload) { CommonData::ViewMode eMode (m_pCommonData->getViewMode()); m_pCommonData->setViewMode(CommonData::ALL, m_pCommonData->getCrtMp3Handler()); m_pCommonData->m_filter.disableAll(); reload(IGNORE_SEL, bForceReload); m_pCommonData->m_filter.restoreAll(); m_pCommonData->setViewMode(eMode, m_pCommonData->getCrtMp3Handler()); } /*override*/ void MainFormDlgImpl::closeEvent(QCloseEvent*) { string strErr; SerSaveThread* p (new SerSaveThread(m_pCommonData, m_strSession, strErr)); //Qt::WindowStaysOnTopHint ThreadRunnerDlgImpl dlg (this, getNoResizeWndFlags(), p, ThreadRunnerDlgImpl::SHOW_COUNTER, ThreadRunnerDlgImpl::TRUNCATE_BEGIN, ThreadRunnerDlgImpl::HIDE_PAUSE_ABORT); CB_ASSERT (m_nScanWidth > 400); dlg.resize(m_nScanWidth, dlg.height()); dlg.setWindowIcon(QIcon(":/images/logo.svg")); dlg.setWindowTitle(tr("Saving data")); s_pSerThread = p; dlg.exec(); s_pSerThread = 0; m_nScanWidth = dlg.width(); if (!strErr.empty()) { showCritical(this, tr("Error"), tr("An error occured while saving the MP3 information. You will have to scan your files again.\n\n%1").arg(convStr(strErr))); } } MainFormDlgImpl::~MainFormDlgImpl() { TRACER("MainFormDlgImpl destr"); s_pGlobalDlg = 0; m_settings.saveMainSettings( width(), height(), m_pNotesG->horizontalHeader()->sectionSize(0), m_pNotesG->horizontalHeader()->sectionSize(2), m_pStreamsG->horizontalHeader()->sectionSize(0), m_pStreamsG->horizontalHeader()->sectionSize(1), m_pStreamsG->horizontalHeader()->sectionSize(2), m_pStreamsG->horizontalHeader()->sectionSize(3), m_pUniqueNotesG->horizontalHeader()->sectionSize(0), m_pMainSplitter->saveState(), m_pLowerSplitter->saveState(), m_pCommonData->m_nMainWndIconSize, m_nScanWidth ); m_settings.saveDbDirty(false); // !!! it would seem better to delay marking the data clean until a full reload is completed; however, if the user aborted the rescan, it was probably for a good reason (e.g. to rescan only a part of the files), so it makes more sense to mark the data as clean regardless of how it was when it was loaded and if the rescan completed or not //QMessageBox dlg (this); dlg.show(); //CursorOverrider crs;// (Qt::ArrowCursor); delete m_pCommonData; } void SessionSettings::saveDbDirty(bool bDirty) { m_pSettings->setValue("main/dirty", bDirty); m_pSettings->sync(); } void SessionSettings::loadDbDirty(bool& bDirty) { bDirty = m_pSettings->value("main/dirty", false).toBool(); } void SessionSettings::saveCrashedAtStartup(bool bCrashedAtStartup) { m_pSettings->setValue("debug/crashedAtStartup", bCrashedAtStartup); m_pSettings->sync(); } void SessionSettings::loadCrashedAtStartup(bool& bCrashedAtStartup) { bCrashedAtStartup = m_pSettings->value("debug/crashedAtStartup", false).toBool(); } void SessionSettings::saveVersion(const string& strVersion) { m_pSettings->setValue("main/version", convStr(strVersion)); } void SessionSettings::loadVersion(string& strVersion) { strVersion = convStr(m_pSettings->value("main/version", "x").toString()); } MainFormDlgImpl::CloseOption MainFormDlgImpl::run() { if (m_bShowMaximized) { showMaximized(); } return QDialog::Accepted == exec() ? OPEN_SESS_DLG : EXIT; } void MainFormDlgImpl::onCrtFileChanged() { delete m_pTagDetailsW; m_pTagDetailsW = new QWidget(m_pTagDetailsTab); m_pTagDetailsLayout->addWidget(m_pTagDetailsW); int nCrtFile (m_pCommonData->getFilesGCrtRow()); if (-1 == nCrtFile) { m_pCrtDirE->setText(""); return; } QHBoxLayout* pLayout = new QHBoxLayout(); pLayout->setSpacing(12); //pLayout->setContentsMargins(6, 6, 6, 6); pLayout->setContentsMargins(1, 1, 1, 1); m_pTagDetailsW->setLayout(pLayout); //pLayout->setSpacing(1); const Mp3Handler* pMp3Handler (m_pCommonData->getViewHandlers()[nCrtFile]); const vector& vpStreams (pMp3Handler->getStreams()); if (m_pViewTagDetailsB->isChecked()) { for (int i = 0, n = cSize(vpStreams); i < n; ++i) { DataStream* pStream (vpStreams[i]); TagReader* pReader (dynamic_cast(pStream)); if (0 != pReader) { TagReadPanel* pPanel (new TagReadPanel(m_pTagDetailsW, pReader)); pLayout->addWidget(pPanel, 10); } } } QString qs (toNativeSeparators(convStr(m_pCommonData->getViewHandlers()[nCrtFile]->getDir()))); #ifndef WIN32 #else if (2 == qs.size() && ':' == qs[1]) { qs += "\\"; } #endif m_pCrtDirE->setText(qs); pLayout->addStretch(0); } /*override*/ void MainFormDlgImpl::resizeEvent(QResizeEvent* pEvent) { if (m_pCommonData->m_bAutoSizeIcons || m_pCommonData->m_nMainWndIconSize < 16) { //const QRect& r (QApplication::desktop()->availableGeometry()); int w (width()); int k (w <= 800 ? 28 : w <= 1180 ? 32 : w <= 1400 ? 40 : w <= 1600 ? 48 : w <= 1920 ? 56 : 64); m_pCommonData->m_nMainWndIconSize = k; resizeIcons(); } m_pCommonData->resizeFilesGCols(); m_pUniqueNotesG->resizeRowsToContents(); m_pNotesG->resizeRowsToContents(); m_pStreamsG->resizeRowsToContents(); QDialog::resizeEvent(pEvent); } namespace { struct Mp3ProcThread : public PausableThread { FileEnumerator& m_fileEnum; bool m_bForce; CommonData* m_pCommonData; vector m_vpAdd, m_vpDel; deque m_vpExisting; // some (or all) of these will may get copied to m_vpDel vector m_vpKeep; // subset of m_vpExisting Mp3ProcThread(FileEnumerator& fileEnum, bool bForce, CommonData* pCommonData, deque vpExisting) : m_fileEnum(fileEnum), m_bForce(bForce), m_pCommonData(pCommonData), m_vpExisting(vpExisting) {} /*override*/ void run() { try { CompleteNotif notif(this); bool bAborted (!scan()); if (!bAborted) { sort(m_vpKeep.begin(), m_vpKeep.end(), CmpMp3HandlerPtrByName()); set_difference(m_vpExisting.begin(), m_vpExisting.end(), m_vpKeep.begin(), m_vpKeep.end(), back_inserter(m_vpDel), CmpMp3HandlerPtrByName()); } notif.setSuccess(!bAborted); } catch (...) { LAST_STEP("Mp3ProcThread::run()"); CB_ASSERT (false); //ttt0 triggered according to https://sourceforge.net/apps/mantisbt/mp3diags/view.php?id=50 and https://sourceforge.net/apps/mantisbt/mp3diags/view.php?id=54 } } bool scan(); }; // a subset of m_vpExisting gets copied to m_vpDel; so if m_vpExisting is empty, m_vpDel will be empty too; bool Mp3ProcThread::scan() { //cout << "################### procRec(" << strDir << ")\n"; //FileSearcher fs ((strDir + "/*").c_str()); m_fileEnum.reset(); for (;;) { string strName (m_fileEnum.next()); if (strName.empty()) { return true; } QString qs; if (strName.size() > 4) { qs = convStr(strName.substr(strName.size() - 4)).toLower(); } if (qs == ".mp3" || qs == ".id3") { if (isAborted()) { return false; } checkPause(); StrList l; l.push_back(toNativeSeparators(convStr(strName))); emit stepChanged(l, -1); if (!m_bForce) { deque::iterator it (lower_bound(m_vpExisting.begin(), m_vpExisting.end(), strName, CmpMp3HandlerPtrByName())); if (m_vpExisting.end() != it && (*it)->getName() == strName && !(*it)->needsReload(Mp3Handler::DONT_USE_FAST_SAVE)) { m_vpKeep.push_back(*it); continue; } } try { const Mp3Handler* p (new Mp3Handler(strName, m_pCommonData->m_bUseAllNotes, m_pCommonData->getQualThresholds())); m_vpAdd.push_back(p); } catch (const Mp3Handler::FileNotFound&) //ttt2 see if it should catch more { } } } } } // namespace //ttt2 perhaps: Too many notes that you don't care about are cluttering your screen? You can hide such notes, so you don't see them again. To do this, open the configuration dialog and go to the "Ignored notes" tab. See more details at ... //ttt2 album detection: folder / tags /both void MainFormDlgImpl::scan(FileEnumerator& fileEnum, bool bForce, deque vpExisting, int nKeepWhenUpdate) { m_pCommonData->clearLog(); //m_pModeAllB->setChecked(true); vector vpAdd, vpDel; { Mp3ProcThread* p (new Mp3ProcThread(fileEnum, bForce, m_pCommonData, vpExisting)); ThreadRunnerDlgImpl dlg (this, getNoResizeWndFlags(), p, ThreadRunnerDlgImpl::SHOW_COUNTER, ThreadRunnerDlgImpl::TRUNCATE_BEGIN); CB_ASSERT (m_nScanWidth > 400); dlg.resize(m_nScanWidth, dlg.height()); dlg.setWindowIcon(QIcon(":/images/logo.svg")); dlg.setWindowTitle(tr("Scanning MP3 files")); dlg.exec(); //ttt2 perhaps see if it ended with ok/reject and clear all on reject m_nScanWidth = dlg.width(); vpAdd = p->m_vpAdd; vpDel = p->m_vpDel; } m_pCommonData->mergeHandlerChanges(vpAdd, vpDel, nKeepWhenUpdate); if (!m_pCommonData->m_bToldAboutSupport && !s_bToldAboutSupportInCrtRun) { const vector& v (m_pCommonData->getUniqueNotes().getFltVec()); vector::const_iterator it (lower_bound(v.begin(), v.end(), &Notes::unsupportedFound(), CmpNotePtrById())); if (v.end() != it && &Notes::unsupportedFound() == *it) { s_bToldAboutSupportInCrtRun = true; HtmlMsg::msg(this, 0, 0, &m_pCommonData->m_bToldAboutSupport, HtmlMsg::DEFAULT, tr("Info"), "

" + tr("Your files are not fully supported by the current version of MP3 Diags. The main reason for this is that the developer is aware of some MP3 features but doesn't have actual MP3 files to implement support for those features and test the code.") + "

" "

" + tr("You can help improve MP3 Diags by making files with unsupported notes available to the developer. The preferred way to do this is to report an issue on the project's Issue Tracker at %1, after checking if others made similar files available. To actually send the files, you can mail them to %2 or put them on a file sharing site. It would be a good idea to make sure that you have the latest version of MP3 Diags.").arg(g_qstrBugReport).arg(g_qstrSupportMail) + "

" "

" + tr("You can identify unsupported notes by the blue color that is used for their labels.") + "

", 750, 300, tr("O&K")); if (m_pCommonData->m_bToldAboutSupport) { m_settings.saveMiscConfigSettings(m_pCommonData); } } } } void MainFormDlgImpl::on_m_pScanB_clicked() { bool bForce; bool bOk; { ScanDlgImpl dlg (this, m_pCommonData); bOk = dlg.run(bForce); } if (!bOk) { return; } scan(bForce); } void MainFormDlgImpl::scan(bool bForce) { long nPrevMem(getMemUsage()); m_pCommonData->m_filter.disableNote(); m_pCommonData->m_filter.setDirs(vector()); m_pCommonData->setViewMode(CommonData::ALL); //scan(m_pCommonData->m_dirTreeEnum, bForce, m_pCommonData->getViewHandlers(), CommonData::SEL | CommonData::CURRENT); scan(m_pCommonData->m_dirTreeEnum, bForce, m_pCommonData->getViewHandlers(), CommonData::NOTHING); long nCrtMem(getMemUsage()); ostringstream out; time_t t (time(0)); out << "current memory used by the whole program: " << nCrtMem << "; memory used by the current data (might be very inaccurate): " << nCrtMem - nPrevMem << "; time: " << ctime(&t); string s (out.str()); s.erase(s.size() - 1); // needed because ctime() uses a terminating '\n' qDebug("%s", s.c_str()); trace(""); trace("************************* " + s); //exportAsText(); } void MainFormDlgImpl::on_m_pNoteFilterB_clicked() { if (m_pCommonData->m_filter.isNoteEnabled()) { m_pCommonData->m_filter.disableNote(); return; } NoteFilterDlgImpl dlg (m_pCommonData, this); if (QDialog::Accepted == dlg.exec()) { // !!! no need to do anything with m_pCommonData->m_filter, because dlg.exec() took care of that } else { m_pNoteFilterB->setChecked(false); // !!! no guard needed, because the event that calls the filter is "clicked", not "checked" } } void MainFormDlgImpl::on_m_pDirFilterB_clicked() { if (m_pCommonData->m_filter.isDirEnabled()) { m_pCommonData->m_filter.disableDir(); return; } DirFilterDlgImpl dlg (m_pCommonData, this); if (QDialog::Accepted == dlg.exec()) { // !!! no need to do anything with m_pCommonData->m_filter, because dlg.exec() took care of that } else { m_pDirFilterB->setChecked(false); // !!! no guard needed, because the event that calls the filter is "clicked", not "checked" } } //ttt2 ? operation to "add null frame", which would allow to rescue some frames in case of overwriting void MainFormDlgImpl::saveCustomTransf(int k) { const vector& v (m_pCommonData->getCustomTransf()[k]); int n (cSize(v)); vector vstrActionNames; for (int i = 0; i < n; ++i) { vstrActionNames.push_back(m_pCommonData->getAllTransf()[v[i]]->getActionName()); } char bfr [50]; sprintf(bfr, "customTransf/set%04d", k); m_settings.saveVector(bfr, vstrActionNames); } void MainFormDlgImpl::loadCustomTransf(int k) { char bfr [50]; sprintf(bfr, "customTransf/set%04d", k); //vector vstrNames (m_settings.loadCustomTransf(k)); bool bErr; vector vstrNames (m_settings.loadVector(bfr, bErr)); vector v; const vector& u (m_pCommonData->getAllTransf()); int m (cSize(u)); for (int i = 0, n = cSize(vstrNames); i < n; ++i) { string strName (vstrNames[i]); int j (0); for (; j < m; ++j) { if (u[j]->getActionName() == strName) { v.push_back(j); break; } } if (j == m) { showWarning(this, tr("Error setting up custom transformations"), tr("Couldn't find a transformation with the name \"%1\". The program will proceed, but you should review the custom transformations lists.").arg(convStr(strName))); } } if (v.empty()) { vector > vv (CUSTOM_TRANSF_CNT); initDefaultCustomTransf(k, vv, m_pCommonData); v = vv[k]; } m_pCommonData->setCustomTransf(k, v); } void MainFormDlgImpl::saveVisibleTransf() { const vector& v (m_pCommonData->getVisibleTransf()); int n (cSize(v)); vector vstrActionNames; for (int i = 0; i < n; ++i) { vstrActionNames.push_back(m_pCommonData->getAllTransf()[v[i]]->getActionName()); } m_settings.saveVector("visibleTransf", vstrActionNames); } void MainFormDlgImpl::loadVisibleTransf() { bool bErr; vector vstrNames (m_settings.loadVector("visibleTransf", bErr)); //ttt2 check bErr vector v; const vector& u (m_pCommonData->getAllTransf()); int m (cSize(u)); for (int i = 0, n = cSize(vstrNames); i < n; ++i) { string strName (vstrNames[i]); int j (0); for (; j < m; ++j) { if (u[j]->getActionName() == strName) { v.push_back(j); break; } } if (j == m) { showWarning(this, tr("Error setting up visible transformations"), tr("Couldn't find a transformation with the name \"%1\". The program will proceed, but you should review the visible transformations list.").arg(convStr(strName))); } } m_pCommonData->setVisibleTransf(v); } void MainFormDlgImpl::saveExternalTools() { vector vstrExternalTools; for (int i = 0, n = cSize(m_pCommonData->m_vExternalToolInfos); i < n; ++i) { vstrExternalTools.push_back(m_pCommonData->m_vExternalToolInfos[i].asString()); } m_settings.saveVector("externalTools", vstrExternalTools); } void MainFormDlgImpl::loadExternalTools() { bool bErr; vector vstrExternalTools (m_settings.loadVector("externalTools", bErr)); //ttt2 check bErr m_pCommonData->m_vExternalToolInfos.clear(); for (int i = 0, n = cSize(vstrExternalTools); i < n; ++i) { try { m_pCommonData->m_vExternalToolInfos.push_back(ExternalToolInfo(vstrExternalTools[i])); } catch (const ExternalToolInfo::InvalidExternalToolInfo&) { showWarning(this, tr("Error setting up external tools"), tr("Unable to parse \"%1\". The program will proceed, but you should review the external tools list.").arg(convStr(vstrExternalTools[i]))); } } } //ttt2 keyboard shortcuts: next, prev, ... ; //ttt3 set focus on some edit box when changing tabs; //ttt2 perhaps add "whatsThis" texts //ttt2 perhaps add "what's this" tips void MainFormDlgImpl::on_m_pTransformB_clicked() //ttt2 an alternative is to use QToolButton::setMenu(); see if that is really simpler { showBackupWarn(); showSelWarn(); ModifInfoMenu menu; vector vpAct; const vector& vpTransf (m_pCommonData->getAllTransf()); const vector& vnVisualNdx (m_pCommonData->getVisibleTransf()); for (int i = 0, n = cSize(vnVisualNdx); i < n; ++i) { Transformation* pTransf (vpTransf.at(vnVisualNdx[i])); QAction* pAct (new QAction(pTransf->getVisibleActionName(), &menu)); pAct->setToolTip(makeMultiline(Transformation::tr(pTransf->getDescription()))); //connect(pAct, SIGNAL(triggered()), this, SLOT(onExecTransform(i))); // !!! Qt doesn't seem to support parameter binding menu.addAction(pAct); vpAct.push_back(pAct); } connect(&menu, SIGNAL(hovered(QAction*)), this, SLOT(onMenuHovered(QAction*))); QAction* p (menu.exec(m_pTransformB->mapToGlobal(QPoint(0, m_pTransformB->height())))); if (0 != p) { int nIndex (std::find(vpAct.begin(), vpAct.end(), p) - vpAct.begin()); vector v; v.push_back(vpTransf.at(vnVisualNdx[nIndex])); transform(v, 0 == (Qt::ShiftModifier & menu.getModifiers()) ? ALL : SELECTED); } } void MainFormDlgImpl::onMenuHovered(QAction* pAction) { //QToolTip::showText(QCursor::pos(), ""); // this was needed initially but at some time tooltips on menus stopped working (e.g. in 11.4) and commenting this out fixed the problem QToolTip::showText(QCursor::pos(), pAction->toolTip()); // see http://www.mail-archive.com/pyqt@riverbankcomputing.com/msg17214.html and http://www.mail-archive.com/pyqt@riverbankcomputing.com/msg17245.html ; apparently there's some inconsistency in when the menus are shown } void MainFormDlgImpl::on_m_pNormalizeB_clicked() { showBackupWarn(); showSelWarn(); bool bSel (0 != (Qt::ShiftModifier & m_pModifNormalizeB->getModifiers())); const deque& vpHndlr (bSel ? m_pCommonData->getSelHandlers() : m_pCommonData->getViewHandlers()); QStringList l; for (int i = 0, n = cSize(vpHndlr); i < n; ++i) { l << vpHndlr[i]->getUiName(); } if (l.isEmpty()) { showCritical(this, tr("Error"), tr("There are no files to normalize.")); return; } int nIssueCount (0); QString qstrWarn; if (bSel && cSize(m_pCommonData->getViewHandlers()) != cSize(m_pCommonData->getSelHandlers())) { ++nIssueCount; qstrWarn += "\n- " + tr("you are requesting to normalize only some of the files"); } if (CommonData::FOLDER != m_pCommonData->getViewMode()) { ++nIssueCount; qstrWarn += "\n- " + tr("the \"Album\" mode is not selected"); } if (m_pCommonData->m_filter.isNoteEnabled() || m_pCommonData->m_filter.isDirEnabled()) { ++nIssueCount; if (m_pCommonData->m_filter.isNoteEnabled() && m_pCommonData->m_filter.isDirEnabled()) { qstrWarn += "\n- " + tr("filters are applied"); } else { qstrWarn += "\n- " + tr("a filter is applied"); } } if (cSize(vpHndlr) > 50) { ++nIssueCount; qstrWarn += "\n- " + tr("the normalization will process more than 50 files, which is more than what an album usually has"); } if (0 != nIssueCount) { QString s; if (1 == nIssueCount) { qstrWarn.remove(0, 3); s = tr("Normalization should process one whole album at a time, so it should only be run in \"Album\" mode, when no filters are active, and it should be applied to all the files in that album. But in the current case %1.").arg(qstrWarn); } else { s = tr("Normalization should process one whole album at a time, so it should only be run in \"Album\" mode, when no filters are active, and it should be applied to all the files in that album. But in the current case there are some issues:\n%1").arg(qstrWarn); } int k (showMessage(this, QMessageBox::Warning, 1, 1, tr("Warning"), s + "\n\n" + tr("Normalize anyway?"), tr("Normalize"), tr("Cancel"))); if (k != 0) { return; } } if (0 == nIssueCount) { if (0 != showMessage(this, QMessageBox::Question, 1, 1, tr("Confirm"), tr("Normalize all the files in the current album? (Note that normalization is done \"in place\", by an external program, so it doesn't care about the transformation settings for original and modified files.)"), tr("Normalize"), tr("Cancel"))) { return; } } ExternalToolDlgImpl dlg (this, m_pCommonData->m_bKeepNormWndOpen, m_settings, m_pCommonData, "Normalize", "230_normalize.html"); dlg.run(convStr(m_pCommonData->m_strNormalizeCmd), l); reload(bSel, FORCE); } void MainFormDlgImpl::on_m_pReloadB_clicked() { bool bSel (0 != (Qt::ShiftModifier & m_pModifReloadB->getModifiers())); bool bForce (0 != (Qt::ControlModifier & m_pModifReloadB->getModifiers())); reload(bSel, bForce); } void MainFormDlgImpl::reload(bool bSelOnly, bool bForce) { auto_ptr ptr; FileEnumerator* pEnum; const deque* pvpExisting; bool bNoteFlt (m_pCommonData->m_filter.isNoteEnabled()); bool bDirFlt (m_pCommonData->m_filter.isDirEnabled()); if (bSelOnly || bNoteFlt || bDirFlt) { vector v; const deque& vpHndlr (bSelOnly ? m_pCommonData->getSelHandlers() : m_pCommonData->getViewHandlers()); for (int i = 0, n = cSize(vpHndlr); i < n; ++i) { v.push_back(vpHndlr[i]->getName()); } pEnum = new ListEnumerator(v); ptr.reset(pEnum); } else { CB_ASSERT (!m_pCommonData->m_filter.isNoteEnabled() && !m_pCommonData->m_filter.isDirEnabled()); const deque& vpHndlr (m_pCommonData->getViewHandlers()); switch (m_pCommonData->getViewMode()) { case CommonData::ALL: pEnum = &m_pCommonData->m_dirTreeEnum; break; case CommonData::FOLDER: pEnum = new ListEnumerator(vpHndlr[0]->getDir()); ptr.reset(pEnum); break; case CommonData::FILE: { vector v; v.push_back(vpHndlr[0]->getName()); pEnum = new ListEnumerator(v); ptr.reset(pEnum); } break; default: CB_ASSERT (false); } } pvpExisting = &(bSelOnly ? m_pCommonData->getSelHandlers() : m_pCommonData->getViewHandlers()); scan(*pEnum, bForce, *pvpExisting, CommonData::CURRENT | CommonData::SEL); if (bNoteFlt || bDirFlt) { m_pCommonData->m_filter.disableAll(); m_pCommonData->m_filter.restoreAll(); if (!m_pCommonData->m_filter.isNoteEnabled() && !m_pCommonData->m_filter.isDirEnabled() && !bSelOnly) { // the filter got removed, because nothing matched; so wee need to repeat; (otherwise the user has to press twice: once to remove the filter and a second time to see what's new) reload(false, bForce); } } } void MainFormDlgImpl::applyCustomTransf(int k) { showBackupWarn(); showSelWarn(); vector v; for (int i = 0, n = cSize(m_pCommonData->getCustomTransf()[k]); i < n; ++i) { v.push_back(m_pCommonData->getAllTransf()[m_pCommonData->getCustomTransf()[k][i]]); } transform(v, 0 == (Qt::ShiftModifier & m_vpTransfButtons[k]->getModifiers()) ? ALL : SELECTED); } // The file list is updated in the sense that if a file was changed or removed, this is reflected in the UI. However, new files are not seen. For one thing, rebuilding a 10000-file list takes a lot of time. OTOH perhaps just the new files could be added. Also, perhaps the user could be asked about updating the list. //ttt2 review void MainFormDlgImpl::transform(std::vector& vpTransf, Subset eSubset) { if (m_pCommonData->getViewHandlers().empty()) { showWarning(this, tr("Warning"), tr("The file list is empty, therefore no transformations can be applied.\n\nExiting ...")); return; } QString qstrListInfo; switch (eSubset) { case ALL: { int nCnt (cSize(m_pCommonData->getViewHandlers())); if (nCnt < 10) { qstrListInfo = tr("all the files shown in the file list"); } else { qstrListInfo = tr("all %1 files shown in the file list").arg(nCnt); } break; } case SELECTED: { int nCnt (cSize(m_pCommonData->getSelHandlers())); if (0 == nCnt) { showWarning(this, tr("Warning"), tr("No file is selected, therefore no transformations can be applied.\n\nExiting ...")); return; } else if (1 == nCnt) { qstrListInfo = "\"" + convStr(m_pCommonData->getSelHandlers()[0]->getShortName()) + "\""; } else if (2 == nCnt) { qstrListInfo = "\"" + convStr(m_pCommonData->getSelHandlers()[0]->getShortName()) + "\" " + tr("and the other selected file"); } else { qstrListInfo = "\"" + convStr(m_pCommonData->getSelHandlers()[0]->getShortName()) + "\" " + tr("and the other %1 selected files").arg(nCnt - 1); } break; } case CURRENT: { CB_ASSERT (0 != m_pCommonData->getCrtMp3Handler()); qstrListInfo = "\"" + convStr(m_pCommonData->getCrtMp3Handler()->getShortName()) + "\""; break; } default: CB_ASSERT (false); } QString qstrConf; if (vpTransf.empty()) { if (TransfConfig::Options::UPO_DONT_CHG == m_transfConfig.m_options.m_eUnprocOrigChange) { showWarning(this, tr("Warning"), tr("The transformation list is empty.\n\nBased on the configuration, it is possible for changes to the files in the list to be performed, even in this case (the files may still be moved, renamed or erased). However, the current settings are to leave the original files unchanged, so currently there's no point in applying an empty transformation list.\n\nExiting ...")); return; } qstrConf = tr("Apply an empty transformation list to all the files shown in the file list? (Note that even if no transformations are performed, the files may still be moved, renamed or erased, based on the current settings.)"); } else if (1 == cSize(vpTransf)) { qstrConf = tr("Apply transformation \"%1\" to %2?").arg(vpTransf[0]->getVisibleActionName()).arg(qstrListInfo); } else { qstrConf = tr("Apply the following transformations to %1?").arg(qstrListInfo); for (int i = 0, n = cSize(vpTransf); i < n; ++i) { qstrConf += "\n "; qstrConf += vpTransf[i]->getVisibleActionName(); } } { const char* aOrig[] = { QT_TR_NOOP("don't change"), QT_TR_NOOP("erase"), QT_TR_NOOP("move"), QT_TR_NOOP("move"), QT_TR_NOOP("rename"), QT_TR_NOOP("move if destination doesn't exist") }; if ((m_transfConfig.m_options.m_eProcOrigChange != TransfConfig::Options::PO_MOVE_OR_ERASE && m_transfConfig.m_options.m_eProcOrigChange != TransfConfig::Options::PO_ERASE) || m_transfConfig.m_options.m_eUnprocOrigChange != TransfConfig::Options::UPO_DONT_CHG) //ttt2 improve { qstrConf += "\n\n" + tr("Actions to be taken:"); if (!vpTransf.empty()) { qstrConf += "\n- " + tr("original file that has been transformed: %1").arg(tr(aOrig[m_transfConfig.m_options.m_eProcOrigChange])); } qstrConf += "\n- " + tr("original file that has not been transformed: %1").arg(tr(aOrig[m_transfConfig.m_options.m_eUnprocOrigChange])); } } if (showMessage(this, QMessageBox::Question, 1, 1, tr("Confirm"), qstrConf, tr("&Yes"), tr("&No")) != 0) { return; } deque vpCrt; const deque* pvpHandlers; switch (eSubset) { case SELECTED: pvpHandlers = &m_pCommonData->getSelHandlers(); break; case ALL: pvpHandlers = &m_pCommonData->getViewHandlers(); break; case CURRENT: vpCrt.push_back(m_pCommonData->getCrtMp3Handler()); pvpHandlers = &vpCrt; break; default: CB_ASSERT (false); } ::transform(*pvpHandlers, vpTransf, convStr(tr("Applying transformations to MP3 files")), this, m_pCommonData, m_transfConfig); } //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== void MainFormDlgImpl::on_m_pConfigB_clicked() { ConfigDlgImpl dlg (m_transfConfig, m_pCommonData, this, ConfigDlgImpl::ALL_TABS); string s (m_pCommonData->getCrtName()); if (dlg.run()) { m_settings.saveMiscConfigSettings(m_pCommonData); m_settings.saveScanAtStartup(m_pCommonData->m_bScanAtStartup); m_settings.saveTransfConfig(m_transfConfig); // transformation updateUi(s); } } void MainFormDlgImpl::setTransfTooltip(int k) { QString s1 (tr("Apply custom transformation list #%1\n").arg(k + 1)); QString s2; for (int i = 0, n = cSize(m_pCommonData->getCustomTransf()[k]); i < n; ++i) { s2 += " "; s2 += m_pCommonData->getAllTransf()[m_pCommonData->getCustomTransf()[k][i]]->getVisibleActionName(); if (i < n - 1) { s2 += "\n"; } } if (s2.isEmpty()) { s2 = tr(" \n\n(you can edit the list in the Settings dialog)"); } // well; at startup it will get repopulated, so the only way for this to be empty is if it was configured like this in the current session m_vpTransfButtons[k]->setToolTip(s1 + s2); } void MainFormDlgImpl::on_m_pModeAllB_clicked() { m_pCommonData->setViewMode(CommonData::ALL, m_pCommonData->getCrtMp3Handler()); m_pFilesG->setFocus(); } void MainFormDlgImpl::on_m_pModeAlbumB_clicked() { m_pCommonData->setViewMode(CommonData::FOLDER, m_pCommonData->getCrtMp3Handler()); m_pFilesG->setFocus(); } void MainFormDlgImpl::on_m_pModeSongB_clicked() { m_pCommonData->setViewMode(CommonData::FILE, m_pCommonData->getCrtMp3Handler()); m_pFilesG->setFocus(); } void MainFormDlgImpl::on_m_pPrevB_clicked() { //LAST_STEP("MainFormDlgImpl::on_m_pPrevB_clicked"); //CB_ASSERT("345" == "ab"); //traceLastStep("tsterr", 0); char* p (0); *p = 11; //throw 1; //int x (2), y (3); CB_ASSERT(x >= y); m_pCommonData->previous(); //updateWidgets(); m_pFilesG->setFocus(); } void MainFormDlgImpl::on_m_pNextB_clicked() { m_pCommonData->next(); //updateWidgets(); m_pFilesG->setFocus(); } void MainFormDlgImpl::on_m_pTagEdtB_clicked() { showBackupWarn(); if (m_pCommonData->getViewHandlers().empty()) { showCritical(this, tr("Error"), tr("The file list is empty. You need to populate it before opening the tag editor.")); return; } m_pCommonData->setSongInCrtAlbum(); bool bDataSaved (false); TagEditorDlgImpl dlg (this, m_pCommonData, m_transfConfig, bDataSaved); //if (QDialog::Accepted == dlg.exec()) bool bToldAboutPatterns (m_pCommonData->m_bToldAboutPatterns); string strCrt (dlg.run()); if (!bToldAboutPatterns && m_pCommonData->m_bToldAboutPatterns) { m_settings.saveMiscConfigSettings(m_pCommonData); } updateUi(strCrt); // needed because the tag editor might have called the config and changed things; it would be nicer to send a signal when config changes, but IIRC in Qt 4.3.1 resizing things in a dialog that opened another one doesn't work very well; (see also TagEditorDlgImpl::on_m_pQueryDiscogsB_clicked()) if (m_pCommonData->useFastSave()) { if (bDataSaved) { fullReload(DONT_FORCE); } } else { emit tagEditorClosed(); } } void MainFormDlgImpl::on_m_pRenameFilesB_clicked() { bool bUseCrtView (0 != (Qt::ControlModifier & m_pModifRenameFilesB->getModifiers())); if (m_pCommonData->getViewHandlers().empty()) { showCritical(this, tr("Error"), tr("The file list is empty. You need to populate it before opening the file rename tool.")); return; } m_pCommonData->setSongInCrtAlbum(); FileRenamerDlgImpl dlg (this, m_pCommonData, bUseCrtView); //if (QDialog::Accepted == dlg.exec()) string strCrt (dlg.run()); updateUi(strCrt); } void MainFormDlgImpl::updateUi(const string& strCrt) // strCrt may be empty { saveIgnored(); { // custom transf for (int i = 0; i < CUSTOM_TRANSF_CNT; ++i) { saveCustomTransf(i); } setTransfTooltips(); } saveVisibleTransf(); saveExternalTools(); if (m_pCommonData->m_bShowExport || m_pCommonData->m_bShowSessions) { m_pOptBtn1W->show(); } else { m_pOptBtn1W->hide(); } if (m_pCommonData->m_bShowExport) { m_pExportB->show(); m_pExportB->parentWidget()->layout()->update(); // it is probably a Qt bug the fact that this is needed; should have been automatic; } else { m_pExportB->hide(); } if (m_pCommonData->m_bShowDebug) { m_pDebugB->show(); m_pDebugB->parentWidget()->layout()->update(); // it is probably a Qt bug the fact that this is needed; should have been automatic; } else { m_pDebugB->hide(); } if (m_pCommonData->m_bShowSessions) { m_pSessionsB->show(); m_pSessionsB->parentWidget()->layout()->update(); // it is probably a Qt bug the fact that this is needed; should have been automatic; } else { m_pSessionsB->hide(); } resizeIcons(); m_pCommonData->updateWidgets(strCrt); // needed for filters and unique notes } void MainFormDlgImpl::on_m_pDebugB_clicked() { DebugDlgImpl dlg (this, m_pCommonData); dlg.run(); m_settings.saveMiscConfigSettings(m_pCommonData); } void MainFormDlgImpl::on_m_pExportB_clicked() { //TranslatorHandler& hndl (TranslatorHandler::getGlobalTranslator()); //hndl.setTranslation(hndl.getTranslations().back()); //retranslateUi(this); ExportDlgImpl dlg (this); dlg.run(); //ttt2 perhaps use ModifInfoToolButton m_settings.saveMiscConfigSettings(m_pCommonData); } void MainFormDlgImpl::on_m_pAboutB_clicked() { AboutDlgImpl dlg (this); dlg.exec(); } class AddrRemover : public GenericRemover { /*override*/ bool matches(DataStream* p) const { return m_spToRemove.count(p) > 0; } public: /*override*/ const char* getActionName() const { return getClassName(); } /*override*/ const char* getDescription() const { return QT_TRANSLATE_NOOP("Transformation", "Removes selected streams."); } static const char* getClassName() { return QT_TRANSLATE_NOOP("Transformation", "Remove selected stream(s)"); } set m_spToRemove; }; //void MainFormDlgImpl::onStreamsGKeyPressed(int nKey) /*override*/ bool MainFormDlgImpl::eventFilter(QObject* pObj, QEvent* pEvent) { //qDebug("type %d", pEvent->type()); //if (pObj == m_pFilesG) qDebug("type %d", pEvent->type()); QKeyEvent* pKeyEvent (dynamic_cast(pEvent)); int nKey (0 == pKeyEvent ? 0 : pKeyEvent->key()); if (m_pStreamsG == pObj && 0 != pKeyEvent && Qt::Key_Delete == nKey && QEvent::ShortcutOverride == pKeyEvent->type()) { showBackupWarn(); //showSelWarn(); //qDebug("type %d", pKeyEvent->type()); QItemSelectionModel* pSelModel (m_pStreamsG->selectionModel()); QModelIndexList lstSel (pSelModel->selection().indexes()); set sStreams; for (QModelIndexList::iterator it = lstSel.begin(), end = lstSel.end(); it != end; ++it) { sStreams.insert(it->row()); } if (sStreams.empty()) { return true; } AddrRemover rmv; for (set::iterator it = sStreams.begin(), end = sStreams.end(); it != end; ++it) { rmv.m_spToRemove.insert(m_pCommonData->getCrtStreams()[*it]); } vector v; v.push_back(&rmv); transform(v, CURRENT); return true; } else if (m_pFilesG == pObj && 0 != pKeyEvent && Qt::Key_Delete == nKey && QEvent::ShortcutOverride == pKeyEvent->type()) { const deque& vpSelHandlers (m_pCommonData->getSelHandlers()); if (askConfirm(vpSelHandlers, tr("Delete %1?"))) { for (int i = 0; i < cSize(vpSelHandlers); ++i) { try { deleteFile(vpSelHandlers[i]->getName()); } catch (const CannotDeleteFile&) { showCritical(this, tr("Error"), tr("Cannot delete file %1").arg(convStr(vpSelHandlers[i]->getName()))); break; } } reload(IGNORE_SEL, DONT_FORCE); } return true; } else if (pObj == m_pFilesG) { //qDebug("type %d", pEvent->type()); QContextMenuEvent* pCtx (dynamic_cast(pEvent)); if (0 != pCtx) { m_nGlobalX = pCtx->globalX(); m_nGlobalY = pCtx->globalY(); QTimer::singleShot(1, this, SLOT(onMainGridRightClick())); } } return QDialog::eventFilter(pObj, pEvent); } void MainFormDlgImpl::on_m_pViewFileInfoB_clicked() { m_pLowerHalfLayout->setCurrentWidget(m_pFileInfoTab); m_pViewFileInfoB->setChecked(true); //ttt2 use autoExclusive instead m_pViewAllNotesB->setChecked(false); m_pViewTagDetailsB->setChecked(false); m_pNotesG->resizeRowsToContents(); m_pStreamsG->resizeRowsToContents(); } void MainFormDlgImpl::on_m_pViewAllNotesB_clicked() { m_pLowerHalfLayout->setCurrentWidget(m_pAllNotesTab); m_pViewFileInfoB->setChecked(false); m_pViewAllNotesB->setChecked(true); m_pViewTagDetailsB->setChecked(false); m_pUniqueNotesG->resizeRowsToContents(); } void MainFormDlgImpl::on_m_pViewTagDetailsB_clicked() { m_pLowerHalfLayout->setCurrentWidget(m_pTagDetailsTab); m_pViewFileInfoB->setChecked(false); m_pViewAllNotesB->setChecked(false); m_pViewTagDetailsB->setChecked(true); onCrtFileChanged(); // to populate m_pTagDetailsW } void MainFormDlgImpl::resizeIcons() { vector v; v.push_back(m_pScanB); v.push_back(m_pSessionsB); v.push_back(m_pNoteFilterB); v.push_back(m_pDirFilterB); v.push_back(m_pModeAllB); v.push_back(m_pModeAlbumB); v.push_back(m_pModeSongB); v.push_back(m_pPrevB); v.push_back(m_pNextB); v.push_back(m_pTransformB); v.push_back(m_pCustomTransform1B); v.push_back(m_pCustomTransform2B); v.push_back(m_pCustomTransform3B); v.push_back(m_pCustomTransform4B); v.push_back(m_pTagEdtB); v.push_back(m_pNormalizeB); v.push_back(m_pRenameFilesB); v.push_back(m_pReloadB); v.push_back(m_pConfigB); v.push_back(m_pDebugB); v.push_back(m_pAboutB); v.push_back(m_pExportB); int k (m_pCommonData->m_nMainWndIconSize); for (int i = 0, n = cSize(v); i < n; ++i) { QToolButton* p (v[i]); p->setMaximumSize(k, k); p->setMinimumSize(k, k); p->setIconSize(QSize(k - 4, k - 4)); } } void MainFormDlgImpl::on_m_pSessionsB_clicked() { closeEvent(0); accept(); } void MainFormDlgImpl::checkForNewVersion() // returns immediately; when the request completes it will send a signal { const int MIN_INTERVAL_BETWEEN_CHECKS (24); // hours if ("yes" != m_pCommonData->m_strCheckForNewVersions && "no" != m_pCommonData->m_strCheckForNewVersions) { int nRes (HtmlMsg::msg(this, 0, 0, 0, HtmlMsg::DEFAULT, tr("Info"), "

" + tr("MP3 Diags can check at startup if a new version of the program has been released. Here's how this is supposed to work:") + "

    " "
  • " + tr("The check is done in the background, when the program starts, so there should be no performance penalties") + "
  • " "
  • " + tr("A notification message is displayed only if there's a new version available") + "
  • " "
  • " + tr("The update is manual. You are told that there is a new version and are offered links to see what's new, but nothing gets downloaded and / or installed automatically") + "
  • " "
  • " + tr("There is no System Tray process checking periodically for updates") + "
  • " "
  • " + tr("You can turn the notifications on and off from the configuration dialog") + "
  • " "
  • " + tr("If you restart the program within a day after a check, no new check is done") + "
  • " "
" "

" /*" "

QQQ

" "

QQQ

" "

QQQ

" */ , 750, 300, tr("Disable checking for new versions"), tr("Enable checking for new versions"))); qDebug("ret %d", nRes); m_pCommonData->m_strCheckForNewVersions = (1 == nRes ? "yes" : "no"); m_settings.saveMiscConfigSettings(m_pCommonData); } if ("yes" != m_pCommonData->m_strCheckForNewVersions) { return; } QDateTime t1 (QDateTime::currentDateTime()); t1 = t1.addSecs(-MIN_INTERVAL_BETWEEN_CHECKS*3600); //qDebug("ini: %s, crt: %s", m_pCommonData->m_timeLastNewVerCheck.toString().toUtf8().constData(), t1.toString().toUtf8().constData()); if (t1 < m_pCommonData->m_timeLastNewVerCheck) { return; } m_pCommonData->m_timeLastNewVerCheck = QDateTime(QDateTime::currentDateTime()); m_pQHttp = new QHttp (this); connect(m_pQHttp, SIGNAL(requestFinished(int, bool)), this, SLOT(onNewVersionQueryFinished(int, bool))); //connect(m_pQHttp, SIGNAL(responseHeaderReceived(const QHttpResponseHeader &)), this, SLOT(readResponseHeader(const QHttpResponseHeader&))); m_pQHttp->setHost("mp3diags.sourceforge.net"); //http://mp3diags.sourceforge.net/010_getting_the_program.html QHttpRequestHeader header ("GET", QString(getWebBranch()) + "/version.txt"); header.setValue("Host", "mp3diags.sourceforge.net"); //QHttpRequestHeader header ("GET", "/mciobanu/mp3diags/010_getting_the_program.html"); header.setValue("Host", "web.clicknet.ro"); m_pQHttp->request(header); } //mp3diags.sourceforge.net/010_getting_the_program.html //web.clicknet.ro/mciobanu/mp3diags/010_getting_the_program.html void MainFormDlgImpl::readResponseHeader(const QHttpResponseHeader& h) { qDebug("status %d", h.statusCode()); } void MainFormDlgImpl::onNewVersionQueryFinished(int /*nId*/, bool bError) { if (bError) { //ttt2 log something, tell user after a while qDebug("HTTP error"); return; } qint64 nAv (m_pQHttp->bytesAvailable()); if (0 == nAv) { // !!! JUST return; empty responses come for no identifiable requests and they should be just ignored return; } QByteArray b (m_pQHttp->readAll()); CB_ASSERT (b.size() == nAv); qDebug("ver: %s", b.constData()); m_qstrNewVer = b; m_qstrNewVer = m_qstrNewVer.trimmed(); if (m_qstrNewVer.size() > 50) { //ttt2 return; // most likely some error message } if (getAppVer() == m_qstrNewVer) { return; } #if 0 //const int WAIT (15); //crashes //const int WAIT (12); // doesn't crash const int WAIT (14); //crashes #ifndef WIN32 qDebug("wait %d seconds", WAIT); sleep(WAIT); showCritical(this, "resume", "resume resume resume resume resume"); // crashes //ttt2 see if this crash affects discogs dwnld //??? why doesn't crash if no message is shown? #else //Sleep(WAIT*1000); // Qt 4.5 doesn't seem to crash #endif #endif QTimer::singleShot(1, this, SLOT(onNewVersionQueryFinished2())); } void MainFormDlgImpl::onNewVersionQueryFinished2() { //QMessageBox::critical(this, "wait", "bb urv huervhuervhuerv erve rvhu ervervhuer vher vrhe rv evr ev erv erv"); //qDebug("ver: %s", b.constData()); //QMessageBox::critical(this, "resume", "bb urv huervhuervhuerv erve rvhu ervervhuer vher vrhe rv evr ev erv erv"); if (m_pCommonData->m_strDontTellAboutVer == convStr(m_qstrNewVer)) { return; } QString qstrMsg; qstrMsg = "

" + tr("Version %1 has been published. You are running %2. You can see what's new in %3. A more technical list with changes can be seen in %4.") .arg(m_qstrNewVer) .arg(getAppVer()) .arg( tr("the %1MP3 Diags blog%2", "arguments are HTML elements") .arg("") .arg("")) .arg( tr("the %1change log%2", "arguments are HTML elements") .arg("") .arg("")) + "

"; #ifndef WIN32 qstrMsg += "

" + tr("This notification is about the availability of the source code. Binaries may or may not be available at this time, depending on your particular platform.") + "

"; #else #endif qstrMsg += "

" + tr("You should review the changes and decide if you want to upgrade or not.") + "

"; qstrMsg += "

" + tr("Note: if you want to upgrade, you should %1close MP3 Diags%2 first.", "arguments are HTML elements").arg("").arg("") + "

"; qstrMsg += "

" + tr("Choose what do you want to do:") + "

"; /*"

QQQ

"*/ int nRes (HtmlMsg::msg(this, 0, 0, 0, HtmlMsg::VERT_BUTTONS, tr("Info"), qstrMsg , 600, 400, tr("Just close this message"), tr("Don't tell me about version %1 again").arg(m_qstrNewVer), tr("Disable checking for new versions"))); //qDebug("ret %d", nRes); switch (nRes) { case 0: m_pCommonData->m_strDontTellAboutVer.clear(); break; case 1: m_pCommonData->m_strDontTellAboutVer = convStr(m_qstrNewVer); break; case 2: m_pCommonData->m_strDontTellAboutVer.clear(); m_pCommonData->m_strCheckForNewVersions = "no"; break; default: CB_ASSERT (false); } m_settings.saveMiscConfigSettings(m_pCommonData); } //============================================================================================================================= //============================================================================================================================= //============================================================================================================================= namespace { struct CmpTransfAndName { const char* m_szName; CmpTransfAndName(const char* szName) : m_szName(szName) {} bool operator()(const Transformation* p) const { return 0 == strcmp(p->getActionName(), m_szName); } }; class FixedAddrRemover : public GenericRemover { Q_DECLARE_TR_FUNCTIONS(FixedAddrRemover) /*override*/ bool matches(DataStream* p) const { return m_pStream == p; // !!! normally there might be an issue with comparing pointers, in case something else gets allocated at the same address as a deleted object; however, in this case the stream passed on setStream() doesn't get destroyed } streampos m_pos; QString m_qstrAction; const DataStream* m_pStream; public: FixedAddrRemover() : m_pos(-1), m_pStream(0) {} void setStream(const DataStream* p) { m_pStream = p; m_pos = p->getPos(); m_qstrAction = tr("Remove stream %1 at address 0x%2").arg(p->getTranslatedDisplayName()).arg(m_pos, 0, 16); } /*override*/ const char* getActionName() const { return getClassName(); } /*override*/ QString getVisibleActionName() const { return m_qstrAction; } /*override*/ const char* getDescription() const { return QT_TRANSLATE_NOOP("Transformation", "Removes specified stream."); } static const char* getClassName() { return QT_TRANSLATE_NOOP("Transformation", "Remove specified stream"); } }; } vector MainFormDlgImpl::getFixes(const Note* pNote, const Mp3Handler* pHndl) const // what might fix a note { //qDebug("strm %s", pStream ? pStream->getDisplayName() : ""); //CB_ASSERT (0 == pStream || (pNote->getPos() >= pStream->getPos() && pNote->getPos() < pStream->getPos() + pStream->getSize())); //CB_ASSERT (0 == pHndl || -1 != pNote->getPos()); static map > s_mFixes; static bool s_bInitialized (false); const vector& vpAllTransf (m_pCommonData->getAllTransf()); if (!s_bInitialized) { s_bInitialized = true; vector::const_iterator it; #define ADD_FIX(NOTE, TRANSF) \ it = find_if(vpAllTransf.begin(), vpAllTransf.end(), CmpTransfAndName(TRANSF::getClassName())); \ CB_ASSERT (vpAllTransf.end() != it); \ s_mFixes[Notes::NOTE().getNoteId()].push_back(*it); ADD_FIX(twoAudio, InnerNonAudioRemover); ADD_FIX(twoAudio, SingleBitRepairer); ADD_FIX(incompleteFrameInAudio, TruncatedMpegDataStreamRemover); ADD_FIX(incompleteFrameInAudio, TruncatedAudioPadder); //ADD_FIX(twoLame, VbrRepairer); //ADD_FIX(twoLame, VbrRebuilder); ADD_FIX(xingAddedByMp3Fixer, VbrRepairer); ADD_FIX(xingFrameCountMismatch, VbrRepairer); ADD_FIX(xingFrameCountMismatch, MismatchedXingRemover); //ADD_FIX(twoXing, VbrRepairer); //ttt2 ADD_FIX(xingNotBeforeAudio, VbrRepairer); ADD_FIX(incompatXing, VbrRebuilder); ADD_FIX(missingXing, VbrRebuilder); ADD_FIX(vbriFound, VbrRepairer); ADD_FIX(foundVbriAndXing, VbrRepairer); ADD_FIX(id3v2FrameTooShort, Id3V2Rescuer); //ADD_FIX(id3v2FrameTooShort, Id3V2Cleaner); ADD_FIX(id3v2InvalidName, Id3V2Rescuer); //ADD_FIX(id3v2InvalidName, Id3V2Cleaner); ADD_FIX(id3v2TextError, Id3V2Rescuer); //ADD_FIX(id3v2TextError, Id3V2Cleaner); ADD_FIX(id3v2HasLatin1NonAscii, Id3V2UnicodeTransformer); ADD_FIX(id3v2EmptyTcon, Id3V2Rescuer); //ADD_FIX(id3v2EmptyTcon, Id3V2Cleaner); ADD_FIX(id3v2MultipleFramesWithSameName, Id3V2Rescuer); ADD_FIX(id3v2MultipleFramesWithSameName, Id3V2Cleaner); ADD_FIX(id3v2PaddingTooLarge, Id3V2Compactor); ADD_FIX(id3v2UnsuppVer, UnsupportedId3V2Remover); ADD_FIX(id3v2UnsuppFlag, UnsupportedDataStreamRemover); ADD_FIX(id3v2UnsuppFlags1, UnsupportedDataStreamRemover); ADD_FIX(id3v2UnsuppFlags2, UnsupportedDataStreamRemover); ADD_FIX(id3v2CouldntLoadPic, Id3V2Rescuer); //ADD_FIX(id3v2CouldntLoadPic, Id3V2Cleaner); ADD_FIX(id3v2NotCoverPicture, SmallerImageRemover); ADD_FIX(id3v2ErrorLoadingApic, Id3V2Rescuer); //ADD_FIX(id3v2ErrorLoadingApic, Id3V2Cleaner); ADD_FIX(id3v2ErrorLoadingApicTooShort, Id3V2Rescuer); //ADD_FIX(id3v2ErrorLoadingApicTooShort, Id3V2Cleaner); ADD_FIX(id3v2DuplicatePic, Id3V2Rescuer); ADD_FIX(id3v2DuplicatePic, Id3V2Cleaner); ADD_FIX(id3v2DuplicatePic, SmallerImageRemover); ADD_FIX(id3v2MultipleApic, Id3V2Rescuer); ADD_FIX(id3v2MultipleApic, Id3V2Cleaner); ADD_FIX(id3v2MultipleApic, SmallerImageRemover); ADD_FIX(id3v2UnsupApicTextEnc, Id3V2Rescuer); ADD_FIX(id3v2UnsupApicTextEnc, Id3V2Cleaner); ADD_FIX(id3v2LinkInApic, Id3V2Rescuer); ADD_FIX(id3v2LinkInApic, Id3V2Cleaner); ADD_FIX(twoId3V230, MultipleId3StreamRemover); ADD_FIX(bothId3V230_V240, MultipleId3StreamRemover); ADD_FIX(id3v230UsesUtf8, Id3V2Rescuer); //ADD_FIX(id3v230UsesUtf8, Id3V2Cleaner); ADD_FIX(twoId3V240, MultipleId3StreamRemover); ADD_FIX(id3v240IncorrectSynch, Id3V2Rescuer); ADD_FIX(id3v240IncorrectSynch, Id3V2Cleaner); ADD_FIX(id3v240DeprTyerAndTdrc, Id3V2Rescuer); ADD_FIX(id3v240DeprTyerAndTdrc, Id3V2Cleaner); ADD_FIX(id3v240DeprTyer, Id3V2Rescuer); ADD_FIX(id3v240DeprTyer, Id3V2Cleaner); ADD_FIX(id3v240DeprTdatAndTdrc, Id3V2Rescuer); ADD_FIX(id3v240DeprTdatAndTdrc, Id3V2Cleaner); ADD_FIX(id3v240DeprTdat, Id3V2Rescuer); ADD_FIX(id3v240DeprTdat, Id3V2Cleaner); ADD_FIX(twoId3V1, MultipleId3StreamRemover); ADD_FIX(brokenAtTheEnd, BrokenDataStreamRemover); ADD_FIX(brokenInTheMiddle, BrokenDataStreamRemover); ADD_FIX(truncAudioWithWholeFile, TruncatedMpegDataStreamRemover); ADD_FIX(truncAudioWithWholeFile, TruncatedAudioPadder); ADD_FIX(truncAudio, TruncatedMpegDataStreamRemover); ADD_FIX(truncAudio, TruncatedAudioPadder); ADD_FIX(unknownAtTheEnd, UnknownDataStreamRemover); ADD_FIX(unknownInTheMiddle, UnknownDataStreamRemover); ADD_FIX(foundNull, NullStreamRemover); } vector vpTransf (s_mFixes[pNote->getNoteId()]); /* if (0 != pStream) { if (pNote->getNoteId() == Notes::audioTooShort().getNoteId()) { if (pStream->getDisplayName() == TruncatedMpegDataStream::getClassDisplayName()) { vector::const_iterator it (find_if(vpAllTransf.begin(), vpAllTransf.end(), CmpTransfAndName(TruncatedMpegDataStreamRemover::getClassName()))); CB_ASSERT (vpAllTransf.end() != it); vpTransf.push_back(*it); } } }*/ #define ADD_CUSTOM_FIX(NOTE, STREAM, TRANSF) \ if (pNote->getNoteId() == Notes::NOTE().getNoteId()) \ { \ if (pCrtStream->getDisplayName() == STREAM::getClassDisplayName()) \ { \ vector::const_iterator it (find_if(vpAllTransf.begin(), vpAllTransf.end(), CmpTransfAndName(TRANSF::getClassName()))); \ CB_ASSERT (vpAllTransf.end() != it); \ Transformation* pTransf (*it); \ if (0 == spTransf.count(pTransf)) \ { \ vpTransf.push_back(pTransf); \ spTransf.insert(pTransf); \ } \ } \ } //ttt2 None of the ADD_CUSTOM_FIX fixes is shown for header (e.g. SingleBitRepairer is shown for validFrameDiffVer only when clicking on circle, not when clicking on header), maybe we should drop the stream test; OTOH the case of audioTooShort shows that it matters what stream the error is occuring on; so maybe drop the stream check only for some ... // workaround: see what's available for single song, then use the menu for all; //ttt2 other example: mismatched xing fixable by SingleBitRepairer to other stream; probably document that all the notes should be looked at; // or: add all transforms that in some context might fix a note // or: extend ADD_CUSTOM_FIX to look at the other notes, similarly to how it looks at the stream it's in; add some transform if some other note is present; (note: use a set to not have duplicates entered via different rules) // perhaps unknown size 16 between xing and audio -> repair vbr, but seems too specific // unknown between audio and audio -> restore flipped //ttt2 perhaps just this: if there's a chance that by using transf T the note N will disappear, show it; well, this is again context-dependant if (0 != pHndl) { // for each matching note, see if additional fixes exist that take into account the stream the note is in and (in the future) other streams, their sizes, ... const vector& vpHndlNotes (pHndl->getNotes().getList()); const vector& vpStreams (pHndl->getStreams()); set spTransf (vpTransf.begin(), vpTransf.end()); static vector s_vFixedAddrRemovers; s_vFixedAddrRemovers.clear(); set spRemovableStreams; for (int i = 0; i < cSize(vpHndlNotes); ++i) { if (-1 == vpHndlNotes[i]->getNoteId()) { goto e1; } // takes care of trace notes const Note* pCrtNote (vpHndlNotes[i]); if (pCrtNote->getNoteId() == pNote->getNoteId() && -1 != pCrtNote->getPos()) { for (int i = 0, n = cSize(vpStreams); i < n; ++i) { //qDebug("s %s", v[i]->getDisplayName()); if (n - 1 == i || pCrtNote->getPos() < vpStreams[i + 1]->getPos()) //ttt2 lower_bound { const DataStream* pCrtStream (vpStreams[i]); CB_ASSERT (pCrtNote->getPos() >= pCrtStream->getPos() && pCrtNote->getPos() < pCrtStream->getPos() + pCrtStream->getSize()); ADD_CUSTOM_FIX(validFrameDiffVer, UnknownDataStream, SingleBitRepairer); ADD_CUSTOM_FIX(validFrameDiffLayer, UnknownDataStream, SingleBitRepairer); ADD_CUSTOM_FIX(validFrameDiffMode, UnknownDataStream, SingleBitRepairer); ADD_CUSTOM_FIX(validFrameDiffFreq, UnknownDataStream, SingleBitRepairer); ADD_CUSTOM_FIX(validFrameDiffCrc, UnknownDataStream, SingleBitRepairer); ADD_CUSTOM_FIX(audioTooShort, TruncatedMpegDataStream, TruncatedMpegDataStreamRemover); ADD_CUSTOM_FIX(audioTooShort, TruncatedMpegDataStream, TruncatedAudioPadder); ADD_CUSTOM_FIX(audioTooShort, UnknownDataStream, UnknownDataStreamRemover); ADD_CUSTOM_FIX(audioTooShort, UnknownDataStream, SingleBitRepairer); // ADD_CUSTOM_FIX(brokenInTheMiddle, ??? , Id3V2Rescuer); //ttt2 see about this if (pCrtNote->allowErase() && 0 == spRemovableStreams.count(pCrtStream)) { spRemovableStreams.insert(pCrtStream); s_vFixedAddrRemovers.push_back(FixedAddrRemover()); s_vFixedAddrRemovers.back().setStream(pCrtStream); } break; } } } } e1:; for (int i = 0; i < cSize(s_vFixedAddrRemovers); ++i) { vpTransf.push_back(&s_vFixedAddrRemovers[i]); } } return vpTransf; } int getHeaderDrawOffset(); void MainFormDlgImpl::onMainGridRightClick() { QPoint coords (m_pFilesG->mapFromGlobal(QPoint(m_nGlobalX, m_nGlobalY))); int nCol (m_pFilesG->columnAt(coords.x() - m_pFilesG->verticalHeader()->width())); if (nCol >= 1) { fixCurrentNote(coords); return; } // header or file name if (0 == nCol && coords.y() >= m_pFilesG->horizontalHeader()->height()) { showExternalTools(); } } void MainFormDlgImpl::fixCurrentNote(const QPoint& coords) { LAST_STEP("MainFormDlgImpl::onFixCurrentNote()"); //QPoint coords (m_pFilesG->mapFromGlobal(QPoint(m_nGlobalX, m_nGlobalY))); //int nHorHdrHght (); //if (coords.x() < nVertHdrWdth) { return; } int nCol (m_pFilesG->columnAt(coords.x() - m_pFilesG->verticalHeader()->width())); //if (nCol < 1) { return; } // header or file name CB_ASSERT(nCol >= 1); if (coords.y() < m_pFilesG->horizontalHeader()->height()) { int x (coords.x()); x -= 2; // hard-coded x += getHeaderDrawOffset() / 2; //ttt2 this "/2" doesn't make sense but it works best //qDebug("offs %d", getHeaderDrawOffset()); nCol = m_pFilesG->columnAt(x - m_pFilesG->verticalHeader()->width()); if (nCol != m_pFilesG->columnAt(x - m_pFilesG->verticalHeader()->width() - 3) || nCol != m_pFilesG->columnAt(x - m_pFilesG->verticalHeader()->width() + 3)) { // too close to adjacent cells; return to avoid confusions return; } fixCurrentNoteAllFiles(nCol - 1); } else { fixCurrentNoteOneFile(); } //qDebug("r %d, c %d", m_pFilesG->rowAt(coords.y() - nHorHdrHght), ); } void MainFormDlgImpl::fixCurrentNoteOneFile() { QModelIndex ndx (m_pFilesG->currentIndex()); if (!ndx.isValid() || 0 == ndx.column()) { return; } //qDebug("fixCurrentNoteOneFile %d %d", nGlobalX, nGlobalY); const Mp3Handler* pHndl (m_pCommonData->getCrtMp3Handler()); const vector& vpNotes (m_pCommonData->getUniqueNotes().getFltVec()); const Note* pNote (vpNotes.at(ndx.column() - 1)); //qDebug("fixing note '%s' for file '%s'", pNote->getDescription(), pHndl->getName().c_str()); //int nCnt (0); vector vpTransf (getFixes(pNote, pHndl)); if (vpTransf.empty()) { return; } showFixes(vpTransf, CURRENT); } void MainFormDlgImpl::fixCurrentNoteAllFiles(int nCol) { const vector& vpNotes (m_pCommonData->getUniqueNotes().getFltVec()); const Note* pNote (vpNotes.at(nCol)); vector vpTransf (getFixes(pNote, 0)); if (vpTransf.empty()) { return; } showFixes(vpTransf, ALL); } void MainFormDlgImpl::showFixes(vector& vpTransf, Subset eSubset) { ModifInfoMenu menu; vector vpAct; for (int i = 0, n = cSize(vpTransf); i < n; ++i) { Transformation* pTransf (vpTransf[i]); QAction* pAct (new QAction(pTransf->getVisibleActionName(), &menu)); pAct->setToolTip(makeMultiline(Transformation::tr(pTransf->getDescription()))); //connect(pAct, SIGNAL(triggered()), this, SLOT(onExecTransform(i))); // !!! Qt doesn't seem to support parameter binding menu.addAction(pAct); vpAct.push_back(pAct); } connect(&menu, SIGNAL(hovered(QAction*)), this, SLOT(onMenuHovered(QAction*))); //QAction* p (menu.exec(m_pTransformB->mapToGlobal(QPoint(0, m_pTransformB->height())))); QAction* p (menu.exec(QPoint(m_nGlobalX, m_nGlobalY + 10))); if (0 != p) { int nIndex (std::find(vpAct.begin(), vpAct.end(), p) - vpAct.begin()); vector v; v.push_back(vpTransf.at(nIndex)); if (ALL == eSubset) { eSubset = 0 == (Qt::ShiftModifier & menu.getModifiers()) ? ALL : SELECTED; } transform(v, eSubset); } } void MainFormDlgImpl::showExternalTools() { ModifInfoMenu menu; vector vpAct; QAction* pAct (new QAction(tr("Open containing folder ..."), &menu)); menu.addAction(pAct); vpAct.push_back(pAct); if (!m_pCommonData->m_vExternalToolInfos.empty()) { menu.addSeparator(); } for (int i = 0; i < cSize(m_pCommonData->m_vExternalToolInfos); ++i) { QAction* pAct (new QAction(convStr(m_pCommonData->m_vExternalToolInfos[i].m_strName), &menu)); menu.addAction(pAct); vpAct.push_back(pAct); } QAction* p (menu.exec(QPoint(m_nGlobalX, m_nGlobalY + 10))); if (0 != p) { int nIndex (std::find(vpAct.begin(), vpAct.end(), p) - vpAct.begin()); //qDebug("pressed %d", nIndex); if (0 == nIndex) { CB_ASSERT (0 != m_pCommonData->getCrtMp3Handler()); QString qstrDir (convStr(m_pCommonData->getCrtMp3Handler()->getDir())); #if defined(WIN32) || defined(__OS2__) //qstrDir = QDir::toNativeSeparators(qstrDir); QDesktopServices::openUrl(QUrl("file:///" + qstrDir, QUrl::TolerantMode)); #else QDesktopServices::openUrl(QUrl("file://" + qstrDir, QUrl::TolerantMode)); #endif } else { // ttt1 copied from void MainFormDlgImpl::transform(std::vector& vpTransf, Subset eSubset) const ExternalToolInfo& info (m_pCommonData->m_vExternalToolInfos[nIndex - 1]); Subset eSubset (0 != (Qt::ControlModifier & menu.getModifiers()) ? ALL : SELECTED); //ttt0 it's confusing that the external tools apply to selected files by default (you have to press CTRL to get all) while transformations apply to all files by default (you have to right-click to process selected ones; OTOH it seems to make sense that the default for transforms to be all the files while the default for external tools to be a single file //deque vpCrt; const deque* pvpHandlers; switch (eSubset) { case SELECTED: pvpHandlers = &m_pCommonData->getSelHandlers(); break; case ALL: pvpHandlers = &m_pCommonData->getViewHandlers(); break; //case CURRENT: vpCrt.push_back(m_pCommonData->getCrtMp3Handler()); pvpHandlers = &vpCrt; break; default: CB_ASSERT (false); } //qDebug("ctrl=%d", eSubset); QString qstrAction (tr("Run \"%1\" on %2?").arg(convStr(info.m_strName))); qstrAction.replace("%2", "%1"); if (info.m_bConfirmLaunch && !askConfirm(*pvpHandlers, qstrAction)) { return; } QStringList lFiles; for (int i = 0; i < cSize(*pvpHandlers); ++i) { lFiles << convStr((*pvpHandlers)[i]->getName()); } switch (info.m_eLaunchOption) { case ExternalToolInfo::WAIT_AND_KEEP_WINDOW_OPEN: case ExternalToolInfo::WAIT_THEN_CLOSE_WINDOW: { ExternalToolDlgImpl dlg (this, info.m_eLaunchOption == ExternalToolInfo::WAIT_AND_KEEP_WINDOW_OPEN, m_settings, m_pCommonData, info.m_strName, "299_ext_tools.html"); dlg.run(convStr(info.m_strCommand), lFiles); } break; case ExternalToolInfo::DONT_WAIT: { QString qstrProg; QStringList lArgs; ExternalToolDlgImpl::prepareArgs(convStr(info.m_strCommand), lFiles, qstrProg, lArgs); if (!QProcess::startDetached(qstrProg, lArgs)) { showCritical(this, tr("Error"), tr("Cannot start process. Check that the executable name and the parameters are correct.")); //ttt0 add process name } } break; } //l << "p1" << "p 2" << "p:3'" << "p\"4" << "p5"; //QProcess proc (this); //proc.startDetached("konqueror"); //proc.start("konqueror"); PausableThread::sleep(10); //proc.start("ParamsGui", l); PausableThread::sleep(10); //proc.startDetached("ParamsGui", l); //proc.kill(); } } //add external tools } bool MainFormDlgImpl::askConfirm(const deque& vpHandlers, const QString& qstrAction) { if (vpHandlers.empty()) { return false; } QString qstrList; if (vpHandlers.size() == 1) { qstrList = convStr(vpHandlers[0]->getShortName()); } else if (vpHandlers.size() == 2) { qstrList = tr("%1 and %2").arg(convStr(vpHandlers[0]->getShortName())).arg(convStr(vpHandlers[1]->getShortName())); } else if (vpHandlers.size() == 3) { qstrList = tr("%1, %2 and %3").arg(convStr(vpHandlers[0]->getShortName())).arg(convStr(vpHandlers[1]->getShortName())).arg(convStr(vpHandlers[2]->getShortName())); } else { qstrList = tr("%1, %2 and %3 other files").arg(convStr(vpHandlers[0]->getShortName())).arg(convStr(vpHandlers[1]->getShortName())).arg((int)vpHandlers.size() - 2); } return showMessage(this, QMessageBox::Question, 1, 1, tr("Confirm"), qstrAction.arg(qstrList), tr("&Yes"), tr("&No")) == 0; } //============================================================================================================================= //============================================================================================================================= //============================================================================================================================= void MainFormDlgImpl::testSlot() { static int c (0); qDebug("%d", c++); } //============================================================================================================================= //============================================================================================================================= //============================================================================================================================= struct TestThread01 : public PausableThread { /*override*/ void run() { CompleteNotif notif(this); for (int i = 0; i < 7; ++i) { if (isAborted()) return; checkPause(); QString s; s.sprintf("step %d", i); StrList l; l.push_back(s); emit stepChanged(l, -1); //qDebug("step %d", i); sleep(1); } notif.setSuccess(true); } }; //============================================================================================================================= //============================================================================================================================= //============================================================================================================================= /* ttt2 To look at: QDir QDirModel QFontComboBox QStyle ; ./myapplication -style motif */ //ttt2 ? option to discard errors in unknown streams: probably not; at any rate, that's the only chance to learn that there was an error there (instead of a really unknown stream) // ttt2 make sure that everything that doesn't take a "parent" on the constructor gets deleted; /* Development machine: -style=Plastique -style=Cleanlooks -style=CDE -style=Motif -style=Oxygen ?? Windows, Windows Vista, Plastik */ //ttt2 build: not sure is possible, but have the .pro file include another file, which is generated by a "pre-config" script, which figures out if the lib is installed //ttt2 perhaps a "consider unknown" function for streams; then it would be possible for a truncated id3v1 tag that seems ok to be marked as unknown, thus allowing the following tag's start to be detected (see "c09 Mark Knopfler - The Long Road.mp3") //ttt2 handle symbolic links to ancestors //ttt2 fix on right-click for notes table //ttt0 look at /d/test_mp3/1/tmp4/tmp2/unsupported/bad-decoding // favicon.ico : not sure how to create it; currently it's done with GIMP, with 8bpp/1 bit alpha, not compressed; however, konqueror doesn't show it when using a local page //ttt0 run CppCheck //ttt1 warn when folders are missing (perhaps as network drives are not mounted, usb sticks not inserted, ...), to avoid erasing the database //ttt0 link from stable to unstable in doc. perhaps also have a notification popup /* ttt0: http://doc.qt.nokia.com/4.7-snapshot/internationalization.html Use QKeySequence() for Accelerator Values Accelerator values such as Ctrl+Q or Alt+F need to be translated too. If you hardcode Qt::CTRL + Qt::Key_Q for "quit" in your application, translators won't be able to override it. The correct idiom is exitAct = new QAction(tr("E&xit"), this); exitAct->setShortcuts(QKeySequence::Quit); Typically, your application's main() function will look like this: int main(int argc, char *argv[]) { QApplication app(argc, argv); QTranslator qtTranslator; qtTranslator.load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath)); app.installTranslator(&qtTranslator); QTranslator myappTranslator; myappTranslator.load("myapp_" + QLocale::system().name()); app.installTranslator(&myappTranslator); ... return app.exec(); } Note the use of QLibraryInfo::location() to locate the Qt translations. Developers should request the path to the translations at run-time by passing QLibraryInfo::TranslationsPath to this function instead of using the QTDIR environment variable in their applications. */ //ttt0 doc & screenshots for translation //ttt0 something to add to SF in unstable, to get the date to change //ttt0 german translates "Previous [Ctrl+P]" as "Vorherige [Strg+V]" //ttt0 copy ID3V2 to ID3V1 //ttt0 update references based on traffic volume //ttt0 delete LAME for CBR - https://sourceforge.net/apps/mantisbt/mp3diags/view.php?id=117 //ttt0 don't scan backup dir if it's inside the source //ttt0 compute bitrate in VBR headers //ttt0 see why the bitrate computed manually based on VBR data doesn't match exactly the one computed for the audio (see mail sent on 2012.10.14) //ttt1 mail on 2012.12.16 - It would be nice if MP3 Diags had more explicit support for multiple artists, too. For example, in WMP 11, if you select an artist field that contains multiple artists and try to edit the artist, it will only select one of the artists - clearly it knows that there are separate individual artists. //ttt2 ID3V2 frame TBPM should be numeric - see: 20 Rimsky-Korsakov - Flight of the Bumblebee from Tsar Sultan.mp3 //ttt0 https://sourceforge.net/projects/mp3diags/forums/forum/947206/topic/6884554 - use http://coverartarchive.org/ //ttt2 when processing a file (e.g. deleting a stream) the program will tend to scroll the file view so that file is the last; it should stay where it was. //ttt1 switch to album mode and move between folders that need/don't need scrollbar; the horizontal resizing doesn't work well, so many times there is either a scrollbar or empty space //ttt0 screenshots for language selection MP3Diags-1.2.02/src/Translation.cpp0000644000175000001440000001526311743620712015752 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include "Translation.h" #include "Helpers.h" #include "Version.h" using namespace std; using namespace Version; /* /usr/bin/MP3Diags-unstable /usr/share/mp3diags-unstable/translations /usr/local/bin/MP3Diags-unstable /usr/local/share/mp3diags-unstable/translations */ TranslatorHandler::TranslatorHandler() { m_vstrTranslations.push_back("mp3diags_en_US.qm"); m_vstrLongTranslations.push_back("mp3diags_en_US.qm"); addTranslations(convStr(QCoreApplication::instance()->applicationDirPath())); if (cSize(m_vstrTranslations) == 1) { addTranslations(convStr(QCoreApplication::instance()->applicationDirPath() + "/../share/" + getTranslationPackageName() + "/translations")); } //ttt0 perhaps also look in "my documents", so users can easily add translations #if defined(WIN32) || defined(__OS2__) m_qstrSystemTranslDir = QCoreApplication::instance()->applicationDirPath(); #else m_qstrSystemTranslDir = "/usr/share/qt4/translations"; //ttt1 maybe improve #endif } void TranslatorHandler::addTranslations(const std::string& strDir) { qDebug("trying %s", strDir.c_str()); QFileInfoList fileInfos (QDir(convStr(strDir), "mp3diags_*.qm").entryInfoList(QDir::NoDotAndDotDot | QDir::Files)); for (int i = 0; i < fileInfos.size(); ++i) { m_vstrLongTranslations.push_back(convStr(fileInfos[i].canonicalFilePath())); m_vstrTranslations.push_back(convStr(fileInfos[i].fileName())); qDebug("added %s", m_vstrLongTranslations.back().c_str()); } } string TranslatorHandler::getLocale(string strTranslation) { strTranslation.erase(strTranslation.size() - 3, 3); strTranslation.erase(0, 8); return strTranslation; } void TranslatorHandler::setTranslation(const string& strTranslation) { for (int i = 0; i < cSize(m_vstrTranslations); ++i) { if (m_vstrTranslations[i] == strTranslation) { m_appTranslator.load(convStr(m_vstrLongTranslations[i])); break; } } try { QCoreApplication::instance()->removeTranslator(&m_appTranslator); QCoreApplication::instance()->installTranslator(&m_appTranslator); // http://www.qtcentre.org/threads/20889-Translation-of-app-and-Qt-Dialogs //m_systemTranslator.load("qt_cs.qm", m_qstrSystemTranslDir); try { m_systemTranslator.load("qt" + convStr(getLocale(strTranslation)) + ".qm", m_qstrSystemTranslDir); // ttt0 see what is available in 4.7 and recheck that "de_DE" is loaded if there's no "de" or "de_CH" QCoreApplication::instance()->removeTranslator(&m_systemTranslator); QCoreApplication::instance()->installTranslator(&m_systemTranslator); } catch (...) { // !!! nothing; usually there is no system translator for this locale, so the file and color dialogs will be shown in English } } catch (...) { if (strTranslation != "") { QMessageBox::critical(0, "Error", "An error was encountered while setting up the translation. The program will continue, but the language may be incorrect."); } } } /*static*/ TranslatorHandler& TranslatorHandler::getGlobalTranslator() { static TranslatorHandler hndl; return hndl; } /* LocaleInfo::LocaleInfo(std::string strFileName) : m_strCountry("err"), m_strLanguage("err") { // QLocale::nativeLanguageName and QLocale::nativeCountryName - in Qt 4.8 if (endsWith(strFileName), "_en_US.qm") { m_strCountry = "USA"; m_strLanguage = "English"; } else if (endsWith(strFileName), "_cs.qm") { //m_strCountry = "Czech Republic"; //m_strLanguage = "Czech"; m_strCountry = "Czech Republic"; m_strLanguage = "Czech"; } else { if (endsWith(strFileName), ".qm") { strFileName.erase(strFileName.size() - 3); string::size_type k (strFileName.find_last_of("_")); if (k ) } } } */ /*static*/ string TranslatorHandler::getLanguageInfo(string strFileName) { string::size_type k (strFileName.find_last_of(getPathSep())); if (k != string::npos) { strFileName.erase(0, k + 1); } if (strFileName == "mp3diags_cs.qm") { //return "Czech - Czech Republic"; //return "ÄŒesko - ÄŒeská republika"; return "ÄŒeský"; } if (strFileName == "mp3diags_de_DE.qm") { return "Deutsch"; } if (strFileName == "mp3diags_en_US.qm") { return "English"; } if (strFileName == "mp3diags_fr_FR.qm") { return "Français"; } if (endsWith(strFileName, ".qm")) { strFileName.erase(strFileName.size() - 3); if (beginsWith(strFileName, "mp3diags_")) { strFileName.erase(0, strlen("mp3diags_")); //ttt0 look for "xx_XX" or "xx" and define some more return strFileName; } } return "Error - " + strFileName; } // to rebuild the translations use RebuildUkTransl.sh, which also updates all the .cs files, rather than MakeTranslations.sh, which assumes the .cs files are OK MP3Diags-1.2.02/src/licences/0000755000175000001440000000000011203001725014512 5ustar ciobiusersMP3Diags-1.2.02/src/licences/zlib.txt0000644000175000001440000000207511203001660016215 0ustar ciobiusers zlib.h -- interface of the 'zlib' general purpose compression library version 1.2.3, July 18th, 2005 Copyright (C) 1995-2005 Jean-loup Gailly and Mark Adler This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. Jean-loup Gailly jloup@gzip.org Mark Adler madler@alumni.caltech.edu MP3Diags-1.2.02/src/licences/lgplv3.txt0000644000175000001440000001645411107241731016502 0ustar ciobiusersGNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, “this License†refers to version 3 of the GNU Lesser General Public License, and the “GNU GPL†refers to version 3 of the GNU General Public License. “The Library†refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An “Application†is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A “Combined Work†is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the “Linked Versionâ€. The “Minimal Corresponding Source†for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The “Corresponding Application Code†for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: * a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or * b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: * a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. * b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: * a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. * b) Accompany the Combined Work with a copy of the GNU GPL and this license document. * c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. * d) Do one of the following: o 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. o 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. * e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: * a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. * b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License “or any later version†applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library.MP3Diags-1.2.02/src/licences/boost.txt0000644000175000001440000000247211203001725016406 0ustar ciobiusersBoost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. MP3Diags-1.2.02/src/licences/lgpl-2.1.txt0000644000175000001440000005664411203001247016525 0ustar ciobiusersGNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: * a) The modified work must itself be a software library. * b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. * c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. * d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: * a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) * b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. * c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. * d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. * e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: * a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. * b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. MP3Diags-1.2.02/src/licences/gplv3.txt0000644000175000001440000010360611107241715016324 0ustar ciobiusersGNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright © 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. 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 them 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 prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. 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. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. “This License†refers to version 3 of the GNU General Public License. “Copyright†also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. “The Program†refers to any copyrightable work licensed under this License. Each licensee is addressed as “youâ€. “Licensees†and “recipients†may be individuals or organizations. To “modify†a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a “modified version†of the earlier work or a work “based on†the earlier work. A “covered work†means either the unmodified Program or a work based on the Program. To “propagate†a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To “convey†a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays “Appropriate Legal Notices†to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The “source code†for a work means the preferred form of the work for making modifications to it. “Object code†means any non-source form of a work. A “Standard Interface†means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The “System Libraries†of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A “Major Componentâ€, in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The “Corresponding Source†for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey 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; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: * a) The work must carry prominent notices stating that you modified it, and giving a relevant date. * b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to “keep intact all noticesâ€. * c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. * d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an “aggregate†if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: * a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. * b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. * c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. * d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. * e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A “User Product†is either (1) a “consumer productâ€, which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, “normally used†refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. “Installation Information†for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. “Additional permissions†are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: * a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or * b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or * c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or * d) Limiting the use for publicity purposes of names of licensors or authors of the material; or * e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or * f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered “further restrictions†within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An “entity transaction†is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A “contributor†is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's “contributor versionâ€. A contributor's “essential patent claims†are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, “control†includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a “patent license†is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To “grant†such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. “Knowingly relying†means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is “discriminatory†if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If 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 convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU 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 that a certain numbered version of the GNU General Public License “or any later version†applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. 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. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 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. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. 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 state the exclusion of warranty; and each file should have at least the “copyright†line and a pointer to where the full notice is found. Copyright (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 3 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 . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program 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, your program's commands might be different; for a GUI interface, you would use an “about boxâ€. You should also get your employer (if you work as a programmer) or school, if any, to sign a “copyright disclaimer†for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU 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. But first, please read .MP3Diags-1.2.02/src/licences/gplv2.txt0000644000175000001440000004235211107241710016316 0ustar ciobiusersGNU 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. 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 an idea of what it does. Copyright (C) yyyy 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. MP3Diags-1.2.02/src/StreamsModel.cpp0000644000175000001440000002047411724340543016053 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include "StreamsModel.h" #include "NotesModel.h" #include "FilesModel.h" #include "CommonData.h" #include "DataStream.h" using namespace std; //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== StreamsModel::StreamsModel(CommonData* pCommonData) : QAbstractTableModel(pCommonData->m_pStreamsG), m_pCommonData(pCommonData) { } /*override*/ int StreamsModel::rowCount(const QModelIndex&) const { return cSize(m_pCommonData->getCrtStreams()); } /*override*/ int StreamsModel::columnCount(const QModelIndex&) const { return 5; } /*override*/ QVariant StreamsModel::data(const QModelIndex& index, int nRole) const { LAST_STEP("StreamsModel::data()"); /* //ttt2 Excessive calls to this from MultiLineTvDelegate::sizeHint() might make applying a filter pretty slow. To reproduce: 1) The first file that will get selected by the filter should be badly broken, having 1000 streams. 2) Apply a filter by a note of that file The issue is that StreamsModel::data() gets called way too many times from MultiLineTvDelegate::sizeHint(). So even if a single call is pretty quick (not leaving room to much improvement), the total time is noticeable. Since this is a pathologic case and even then it's only a matter of several seconds, the fix can wait. (One idea would be to report that all lines above 200 have a height of one line, but perhaps there's something better.) static int CRT (0); ++CRT; if (0 == CRT % 2000) { qDebug("StreamsModel::data() call %d", CRT); } */ int j (index.column()); //if (nRole == Qt::SizeHintRole && j > 0) { return QSize(CELL_WIDTH - 1, CELL_HEIGHT - 1); } // !!! "-1" so one pixel can be used to draw the grid //if (nRole == Qt::SizeHintRole) { return QSize(CELL_WIDTH - 10, CELL_HEIGHT - 10); } // !!! "-1" so one pixel can be used to draw the grid if (!index.isValid() || nRole != Qt::DisplayRole) { return QVariant(); } const DataStream* pDataStream (m_pCommonData->getCrtStreams()[index.row()]); switch (j) { case 0: { ostringstream out; //out << "0x" << hex << setw(8) << setfill('0') << pDataStream->getPos(); out << "0x" << hex << pDataStream->getPos(); return convStr(out.str()); } case 1: { ostringstream out; out << pDataStream->getSize(); return convStr(out.str()); } case 2: { ostringstream out; out << "0x" << hex << pDataStream->getSize(); return convStr(out.str()); } case 3: { return QString(pDataStream->getTranslatedDisplayName()); } case 4: return convStr(pDataStream->getInfo()); default: CB_ASSERT(false); }//out << "Ape: " <<" offset=0x" << hex << m_pos << ", size=0x" << m_nSize << dec << " (" << m_nSize << ")"; } /*override*/ QVariant StreamsModel::headerData(int nSection, Qt::Orientation eOrientation, int nRole /* = Qt::DisplayRole*/) const { LAST_STEP("StreamsModel::headerData"); if (nRole == Qt::SizeHintRole) { return getNumVertHdrSize(cSize(m_pCommonData->getCrtStreams()), eOrientation); } if (nRole != Qt::DisplayRole) { return QVariant(); } if (Qt::Horizontal == eOrientation) { //return section; switch (nSection) { case 0: return tr("Address"); case 1: return tr("Size (dec)"); case 2: return tr("Size (hex)"); case 3: return tr("Type"); case 4: return tr("Stream details"); default: CB_ASSERT (false); } } return nSection + 1; } static bool containsAnyOf(const DataStream* pStream, const set& sSel) { streampos begPos (pStream->getPos()); streampos endPos (begPos); endPos += pStream->getSize(); for (set::const_iterator it = sSel.begin(), end = sSel.end(); it != end; ++it) //ttt2 binary search { streampos pos (*it); if (begPos <= pos && pos < endPos) { return true; } } return false; } void StreamsModel::matchSelToNotes() { int nCrt (m_pCommonData->getFilesGCrtRow()); if (-1 == nCrt) { return; } set sSel; QModelIndexList lSelNotes (m_pCommonData->m_pNotesG->selectionModel()->selection().indexes()); QItemSelectionModel* pStreamsSelModel (m_pCommonData->m_pStreamsG->selectionModel()); pStreamsSelModel->clearSelection(); for (QModelIndexList::iterator it = lSelNotes.begin(), end = lSelNotes.end(); it != end; ++it) { int nCol (it->column()); if (0 == nCol) // it's "whole row selection", so use only the first column { sSel.insert(m_pCommonData->getCrtNotes()[it->row()]->getPos()); } } const vector& vCrtStreams (m_pCommonData->getViewHandlers()[nCrt]->getStreams()); bool bFirstFound (false); for (int i = 0, n = cSize(vCrtStreams); i < n; ++i) { const DataStream* pStream (vCrtStreams[i]); if (containsAnyOf(pStream, sSel)) { if (!bFirstFound) { bFirstFound = true; m_pCommonData->m_pStreamsG->setCurrentIndex(index(i, 0)); } pStreamsSelModel->select(index(i, 0), QItemSelectionModel::Select | QItemSelectionModel::Rows); } } } void StreamsModel::onStreamsGSelChanged() { NonblockingGuard g (m_pCommonData->m_bChangeGuard); if (!g) { return; } m_pCommonData->m_pNotesModel->matchSelToStreams(); m_pCommonData->m_pFilesModel->matchSelToNotes(); } //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== StreamsGDelegate::StreamsGDelegate(CommonData* pCommonData) : MultiLineTvDelegate(pCommonData->m_pStreamsG), m_pCommonData(pCommonData) { } /*override*/ void StreamsGDelegate::paint(QPainter* pPainter, const QStyleOptionViewItem& option, const QModelIndex& index) const { pPainter->save(); QStyleOptionViewItemV2 myOption (option); if (0 == index.column() || 1 == index.column() || 2 == index.column()) { myOption.displayAlignment |= Qt::AlignRight; myOption.font = m_pCommonData->getFixedFont(); } MultiLineTvDelegate::paint(pPainter, myOption, index); pPainter->restore(); } MP3Diags-1.2.02/src/UniqueNotesModel.h0000644000175000001440000000601111200236502016334 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef UniqueNotesModelH #define UniqueNotesModelH #include #include "MultiLineTvDelegate.h" //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== class CommonData; // all notes struct UniqueNotesModel : public QAbstractTableModel { Q_OBJECT public: UniqueNotesModel(CommonData* pCommonData); /*override*/ int rowCount(const QModelIndex&) const; /*override*/ int columnCount(const QModelIndex&) const; /*override*/ QVariant data(const QModelIndex&, int) const; /*override*/ QVariant headerData(int nSection, Qt::Orientation eOrientation, int nRole = Qt::DisplayRole) const; CommonData* m_pCommonData; void selectTopLeft(); // makes current and selects the element in the top-left corner and emits a change signal regardless of the element that was selected before; makes current the default invalid index (-1,-1) if the table is empty }; class UniqueNotesGDelegate : public MultiLineTvDelegate { Q_OBJECT public: UniqueNotesGDelegate(CommonData* pCommonData); /*override*/ void paint(QPainter* pPainter, const QStyleOptionViewItem& option, const QModelIndex& index) const; CommonData* m_pCommonData; }; #endif // #ifndef UniqueNotesModelH MP3Diags-1.2.02/src/Version.cpp0000644000175000001440000000466212477147652015116 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include using namespace std; namespace Version { const char* getAppVer() { return "1.2.02"; } // used, e.g. for location at SourceForge const char* getWebBranch() { return ""; } // to be shown to the user in various forms (app title, About box, shell integration, ...) const char* getAppName() { return "MP3 Diags"; } // icon name, needed for shell integration in Linux const char* getIconName() { return "MP3Diags"; } // used for location of the documentation const char* getHelpPackageName() { return "mp3diags"; } // used for location of the translation files const char* getTranslationPackageName() { return "mp3diags"; } const char* getSettingsAppName() { return "Mp3Diags"; //ttt1 maybe replace this with "mp3diags", but have some code to import older settings and then clear them } // for config only const char* getOrganization() { return "Ciobi"; } } // namespace Version MP3Diags-1.2.02/src/ApeStream.cpp0000644000175000001440000003217511724457473015351 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include "ApeStream.h" #include "Helpers.h" using namespace std; using namespace pearl; namespace { //ttt3 these might get used uninitialized, if read before main() const char* LBL_TITLE ("Title"); //!!! no translation - these are the names of the standard APE keys, corresponding to "TIT2" in ID3V2 const char* LBL_ARTIST ("Artist"); //ttt2 may be list //const char* LBL_TRACK_NUMBER ("TRCK"); //const char* LBL_TRACK_YEAR ("TYER"); //const char* LBL_TRACK_DATE ("TDAT"); const char* LBL_GENRE ("Genre"); //ttt2 may be list //const char* LBL_IMAGE ("APIC"); const char* LBL_ALBUM ("Album"); const set& getUtf8Keys() { static bool bFirstTime (true); static set sUtf8Keys; if (bFirstTime) { sUtf8Keys.insert(LBL_TITLE); sUtf8Keys.insert(LBL_ARTIST); //sUtf8Keys.insert(LBL_TRACK_NUMBER); //sUtf8Keys.insert(LBL_TRACK_YEAR); //sUtf8Keys.insert(LBL_TRACK_DATE); sUtf8Keys.insert(LBL_GENRE); //sUtf8Keys.insert(LBL_IMAGE); sUtf8Keys.insert(LBL_ALBUM); bFirstTime = false; } return sUtf8Keys; } const set& getUsedKeys() { static bool bFirstTime (true); static set sUsedFrames; if (bFirstTime) { sUsedFrames.insert(LBL_TITLE); sUsedFrames.insert(LBL_ARTIST); // ttt2 really list //sUsedFrames.insert(LBL_TRACK_NUMBER); //sUsedFrames.insert(LBL_TRACK_YEAR); //sUsedFrames.insert(LBL_TRACK_DATE); sUsedFrames.insert(LBL_GENRE); //sUsedFrames.insert(LBL_IMAGE); sUsedFrames.insert(LBL_ALBUM); bFirstTime = false; } return sUsedFrames; } } //============================================================================================================ //============================================================================================================ //============================================================================================================ ApeItem::ApeItem(NoteColl& notes, istream& in) : m_eType(BINARY) { StreamStateRestorer rst (in); streampos pos (in.tellg()); const int BFR_SIZE (4 + 4 + 255 + 1); // key name is up to 255 chars char bfr [BFR_SIZE]; streamsize nRead (read(in, bfr, BFR_SIZE)); MP3_CHECK (nRead >= 4 + 4 + 2 + 1, pos, apeItemTooShort, ApeStream::NotApeStream()); unsigned char* pUnsgBfr (reinterpret_cast(bfr)); m_cFlags1 = pUnsgBfr[4]; m_cFlags2 = pUnsgBfr[5]; m_cFlags3 = pUnsgBfr[6]; m_cFlags4 = pUnsgBfr[7]; MP3_CHECK (0 == m_cFlags1 && 0 == m_cFlags2 && 0 == m_cFlags3 && 0 == m_cFlags4, pos, apeFlagsNotSupported, StreamIsUnsupported(ApeStream::getClassDisplayName(), tr("Ape stream whose items have unsupported flags."))); //MP3_CHECK (0 == (m_cFlags1 & 0xf8u) && 0 == m_cFlags2 && 0 == m_cFlags3 && 0 == m_cFlags4, pos, apeFlagsNotSupported, StreamIsUnsupported(ApeStream::getClassDisplayName(), "Ape stream whose items have unsupported flags.")); // ttt1 allow this; see 02_-_Brave_-_Driven.mp3, which has a "BINARY" value (hence the "2" flag, which is also larger than 256) //inspect(bfr, BFR_SIZE); int nDataSize (pUnsgBfr[0] + (pUnsgBfr[1] << 8) + (pUnsgBfr[2] << 16) + (pUnsgBfr[3] << 24)); char* p (bfr + 8); for (; 0 != *p && p < bfr + nRead; ++p) {} MP3_CHECK (p < bfr + nRead, pos, apeMissingTerminator, ApeStream::NotApeStream()); m_strName = string(bfr + 8, p); if (nDataSize > 256) { MP3_NOTE_D (pos, apeItemTooBig, tr("Ape Item seems too big. Although the size may be any 32-bit integer, 256 bytes should be enough in practice. If this message is determined to be mistaken, it will be removed in the future. Item key: %1; item size: %2").arg(convStr(m_strName)).arg(nDataSize)); throw ApeStream::NotApeStream(); //ttt1 actually it's possible; see 02_-_Brave_-_Driven.mp3 } m_vcValue.resize(nDataSize); pos += getTotalSize() - nDataSize; in.clear(); in.seekg(pos); MP3_CHECK (nDataSize == read(in, &m_vcValue[0], nDataSize), pos, apeItemTooShort, ApeStream::NotApeStream()); if (getUtf8Keys().count(m_strName) > 0) { m_eType = UTF8; } if (UTF8 != m_eType) { if (nDataSize <= 256) { for (int i = 0; i < nDataSize; ++i) { if (m_vcValue[i] < 32 || m_vcValue[i] > 126) { goto e1; } } } m_eType = UTF8; e1:; } rst.setOk(); //inspect(m_aValue, m_nDataSize); } string ApeItem::getUtf8String() const { switch (m_eType) { case BINARY: return convStr(TagReader::tr("")); case UTF8: return string(&m_vcValue[0], &m_vcValue[0] + cSize(m_vcValue)); } CB_ASSERT(false); } //============================================================================================================ //============================================================================================================ //============================================================================================================ ApeStream::ApeStream(int nIndex, NoteColl& notes, istream& in) : DataStream(nIndex) { StreamStateRestorer rst (in); m_pos = in.tellg(); //const int APE_LABEL_SIZE (8); //const int BFR_SIZE (32);//(APE_LABEL_SIZE + 4 + 4); char bfr [32]; streamsize nRead (read(in, bfr, 32)); MP3_CHECK_T (32 == nRead, m_pos, "Not an Ape tag. File too short.", NotApeStream()); //inspect (bfr, m_pos); MP3_CHECK_T (0 == strncmp("APETAGEX", bfr, 8), m_pos, "Not an Ape tag. Invalid header.", NotApeStream()); m_nVersion = *reinterpret_cast(bfr + 8); // ttt2 assume 32-bit int + little-endian m_nSize = *reinterpret_cast(bfr + 12); // ttt2 assume 32-bit int + little-endian MP3_CHECK (0x80 == (0xc0 & (unsigned char)bfr[23]), m_pos, apeUnsupported, StreamIsUnsupported(ApeStream::getClassDisplayName(), tr("Tag missing header or footer."))); //ttt2 assumes both header & footer are present, but they are optional; MP3_CHECK (0 != (0x20 & bfr[23]), m_pos, apeFoundFooter, NotApeHeader()); streampos posEnd (m_pos); posEnd += m_nSize; m_nSize += 32; // account for header in.seekg(posEnd); char bfr2 [32]; nRead = read(in, bfr2, 32); MP3_CHECK (32 == nRead, m_pos, apeTooShort, NotApeStream()); MP3_CHECK (0 == (0x20 & bfr2[23]), m_pos, apeFoundHeader, NotApeFooter()); bfr2[23] |= 0x20; //inspect (bfr2, posEnd); for (int i = 0; i < 32; ++i) { MP3_CHECK (bfr[i] == bfr2[i], m_pos, apeHdrFtMismatch, HeaderFooterMismatch()); } readKeys(notes, in); MP3_TRACE (m_pos, "ApeStream built."); rst.setOk(); } ApeStream::~ApeStream() { clearPtrContainer(m_vpItems); } /*override*/ void ApeStream::copy(std::istream& in, std::ostream& out) { appendFilePart(in, out, m_pos, m_nSize); } /*override*/ std::string ApeStream::getInfo() const { return ""; } void ApeStream::readKeys(NoteColl& notes, std::istream& in) { streampos posEnd (m_pos); posEnd += m_nSize - 32; // where the footer begins streampos posCrt (m_pos); posCrt += 32; in.seekg(posCrt); while (posCrt < posEnd) { ApeItem* p (new ApeItem(notes, in)); posCrt += p->getTotalSize(); m_vpItems.push_back(p); } } ApeItem* ApeStream::findItem(const char* szFrameName) //ttt2 finds the first item, but doesn't care about duplicates; not sure how Ape views duplicate keys { for (int i = 0, n = cSize(m_vpItems); i < n; ++i) { ApeItem* p = m_vpItems[i]; if (szFrameName == p->m_strName) { return p; } } return 0; } const ApeItem* ApeStream::findItem(const char* szFrameName) const //ttt2 finds the first item, but doesn't care about duplicates; not sure how Ape views duplicate keys { for (int i = 0, n = cSize(m_vpItems); i < n; ++i) { const ApeItem* p = m_vpItems[i]; if (szFrameName == p->m_strName) { return p; } } return 0; } //Mp3Gain ApeStream::getMp3GainStatus() const /* There are MP3GAIN and REPLAYGAIN. Not sure how they are related, because the program mp3gain creates both MP3GAIN and REPLAYGAIN entries. Here's a newly generated tag: MP3GAIN_MINMAX="139,240", MP3GAIN_UNDO="+005,+005,N", REPLAYGAIN_TRACK_GAIN="-0.205000 dB", REPLAYGAIN_TRACK_PEAK="0.466454 and an older one: MP3GAIN_MINMAX="089,179", MP3GAIN_ALBUM_MINMAX="000,196", MP3GAIN_UNDO="+005,+005,N", REPLAYGAIN_TRACK_GAIN="+6.915000 dB", REPLAYGAIN_TRACK_PEAK="0.289101", REPLAYGAIN_ALBUM_GAIN="+6.600000 dB", REPLAYGAIN_ALBUM_PEAK="0.886662" It seems that MP3GAIN is about changing audio data (for which there is an undo as long as the Ape tag is not deleted), while REPLAYGAIN only adds tags, which require compatible players. Not sure how to tell from these if album and/or track info are found. Currently (2009.03.14) it doesn't matter, so a bool is better. */ bool ApeStream::hasMp3Gain() const { return 0 != findItem("MP3GAIN_MINMAX") || 0 != findItem("MP3GAIN_ALBUM_MINMAX") || 0 != findItem("REPLAYGAIN_TRACK_GAIN") || 0 != findItem("REPLAYGAIN_ALBUM_GAIN"); } // ================================ TagReader ========================================= /*override*/ TagReader::SuportLevel ApeStream::getSupport(Feature eFeature) const { switch (eFeature) { case TITLE: case ARTIST: //case TRACK_NUMBER: //case TRACK_YEAR: //case TRACK_DATE: case GENRE: //case PICTURE: case ALBUM: return READ_ONLY; default: return NOT_SUPPORTED; } // enum SuportLevel { NOT_SUPPORTED, , READ_WRITE }; } /*override*/ std::string ApeStream::getTitle(bool* pbFrameExists /* = 0*/) const { const ApeItem* p (findItem(LBL_TITLE)); if (0 != pbFrameExists) { *pbFrameExists = 0 != p; } if (0 == p) { return ""; } return p->getUtf8String(); } /*override*/ std::string ApeStream::getArtist(bool* pbFrameExists /* = 0*/) const { const ApeItem* p (findItem(LBL_ARTIST)); if (0 != pbFrameExists) { *pbFrameExists = 0 != p; } if (0 == p) { return ""; } return p->getUtf8String(); } /*override*/ std::string ApeStream::getTrackNumber(bool* /*pbFrameExists*/ /* = 0*/) const { /*const ApeItem* p (findItem(LBL_TRACK_NUMBER)); if (0 == p) { return ""; } return p->getUtf8String();*/ throw NotSupportedOp(); } /*override*/ TagTimestamp ApeStream::getTime(bool* /* = 0*/) const { throw NotSupportedOp(); } /*override*/ std::string ApeStream::getGenre(bool* pbFrameExists /* = 0*/) const { const ApeItem* p (findItem(LBL_GENRE)); // not always correct; the specs say it's a "numeric string"; usually a descriptive string seems to be used though, not a number; anyway, "Cenaclul Flacara 3/c06 Anda Calugareanu - Noi, nu.mp3" has a "(80)" in this field if (0 != pbFrameExists) { *pbFrameExists = 0 != p; } if (0 == p) { return ""; } return p->getUtf8String(); } /*override*/ std::string ApeStream::getAlbumName(bool* pbFrameExists /* = 0*/) const { const ApeItem* p (findItem(LBL_ALBUM)); if (0 != pbFrameExists) { *pbFrameExists = 0 != p; } if (0 == p) { return ""; } return p->getUtf8String(); } /*override*/ std::string ApeStream::getOtherInfo() const { const set& sUsedKeys (getUsedKeys()); set sUsedFrames; ostringstream out; bool b (false); for (int i = 0, n = cSize(m_vpItems); i < n; ++i) { ApeItem* p = m_vpItems[i]; if (sUsedKeys.count(p->m_strName) > 0 && sUsedFrames.count(p->m_strName) == 0) { sUsedFrames.insert(p->m_strName); } else { if (b) { out << ", "; } out << p->m_strName << "=\"" << p->getUtf8String() << "\""; b = true; } } return out.str(); } MP3Diags-1.2.02/src/AlbumInfoDownloader.ui0000644000175000001440000004330611700344754017203 0ustar ciobiusers AlbumInfoDownloaderDlg 0 0 964 686 WWW true Search 0 Artist 4 0 false Album 5 0 false Match count Search Results 0 0 Artist 4 0 true Album 5 0 true Released 2 0 true 0 Genre 3 0 true Use Format 2 0 true Amazon 0 0 4 0 0 0 0 0 300 300 300 300 QFrame::Box Image Qt::AlignCenter Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse 0 0 0 0 10 Image size Qt::Vertical 20 3 0 1 0 5 0 3 0 0 0 170 0 170 16777215 ResultNo Previous album ... :/images/arrow-left-double.svg:/images/arrow-left-double.svg Previous image or album p :/images/arrow-left.svg:/images/arrow-left.svg Next image or album n :/images/arrow-right.svg:/images/arrow-right.svg Next album ... :/images/arrow-right-double.svg:/images/arrow-right-double.svg Filter: Image CD Track count Qt::Horizontal 40 20 0 Qt::Horizontal 40 20 false Volume false Save image Save all Cancel MP3Diags-1.2.02/src/Notes.cpp0000644000175000001440000004135611724743626014557 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include "Notes.h" #include "Helpers.h" using namespace std; using namespace pearl; /*static*/ Notes::NoteSet Notes::s_spAllNotes; /*static*/ vector > Notes::s_vpNotesByCateg (static_cast(Note::CATEG_CNT)); /*static*/ vector Notes::s_vpAllNotes; static const char* s_szPlaceholderDescr (QT_TRANSLATE_NOOP("Notes", "")); // to be used by serialization: if a description is no longer found, the note gets replace with a default, "missing", one /*static*/ const Note* Notes::getMissingNote() { static Note::SharedData sd (Note::SUPPORT, Note::CUSTOM, s_szPlaceholderDescr, false); // !!! m_nLabelIndex is initialized to -1, which will result in an empty label; static Note note (sd, -1); return ¬e; } /*static*/ const Note* Notes::getMaster(const Note* p) { const Note* q (getNote(p->getDescription())); if (0 != q) { return q; } q = getMissingNote(); CB_ASSERT (p->m_pSharedData == q->m_pSharedData); return q; } // destoys the pointers added by addToDestroyList(); to be called after loading is complete; /*static*/void Notes::clearDestroyList() { clearPtrContainer(s_spDestroyList); } /*static*/ set Notes::s_spDestroyList; /*static*/ void Notes::addNote(Note* p) { CB_ASSERT (0 == s_spAllNotes.count(p)); int nCateg (p->getCategory()); CB_ASSERT (0 <= nCateg && nCateg < Note::CATEG_CNT); p->m_pSharedData->m_nLabelIndex = cSize(s_vpNotesByCateg[nCateg]); s_vpNotesByCateg[nCateg].push_back(p); p->m_pSharedData->m_nNoteId = cSize(s_vpAllNotes); s_vpAllNotes.push_back(p); s_spAllNotes.insert(p); } //ttt2 if one of the addNote() is missing, the program just crashes instead of showing an assertion; the reason seems to be that the UI will ask for the color of an invalid note; /*static*/ void Notes::initVec() { static bool s_bInit (false); if (s_bInit) { return; } s_bInit = true; // audio addNote(&Notes::twoAudio()); // e addNote(&Notes::lowQualAudio()); // w addNote(&Notes::noAudio()); // e addNote(&Notes::vbrUsedForNonMpg1L3()); // w addNote(&Notes::incompleteFrameInAudio()); // e addNote(&Notes::validFrameDiffVer()); // e addNote(&Notes::validFrameDiffLayer()); // e addNote(&Notes::validFrameDiffMode()); // e addNote(&Notes::validFrameDiffFreq()); // e addNote(&Notes::validFrameDiffCrc()); // e addNote(&Notes::audioTooShort()); // e addNote(&Notes::diffBitrateInFirstFrame()); // e addNote(&Notes::noMp3Gain()); // w addNote(&Notes::untestedEncoding()); // s // xing addNote(&Notes::twoLame()); // e addNote(&Notes::xingAddedByMp3Fixer()); // e addNote(&Notes::xingFrameCountMismatch()); // e addNote(&Notes::twoXing()); // e addNote(&Notes::xingNotBeforeAudio()); // e addNote(&Notes::incompatXing()); // e addNote(&Notes::missingXing()); // w // vbri addNote(&Notes::twoVbri()); addNote(&Notes::vbriFound()); addNote(&Notes::foundVbriAndXing()); // w // id3 v2 addNote(&Notes::id3v2FrameTooShort()); // e addNote(&Notes::id3v2InvalidName()); // e addNote(&Notes::id3v2IncorrectFlg1()); // w addNote(&Notes::id3v2IncorrectFlg2()); // w addNote(&Notes::id3v2TextError()); // e addNote(&Notes::id3v2HasLatin1NonAscii()); // w addNote(&Notes::id3v2EmptyTcon()); // w addNote(&Notes::id3v2MultipleFramesWithSameName()); // w addNote(&Notes::id3v2PaddingTooLarge()); // w addNote(&Notes::id3v2UnsuppVer()); // s addNote(&Notes::id3v2UnsuppFlag()); // s addNote(&Notes::id3v2UnsuppFlags1()); // s addNote(&Notes::id3v2UnsuppFlags2()); // s addNote(&Notes::id3v2DuplicatePopm()); //s addNote(&Notes::id3v2EmptyTag()); //w // ttt2 perhaps move up in a new release, so it isn't shown after support notes; better: assign ids to support notes at the end of the alphabet; addNote(&Notes::id3v2EmptyTextFrame()); //w // ttt2 perhaps move up in a new release, so it isn't shown after support notes; better: assign ids to support notes at the end of the alphabet; // apic addNote(&Notes::id3v2NoApic()); // w addNote(&Notes::id3v2CouldntLoadPic()); // w //addNote(&Notes::id3v2LinkNotSupported()); // s addNote(&Notes::id3v2NotCoverPicture()); // w addNote(&Notes::id3v2ErrorLoadingApic()); // w addNote(&Notes::id3v2ErrorLoadingApicTooShort()); // w addNote(&Notes::id3v2DuplicatePic()); // e addNote(&Notes::id3v2MultipleApic()); // w addNote(&Notes::id3v2UnsupApicTextEnc()); //s addNote(&Notes::id3v2LinkInApic()); //s addNote(&Notes::id3v2PictDescrIgnored()); //s // id3 v2.3.0 addNote(&Notes::noId3V230()); // w addNote(&Notes::twoId3V230()); // e addNote(&Notes::bothId3V230_V240()); // w addNote(&Notes::id3v230AfterAudio()); // e addNote(&Notes::id3v230UsesUtf8()); // w addNote(&Notes::id3v230UnsuppText()); // s addNote(&Notes::id3v230CantReadFrame()); // e // id3 v2.4.0 addNote(&Notes::twoId3V240()); // e addNote(&Notes::id3v240CantReadFrame()); // e addNote(&Notes::id3v240IncorrectSynch()); // w addNote(&Notes::id3v240DeprTyerAndTdrc()); // w addNote(&Notes::id3v240DeprTyer()); // w addNote(&Notes::id3v240DeprTdatAndTdrc()); // w addNote(&Notes::id3v240IncorrectDli()); // w addNote(&Notes::id3v240IncorrectFrameSynch()); // w addNote(&Notes::id3v240DeprTdat()); // w addNote(&Notes::id3v240UnsuppText()); // s // id3 v1 addNote(&Notes::onlyId3V1()); // w addNote(&Notes::id3v1BeforeAudio()); // w addNote(&Notes::id3v1TooShort()); // e addNote(&Notes::twoId3V1()); // e //addNote(&Notes::zeroInId3V1()); addNote(&Notes::mixedPaddingInId3V1()); // w addNote(&Notes::mixedFieldPaddingInId3V1()); // w addNote(&Notes::id3v1InvalidName()); // e addNote(&Notes::id3v1InvalidArtist()); // e addNote(&Notes::id3v1InvalidAlbum()); // e addNote(&Notes::id3v1InvalidYear()); // e addNote(&Notes::id3v1InvalidComment()); // e // broken addNote(&Notes::brokenAtTheEnd()); // e addNote(&Notes::brokenInTheMiddle()); // e // trunc addNote(&Notes::truncAudioWithWholeFile()); // e addNote(&Notes::truncAudio()); // e // unknown addNote(&Notes::unknTooShort()); // w addNote(&Notes::unknownAtTheEnd()); // e addNote(&Notes::unknownInTheMiddle()); // e addNote(&Notes::foundNull()); // w // lyrics addNote(&Notes::lyrTooShort()); // e addNote(&Notes::twoLyr()); // s addNote(&Notes::invalidLyr()); // e addNote(&Notes::duplicateFields()); // s //addNote(&Notes::imgInLyrics()); // s addNote(&Notes::infInLyrics()); // s // ape addNote(&Notes::apeItemTooShort()); // e addNote(&Notes::apeItemTooBig()); // e addNote(&Notes::apeMissingTerminator()); // e addNote(&Notes::apeFoundFooter()); // e addNote(&Notes::apeTooShort()); // e addNote(&Notes::apeFoundHeader()); // e addNote(&Notes::apeHdrFtMismatch()); // e addNote(&Notes::twoApe()); // s addNote(&Notes::apeFlagsNotSupported()); // s addNote(&Notes::apeUnsupported()); // s // misc addNote(&Notes::fileWasChanged()); // w addNote(&Notes::noInfoTag()); // w addNote(&Notes::tooManyTraceNotes()); // w addNote(&Notes::tooManyNotes()); // w addNote(&Notes::tooManyStreams()); // w addNote(&Notes::unsupportedFound()); // w addNote(&Notes::rescanningNeeded()); // w { CB_ASSERT (Note::CUSTOM == Note::CATEG_CNT - 1); for (int i = 1; i < cSize(s_vpAllNotes); ++i) { const Note* p1 (s_vpAllNotes[i - 1]); const Note* p2 (s_vpAllNotes[i]); CB_ASSERT (p1->getCategory() <= p2->getCategory()); CB_ASSERT (p1->getNoteId() <= p2->getNoteId()); } } // qDebug("%d errors, %d warnings, %d support notes", cSize(s_vpErrNotes), cSize(s_vpWarnNotes), cSize(s_vpSuppNotes)); } //ttt2 perhaps warn that file has multiple pictures, so will get deleted; probably like unsupportedFound; anyway after deciding on some standard way to tell the user about features and limitations; a class is probably a better answer than the current approach of "told/warned/..." settings scattered over the config file; should not show the messages too soon one after another, should get rid of all the static variables, ... /*static*/ const Note* Notes::getNote(const std::string& strDescr) { initVec(); Note::SharedData d (strDescr.c_str(), false); // !!! sev doesn't matter Note n (d); NoteSet::iterator it; it = (s_spAllNotes.find(&n)); if (s_spAllNotes.end() == it) { if (strDescr == s_szPlaceholderDescr) { return getMissingNote(); } return 0; } return *it; } /*static*/ const Note* Notes::getNote(int n) // returns 0 if n is out of range { initVec(); if (n < 0 || n >= cSize(s_vpAllNotes)) { return 0; } return s_vpAllNotes[n]; } /*static*/ const std::vector& Notes::getAllNotes() { initVec(); return s_vpAllNotes; } /*static*/ const vector& Notes::getDefaultIgnoredNoteIds() { initVec(); static bool bInit (false); static vector v; if (!bInit) { bInit = true; //v.push_back(zeroInId3V1().getNoteId()); v.push_back(mixedPaddingInId3V1().getNoteId()); v.push_back(mixedFieldPaddingInId3V1().getNoteId()); //v.push_back(lyricsNotSupported().getNoteId()); v.push_back(tooManyTraceNotes().getNoteId()); v.push_back(tooManyNotes().getNoteId()); } return v; } //============================================================================================================ //============================================================================================================ //============================================================================================================ //ttt2 maybe new type for Note::Severity: BROKEN, which is basically the same as ERR, but shown in UI with a different color //ttt2 maybe new type for Note::Severity: INFO, to be used for searches; normally they are "ignored", but can be used to search for, e.g., "CBR files" //====================================================================================================== //====================================================================================================== Note::Note(const Note& note, std::streampos pos, const std::string& strDetail /* = ""*/) : m_pSharedData(note.m_pSharedData), m_pos(pos), m_strDetail(strDetail) { //char a [30]; sprintf(a, "1 Note::Note() %p", this); TRACER(a); } Note::Note(SharedData& sharedData, std::streampos pos, const std::string& strDetail /* = ""*/) : m_pSharedData(&sharedData), m_pos(pos), m_strDetail(strDetail) { //char a [30]; sprintf(a, "2 Note::Note() %p", this); TRACER(a); } Note::Note(SharedData& sharedData) : m_pSharedData(&sharedData), m_pos(-1) { //char a [30]; sprintf(a, "3 Note::Note() %p", this); TRACER(a); } Note::Note() { //char a [30]; sprintf(a, "4 Note::Note() %p", this); TRACER(a); } Note::~Note() { //qDebug("destroyed note at %p", this); //char a [30]; sprintf(a, "Note::~Note() %p", this); TRACER(a); } //ttt2 maybe get rid of some/most ser-specific constructors, revert const changes, and call real constructors from the parent (adding serialization as member functions required switching from references to pointers and from const to non-const data members) bool Note::operator==(const Note& other) const { return m_pSharedData == other.m_pSharedData && m_pos == other.m_pos && m_strDetail == other.m_strDetail; } // returns an empty string for an invalid position (i.e. one initialized from -1) string Note::getPosHex() const { if (-1 == m_pos) { return ""; } ostringstream s; s << "0x" << hex << m_pos; return s.str(); } /*static*/ const std::string Note::severityToString(Severity s) { if (s == ERR) return convStr(Notes::tr("ERROR")); if (s == WARNING) return convStr(Notes::tr("WARNING")); if (s == SUPPORT) return convStr(Notes::tr("SUPPORT")); throw std::invalid_argument(boost::lexical_cast((int)s)); } //====================================================================================================== //====================================================================================================== NoteColl::~NoteColl() { pearl::clearPtrContainer(m_vpNotes); } void NoteColl::add(Note* pNote) { if (Note::TRACE == pNote->getSeverity()) { if (m_nMaxTrace == m_nTraceCount) { m_vpNotes.push_back(new Note(Notes::tooManyTraceNotes(), -1)); ++m_nTraceCount; } if (m_nTraceCount > m_nMaxTrace) { delete pNote; return; } ++m_nTraceCount; } else { if (200 == m_nCount) { m_vpNotes.push_back(new Note(Notes::tooManyNotes(), -1)); ++m_nCount; } if (m_nCount > 200) { delete pNote; return; } ++m_nCount; } trace(pNote->getPosHex() + string(": ") + pNote->getDescription()); if (!pNote->getDetail().empty()) { trace(pNote->getDetail()); // ttt2 perhaps log the description only if the detail is empty (so strDetail would be expected to hold all the info in strDescription) } // try to avoid adding duplicates by comparing pNote to the last 10 notes for (int i = 10, n = cSize(m_vpNotes) - 1; i > 0 && n >= 0; --i, --n) { const Note* pLast (m_vpNotes[n]); if (*pLast == *pNote) { delete pNote; return; } } m_vpNotes.push_back(pNote); } void NoteColl::sort() { std::sort(m_vpNotes.begin(), m_vpNotes.end(), CmpNotePtrById()); // !!! needed when applying filters } void NoteColl::removeTraceNotes() { vector v; for (int i = 0, n = cSize(m_vpNotes); i < n; ++i) { Note* p (m_vpNotes[i]); if (Note::TRACE == p->getSeverity()) { delete p; } else { v.push_back(p); } } m_vpNotes.swap(v); } bool NoteColl::hasFastSaveWarn() const { for (int i = cSize(m_vpNotes) - 1; i >= 0; --i) { Note* pNote (m_vpNotes[i]); if (*pNote == Notes::rescanningNeeded()) { return true; } } return false; } void NoteColl::addFastSaveWarn() { if (hasFastSaveWarn()) { return; } add(new Note(Notes::rescanningNeeded(), -1)); } void NoteColl::removeNotes(const std::streampos& posFrom, const std::streampos& posTo) // removes notes with addresses in the given range; posFrom is included, but posTo isn't { for (int i = cSize(m_vpNotes) - 1; i >= 0; --i) { Note* pNote (m_vpNotes[i]); if (pNote->getPos() >= posFrom && pNote->getPos() < posTo) { delete pNote; m_vpNotes.erase(m_vpNotes.begin() + i); } } } bool Notes::CompNoteByName::operator()(const Note* p1, const Note* p2) const { return strcmp(p1->getDescription(), p2->getDescription()) < 0; } MP3Diags-1.2.02/src/TagEditorDlgImpl.cpp0000644000175000001440000022325712361702611016607 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include #include #include "TagEditorDlgImpl.h" #include "Helpers.h" #include "TagWriter.h" #include "DataStream.h" #include "CommonData.h" #include "ConfigDlgImpl.h" #include "ColumnResizer.h" #include "TagEdtPatternsDlgImpl.h" #include "DiscogsDownloader.h" #include "MusicBrainzDownloader.h" #include "ImageInfoPanelWdgImpl.h" #include "PaletteDlgImpl.h" #include "Id3V230Stream.h" #include "Id3V240Stream.h" #include "MpegStream.h" #include "Widgets.h" #include "Transformation.h" #include "Mp3TransformThread.h" #include "StoredSettings.h" #include "Mp3Manip.h" #include "OsFile.h" #include "Version.h" using namespace std; using namespace pearl; using namespace TagEditor; using namespace Version; //====================================================================================================================== //====================================================================================================================== //====================================================================================================================== CurrentAlbumModel::CurrentAlbumModel(TagEditorDlgImpl* pTagEditorDlgImpl) : m_pTagEditorDlgImpl(pTagEditorDlgImpl), m_pTagWriter(pTagEditorDlgImpl->getTagWriter()), m_pCommonData(pTagEditorDlgImpl->getCommonData()) { } /*override*/ int CurrentAlbumModel::rowCount(const QModelIndex&) const { //qDebug("rowCount ret %d", cSize(m_pTagWriter->m_vpMp3HandlerTagData)); return cSize(m_pTagWriter->m_vpMp3HandlerTagData); } /*override*/ int CurrentAlbumModel::columnCount(const QModelIndex&) const { return TagReader::LIST_END + 1; } /*override*/ QVariant CurrentAlbumModel::data(const QModelIndex& index, int nRole) const { LAST_STEP("CurrentAlbumModel::data()"); if (!index.isValid()) { return QVariant(); } int i (index.row()); int j (index.column()); //qDebug("ndx %d %d", i, j); if (Qt::DisplayRole != nRole && Qt::ToolTipRole != nRole && Qt::EditRole != nRole) { return QVariant(); } QString s; if (m_pTagEditorDlgImpl->isSaving()) { return tr("N/A"); } if (m_pTagEditorDlgImpl->isNavigating()) { return ""; } if (0 == j) { const Mp3Handler* p (m_pTagWriter->m_vpMp3HandlerTagData[i]->getMp3Handler()); s = convStr(p->getShortName()); } else { /*const Mp3HandlerTagData& d (*m_pTagWriter->m_vpMp3HandlerTagData[i]); s = convStr(d.getData(TagReader::FEATURE_ON_POS[j - 1]));*/ s = convStr(m_pTagWriter->getData(i, TagReader::FEATURE_ON_POS[j - 1])); } if (Qt::DisplayRole == nRole || Qt::EditRole == nRole) { return s; } QFontMetrics fm (m_pTagEditorDlgImpl->m_pCurrentAlbumG->fontMetrics()); // !!! no need to use "QApplication::fontMetrics()" int nWidth (fm.width(s)); int nMargin (QApplication::style()->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1); // this "1" should probably be another "metric" constant if (nWidth + 2*nMargin + 1 <= m_pTagEditorDlgImpl->m_pCurrentAlbumG->horizontalHeader()->sectionSize(j)) // ttt2 not sure this "nMargin" is correct { //return QVariant(); return ""; // !!! with "return QVariant()" the previous tooltip remains until the cursor moves over another cell that has a tooltip } return s; } //ttt2 perhaps: "right click on crt file header moves to last; left click moves to first"; dragging the columns seems ok, though /*override*/ QVariant CurrentAlbumModel::headerData(int nSection, Qt::Orientation eOrientation, int nRole /* = Qt::DisplayRole*/) const { LAST_STEP("CurrentAlbumModel::headerData"); if (nRole != Qt::DisplayRole) { return QVariant(); } if (Qt::Horizontal == eOrientation) { if (0 == nSection) { return tr("File name"); } return TagReader::tr(TagReader::getLabel(TagReader::FEATURE_ON_POS[nSection - 1])); } return nSection + 1; } /*override*/ Qt::ItemFlags CurrentAlbumModel::flags(const QModelIndex& /*index*/) const { return Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled; } /*override*/ bool CurrentAlbumModel::setData(const QModelIndex& index, const QVariant& value, int nRole /* = Qt::EditRole*/) { if (Qt::EditRole != nRole) { return false; } Mp3HandlerTagData* pData (m_pTagWriter->m_vpMp3HandlerTagData[index.row()]); try { pData->setData(TagReader::FEATURE_ON_POS[index.column() - 1], convStr(value.toString())); m_pTagEditorDlgImpl->updateAssigned(); return true; } catch (const Mp3HandlerTagData::InvalidValue&) { showCritical(m_pTagEditorDlgImpl, tr("Error"), tr("The data contained errors and couldn't be saved")); //ttt2 put focus on album table return false; // ttt2 if it gets here the data is lost; perhaps CurrentAlbumDelegate should be modified more extensively, to not close the editor on Enter if this returns false; } } //====================================================================================================================== //====================================================================================================================== CurrentFileModel::CurrentFileModel(const TagEditorDlgImpl* pTagEditorDlgImpl) : m_pTagEditorDlgImpl(pTagEditorDlgImpl), m_pTagWriter(pTagEditorDlgImpl->getTagWriter()), m_pCommonData(pTagEditorDlgImpl->getCommonData()) { } /*override*/ int CurrentFileModel::rowCount(const QModelIndex&) const { return TagReader::LIST_END; } /*override*/ int CurrentFileModel::columnCount(const QModelIndex&) const { return cSize(m_pTagWriter->m_vTagReaderInfo); } /*override*/ QVariant CurrentFileModel::data(const QModelIndex& index, int nRole) const { LAST_STEP("CurrentFileModel::data()"); if (!index.isValid() || 0 == m_pTagWriter->getCurrentHndl()) { return QVariant(); } int i (index.row()); int j (index.column()); if (Qt::DisplayRole != nRole && Qt::ToolTipRole != nRole) { return QVariant(); } if (m_pTagEditorDlgImpl->isSaving()) { return tr("N/A"); } if (m_pTagEditorDlgImpl->isNavigating()) { return ""; } if (0 == m_pTagWriter->getCurrentHndl()) { return QVariant(); } // may happen in transient states (prev/next album) const Mp3HandlerTagData& d (*m_pTagWriter->getCrtMp3HandlerTagData()); string s1 (d.getData(TagReader::FEATURE_ON_POS[i], j)); QString s (convStr(s1)); if (Qt::DisplayRole == nRole) { return s; } QFontMetrics fm (m_pTagEditorDlgImpl->m_pCurrentFileG->fontMetrics()); // !!! no need to use "QApplication::fontMetrics()" int nWidth (fm.width(s)); int nMargin (QApplication::style()->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1); // this "1" should probably be another "metric" constant if (nWidth + 2*nMargin + 1 <= m_pTagEditorDlgImpl->m_pCurrentFileG->horizontalHeader()->sectionSize(j)) // ttt2 not sure this "nMargin" is correct { //return QVariant(); return ""; // !!! with "return QVariant()" the previous tooltip remains until the cursor moves over another cell that has a tooltip } return s; } /*override*/ QVariant CurrentFileModel::headerData(int nSection, Qt::Orientation eOrientation, int nRole /* = Qt::DisplayRole*/) const { LAST_STEP("CurrentFileModel::headerData"); //if (nRole == Qt::SizeHintRole) { return QSize(CELL_WIDTH - 1, CELL_HEIGHT); } if (nRole != Qt::DisplayRole) { return QVariant(); } if (Qt::Horizontal == eOrientation) { if (nSection >= cSize(m_pTagWriter->m_vTagReaderInfo)) { return QVariant(); } QString sSuff; const TagReaderInfo& inf (m_pTagWriter->m_vTagReaderInfo.at(nSection)); if ( 0 != m_pTagWriter->getCrtMp3HandlerTagData() && ( TrackTextReader::getClassDisplayName() == inf.m_strName || WebReader::getClassDisplayName() == inf.m_strName ) ) { const TagReader* p (m_pTagWriter->getCrtMp3HandlerTagData()->getMatchingReader(nSection)); if (0 != p) { if (TrackTextReader::getClassDisplayName() == inf.m_strName) { const TrackTextReader* pRd (dynamic_cast(p)); CB_ASSERT (0 != pRd); sSuff = QString(" ") + TagReader::tr(pRd->getType()); } else if (WebReader::getClassDisplayName() == inf.m_strName) { const WebReader* pRd (dynamic_cast(p)); CB_ASSERT (0 != pRd); sSuff = " (" + convStr(pRd->getType()) + ")"; // !!! no translation of "Discogs" or MusicBrainz } } } QString s; if (inf.m_bAlone) {//ttt2 see if right-to-left languages would need sSuff first; (they probably need mirror dialogs as well) s = TagReader::tr(inf.m_strName.c_str()) + sSuff; // !!! the "tr()" won't do anything in most cases; the argument can be "Pattern", "Web", "ID3V2.3.0", "ID3V2.4.0", "ID3V1", ... ; only the first 2 are translatable } else { s = QString("%1 %2").arg(TagReader::tr(inf.m_strName.c_str())).arg(inf.m_nPos + 1) + sSuff; // !!! the "tr()" won't do anything in most cases; the argument can be "Pattern", "Web", "ID3V2.3.0", "ID3V2.4.0", "ID3V1", ... ; only the first 2 are translatable } return s; } return TagReader::tr(TagReader::getLabel(TagReader::FEATURE_ON_POS[nSection])); } //====================================================================================================================== //====================================================================================================================== //====================================================================================================================== TagEditorDlgImpl::TagEditorDlgImpl(QWidget* pParent, CommonData* pCommonData, TransfConfig& transfConfig, bool& bDataSaved) : QDialog(pParent, getDialogWndFlags()), Ui::TagEditorDlg(), m_pCommonData(pCommonData), m_bSectionMovedLock(false), m_transfConfig(transfConfig), m_bIsFastSaving(false), m_bIsSaving(false), m_bIsNavigating(false), m_bDataSaved(bDataSaved), m_bWaitingAlbumResize(false), m_bWaitingFileResize(false), m_eArtistsCase(TC_NONE), m_eOthersCase(TC_NONE) { setupUi(this); m_bDataSaved = false; setupVarArtistsBtn(); m_pAssgnBtnWrp = new AssgnBtnWrp (m_pToggleAssignedB); { m_pTagWriter = new TagWriter(m_pCommonData, this, m_bIsFastSaving, m_eArtistsCase, m_eOthersCase); loadTagWriterInf(); connect(m_pTagWriter, SIGNAL(albumChanged()), this, SLOT(onAlbumChanged())); connect(m_pTagWriter, SIGNAL(fileChanged()), this, SLOT(onFileChanged())); connect(m_pTagWriter, SIGNAL(imagesChanged()), this, SLOT(onImagesChanged())); connect(m_pTagWriter, SIGNAL(requestSave()), this, SLOT(on_m_pSaveB_clicked())); connect(m_pTagWriter, SIGNAL(varArtistsUpdated(bool)), this, SLOT(onVarArtistsUpdated(bool))); } m_pCurrentAlbumModel = new CurrentAlbumModel(this); m_pCurrentFileModel = new CurrentFileModel(this); { m_pCurrentAlbumG->verticalHeader()->setResizeMode(QHeaderView::Interactive); m_pCurrentAlbumG->verticalHeader()->setMinimumSectionSize(CELL_HEIGHT); m_pCurrentAlbumG->verticalHeader()->setDefaultSectionSize(CELL_HEIGHT);//*/ m_pCurrentAlbumG->setModel(m_pCurrentAlbumModel); m_pAlbumDel = new CurrentAlbumDelegate(m_pCurrentAlbumG, this); m_pCurrentAlbumG->setItemDelegate(m_pAlbumDel); //connect(m_pCurrentAlbumG, SIGNAL(clicked(const QModelIndex &)), this, SLOT(onAlbSelChanged())); // ttt2 see if both this and next are needed (next seems enough) connect(m_pCurrentAlbumG->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), this, SLOT(onAlbSelChanged())); connect(m_pCurrentAlbumG->selectionModel(), SIGNAL(currentChanged(const QModelIndex&, const QModelIndex &)), this, SLOT(onAlbCrtChanged())); } { m_pCurrentFileG->setVerticalHeader(new NoCropHeaderView(m_pCurrentFileG)); m_pCurrentFileG->verticalHeader()->setResizeMode(QHeaderView::Interactive); m_pCurrentFileG->verticalHeader()->setMinimumSectionSize(CELL_HEIGHT); m_pCurrentFileG->verticalHeader()->setDefaultSectionSize(CELL_HEIGHT);//*/ m_pCurrentFileG->setModel(m_pCurrentFileModel); CurrentFileDelegate* pDel (new CurrentFileDelegate(m_pCurrentFileG, m_pCommonData)); connect(m_pCurrentFileG->horizontalHeader(), SIGNAL(sectionMoved(int, int, int)), this, SLOT(onFileSelSectionMoved(int, int, int))); m_pCurrentFileG->setItemDelegate(pDel); m_pCurrentFileG->horizontalHeader()->setMovable(true); } { int nWidth, nHeight; int nArtistsCase, nOthersCase; m_pCommonData->m_settings.loadTagEdtSettings(nWidth, nHeight, nArtistsCase, nOthersCase); m_eArtistsCase = TextCaseOptions(nArtistsCase); m_eOthersCase = TextCaseOptions(nOthersCase); if (nWidth > 400 && nHeight > 400) { resize(nWidth, nHeight); } else { defaultResize(*this); } } { delete m_pRemovable1L; // m_pRemovableL's only job is to make m_pImagesW visible in QtDesigner QWidget* pWidget (new QWidget (this)); pWidget->setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding)); QHBoxLayout* pScrlLayout (new QHBoxLayout (pWidget)); pWidget->setLayout(pScrlLayout); pWidget->layout()->setContentsMargins(0, 0, 0, 4 + 1); // "+1" added to look better, but don't know how to calculate m_pImgScrollArea = new QScrollArea (m_pImagesW); m_pImagesW->layout()->addWidget(m_pImgScrollArea); m_pImgScrollArea->setWidget(pWidget); m_pImgScrollArea->setFrameShape(QFrame::NoFrame); } { delete m_pRemovable2L; // m_pRemovable2L's only job is to make m_pPatternsW visible in QtDesigner createPatternButtons(); } //layout()->update(); /*layout()->update(); widget_4->layout()->update(); widget->layout()->update(); layout()->update(); widget_4->layout()->update(); widget->layout()->update();*/ m_pTagWriter->reloadAll("", TagWriter::CLEAR_DATA, TagWriter::CLEAR_ASSGN); selectMainCrt(); if (!m_pCommonData->m_bShowCustomCloseButtons) { m_pCloseB->hide(); } resizeIcons(); m_pCurrentAlbumG->installEventFilter(this); installEventFilter(this); { int nFileHght ((CELL_HEIGHT)*10 + m_pCurrentFileG->horizontalHeader()->height() + 2*QApplication::style()->pixelMetric(QStyle::PM_DefaultFrameWidth, 0, m_pCurrentFileG)); m_pCurrentFileG->setMaximumHeight(nFileHght); m_pCurrentFileG->setMinimumHeight(nFileHght); } { QAction* p (new QAction(this)); p->setShortcut(QKeySequence("F1")); connect(p, SIGNAL(triggered()), this, SLOT(onHelp())); addAction(p); } QTimer::singleShot(1, this, SLOT(onShow())); // just calls resizeTagEditor(); !!! needed to properly resize the table columns; album and file tables have very small widths until they are actually shown, so calling resizeTagEditor() earlier is pointless; calling update() on various layouts seems pointless as well; (see also DoubleList::resizeEvent() ) } void TagEditorDlgImpl::createPatternButtons() { QBoxLayout* pLayout (dynamic_cast(m_pPatternsW->layout())); CB_ASSERT (0 != pLayout); QObjectList l (m_pPatternsW->children()); for (int i = 0, n = l.size(); i < n; ++i) { if (l[i] != pLayout) { delete l[i]; } } const set& snActivePatterns (m_pTagWriter->getActivePatterns()); m_vpPattButtons.clear(); const vector& vstrPatterns (m_pTagWriter->getPatterns()); int n (cSize(vstrPatterns)); for (int i = 0; i < n; ++i) { QToolButton* p (new QToolButton(m_pPatternsW)); p->setText(toNativeSeparators(convStr(vstrPatterns[i]))); p->setCheckable(true); m_vpPattButtons.push_back(p); if (snActivePatterns.count(i) > 0) { p->setChecked(true); } connect(p, SIGNAL(clicked()), this, SLOT(onPatternClicked())); pLayout->insertWidget(i, p); } pLayout->insertStretch(n); m_pPatternsW->setVisible(!m_vpPattButtons.empty()); } void TagEditorDlgImpl::onPatternClicked() { set s; for (int i = 0; i < cSize(m_vpPattButtons); ++i) { if (m_vpPattButtons[i]->isChecked()) { s.insert(i); } } m_pTagWriter->setActivePatterns(s); } TagEditorDlgImpl::~TagEditorDlgImpl() { saveTagWriterInf(); m_pCommonData->m_settings.saveTagEdtSettings(width(), height(), int(m_eArtistsCase), int(m_eOthersCase)); delete m_pCurrentAlbumModel; delete m_pCurrentFileModel; delete m_pTagWriter; delete m_pAssgnBtnWrp; } string TagEditorDlgImpl::run() { TRACER("TagEditorDlgImpl::run()"); exec(); return m_pTagWriter->getCurrentName(); } void TagEditorDlgImpl::setupVarArtistsBtn() { m_pVarArtistsB->setEnabled(m_pCommonData->m_bItunesVarArtists || m_pCommonData->m_bWmpVarArtists); m_pVarArtistsB->setToolTip(m_pVarArtistsB->isEnabled() ? tr("Toggle \"Various Artists\"") : tr("To enable \"Various Artists\" you need to open the\n" "configuration dialog, go to the \"Others\" tab and\n" "check the corresponding checkbox(es)", "this is a multi-line tooltip")); } void TagEditorDlgImpl::resizeTagEditor() { if (!m_bWaitingAlbumResize) { m_bWaitingAlbumResize = true; QTimer::singleShot(1, this, SLOT(onResizeTagEditorDelayed())); } } void TagEditorDlgImpl::onResizeTagEditorDelayed() // needed because it's pointless to call this while navigating, when the text for all cells is returned as "" { m_bWaitingAlbumResize = false; QWidget* pParent (m_pCurrentAlbumG->parentWidget()); //cout << "\n========================\n" << endl; listWidget(pParent); m_pImgScrollArea->widget()->adjustSize(); //m_pImgScrollArea->adjustSize(); int nHeight (m_pImgScrollArea->widget()->height() /*+ 10*/); if (m_pImgScrollArea->widget()->width() > m_pImgScrollArea->width()) { int nScrollBarWidth (QApplication::style()->pixelMetric(QStyle::PM_ScrollBarExtent)); /*if (0 != QApplication::style()->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents)) // see if this is needed for Oxygen { nRes -= 2*QApplication::style()->pixelMetric(QStyle::PM_DefaultFrameWidth, 0, &m_tbl); //ttt2 Qt 4.4 (and below) - specific; in 4.5 there's a QStyle::PM_ScrollView_ScrollBarSpacing }*/ nHeight += nScrollBarWidth; } m_pImagesW->setMaximumSize(100000, nHeight); // ttt2 see about adding space for horizontal scrollbar pParent->layout()->activate(); // resize m_pCurrentAlbumG based on the image list being visible or not m_pCurrentAlbumModel->emitLayoutChanged(); // !!! force recalculation of the vertical scrollbar's visibility //m_pCurrentAlbumG->horizontalHeader()->hideSection(1); SimpleQTableViewWidthInterface intf1 (*m_pCurrentAlbumG); /*intf1.setMinWidth(2, 600); intf1.setMinWidth(0, 50); intf1.setMinWidth(1, 50);*/ /*intf1.setFixedWidth(0, 50); intf1.setFixedWidth(1, 50); intf1.setFixedWidth(2, 50); intf1.setFixedWidth(3, 50); intf1.setFixedWidth(4, 50); intf1.setFixedWidth(5, 50);*/ ColumnResizer rsz1 (intf1, 100, ColumnResizer::FILL, ColumnResizer::CONSISTENT_RESULTS); resizeFile(); } void TagEditorDlgImpl::resizeFile() // resizes the "current file" grid; called by resizeTagEditor() and by onFileChanged(); { if (!m_bWaitingFileResize) { m_bWaitingFileResize = true; QTimer::singleShot(1, this, SLOT(onResizeFileDelayed())); } } void TagEditorDlgImpl::onResizeFileDelayed() // resizes the "current file" grid; called by resizeTagEditor() and by onFileChanged(); { m_bWaitingFileResize = false; SimpleQTableViewWidthInterface intf2 (*m_pCurrentFileG); ColumnResizer rsz2 (intf2, 100, ColumnResizer::DONT_FILL, ColumnResizer::CONSISTENT_RESULTS); } /*override*/ void TagEditorDlgImpl::resizeEvent(QResizeEvent* pEvent) { resizeTagEditor(); QDialog::resizeEvent(pEvent); } void TagEditorDlgImpl::on_m_pQueryDiscogsB_clicked() { if (!closeEditor()) { return; } /*QPixmap pic ("/r/temp/1/tmp2/c pic/b/A/cg.jpg"); ImageInfo img (ImageInfo::OK, pic); m_pCommonData->m_imageColl.addImage(img); onTagWriterChanged(); //m_pCurrentAlbumG->adjustSize(); m_pTagWriter->resizeCols(); return;//*/ AlbumInfo* pAlbumInfo; ImageInfo* pImageInfo; bool bOk; { DiscogsDownloader dlg (this, m_pCommonData->m_settings, m_pCommonData->m_bSaveDownloadedData); //dlg.exec(); //dlg.getInfo("Beatles", "Help"); //dlg.getInfo("Roxette", "Tourism"); //if (dlg.getInfo("Kaplansky", "", 12, pAlbumInfo, pImageInfo)) string strArtist, strAlbum; m_pTagWriter->getAlbumInfo(strArtist, strAlbum); bOk = dlg.getInfo(strArtist, strAlbum, cSize(m_pTagWriter->m_vpMp3HandlerTagData), pAlbumInfo, pImageInfo); // !!! it's important that the dialog is in an enclosed scope; otherwise column resizing doesn't work well or m_pCurrentAlbumG gets shrinked to a really small size } if (bOk) { int nImgPos (-1); if (0 != pImageInfo) { nImgPos = m_pTagWriter->addImage(*pImageInfo, 0 == pAlbumInfo ? TagWriter::CONSIDER_UNASSIGNED : TagWriter::CONSIDER_ASSIGNED); delete pImageInfo; } if (0 != pAlbumInfo) { if (-1 != nImgPos) { pAlbumInfo->m_imageInfo = m_pTagWriter->getImageColl()[nImgPos].m_imageInfo; } pAlbumInfo->m_strSourceName = "Discogs"; m_pTagWriter->addAlbumInfo(*pAlbumInfo); delete pAlbumInfo; } //onTagWriterChanged(); // also calls resizeTagEditor(); } } void TagEditorDlgImpl::on_m_pQueryMusicBrainzB_clicked() { if (!closeEditor()) { return; } AlbumInfo* pAlbumInfo; ImageInfo* pImageInfo; bool bOk; { MusicBrainzDownloader dlg (this, m_pCommonData->m_settings, m_pCommonData->m_bSaveDownloadedData); //dlg.exec(); //dlg.getInfo("Beatles", "Help"); //dlg.getInfo("Roxette", "Tourism"); //if (dlg.getInfo("Kaplansky", "", 12, pAlbumInfo, pImageInfo)) string strArtist, strAlbum; m_pTagWriter->getAlbumInfo(strArtist, strAlbum); bOk = dlg.getInfo(strArtist, strAlbum, cSize(m_pTagWriter->m_vpMp3HandlerTagData), pAlbumInfo, pImageInfo); // !!! it's important that the dialog is in an enclosed scope; otherwise column resizing doesn't work well or m_pCurrentAlbumG gets shrinked to a really small size } if (bOk) { int nImgPos (-1); if (0 != pImageInfo) { nImgPos = m_pTagWriter->addImage(*pImageInfo, 0 == pAlbumInfo ? TagWriter::CONSIDER_UNASSIGNED : TagWriter::CONSIDER_ASSIGNED); //resizeTagEditor(); delete pImageInfo; } if (0 != pAlbumInfo) { if (-1 != nImgPos) { pAlbumInfo->m_imageInfo = m_pTagWriter->getImageColl()[nImgPos].m_imageInfo; } pAlbumInfo->m_strSourceName = "MusicBrainz"; m_pTagWriter->addAlbumInfo(*pAlbumInfo); delete pAlbumInfo; } //onTagWriterChanged(); // also calls resizeTagEditor(); } } void TagEditorDlgImpl::on_m_pEditPatternsB_clicked() { if (!closeEditor()) { return; } vector > v; vector v1 (m_pTagWriter->getPatterns()); for (int i = 0, n = cSize(v1); i < n; ++i) { v.push_back(make_pair(v1[i], -1)); } vector u; //u.push_back("%a/%b[[ ](%y)]/[[%r]%n][ ][-[ ]]%t"); /*u.push_back("%a/%b[[ ](%y)]/[[%r]%n][.][ ][-[ ]]%t"); //ttt OS specific u.push_back("%b[[ ](%y)]/[[%r]%n][.][ ]%a[ ]-[ ]%t"); u.push_back("%a - %b/[[%r]%n][.][ ][-[ ]]%t"); u.push_back("[%n][.][ ][-[ ]]%t");*/ u.push_back("%a/%b[(%y)]/[[%r]%n][.][ ][-]%t"); u.push_back("%b[(%y)]/[[%r]%n][.]%a-%t"); u.push_back("%a-%b/[[%r]%n][.][ ][-]%t"); u.push_back("/%n[.]%a-%t"); u.push_back("[%n][.][ ][-]%t"); TagEdtPatternsDlgImpl dlg (this, m_pCommonData->m_settings, u); if (dlg.run(v)) { m_pTagWriter->updatePatterns(v); createPatternButtons(); } } void TagEditorDlgImpl::on_m_pPaletteB_clicked() { if (!closeEditor()) { return; } PaletteDlgImpl dlg (m_pCommonData, this); dlg.exec(); m_pCommonData->m_settings.saveMiscConfigSettings(m_pCommonData); } // adds new ImageInfoPanelWdgImpl instances, connects assign button and calls resizeTagEditor() void TagEditorDlgImpl::onImagesChanged() { QLayout* pLayout (m_pImgScrollArea->widget()->layout()); const ImageColl& imgColl (m_pTagWriter->getImageColl()); int nVecCnt (imgColl.size()); int nPanelCnt (pLayout->count()); CB_ASSERT (nVecCnt >= nPanelCnt); for (int i = nPanelCnt; i < nVecCnt; ++i) { ImageInfoPanelWdgImpl* p (new ImageInfoPanelWdgImpl(this, imgColl[i], i)); pLayout->addWidget(p); p->show(); connect(p, SIGNAL(assignImage(int)), m_pTagWriter, SLOT(onAssignImage(int))); connect(p, SIGNAL(eraseFile(int)), m_pTagWriter, SLOT(onEraseFile(int))); m_pTagWriter->addImgWidget(p); } resizeTagEditor(); } void TagEditorDlgImpl::onVarArtistsUpdated(bool bVarArtists) { static QPixmap picSa (":/images/va_sa.svg"); static QPixmap picVa (":/images/va_va.svg"); m_pVarArtistsB->setIcon(bVarArtists ? picVa : picSa); } void TagEditorDlgImpl::onAlbumChanged(/*bool bContentOnly*/) { if (m_pTagWriter->m_vpMp3HandlerTagData.empty()) { // there were some files, the transform settings caused the files to be removed when saving; close the dialog; reject(); return; } resizeTagEditor(); m_pCurrentAlbumModel->emitLayoutChanged(); const Mp3Handler* p (m_pTagWriter->m_vpMp3HandlerTagData.at(0)->getMp3Handler()); QString qs (toNativeSeparators(convStr(p->getDir()))); #ifndef WIN32 #else if (2 == qs.size() && ':' == qs[1]) { qs += "\\"; } #endif m_pCrtDirTagEdtE->setText(qs); } void TagEditorDlgImpl::on_m_pConfigB_clicked() { if (!closeEditor()) { return; } SaveOpt eSaveOpt (save(IMPLICIT)); if (SAVED != eSaveOpt && DISCARDED != eSaveOpt) { return; } ConfigDlgImpl dlg (m_transfConfig, m_pCommonData, this, ConfigDlgImpl::SOME_TABS); if (dlg.run()) { m_pCommonData->m_settings.saveMiscConfigSettings(m_pCommonData); m_pCommonData->m_settings.saveTransfConfig(m_transfConfig); // transformation resizeIcons(); resizeTagEditor(); } m_pTagWriter->reloadAll("", TagWriter::CLEAR_DATA, TagWriter::CLEAR_ASSGN); // !!! regardless of the conf being cancelled, so it's consistent with the question asked at the beginning setupVarArtistsBtn(); } void TagEditorDlgImpl::on_m_pCloseB_clicked() { SaveOpt eSaveOpt (save(IMPLICIT)); if (SAVED != eSaveOpt && DISCARDED != eSaveOpt) { return; } reject(); } void TagEditorDlgImpl::onFileChanged() { m_pCurrentFileModel->emitLayoutChanged(); const Mp3HandlerTagData* p (m_pTagWriter->getCrtMp3HandlerTagData()); if (0 != p) { int nPic (p->getImage()); m_pTagWriter->selectImg(nPic); } //resizeTagEditor(); resizeFile(); } //======================================================================================================================================================== //======================================================================================================================================================== //======================================================================================================================================================== void TagEditorDlgImpl::loadTagWriterInf() { { // tags m_pTagWriter->clearShowedNonSeqWarn(); bool bErr1 (false), bErr2 (false); vector vstrNames (m_pCommonData->m_settings.loadVector("tagWriter/tagNames", bErr1)); vector vstrPositions (m_pCommonData->m_settings.loadVector("tagWriter/tagPositions", bErr2)); vector v; int n (min(cSize(vstrNames), cSize(vstrPositions))); for (int i = 0; i < n; ++i) { string strName (vstrNames[i]); int nPos (atoi(vstrPositions[i].c_str())); if (strName.empty() || nPos < 0) // ttt2 the test for nPos can be improved; we know that a value was found in the file, but it may even be non-numeric { bErr1 = true; break; } // ttt2 perhaps fix duplicate or missing positions, but it's no big deal; TagWriter takes care of duplicates itself and if something is missing it will get added back when needed v.push_back(TagReaderInfo(strName, nPos, TagReaderInfo::ONE_OF_MANY)); } if (bErr1 || bErr2) { showWarning(this, tr("Error setting up the tag order"), tr("An invalid value was found in the configuration file. You'll have to sort the tags again.")); } if (v.empty()) { // use default v.push_back(TagReaderInfo(Id3V230Stream::getClassDisplayName(), 0, TagReaderInfo::ONE_OF_MANY)); v.push_back(TagReaderInfo(Id3V230Stream::getClassDisplayName(), 1, TagReaderInfo::ONE_OF_MANY)); v.push_back(TagReaderInfo(Id3V240Stream::getClassDisplayName(), 0, TagReaderInfo::ONE_OF_MANY)); v.push_back(TagReaderInfo(Id3V240Stream::getClassDisplayName(), 1, TagReaderInfo::ONE_OF_MANY)); v.push_back(TagReaderInfo(Id3V1Stream::getClassDisplayName(), 0, TagReaderInfo::ONE_OF_MANY)); v.push_back(TagReaderInfo(Id3V1Stream::getClassDisplayName(), 1, TagReaderInfo::ONE_OF_MANY)); } m_pTagWriter->addKnownInf(v); } { // pattern readers bool bErr (false); vector vstrPatterns (m_pCommonData->m_settings.loadVector("tagWriter/patterns", bErr)); //if (bErr) { qDebug("error at loadVector(\"tagWriter/patterns\")"); } //ttt remove int n (cSize(vstrPatterns)); vector > v; { for (int i = 0; i < n; ++i) { string strPatt (vstrPatterns[i]); if (strPatt.empty()) { bErr = true; } v.push_back(make_pair(strPatt, -1)); } } /*if (v.empty()) { // use default (only if the user didn't remove all patterns on purpose) //v.push_back(make_pair(string("%a/%b[[ ](%y)]/[[%r]%n][ ][-[ ]]%t"), -1)); v.push_back(make_pair(string("%a/%b[[ ](%y)]/[[%r]%n][.][ ][-[ ]]%t"), -1)); //ttt OS specific v.push_back(make_pair(string("%b[[ ](%y)]/[[%r]%n][.][ ]%a[ ]-[ ]%t"), -1)); v.push_back(make_pair(string("%a - %b/[[%r]%n][.][ ][-[ ]]%t"), -1)); v.push_back(make_pair(string("[%n][.][ ][-[ ]]%t"), -1)); } else if (1 == cSize(v) && "*" == v[0].first) { v.clear(); }*/ bErr = !m_pTagWriter->updatePatterns(v) || bErr; bool bErr2; vector vstrActivePatterns (m_pCommonData->m_settings.loadVector("tagWriter/activePatterns", bErr2)); set snActivePatterns; for (int i = 0; i < cSize(vstrActivePatterns); ++i) { snActivePatterns.insert(atoi(vstrActivePatterns[i].c_str())); } m_pTagWriter->setActivePatterns(snActivePatterns); //if (bErr) { qDebug("error when reading patterns"); } //ttt remove if (bErr || bErr2) { showWarning(this, tr("Error setting up patterns"), tr("An invalid value was found in the configuration file. You'll have to set up the patterns manually.")); } } } void TagEditorDlgImpl::saveTagWriterInf() { { vector vstrNames, vstrPositions; const vector& v (m_pTagWriter->getSortedKnownTagReaders()); char a [15]; for (int i = 0, n = cSize(v); i < n; ++i) { vstrNames.push_back(v[i].m_strName); sprintf(a, "%d", v[i].m_nPos); vstrPositions.push_back(a); } m_pCommonData->m_settings.saveVector("tagWriter/tagNames", vstrNames); m_pCommonData->m_settings.saveVector("tagWriter/tagPositions", vstrPositions); } { vector v (m_pTagWriter->getPatterns()); /*if (v.empty()) { v.push_back("*"); }*/ m_pCommonData->m_settings.saveVector("tagWriter/patterns", v); vector u; char a [15]; const set& snActivePatterns (m_pTagWriter->getActivePatterns()); for (set::const_iterator it = snActivePatterns.begin(); it != snActivePatterns.end(); ++it) { sprintf(a, "%d", *it); u.push_back(a); } m_pCommonData->m_settings.saveVector("tagWriter/activePatterns", u); } } void TagEditorDlgImpl::on_m_pToggleAssignedB_clicked() { if (!closeEditor()) { return; } m_pAssgnBtnWrp->setState(m_pTagWriter->toggleAssigned(m_pAssgnBtnWrp->getState())); } void TagEditorDlgImpl::on_m_pReloadB_clicked() { if (!closeEditor()) { return; } bool bHasUnsavedAssgn (false); for (int i = 0, n = cSize(m_pTagWriter->m_vpMp3HandlerTagData); i < n; ++i) { bool bAssgn (false); bool bNonId3V2 (false); m_pTagWriter->hasUnsaved(i, bAssgn, bNonId3V2); bHasUnsavedAssgn = bHasUnsavedAssgn || bAssgn; } if (bHasUnsavedAssgn) { int k (showMessage(this, QMessageBox::Warning, 1, 1, tr("Warning"), tr("Reloading the current album causes all unsaved changes to be lost. Really reload?"), tr("Reload"), tr("Cancel"))); if (0 != k) { return; } } m_pTagWriter->reloadAll(m_pTagWriter->getCurrentName(), TagWriter::CLEAR_DATA, TagWriter::CLEAR_ASSGN); updateAssigned(); } //ttt2 perhaps transf to set recording time based on file date; another to set file date based on rec time // copies the values from the first row to the other rows for columns that have at least a cell selected (doesn't matter if more than 1 cells are selected); void TagEditorDlgImpl::on_m_pCopyFirstB_clicked() { if (!closeEditor()) { return; } m_pTagWriter->copyFirst(); } void TagEditorDlgImpl::on_m_pVarArtistsB_clicked() { if (!closeEditor()) { return; } m_pTagWriter->toggleVarArtists(); } void TagEditorDlgImpl::on_m_pCaseB_clicked() { QMenu menu; vector vpAct; QAction* pAct; for (int i = TC_NONE; i <= TC_SENTENCE; ++i) { pAct = new QAction(tr("Artists - %1").arg(TagReader::tr(getCaseAsStr(TextCaseOptions(i)))), &menu); menu.addAction(pAct); vpAct.push_back(pAct); } menu.addSeparator(); for (int i = TC_NONE; i <= TC_SENTENCE; ++i) { pAct = new QAction(tr("Others - %1").arg(TagReader::tr(getCaseAsStr(TextCaseOptions(i)))), &menu); menu.addAction(pAct); vpAct.push_back(pAct); } for (int i = 0; i < cSize(vpAct); ++i) { vpAct[i]->setCheckable(true); } CB_ASSERT(m_eArtistsCase >= TC_NONE && m_eArtistsCase <= TC_SENTENCE); CB_ASSERT(m_eOthersCase >= TC_NONE && m_eOthersCase <= TC_SENTENCE); vpAct[m_eArtistsCase - TC_NONE]->setChecked(true); vpAct[m_eOthersCase - TC_NONE + 5]->setChecked(true); QAction* p (menu.exec(m_pCaseB->mapToGlobal(QPoint(0, m_pCaseB->height())))); if (0 != p) { int nIndex (std::find(vpAct.begin(), vpAct.end(), p) - vpAct.begin()); if (nIndex < 5) { m_eArtistsCase = TextCaseOptions(nIndex - 1); } else { m_eOthersCase = TextCaseOptions(nIndex - 1 - 5); } m_pTagWriter->reloadAll(m_pTagWriter->getCurrentName(), TagWriter::DONT_CLEAR_DATA, TagWriter::DONT_CLEAR_ASSGN); } } void TagEditorDlgImpl::on_m_pSaveB_clicked() //ttt2 perhaps make this save selected list, by using SHIFT { if (!closeEditor()) { return; } save(EXPLICIT); //SaveOpt eRes save(true); /*if (SAVED == save(true)) { //m_pTagWriter->clearOrigVal(); }*/ } void TagEditorDlgImpl::on_m_pPasteB_clicked() { if (!closeEditor()) { return; } m_pTagWriter->paste(); } void TagEditorDlgImpl::on_m_pSortB_clicked() { if (!closeEditor()) { return; } clearSelection(); m_pTagWriter->sort(); //m_pCurrentAlbumModel->emitLayoutChanged(); } void TagEditorDlgImpl::onAlbSelChanged() { updateAssigned(); /*QItemSelectionModel* pSelModel (m_pCurrentAlbumG->selectionModel()); int n (pSelModel->currentIndex().row()); qDebug("onAlbSelChanged - CurrentFile=%d", n); m_pTagWriter->setCrt(n);*/ // !!! incorrect, because currentIndex() returns the previous index; that's why onAlbCrtChanged() is needed } void TagEditorDlgImpl::onAlbCrtChanged() { QItemSelectionModel* pSelModel (m_pCurrentAlbumG->selectionModel()); int n (pSelModel->currentIndex().row()); if (-1 == n) { return; } m_pTagWriter->setCrt(n); } void TagEditorDlgImpl::onFileSelSectionMoved(int /*nLogicalIndex*/, int nOldVisualIndex, int nNewVisualIndex) { NonblockingGuard sectionMovedGuard (m_bSectionMovedLock); if (!sectionMovedGuard) { return; } QItemSelectionModel* pSelModel (m_pCurrentAlbumG->selectionModel()); QModelIndex selNdx (pSelModel->currentIndex()); //QModelIndex topNdx (m_pCurrentAlbumG->indexAt(QPoint(0, 0))); int nHrzPos (m_pCurrentAlbumG->horizontalScrollBar()->value()); int nVertPos (m_pCurrentAlbumG->verticalScrollBar()->value()); m_pCurrentFileG->horizontalHeader()->moveSection(nNewVisualIndex, nOldVisualIndex); m_pTagWriter->moveReader(nOldVisualIndex, nNewVisualIndex); pSelModel->setCurrentIndex(selNdx, QItemSelectionModel::SelectCurrent); m_pCurrentAlbumG->horizontalScrollBar()->setValue(nHrzPos); m_pCurrentAlbumG->verticalScrollBar()->setValue(nVertPos); //ttt2 perhaps detect when files are removed/changed; while most tags in Id3V2 are stored with the handler, the longer ones (e.g. pictures, but others might be affected too) are dumped from memory by Id3V230Frame's constructor and retrieved from disk when needed; the user should be notified when something becomes unavailable; not sure where to do this, though: Id3V2StreamBase::getImage() is good for images, but other frames might need something similar; } void TagEditorDlgImpl::on_m_pNextB_clicked() { if (!closeEditor()) { return; } SaveOpt eSaveOpt (save(IMPLICIT)); if (SAVED != eSaveOpt && DISCARDED != eSaveOpt) { return; } if (m_pCommonData->nextAlbum()) { ValueRestorer rst1 (m_bIsNavigating); m_bIsNavigating = true; m_pTagWriter->clearShowedNonSeqWarn(); m_pTagWriter->reloadAll("", TagWriter::CLEAR_DATA, TagWriter::CLEAR_ASSGN); clearSelection(); // actually here it selects the first cell QTimer::singleShot(1, this, SLOT(onShowPatternNote())); } } void TagEditorDlgImpl::on_m_pPrevB_clicked() { if (!closeEditor()) { return; } SaveOpt eSaveOpt (save(IMPLICIT)); if (SAVED != eSaveOpt && DISCARDED != eSaveOpt) { return; } if (m_pCommonData->prevAlbum()) { ValueRestorer rst1 (m_bIsNavigating); m_bIsNavigating = true; m_pTagWriter->clearShowedNonSeqWarn(); m_pTagWriter->reloadAll("", TagWriter::CLEAR_DATA, TagWriter::CLEAR_ASSGN); clearSelection(); // actually here it selects the first cell QTimer::singleShot(1, this, SLOT(onShowPatternNote())); } } void TagEditorDlgImpl::clearSelection() { QItemSelectionModel* pSelModel (m_pCurrentAlbumG->selectionModel()); pSelModel->setCurrentIndex(m_pCurrentAlbumModel->index(0, 0), QItemSelectionModel::SelectCurrent); // !!! needed because otherwise pSelModel->clear() might trigger onAlbSelChanged() for an invalid cell (e.g. when current album has 15 songs, with 12th selected, and the next only has 10) pSelModel->clear(); pSelModel->setCurrentIndex(m_pCurrentAlbumModel->index(0, 0), QItemSelectionModel::SelectCurrent); // !!! needed to have a cell selected //ttt2 try and get rid of duplicate call to setCurrentIndex; } void TagEditorDlgImpl::selectMainCrt() // selects the song that is current in the main window (to be called on the constructor); { QItemSelectionModel* pSelModel (m_pCurrentAlbumG->selectionModel()); const Mp3Handler* pCrt (m_pCommonData->getCrtMp3Handler()); for (int i = 0, n = cSize(m_pTagWriter->m_vpMp3HandlerTagData); i < n; ++i) { const Mp3Handler* p (m_pTagWriter->m_vpMp3HandlerTagData[i]->getMp3Handler()); if (p == pCrt) { pSelModel->setCurrentIndex(m_pCurrentAlbumModel->index(i, 0), QItemSelectionModel::SelectCurrent); return; } } CB_ASSERT (false); } void TagEditorDlgImpl::updateAssigned() { m_pAssgnBtnWrp->setState(m_pTagWriter->updateAssigned(getSelFields())); } // returns the selceted fields, with the first elem as the song number and the second as the field index in TagReader::Feature (it converts columns to fields using TagReader::FEATURE_ON_POS); first column (file name) is ignored vector > TagEditorDlgImpl::getSelFields() const { QItemSelectionModel* pSelModel (m_pCurrentAlbumG->selectionModel()); QModelIndexList listSel (pSelModel->selectedIndexes()); vector > vFields; for (QModelIndexList::iterator it = listSel.begin(), end = listSel.end(); it != end; ++it) { QModelIndex ndx (*it); int nSong (ndx.row()); int nField (ndx.column()); if (nField > 0) { vFields.push_back(make_pair(nSong, TagReader::FEATURE_ON_POS[nField - 1])); } } return vFields; } void TagEditorDlgImpl::resizeIcons() { vector v; v.push_back(m_pPrevB); v.push_back(m_pNextB); v.push_back(m_pConfigB); v.push_back(m_pSaveB); v.push_back(m_pReloadB); v.push_back(m_pVarArtistsB); v.push_back(m_pCaseB); v.push_back(m_pCopyFirstB); v.push_back(m_pSortB); v.push_back(m_pToggleAssignedB); v.push_back(m_pPasteB); v.push_back(m_pEditPatternsB); v.push_back(m_pPaletteB); v.push_back(m_pCloseB); int k (m_pCommonData->m_nMainWndIconSize); for (int i = 0, n = cSize(v); i < n; ++i) { QToolButton* p (v[i]); p->setMaximumSize(k, k); p->setMinimumSize(k, k); p->setIconSize(QSize(k - 4, k - 4)); } { QToolButton* p (m_pQueryDiscogsB); int h ((k - 4)*2/3); //int w (h*135/49); int w (h*135/60); p->setMaximumSize(w + 4, k); p->setMinimumSize(w + 4, k); p->setIconSize(QSize(w, h)); p->setEnabled(false); } { QToolButton* p (m_pQueryMusicBrainzB); int h (k - 4); int w (h*171/118); p->setMaximumSize(w + 4, k); p->setMinimumSize(w + 4, k); p->setIconSize(QSize(w, h)); } } #if 0 /*override*/ bool TagEditorDlgImpl::eventFilter(QObject* pObj, QEvent* pEvent) { QKeyEvent* pKeyEvent (dynamic_cast(pEvent)); int nKey (0 == pKeyEvent ? 0 : pKeyEvent->key()); //static int s_nCnt (0); /*if (0 != pKeyEvent && Qt::Key_Escape == nKey) { qDebug("%d. %s %d", s_nCnt++, pObj->objectName().toUtf8().constData(), (int)pEvent->type()); //return QDialog::eventFilter(pObj, pEvent); }*/ static bool bIgnoreNextEsc (false); if (0 != pKeyEvent && Qt::Key_Escape == nKey && this == pObj && QEvent::ShortcutOverride == pEvent->type()) { //qDebug("kill esc"); //return true; //SaveOpt eSaveOpt (save(false)); if (SAVED != eSaveOpt && DISCARDED != eSaveOpt) { return true; } //if (this != pObj) { return true; } //qDebug("kill esc"); //return true; qDebug(" >> save"); //qDebug("%d. %s %d", s_nCnt++, pObj->objectName().toUtf8().constData(), (int)pEvent->type()); //return QDialog::eventFilter(pObj, pEvent); //if (this == pObj) { SaveOpt eSaveOpt (save(false)); if (SAVED == eSaveOpt || DISCARDED == eSaveOpt) { //bIgnoreNextEsc = false; goto e1; } } //qDebug(" ###### save"); //SaveOpt eSaveOpt (save(false)); if (SAVED != eSaveOpt && DISCARDED != eSaveOpt) { return true; } bIgnoreNextEsc = true; return true; } //if (0 == pKeyEvent || Qt::Key_Escape != nKey) { return QDialog::eventFilter(pObj, pEvent); } e1: if (0 != pKeyEvent && Qt::Key_Escape == nKey) { //qDebug("passed through: %d. %s %d", s_nCnt++, pObj->objectName().toUtf8().constData(), (int)pEvent->type()); } if (0 != pKeyEvent && Qt::Key_Escape == nKey && this == pObj && QEvent::KeyPress == pEvent->type()) { bool b (bIgnoreNextEsc); bIgnoreNextEsc = false; if (b) { qDebug(" KILL"); return true; } } return QDialog::eventFilter(pObj, pEvent); } #endif /*override*/ bool TagEditorDlgImpl::eventFilter(QObject* pObj, QEvent* pEvent) { QKeyEvent* pKeyEvent (dynamic_cast(pEvent)); int nKey (0 == pKeyEvent ? 0 : pKeyEvent->key()); /*static int s_nCnt (0); if (0 != pKeyEvent //&& Qt::Key_Escape == nKey ) { qDebug("%d. %s %d", s_nCnt++, pObj->objectName().toUtf8().constData(), (int)pEvent->type()); //return QDialog::eventFilter(pObj, pEvent); }//*/ if (0 != pKeyEvent && Qt::Key_Escape == nKey && this == pObj && QEvent::KeyPress == pEvent->type()) { SaveOpt eSaveOpt (save(IMPLICIT)); if (SAVED != eSaveOpt && DISCARDED != eSaveOpt) { return true; } } if (0 != pKeyEvent && Qt::Key_Delete == nKey && m_pCurrentAlbumG == pObj && QEvent::KeyPress == pEvent->type()) { eraseSelFields(); return true; } return QDialog::eventFilter(pObj, pEvent); } void TagEditorDlgImpl::eraseSelFields() // erases the values in the selected fields { vector > vSel (getSelFields()); m_pTagWriter->eraseFields(vSel); //m_pAssgnBtnWrp->setState(m_pTagWriter->updateAssigned(vector >())); m_pAssgnBtnWrp->setState(m_pTagWriter->updateAssigned(vSel)); m_pAssgnBtnWrp->setState(m_pTagWriter->updateAssigned(vector >())); // we don't want to keep any previous value updateAssigned(); // needed for the "assign" button to work, because the previous line cleared m_pTagWriter->m_sSelOrigVal m_pTagWriter->reloadAll(m_pTagWriter->getCurrentName(), TagWriter::DONT_CLEAR_DATA, TagWriter::DONT_CLEAR_ASSGN); //ttt2 way too many ugly calls, including 2 required calls to m_pAssgnBtnWrp->setState(); restructure the whole "assigned" thing; /* some details (not completely up-to-date): TagWriter::toggleAssigned() should be called when the user clicks on the assign button; changes status of selected cells and returns the new state of m_pToggleAssignedB TagWriter::updateAssigned() should be called when the selection changes; returns the new state of m_pToggleAssignedB; both TagEditorDlgImpl and TagWriter have updateAssigned(), which is confusing better have toggleAssigned() and updateAssigned() use signals (and perhaps guards) */ } /*override*/ void TagEditorDlgImpl::closeEvent(QCloseEvent* pEvent) { pEvent->ignore(); QCoreApplication::postEvent(this, new QKeyEvent(QEvent::KeyPress, Qt::Key_Escape, Qt::NoModifier)); // ttt2 not sure is a KeyRelease pair is expected } //ttt2 disabled widgets should have tooltips saying why are they disabled / how to enable them //=================================================================================================================== class Id3V230Writer : public Transformation { //void processId3V2Stream(Id3V2StreamBase& strm, ofstream_utf8& out); const TagWriter* m_pTagWriter; bool m_bKeepOneValidImg; bool m_bFastSave; void setupWriter(Id3V230StreamWriter& wrt, const Mp3HandlerTagData* pMp3HandlerTagData); public: Id3V230Writer(const TagWriter* pTagWriter, bool bKeepOneValidImg, bool bFastSave) : m_pTagWriter(pTagWriter), m_bKeepOneValidImg(bKeepOneValidImg), m_bFastSave(bFastSave) {} /*override*/ Transformation::Result apply(const Mp3Handler&, const TransfConfig&, const std::string& strOrigSrcName, std::string& strTempName); /*override*/ const char* getActionName() const { return getClassName(); } /*override*/ const char* getDescription() const { return QT_TRANSLATE_NOOP("Transformation", "Saves user-edited ID3V2.3.0 tags."); } /*override*/ bool acceptsFastSave() const { return true; } static const char* getClassName() { return QT_TRANSLATE_NOOP("Transformation", "Save ID3V2.3.0 tags"); } }; void Id3V230Writer::setupWriter(Id3V230StreamWriter& wrt, const Mp3HandlerTagData* pMp3HandlerTagData) { string s; s = pMp3HandlerTagData->getData(TagReader::TITLE); if (s.empty()) { wrt.removeFrames(KnownFrames::LBL_TITLE()); } else { wrt.addTextFrame(KnownFrames::LBL_TITLE(), s); } s = pMp3HandlerTagData->getData(TagReader::ARTIST); if (s.empty()) { wrt.removeFrames(KnownFrames::LBL_ARTIST()); } else { wrt.addTextFrame(KnownFrames::LBL_ARTIST(), s); } s = pMp3HandlerTagData->getData(TagReader::TRACK_NUMBER); if (s.empty()) { wrt.removeFrames(KnownFrames::LBL_TRACK_NUMBER()); } else { wrt.addTextFrame(KnownFrames::LBL_TRACK_NUMBER(), s); } s = pMp3HandlerTagData->getData(TagReader::GENRE); if (s.empty()) { wrt.removeFrames(KnownFrames::LBL_GENRE()); } else { wrt.addTextFrame(KnownFrames::LBL_GENRE(), s); } s = pMp3HandlerTagData->getData(TagReader::ALBUM); if (s.empty()) { wrt.removeFrames(KnownFrames::LBL_ALBUM()); } else { wrt.addTextFrame(KnownFrames::LBL_ALBUM(), s); } s = pMp3HandlerTagData->getData(TagReader::COMPOSER); if (s.empty()) { wrt.removeFrames(KnownFrames::LBL_COMPOSER()); } else { wrt.addTextFrame(KnownFrames::LBL_COMPOSER(), s); } s = pMp3HandlerTagData->getData(TagReader::VARIOUS_ARTISTS); wrt.setVariousArtists(!s.empty()); s = pMp3HandlerTagData->getData(TagReader::TIME); if (s.empty()) { wrt.removeFrames(KnownFrames::LBL_TIME_DATE_230()); wrt.removeFrames(KnownFrames::LBL_TIME_YEAR_230()); wrt.removeFrames(KnownFrames::LBL_TIME_240()); } else { wrt.setRecTime(TagTimestamp(s)); } s = pMp3HandlerTagData->getData(TagReader::IMAGE); if (s.empty()) { wrt.removeFrames(KnownFrames::LBL_IMAGE(), -1); // !!! this removes all APIC frames, including those non-cover frames that got assigned to Id3V2StreamBase::m_pPicFrame } else { int nImg (pMp3HandlerTagData->getImage()); CB_ASSERT (nImg >= 0); const ImageColl& imgColl (m_pTagWriter->getImageColl()); const ImageInfo& imgInfo (imgColl[nImg].m_imageInfo); int nImgSize (imgInfo.getSize()); const char* pImgData (imgInfo.getComprData()); QByteArray recomprImg; // to be used only for ImageInfo::INVALID const char* szEncoding (0); switch (imgInfo.getCompr()) { case ImageInfo::JPG: szEncoding = "image/jpg"; break; case ImageInfo::PNG: szEncoding = "image/png"; break; case ImageInfo::INVALID: { QImage scaledPic; ImageInfo::compress(imgInfo.getImage(), scaledPic, recomprImg); nImgSize = recomprImg.size(); pImgData = recomprImg.constData(); szEncoding = "image/jpg"; break; } default: CB_ASSERT (false); } int nEncSize (strlen(szEncoding)); int nSize (1 + nEncSize + 1 + 1 + 1 + nImgSize); vector frm (nSize); char* q (&frm[0]); *q++ = 0; // enc strcpy(q, szEncoding); q += nEncSize + 1; // enc + term *q++ = Id3V2Frame::PT_COVER; *q++ = 0; // null-term descr memcpy(q, pImgData, nImgSize); wrt.addImg(frm); } { //s = pMp3HandlerTagData->getData(TagReader::RATING); double d (pMp3HandlerTagData->getRating()); CB_ASSERT (d <= 5); if (d < 0) { wrt.removeFrames(KnownFrames::LBL_RATING()); } else { d = 1 + d*254/5; vector frm (2); frm[0] = 0; frm[1] = (int)d; // ttt2 signed char on some CPUs might have issues; OK on x86, though wrt.addBinaryFrame(KnownFrames::LBL_RATING(), frm); } } } //ttt2 when multiple id3v2 are found, the bkg color doesn't change if the 2 id3v2 are switched; probably ok, though, in the sense that if at least one field is assigned, all the others will be used when saving /*override*/ Transformation::Result Id3V230Writer::apply(const Mp3Handler& h, const TransfConfig& transfConfig, const std::string& strOrigSrcName, std::string& strTempName) { LAST_STEP("Id3V230Writer::apply() " + h.getName()); const vector& vpStreams (h.getStreams()); const Mp3HandlerTagData* pMp3HandlerTagData (0); for (int i = 0, n = cSize(m_pTagWriter->m_vpMp3HandlerTagData); i < n; ++i) { const Mp3HandlerTagData* p (m_pTagWriter->m_vpMp3HandlerTagData[i]); if (p->getMp3Handler() == &h) { pMp3HandlerTagData = p; goto e1; } } //return NOT_CHANGED; CB_ASSERT (false); // only handlers from m_pTagWriter (that also need saving) are supposed to get here; e1: CB_ASSERT (0 != pMp3HandlerTagData); ifstream_utf8 in (h.getName().c_str(), ios::binary); { // temp Id3V2StreamBase* pId3V2Source (0); for (int i = 0, n = cSize(vpStreams); i < n; ++i) { pId3V2Source = dynamic_cast(vpStreams[i]); if (0 != pId3V2Source) { break; } } Id3V230StreamWriter wrt (m_bKeepOneValidImg, m_bFastSave, pId3V2Source, h.getName()); // OK if pId3V2Source is 0 setupWriter(wrt, pMp3HandlerTagData); if (m_bFastSave && !vpStreams.empty() && pId3V2Source == vpStreams[0]) { try { long long nSize, nOrigTime; getFileInfo(h.getName(), nOrigTime, nSize); { fstream_utf8 f (h.getName().c_str(), ios::binary | ios_base::in | ios_base::out); wrt.write(f, int(pId3V2Source->getSize())); } h.reloadId3V2(); //ttt2 perhaps return the pointer to the old Id3V2 instead of destroying it, and keep it somewhere until saving is done; then m_bIsFastSaving won't be needed, and no "N/A"s will be displayed in ID3V2 fields when saving; OTOH those pointers can't be kept fully alive, because they can't retrieve their data from disk; so better leave it as is; if (transfConfig.m_options.m_bKeepOrigTime) { setFileDate(h.getName(), nOrigTime); } else { getFileInfo(h.getName(), nOrigTime, nSize); h.m_nFastSaveTime = nOrigTime; } return NOT_CHANGED; // !!! } catch (const WriteError&) { // !!! nothing: normally this gets triggered before changing the file in any way (the reason being that it doesn't fit), so we go on and create a temporary file; //ttt2 the exception might get thrown on versioned filesystems, that use copy-on-write, if the disk becomes full, bu even then there's not much that can be done; } } transfConfig.getTempName(strOrigSrcName, getActionName(), strTempName); ofstream_utf8 out (strTempName.c_str(), ios::binary); in.seekg(0); wrt.write(out); // may throw, but it will be caught for (int i = 0, n = cSize(vpStreams); i < n; ++i) { DataStream* p (vpStreams[i]); bool bCopy (0 == dynamic_cast(p)); BrokenDataStream* pBrk (dynamic_cast(p)); if (bCopy && 0 != pBrk && (pBrk->getBaseName() == Id3V230Stream::getClassDisplayName() || pBrk->getBaseName() == Id3V240Stream::getClassDisplayName())) { bCopy = false; } if (bCopy) { p->copy(in, out); } } } return CHANGED_NO_RECALL; } //======================================================================================================================== //======================================================================================================================== //======================================================================================================================== //ttt2 perhaps all licences in root dir // based on configuration, either just saves the tags or asks the user for confirmation; returns true iff all tags have been saved or if none needed saving; it should be followed by a reload(), either for the current or for the next/prev album; if bExplicitCall is true, the "ASK" option is turned into "SAVE"; TagEditorDlgImpl::SaveOpt TagEditorDlgImpl::save(bool bImplicitCall) { string strCrt (m_pTagWriter->getCurrentName()); deque vpHndlr; bool bHasUnsavedAssgn (false); bool bHasUnsavedNonId3V2 (false); for (int i = 0, n = cSize(m_pTagWriter->m_vpMp3HandlerTagData); i < n; ++i) { //const Mp3Handler* p (m_pTagWriter->m_vpMp3HandlerTagData[i]->getMp3Handler()); bool bAssgn (false); bool bNonId3V2 (false); m_pTagWriter->hasUnsaved(i, bAssgn, bNonId3V2); if ( (bAssgn && (!bImplicitCall || CommonData::DISCARD != m_pCommonData->m_eAssignSave)) || (bNonId3V2 && (!bImplicitCall || CommonData::DISCARD != m_pCommonData->m_eNonId3v2Save)) ) { vpHndlr.push_back(m_pTagWriter->m_vpMp3HandlerTagData[i]->getMp3Handler()); } bHasUnsavedAssgn = bHasUnsavedAssgn || bAssgn; bHasUnsavedNonId3V2 = bHasUnsavedNonId3V2 || bNonId3V2; } //ttt2 perhaps separate setting for showing and saving non-id3v2 fields { int nUnassignedImagesCnt (m_pTagWriter->getUnassignedImagesCount()); if (bImplicitCall && nUnassignedImagesCnt > 0) { int nOpt (showMessage(this, QMessageBox::Question, 1, 1, tr("Confirm"), (nUnassignedImagesCnt > 1 ? tr("You added %1 images but then you didn't assign them to any songs. What do you want to do?").arg(nUnassignedImagesCnt) : tr("You added an image but then you didn't assign it to any song. What do you want to do?")), tr("&Discard"), tr("&Cancel"))); if (1 == nOpt) { return CANCELLED; } } } if (vpHndlr.empty()) { return SAVED; } if ( bImplicitCall && ( (bHasUnsavedAssgn && CommonData::ASK == m_pCommonData->m_eAssignSave) || (bHasUnsavedNonId3V2 && CommonData::ASK == m_pCommonData->m_eNonId3v2Save) ) ) { // ask int nOpt; if ((bHasUnsavedAssgn && CommonData::ASK == m_pCommonData->m_eAssignSave) && (bHasUnsavedNonId3V2 && CommonData::DISCARD == m_pCommonData->m_eNonId3v2Save)) { nOpt = showMessage(this, QMessageBox::Question, 0, 2, tr("Confirm"), tr("There are unsaved fields that you assigned a value to, as well as fields whose value doesn't match the ID3V2 value. What do you want to do?"), tr("&Save"), tr("&Discard"), tr("&Cancel")); } else if (bHasUnsavedAssgn && CommonData::ASK == m_pCommonData->m_eAssignSave) { nOpt = showMessage(this, QMessageBox::Question, 0, 2, tr("Confirm"), tr("There are unsaved fields that you assigned a value to. What do you want to do?"), tr("&Save"), tr("&Discard"), tr("&Cancel")); } else { nOpt = showMessage(this, QMessageBox::Question, 0, 2, tr("Confirm"), tr("There are fields whose value doesn't match the ID3V2 value. What do you want to do?"), tr("&Save"), tr("&Discard"), tr("&Cancel")); } if (1 == nOpt) { return DISCARDED; } if (2 == nOpt) { return CANCELLED; } } m_bDataSaved = true; Id3V230Writer wrt (m_pTagWriter, m_pCommonData->m_bKeepOneValidImg, m_pCommonData->useFastSave()); vector vpTransf; vpTransf.push_back(&wrt); bool bRes; { ValueRestorer rst (m_bIsFastSaving); m_bIsFastSaving = m_pCommonData->useFastSave(); ValueRestorer rst1 (m_bIsSaving); m_bIsSaving = true; bRes = transform(vpHndlr, vpTransf, convStr(tr("Saving ID3V2.3.0 tags")), this, m_pCommonData, m_transfConfig); } m_pTagWriter->reloadAll(strCrt, TagWriter::DONT_CLEAR_DATA, TagWriter::CLEAR_ASSGN); //m_pTagWriter->updateAssigned(vector >()); m_pAssgnBtnWrp->setState(m_pTagWriter->updateAssigned(vector >())); // we don't want to keep any previous value updateAssigned(); // needed for the "assign" button to work, because the previous line cleared m_pTagWriter->m_sSelOrigVal return bRes ? SAVED : PARTIALLY_SAVED; } //ttt2 hide VA column if not used void TagEditorDlgImpl::onHelp() { openHelp("190_tag_editor.html"); } extern bool s_bToldAboutPatternsInCrtRun; void TagEditorDlgImpl::onShowPatternNote() { if (m_pTagWriter->shouldShowPatternsNote()) { s_bToldAboutPatternsInCrtRun = true; HtmlMsg::msg(this, 0, 0, &m_pCommonData->m_bToldAboutPatterns, HtmlMsg::DEFAULT, tr("Info"), "

" + tr("Some fields are missing or may be incomplete. While this is usually solved by downloading correct information, there are a cases when this approach doesn't work, like custom compilations, rare albums, or missing tracks.") + "

" "

" + tr("If your current folder fits one of these cases or you simply have consistently named files that you would prefer to use as a source of track info, you may want to take a look at the tag editor's patterns, at %1").arg("http://mp3diags.sourceforge.net" + QString(getWebBranch()) + "/220_tag_editor_patterns.html.

"), 550, 300, tr("O&K")); } } //======================================================================================================================================================== //======================================================================================================================================================== //======================================================================================================================================================== CurrentFileDelegate::CurrentFileDelegate(QTableView* pTableView, const CommonData* pCommonData) : QItemDelegate(pTableView), m_pTableView(pTableView), m_pCommonData(pCommonData) { CB_CHECK1 (0 != pTableView, std::runtime_error("NULL QTableView not allowed")); //connect(pTableView->horizontalHeader(), SIGNAL(sectionResized(int, int, int)), pTableView, SLOT(resizeRowsToContents())); } /*override*/ void CurrentFileDelegate::paint(QPainter* pPainter, const QStyleOptionViewItem& option, const QModelIndex& index) const { pPainter->save(); //pPainter->fillRect(option.rect, QBrush(m_listPainter.getColor(m_listPainter.getAvailable()[index.row()], index.column(), pPainter->background().color()))); //ttt2 make sure background() is the option to use /*QStyleOptionViewItemV2 myOption (option); myOption.displayAlignment = m_listPainter.getAlignment(index.column());*/ QString s (index.model()->data(index, Qt::DisplayRole).toString()); if ("\1" == s) { // tag not present pPainter->fillRect(option.rect, QBrush(m_pCommonData->m_vTagEdtColors[CommonData::COLOR_FILE_TAG_MISSING])); } else if ("\2" == s) { // not applicable pPainter->fillRect(option.rect, QBrush(m_pCommonData->m_vTagEdtColors[CommonData::COLOR_FILE_NA])); } else if ("\3" == s) { // applicable, but no data found pPainter->fillRect(option.rect, QBrush(m_pCommonData->m_vTagEdtColors[CommonData::COLOR_FILE_NO_DATA])); } else { pPainter->fillRect(option.rect, QBrush(m_pCommonData->m_vTagEdtColors[CommonData::COLOR_FILE_NORM])); QItemDelegate::paint(pPainter, option, index); } /* //ttt2 first 3 cases don't show the dotted line because QItemDelegate::paint() doesn't get called; perhaps drawDecoration() should be called, but copying these things from qitemdelegate.cpp didn't help: QVariant value; value = index.data(Qt::DecorationRole); QPixmap pixmap; pixmap = decoration(option, value); drawDecoration(pPainter, option, QRect(0, 0, 15, 15), pixmap); ??? or perhaps use QApplication::style()->drawPrimitive(QStyle::PE_FrameFocusRect?, &option, pPainter); */ pPainter->restore(); } QSize CurrentFileDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const { return QItemDelegate::sizeHint(option, index); /* if (!index.isValid()) { return QSize(); } //cout << option.rect.width() << "x" << option.rect.height() << " "; int nMargin (QApplication::style()->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1); //cout << "margin=" << nMargin << endl; int j (index.column()); int nColWidth (m_pTableView->horizontalHeader()->sectionSize(j)); //QRect r (0, 0, nColWidth - 2*nMargin - 1, 10000); // !!! this "-1" is what's different from Qt's implementation //ttt2 see if this is fixed in 4.4 2008.30.06 - apparently it's not fixed and the workaround no longer works //QSize res (option.fontMetrics.boundingRect(r, Qt::AlignTop | Qt::TextWordWrap, index.data(Qt::DisplayRole).toString()).size()); //cout << "at (" << index.row() << "," << index.column() << "): " << res.width() << "x" << res.height(); QSize res (nColWidth, CELL_HEIGHT); //res.setWidth(nColWidth); //cout << " => " << res.width() << "x" << res.height() << endl; //QSize res (fontMetrics().size(0, text())); return res; */ }//*/ //ttt2 perhaps allow selected cells to show background color, so it's possible to know if a cell is assigned, id3v2 or non-id3v2 even when it's selected; options include replaceing background with a frame, background with a darkened color based on the normal background, negation of normal color, some pattern CurrentAlbumDelegate::CurrentAlbumDelegate(QTableView* pTableView, TagEditorDlgImpl* pTagEditorDlgImpl) : QItemDelegate(pTableView), m_pTableView(pTableView), m_pTagEditorDlgImpl(pTagEditorDlgImpl), m_pTagWriter(pTagEditorDlgImpl->getTagWriter()) { CB_CHECK1 (0 != pTableView, std::runtime_error("NULL QTableView not allowed")); //connect(pTableView->horizontalHeader(), SIGNAL(sectionResized(int, int, int)), pTableView, SLOT(resizeRowsToContents())); } /*override*/ void CurrentAlbumDelegate::paint(QPainter* pPainter, const QStyleOptionViewItem& option, const QModelIndex& index) const { if (m_pTagEditorDlgImpl->isSaving() || m_pTagEditorDlgImpl->isNavigating()) { QItemDelegate::paint(pPainter, option, index); return; } pPainter->save(); //pPainter->fillRect(option.rect, QBrush(m_listPainter.getColor(m_listPainter.getAvailable()[index.row()], index.column(), pPainter->background().color()))); //ttt2 make sure background() is the option to use /*QStyleOptionViewItemV2 myOption (option); myOption.displayAlignment = m_listPainter.getAlignment(index.column());*/ //QString s (index.model()->data(index, Qt::DisplayRole).toString()); int nField (index.column()); Mp3HandlerTagData::Status eStatus (nField > 0 ? m_pTagWriter->getStatus(index.row(), TagReader::FEATURE_ON_POS[nField - 1]) : Mp3HandlerTagData::ID3V2_VAL); QColor col; switch (eStatus) { case Mp3HandlerTagData::EMPTY: case Mp3HandlerTagData::ID3V2_VAL: col = m_pTagEditorDlgImpl->getCommonData()->m_vTagEdtColors[CommonData::COLOR_ALB_NORM]; break; case Mp3HandlerTagData::NON_ID3V2_VAL: col = m_pTagEditorDlgImpl->getCommonData()->m_vTagEdtColors[CommonData::COLOR_ALB_NONID3V2]; break; case Mp3HandlerTagData::ASSIGNED: col = m_pTagEditorDlgImpl->getCommonData()->m_vTagEdtColors[CommonData::COLOR_ALB_ASSIGNED]; break; } //if (1 == index.column()) { pPainter->fillRect(option.rect, QBrush(col)); } //else { QItemDelegate::paint(pPainter, option, index); } pPainter->restore(); } //ttt2 2009.04.06 - for all delegates: see if they can be removed by using the standard delegate and adding more functionality to the models; see Qt::ForegroundRole, Qt::TextAlignmentRole, Qt::FontRole & Co /*override*/ QWidget* CurrentAlbumDelegate::createEditor(QWidget* pParent, const QStyleOptionViewItem& style, const QModelIndex& index) const { int nField (index.column()); if (0 == nField || 5 == nField || 8 == nField) { return 0; } QWidget* pEditor (QItemDelegate::createEditor(pParent, style, index)); //qDebug("%s", p->metaObject()->className()); connect(pEditor, SIGNAL(destroyed(QObject*)), this, SLOT(onEditorDestroyed(QObject*))); m_spEditors.insert(pEditor); //qDebug("create %p", pEditor); return pEditor; } void CurrentAlbumDelegate::onEditorDestroyed(QObject* p) { //qDebug("destroy %p", p); CB_ASSERT (1 == m_spEditors.count(p)); m_spEditors.erase(p); } bool CurrentAlbumDelegate::closeEditor() // closes the editor opened with F2, saving the data; returns false if there was some error and it couldn't close { CB_ASSERT (cSize(m_spEditors) <= 1); if (m_spEditors.empty()) { return true; } //delete m_pEditor; if (0 != showMessage(m_pTableView, QMessageBox::Warning, 1, 1, tr("Warning"), tr("You are editing data in a cell. If you proceed that change will be lost. Proceed and lose the data?"), tr("Proceed"), tr("Cancel"))) { return false; } //ttt2 try and post somehow the content of the editor (?? perhaps send it a keyboard message that ENTER was pressed) //ttt2 review this delete *m_spEditors.begin(); return true; } //ttt2 perhaps manufacture track numbers when pasting tables, if track numbers don't exist //====================================================================================================================== //====================================================================================================================== //====================================================================================================================== /* tag editor performance: TagWriter::reloadAll() is called twice when going to a new album, but that's not very important, because reloading no longer takes a lot of time; if there are no images it's very fast, but even with images it's no big deal; most time is used in rescanning a modified file, in Mp3Handler::parse(), as this example of saving a modified song shows: save: 0.23" (copying streams from one file to another) read ID3: 0.29" (because it parses images) read MPEG audio: 1.10" OTOH TagWriter::reloadAll() typically takes less than 0.10" for a whole album, even when pictures are present; what makes this slower is having pictures in the current directory, because they get rescanned at each reload, while the ones inside the MP3s don't. So eliminating the duplicate call wouldn't achieve much. The duplicate was needed at some point in time because of some Qt bug (or so it looked like); not sure if it's really needed, but removing it is almost guaranteed to result in some bugs (like randomly resizing the image panel) that are not consistently reproducible.) Rescanning of saved files might be eliminated if speed is so important, but it seems a bad idea; the main reason is that without rescanning the notes and the streams are going to be out of synch, and this is fundamentally unsolvable. Marking such files as "dirty" doesn't seem a very good idea, and could be quite confusing and hard to get right. Not marking them is worse. Perhaps "fast editor" option ... A less important performance issue is in ImageInfoPanelWdgImpl::ImageInfoPanelWdgImpl(): assigning of an image to a label takes more than 0.2" */ //ttt2 in tag editor: magnify image for one album, Ctrl+C, go to another album, Ctrl+V -> format error MP3Diags-1.2.02/src/OsFile.cpp0000644000175000001440000004055311601705405014631 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include "fstream_unicode.h" #include #include #include #include #include #include #ifndef _MSC_VER #include #else #include #endif #ifndef WIN32 #else #include #endif /* #include //#include //#include #include */ #include "OsFile.h" #include "Helpers.h" //--------------------------------------------------------------------------- using namespace std; using namespace pearl; //namespace ciobi_utils { class FileSearcherImpl { CB_LIB_CALL FileSearcherImpl(const FileSearcherImpl&); FileSearcherImpl& CB_LIB_CALL operator=(const FileSearcherImpl&); public: //CB_LIB_CALL FileSearcherImpl() : m_hFind(INVALID_HANDLE_VALUE), m_findData() {} CB_LIB_CALL FileSearcherImpl() : m_nCrtEntry(0) {} int m_nCrtEntry; QFileInfoList m_vFileInfos; }; CB_LIB_CALL FileSearcher::FileSearcher() : m_pImpl(new FileSearcherImpl) { } CB_LIB_CALL FileSearcher::FileSearcher(const string& strDirName) : m_pImpl(new FileSearcherImpl) { findFirst(strDirName); } //TSearchRec sr; CB_LIB_CALL FileSearcher::~FileSearcher() { //FindClose(sr); close(); } CB_LIB_CALL FileSearcher::operator bool() const { return m_pImpl->m_nCrtEntry < m_pImpl->m_vFileInfos.size(); } bool CB_LIB_CALL FileSearcher::isFile() const { return QFileInfo(convStr(getName())).isFile(); } bool CB_LIB_CALL FileSearcher::isDir() const { return QFileInfo(convStr(getName())).isDir(); } bool CB_LIB_CALL FileSearcher::isSymLink() const { return QFileInfo(convStr(getName())).isSymLink(); } // it's easier to use the constructor, but sometimes may be more convenient to leave the object in an outer loop bool CB_LIB_CALL FileSearcher::findFirst(const string& strDirName) { close(); if (endsWith(strDirName, getPathSepAsStr())) { m_strDir = strDirName; } else { m_strDir = strDirName + getPathSepAsStr(); } m_pImpl->m_vFileInfos = QDir(convStr(m_strDir)).entryInfoList(QDir::NoDotAndDotDot | QDir::Files | QDir::AllDirs); m_pImpl->m_nCrtEntry = 0; return goToNextValidEntry(); } // skips "." and "..", as well as any invalid item (usually file that has been removed after entryInfoList() got called) bool CB_LIB_CALL FileSearcher::goToNextValidEntry() { for (;;) { if (!*this) { return false; } QString s (m_pImpl->m_vFileInfos[m_pImpl->m_nCrtEntry].fileName()); CB_ASSERT (s != "." && s != ".."); // it was QDir::NoDotAndDotDot if (!m_pImpl->m_vFileInfos[m_pImpl->m_nCrtEntry].exists()) { ++m_pImpl->m_nCrtEntry; continue; //ttt2 see if seeking the next is the best way to deal with this error } return true; } close(); return false; } bool CB_LIB_CALL FileSearcher::findNext() { CB_CHECK1 (*this, InvalidOperation()/*"findNext() may only be called on an open searcher"*/); ++m_pImpl->m_nCrtEntry; return goToNextValidEntry(); } void CB_LIB_CALL FileSearcher::close() { m_pImpl->m_vFileInfos.clear(); m_pImpl->m_nCrtEntry = 0; } string CB_LIB_CALL FileSearcher::getName() const { CB_CHECK1 (*this, InvalidOperation()/*"getName() may only be called on an open searcher"*/); return convStr(m_pImpl->m_vFileInfos[m_pImpl->m_nCrtEntry].absoluteFilePath()); } long long CB_LIB_CALL FileSearcher::getSize() const { CB_CHECK1 (*this, InvalidOperation()/*"getSize() may only be called on an open searcher"*/); return m_pImpl->m_vFileInfos[m_pImpl->m_nCrtEntry].size(); } long long CB_LIB_CALL FileSearcher::getChangeTime() const { CB_CHECK1 (*this, InvalidOperation()/*"getChangeTime() may only be called on an open searcher"*/); return m_pImpl->m_vFileInfos[m_pImpl->m_nCrtEntry].lastModified().toTime_t(); //ttt3 32bit } /*int CB_LIB_CALL FileSearcher::getAttribs() const { return m_pImpl->m_findData.dwFileAttributes; }*/ //void CB_LIB_CALL getFileInfo(const char* szFileName, long long & nCreationTime, long long & nChangeTime, long long& nSize) void CB_LIB_CALL getFileInfo(const string& strFileName, long long & nChangeTime, long long& nSize) { QFileInfo fi (convStr(strFileName)); if (!fi.exists()) { throw NameNotFound(); //throw CannotGetData(strFileName, getOsError(), LI); } nChangeTime = fi.lastModified().toTime_t(); //ttt3 32bit nSize = fi.size(); } #ifndef WIN32 #else static wstring wstrFromUtf8(const string& s) { vector w (s.size() + 1); MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, &w[0], w.size()); //inspect(&w[0], w.size()*2); return &w[0]; } #endif //void CB_LIB_CALL setFileDate(const char* szFileName, time_t nCreationTime, long long nChangeTime) void CB_LIB_CALL setFileDate(const string& strFileName, long long nChangeTime) { #ifndef WIN32 utimbuf t; t.actime = (time_t)nChangeTime; t.modtime = (time_t)nChangeTime; if (0 != utime(strFileName.c_str(), &t)) { //throw CannotSetDates(strFileName, getOsError(), LI); throw 1; //ttt2 } #else _utimbuf t; t.actime = (time_t)nChangeTime; t.modtime = (time_t)nChangeTime; if (0 != _wutime(wstrFromUtf8(strFileName).c_str(), &t)) { //throw CannotSetDates(strFileName, getOsError(), LI); throw 1; //ttt2 } #endif } // throws IncorrectDirName if the name ends with a path separator void CB_LIB_CALL checkDirName(const string& strDirName) { CB_CHECK1 (!endsWith(strDirName, getPathSepAsStr()), IncorrectDirName()); //ttt2 more checks } // returns true if there is a file with that name; // returns false if the name doesn't exist or it's a directory; doesn't throw bool CB_LIB_CALL fileExists(const std::string& strFileName) { QFileInfo fi (convStr(strFileName)); return fi.isFile(); // ttt2 not sure if this should allow symlinks } // returns true if there is a directory with that name; // returns false if the name doesn't exist or it's a file; //ttt2 throws IncorrectDirName bool CB_LIB_CALL dirExists(const std::string& strDirName) { if (strDirName.empty()) { return true; } checkDirName(strDirName); string strDir1 (strDirName); #ifndef WIN32 #else if (2 == cSize(strDir1)) { strDir1 += getPathSepAsStr(); } #endif QFileInfo fi (convStr(strDir1)); return fi.isDir(); // ttt2 not sure if this should allow symlinks } void CB_LIB_CALL createDir(const string& strDirName) { if (strDirName.empty()) { return; } // the root dir always exists checkDirName(strDirName); if (dirExists(strDirName)) { return; } string::size_type n (strDirName.rfind(getPathSep())); CB_CHECK1 (string::npos != n, CannotCreateDir(strDirName)); string strParent (strDirName.substr(0, n)); createDir(strParent); QFileInfo fi (convStr(strDirName)); CB_CHECK1 (fi.dir().mkdir(fi.fileName()), CannotCreateDir(strDirName)); CB_CHECK1 (dirExists(strDirName), CannotCreateDir(strDirName)); } void CB_LIB_CALL createDirForFile(const std::string& strFileName) { string::size_type n (strFileName.rfind(getPathSep())); CB_ASSERT (string::npos != n); createDir(strFileName.substr(0, n)); } // does nothing on Linux; replaces "D:" with "/D" on Windows, only when "D:" isn't at the beggining of the string; string replaceDriveLetter(const string& strFileName) { #ifndef WIN32 return strFileName; #else string s (strFileName); for (string::size_type n = 2;; ) { n = s.find(':', n); if (string::npos == n) { return s; } s[n] = s[n - 1]; s[n - 1] = '/'; } #endif } // adds a path separator at the end if none is present; throws IncorrectDirName on invalid file names string getSepTerminatedDir(const string& strDirName) { string strRes (strDirName); if (!endsWith(strDirName, getPathSepAsStr())) { strRes += getPathSep(); } checkDirName(strRes.substr(0, strRes.size() - 1)); return strRes; } // removes the path separator at the end if present; throws IncorrectDirName on invalid file names string getNonSepTerminatedDir(const string& strDirName) { string strRes (strDirName); if (endsWith(strDirName, getPathSepAsStr())) { strRes.erase(strRes.size() - 1); } checkDirName(strRes); return strRes; } // renames a file; // throws FoundDir, AlreadyExists, NameNotFound, CannotRenameFile, ?IncorrectDirName, void CB_LIB_CALL renameFile(const std::string& strOldName, const std::string& strNewName) { CB_CHECK1 (!dirExists(strNewName), FoundDir()); CB_CHECK1 (!dirExists(strOldName), FoundDir()); // ttt2 separate OldFoundDir / NewFoundDir CB_CHECK1 (!fileExists(strNewName), AlreadyExists()); CB_CHECK1 (fileExists(strOldName), NameNotFound()); createDirForFile(strNewName); //ttt3 undo on error /* int n (rename(strOldName.c_str(), strNewName.c_str())); int nErr (errno); if (0 != n && EXDEV != nErr) { qDebug("I/O Error %d: %s", strerror(nErr)); } if (0 != n && EXDEV == nErr)*/ if (!QDir().rename(convStr(strOldName), convStr(strNewName))) { // rename only works on a single drive, so work around this //ttt2 warn if this is called a lot try { copyFile2(strOldName, strNewName); } catch (const CannotCopyFile&) { throw CannotRenameFile(); } catch (...) //ttt2 not quite right //ttt2 perhaps also NameNotFound, AlreadyExists, ... { throw CannotRenameFile(); } deleteFile(strOldName); return; } //CB_CHECK1 (0 == n, CannotRenameFile()); } // creates a copy a file; // doesn't preserve any attributes; // ttt2 add option // throws WriteError or EndOfFile from Helpers //ttt2 switch to: throws FoundDir, AlreadyExists, NameNotFound, CannotCopyFile, ?IncorrectDirName, void CB_LIB_CALL copyFile(const std::string& strSourceName, const std::string& strDestName /*, OverwriteOption eOverwriteOption*/) { ifstream_utf8 in (strSourceName.c_str(), ios::binary); ofstream_utf8 out (strDestName.c_str(), ios::binary); streampos nSize (getSize(in)); appendFilePart(in, out, 0, nSize); CB_CHECK1 (out, WriteError()); streampos nOutSize (out.tellp()); CB_CHECK1 (nOutSize == nSize, WriteError()); } void CB_LIB_CALL copyFile2(const std::string& strSourceName, const std::string& strDestName /*, OverwriteOption eOverwriteOption*/) { CB_CHECK1 (!dirExists(strDestName), FoundDir()); CB_CHECK1 (!dirExists(strSourceName), FoundDir()); // ttt2 separate OldFoundDir / NewFoundDir CB_CHECK1 (!fileExists(strDestName), AlreadyExists()); CB_CHECK1 (fileExists(strSourceName), NameNotFound()); createDirForFile(strDestName); //ttt3 undo on error ifstream_utf8 in (strSourceName.c_str(), ios::binary); ofstream_utf8 out (strDestName.c_str(), ios::binary); CB_CHECK1 (in, CannotCopyFile()); CB_CHECK1 (out, CannotCopyFile()); streampos nSize (getSize(in)); try { appendFilePart(in, out, 0, nSize); } catch (const WriteError&) { throw CannotCopyFile(); } streampos nOutSize (out.tellp()); if (!out || nOutSize != nSize) { out.close(); deleteFile(strDestName); throw CannotCopyFile(); } long long nChangeTime; long long x; getFileInfo(strSourceName.c_str(), nChangeTime, x); setFileDate(strDestName.c_str(), nChangeTime); } // deletes a file; throws FoundDir, // CannotDeleteFile, ?IncorrectDirName; it is OK if the file didn't exist to begin with void CB_LIB_CALL deleteFile(const std::string& strFileName) { CB_CHECK1 (!dirExists(strFileName), FoundDir()); if (!fileExists(strFileName)) { return; } QFileInfo fi (convStr(strFileName)); CB_CHECK1 (fi.dir().remove(fi.fileName()), CannotDeleteFile()); } // just a name that doesn't exist; the file won't be deleted automatically; normally the name is obtained by appending something to strMasterFileName, but a more generic temp is used if the name is too long on wnd string getTempFile(const std::string& strMasterFileName) { QTemporaryFile tmp (convStr(strMasterFileName)); QString qs; if (tmp.open() #ifndef WIN32 #else && convStr(tmp.fileName()).size() < MAX_PATH //ttt3 not sure if MAX_PATH includes the terminator or not, so assume it does #endif ) { qs = tmp.fileName(); } else { QTemporaryFile tmp1; CB_ASSERT (tmp1.open()); //ttt2 if it gets to this on W7 (and perhaps others) the file attributes are wrong, probably allowing only the current user to see it; ttt2 perhaps only use the dir, instead of the full file name in such case; //ttt2 doc qs = tmp1.fileName(); } string s (convStr(qs)); //qDebug("patt: '%s', tmp: '%s'", strMasterFileName.c_str(), s.c_str()); return s; //ttt2 make sure it works OK if strMasterFileName is empty } // the name of a parent directory; returns an empty string if no parent exists; may throw IncorrectDirName string getParent(const string& strName) { checkDirName(strName); string::size_type n (strName.rfind(getPathSep())); //CB_ASSERT(string::npos != n); if (string::npos == n) { return ""; } return strName.substr(0, n); } bool isInsideDir(const std::string& strName, const std::string& strDirName) // if strName is in strDirName or in one of its subdirectories; may throw IncorrectDirName for either param { checkDirName(strName); checkDirName(strDirName); return beginsWith(strName, strDirName) && getPathSep() == strName[strDirName.size()]; } string getExistingDir(const std::string& strName) // if strName exists and is a dir, it is returned; otherwise it returns the closest ancestor that exists; accepts names ending with file separator { string s (strName); if (endsWith(s, getPathSepAsStr())) { s.erase(s.size() - 1); } try { while (!s.empty() && !dirExists(s)) { s = getParent(s); } } catch (const IncorrectDirName&) { s.clear(); } return s; } // erases files matching strRoot followed by anything; returns the name of the first file that couldn't be erased string eraseFiles(const string& strRoot) { string::size_type n (strRoot.rfind("/")); string strDir (strRoot.substr(0, n)); string strFile (strRoot.substr(n + 1)); QStringList filter; filter << convStr(strFile + "*"); QDir dir (convStr(strDir)); QFileInfoList vFileInfos (dir.entryInfoList(filter, QDir::Files)); for (int i = 0; i < vFileInfos.size(); ++i) { cout << convStr(vFileInfos[i].absoluteFilePath()) << " " << convStr(vFileInfos[i].fileName()) << endl; if (!dir.remove(vFileInfos[i].fileName())) { return convStr(vFileInfos[i].absoluteFilePath()); } } return ""; } //ttt2 perhaps use strerror_r() to print file errors //} //namespace ciobi_utils MP3Diags-1.2.02/src/Transformation.h0000644000175000001440000005237611720127276016137 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef TransformationH #define TransformationH #include #include // for translation /* Processing a file by MainFormDlgImpl: There is a list of Transformation instances. Their apply() functions get called, creating a new file from an existing one each time they return "true", until all of them return "false". There is a "source root folder", where all the original files come from. Not all the files inside that folder and its subdirectories are going to be processed; some filtering may be used. It may be "/", but care should be taken to not try to process files from m_strTempRootDir, m_strProcRootDir or m_strCompRootDir. Various Transformation instances get called. If they decide that they will modify the file, they call TransfConfig::getTempName() (which uses m_strTempRootDir) to get the name of the file where they should put their result. The next Transformation to get called will receive an Mp3Handler for this, just created file, along with the name of the original file. After a Transformation::apply() returns "true", the list with Transformation gets called from the beginning, until every element in the list return "false". After that, the last file that got created is moved to the "destination folder", determined by m_strProcRootDir. It may be the same as m_strSrcRootDir, in which case the original file is moved to the corresponding place in m_strTempRootDir. Based on settings, the original and/or all the intermediate files may be deleted. There may also be "comparison" files, which are meant for cases like this: Some bits are modified inside an audio stream, now the file "looks" ok, but we want to see the impact this had on the audio. So the affected frames and several frames before and after them are saved in 2 "comp" files, "before" and "after", so they can be listened to in a separate app. The get<...>Name() functions derive a name from the original name, the purpose of the folder, and a counter, such that it is located in the appropriate folder and there is no name conflict with an existing file. Example 1: source: /media/mp3/new/ dest: /tests/audio/ proc: /tests/proc/ comp: /tests/tmp/ orig. name: /media/mp3/new/pop/act_two.mp3 proc: /tests/proc/pop/act_two.proc.001.mp3 comp: /tests/tmp/pop/act_two.tmp.before.003.mp3 and /tests/tmp/pop/act_two.tmp.after.003.mp3 Example 2: source: /media/mp3/new/ dest: /media/mp3/new/ proc: /tests/proc/ comp: /tests/tmp/ orig. name: /media/mp3/new/pop/act_two.mp3 proc: /tests/proc/pop/act_two.proc.001.mp3 comp: /tests/tmp/pop/act_two.tmp.before.003.mp3 and /tests/tmp/pop/act_two.tmp.after.003.mp3 backup: /tests/proc/pop/act_two.mp3 (or /tests/proc/pop/act_two.orig.002.mp3) It's better for the directories to not overlap, but this is not enforced, as there may be valid reasons to use a folder for multiple purposes. One thing that should be avoided is to end up with comp or intermediate files in the same folder as source files. Folder names must not end with the path separator character ("/" or "\"), or an IncorrectDirName is thrown (defined in OsFile.h). Note: getTempName() and getCompNames() create the directories, if needed, so files can be created using the returned names. Note: all file names are absolute. //ttt2 more options to change what getBackupName() and getDestName() return and some other things: 1) the name can be the original one or changed; 2) the orig file may be left where it is, moved or deleted 3) the dest file may be left where it is (in temp), moved to src, moved to dest, or perhaps even deleted (so only the "comp" files are left, for comparison) 4) the comp and proc file may be left or removed; if they get deleted, it's probably better to not create a tree structure for directories 5) perhaps "flatten" file names: "/tests/tmp/pop/JoQ/act_two.tmp.before.003.mp3" -> /tests/tmp/pop - JoQ - act_two.tmp.before.003.mp3" */ class TransfConfig { Q_DECLARE_TR_FUNCTIONS(TransfConfig) //bool m_bRename std::string m_strSrcDir; // all scanned files are supposed to be here; // 2009.04.08 - for now it seems better to force this to be empty, by not allowing it to be edited // ttt2 perhaps put back, or calculate it from a session's folders in saveTransfConfig(), but seems likely to create confusion; can be edited manually in the config file to see what it does (or by commenting out "m_pSourceDirF->hide()") std::string m_strProcOrigDir; std::string m_strUnprocOrigDir; std::string m_strProcessedDir; std::string m_strTempDir; std::string m_strCompDir; // bit mapping: // //---------------------------- // // 0-2: 000 = don't change processed orig file // 001 = erase processed orig file // 010 = move processed orig file to m_strProcOrigDir; always rename // 011 = move processed orig file to m_strProcOrigDir; rename only if name is in use // 100 = rename in the same dir // 101 = move w/o renaming, if doesn't exist; discard if it exists; (so there's only 1 orig) // ??? move w/ renaming, if doesn't exist // 3: 0 = don't use identifying label when renaming proc orig // 1 = use identifying label when renaming proc orig // 4: 0 = use counter if name is in use when renaming proc orig // 1 = always use counter when renaming proc orig // //---------------------------- // // 5-7: 000 = don't change unprocessed orig file // 001 = erase unprocessed orig file // 010 = move unprocessed orig file to m_strUnprocOrigDir; always rename // 011 = move unprocessed orig file to m_strUnprocOrigDir; rename only if name is in use // 100 = rename in the same dir // 8: 0 = don't use identifying label when renaming unproc orig // 1 = use identifying label when renaming unproc orig // 9: 0 = use counter if name is in use when renaming unproc orig // 1 = always use counter when renaming unproc orig // //---------------------------- // // 10-11: 00 = don't create proc files // 01 = create proc files and always rename // 10 = create proc files and rename if the name is in use // 12: 0 = don't use identifying label when renaming proc // 1 = use identifying label when renaming proc // 13: 0 = use counter if name is in use when renaming proc // 1 = always use counter when renaming proc // 14: 0 = use the source dir as destination for proc files // 1 = use m_strProcessedDir as destination for proc files // //---------------------------- // // 15: 0 = don't create temp files // 1 = create temp files in m_strTempDir // //---------------------------- // // 16: 0 = don't create comp files // 1 = create comp files in m_strCompDir // //---------------------------- // // 17: 0 = keep orig time // 1 = don't keep orig time // /*int m_nOptions; int getOpt(int nStartBit, int nCount) const { return (m_nOptions >> nStartBit) & ((1 << nCount) - 1); }*/ void splitOrigName(const std::string& strOrigSrcName, std::string& strRelDir, std::string& strBaseName, std::string& strExt) const; // last '/' goes to dir; last '.' goes to ext (if extension present) enum { USE_COUNTER_IF_NEEDED, ALWAYS_USE_COUNTER }; enum { RENAME_IF_NEEDED, ALWAYS_RENAME }; enum { DONT_ALLOW_DUPLICATES, ALLOW_DUPLICATES }; static std::string addLabelAndCounter(const std::string& s1, const std::string& s2, const std::string& strLabel, bool bAlwaysUseCounter, bool bAlwaysRename); // adds a counter and/or a label, such that a file with the resulting name doesn't exist; // normally makes sure that the name returned doesn't exist, by applying any renaming specified in the params; there's an exception, though: is bAllowDup is true, duplicates are allowed std::string getRenamedName(const std::string& strOrigSrcName, const std::string& strNewRootDir, const std::string& strLabel, bool bAlwayUseCounter, bool bAlwaysRename, bool bAllowDup = false) const; public: TransfConfig( const std::string& strSrcDir, const std::string& strProcOrigDir, const std::string& strUnprocOrigDir, const std::string& strProcessedDir, const std::string& strTempDir, const std::string& strCompDir, int nOptions // may be -1 to indicate "default" values ); TransfConfig(); class Options { public: enum ProcOrig { PO_DONT_CHG, PO_ERASE, PO_MOVE_ALWAYS_RENAME, PO_MOVE_RENAME_IF_USED, PO_RENAME_SAME_DIR, PO_MOVE_OR_ERASE }; enum UnprocOrig { UPO_DONT_CHG, UPO_ERASE, UPO_MOVE_ALWAYS_RENAME, UPO_MOVE_RENAME_IF_USED, UPO_RENAME_SAME_DIR }; // !!! the values in UnprocOrig must match those in ProcOrig for getChangedOrigNameHlp() to work correctly enum Processed { PR_DONT_CREATE, PR_CREATE_ALWAYS_RENAME, PR_CREATE_RENAME_IF_USED }; private: static unsigned uns(bool b) { return b ? 1 : 0; } static unsigned uns(unsigned x) { return x; } static unsigned uns(ProcOrig x) { return x; } static unsigned uns(UnprocOrig x) { return x; } static unsigned uns(Processed x) { return x; } public: Options(); // initializes the fields to a "non-backup" state ProcOrig m_eProcOrigChange : 4; // !!! really "3", but declares more bits than used to avoid issues with signed/unsigned enums bool m_bProcOrigUseLabel : 1; bool m_bProcOrigAlwayUseCounter : 1; UnprocOrig m_eUnprocOrigChange : 4; // !!! really "3", but declares more bits than used to avoid issues with signed/unsigned enums bool m_bUnprocOrigUseLabel : 1; bool m_bUnprocOrigAlwayUseCounter : 1; Processed m_eProcessedCreate : 3; // !!! really "2", but declares more bits than used to avoid issues with signed/unsigned enums bool m_bProcessedUseLabel : 1; bool m_bProcessedAlwayUseCounter : 1; bool m_bProcessedUseSeparateDir : 1; bool m_bTempCreate : 1; bool m_bCompCreate : 1; bool m_bKeepOrigTime : 1; int getVal() const { unsigned x (0); unsigned s (0); x ^= (uns(m_eProcOrigChange) << s); s += 3; // unsigned m_nProcOrigChange : 3; x ^= (uns(m_bProcOrigUseLabel) << s); s += 1; // bool m_bProcOrigUseLabel : 1; x ^= (uns(m_bProcOrigAlwayUseCounter) << s); s += 1; // bool m_bProcOrigAlwayUseCounter : 1; x ^= (uns(m_eUnprocOrigChange) << s); s += 3; // unsigned m_nUnprocOrigChange : 3; x ^= (uns(m_bUnprocOrigUseLabel) << s); s += 1; // bool m_bUnprocOrigUseLabel : 1; x ^= (uns(m_bUnprocOrigAlwayUseCounter) << s); s += 1; // bool m_bUnprocOrigAlwayUseCounter : 1; x ^= (uns(m_eProcessedCreate) << s); s += 2; // unsigned m_nProcessedCreate : 2; x ^= (uns(m_bProcessedUseLabel) << s); s += 1; // bool m_bProcessedUseLabel : 1; x ^= (uns(m_bProcessedAlwayUseCounter) << s); s += 1; // bool m_bProcessedAlwayUseCounter : 1; x ^= (uns(m_bProcessedUseSeparateDir) << s); s += 1; // bool m_bProcessedUseSeparateDir : 1; x ^= (uns(m_bTempCreate) << s); s += 1; // bool m_bTempCreate : 1; x ^= (uns(m_bCompCreate) << s); s += 1; // bool m_bCompCreate : 1; x ^= (uns(m_bKeepOrigTime) << s); s += 1; // bool m_bKeepOrigTime : 1; return int(x); } void setVal(int x) { m_eProcOrigChange = ProcOrig(x & 0x07); x >>= 3; // unsigned m_nProcOrigChange : 3; m_bProcOrigUseLabel = x & 0x01; x >>= 1; // bool m_bProcOrigUseLabel : 1; m_bProcOrigAlwayUseCounter = x & 0x01; x >>= 1; // bool m_bProcOrigAlwayUseCounter : 1; m_eUnprocOrigChange = UnprocOrig(x & 0x07); x >>= 3; // unsigned m_nUnprocOrigChange : 3; m_bUnprocOrigUseLabel = x & 0x01; x >>= 1; // bool m_bUnprocOrigUseLabel : 1; m_bUnprocOrigAlwayUseCounter = x & 0x01; x >>= 1; // bool m_bUnprocOrigAlwayUseCounter : 1; m_eProcessedCreate = Processed(x & 0x03); x >>= 2; // unsigned m_nProcessedCreate : 2; m_bProcessedUseLabel = x & 0x01; x >>= 1; // bool m_bProcessedUseLabel : 1; m_bProcessedAlwayUseCounter = x & 0x01; x >>= 1; // bool m_bProcessedAlwayUseCounter : 1; m_bProcessedUseSeparateDir = x & 0x01; x >>= 1; // bool m_bProcessedUseSeparateDir : 1; m_bTempCreate = x & 0x01; x >>= 1; // bool m_bTempCreate : 1; m_bCompCreate = x & 0x01; x >>= 1; // bool m_bCompCreate : 1; m_bKeepOrigTime = x & 0x01; x >>= 1; // bool m_bKeepOrigTime : 1; } bool operator==(const Options& opt) const { return getVal() == opt.getVal(); } // backup is 3-state; based on various fields, options may be "backup", "non-backup", or neither of them Options asBackup() const; // returns *this, with some fields changed to match a "backup" configuration; touches many field, but ignores others (m_bProcOrigUseLabel, m_bProcOrigAlwayUseCounter, m_bUnprocOrigUseLabel, m_bUnprocOrigAlwayUseCounter, m_bKeepOrigTime) Options asNonBackup() const; // returns *this, with some fields changed to match a "non-backup" configuration; touches many field, but ignores others (m_bProcOrigUseLabel, m_bProcOrigAlwayUseCounter, m_bUnprocOrigUseLabel, m_bUnprocOrigAlwayUseCounter, m_bKeepOrigTime) }; Options m_options; /* // the name for an "intermediate" file; it's obtained from the "temp dir" and from the short name of the original file, with a ".temp." inserted before the extension, where is a number chosen such that a file with this new name doesn't exist; std::string getTempName(const std::string& strOrigSrcName, const std::string& strOpName) const; // the names for a pair of "compare", "before and after", files; they are obtained from the "comp dir" and from the short name of the original file, with ".before." / ".after." inserted before the extension, where is a number chosen such that files with the new names don't exist; void getCompNames(const std::string& strOrigSrcName, const std::string& strOpName, std::string& strBefore, std::string& strAfter) const; // to what an original file should be renamed, if it was "processed"; // if m_strSrcRootDir and m_strProcRootDir are different, it returns ""; (the original file doesn't get touched); // if m_strSrcRootDir and m_strProcRootDir are equal, it returns a name based on the original, with an ".orig." inserted before the extension, where is a number chosen such that a file with this new name doesn't exist; std::string getBackupName(const std::string& strOrigSrcName) const; // to what the last "processed" file should be renamed; // if m_strSrcRootDir and m_strProcRootDir are different, it returns strOrigSrcName, moved to m_strProcRootDir; // if m_strSrcRootDir and m_strProcRootDir are equal, it returns strOrigSrcName; // if the name that it would return already exists, it inserts a ".Proc." such that a file with this new name doesn't exist; (so if m_strSrcRootDir and m_strProcRootDir are equal, the original file should be renamed before calling this) std::string getProcName(const std::string& strOrigSrcName) const; */ enum OrigFile { ORIG_DONT_CHANGE, ORIG_ERASE, ORIG_MOVE, ORIG_MOVE_OR_ERASE }; enum TransfFile { TRANSF_DONT_CREATE, TRANSF_CREATE }; OrigFile getProcOrigAction() const; OrigFile getUnprocOrigAction() const; TransfFile getProcessedAction() const; TransfFile getTempAction() const; TransfFile getCompAction() const; OrigFile getProcOrigName(std::string strOrigSrcName, std::string& strNewName) const; OrigFile getUnprocOrigName(std::string strOrigSrcName, std::string& strNewName) const; TransfFile getProcessedName(std::string strOrigSrcName, std::string& strName) const; TransfFile getTempName(std::string strOrigSrcName, const std::string& strOpName, std::string& strName) const; TransfFile getCompNames(std::string strOrigSrcName, const std::string& strOpName, std::string& strBefore, std::string& strAfter) const; //these are needed for saving the configuration const std::string& getSrcDir() const { return m_strSrcDir; } const std::string& getProcOrigDir() const { return m_strProcOrigDir; } const std::string& getUnprocOrigDir() const { return m_strUnprocOrigDir; } const std::string& getProcessedDir() const { return m_strProcessedDir; } const std::string& getTempDir() const { return m_strTempDir; } const std::string& getCompDir() const { return m_strCompDir; } void setProcOrigDir(const std::string& s) { m_strProcOrigDir = s; } // needed by the session dialog //ttt2 perhaps some checks int getOptions() const { return m_options.getVal(); } bool hadInitError() const { return m_bInitError; } //struct DirOverlap {}; //struct InvalidName {}; // something is wrong with the file name struct IncorrectPath {}; // thrown if a "source file" is outside of the "source folder" (takes care of this case too, where it's not enough to check for a substring: src="/tst/dir1", origName="/tst/dir10/file1.mp3") void testRemoveSuffix() const; private: OrigFile getChangedOrigNameHlp(const std::string& strOrigSrcName, const std::string& strDestDir, int nChange, bool bUseLabel, bool bAlwayUseCounter, std::string& strNewName) const; // !!! nChange will get initialized from either ProcOrig or UnprocOrig, so their values must match void removeSuffix(std::string& s) const; bool m_bInitError; }; class Mp3Handler; class Transformation { Q_DECLARE_TR_FUNCTIONS(Transformation) public: virtual ~Transformation() {} enum Result { NOT_CHANGED, CHANGED, CHANGED_NO_RECALL }; /*struct Result {}; static Result NOT_CHANGED; static Result CHANGED; static Result CHANGED_NO_RECALL;*/ virtual Result apply(const Mp3Handler&, const TransfConfig&, const std::string& strOrigSrcName, std::string& strTempName) = 0; // this may throw virtual const char* getActionName() const = 0; // should return the same thing for all objects of a class, as this is used in string comparisons in several places (visible transformations, custom transformation lists, ...) virtual QString getVisibleActionName() const { return tr(getActionName()); } // to be used only by the UI, providing more details to the user virtual const char* getDescription() const = 0; virtual bool acceptsFastSave() const { return false; } // whether to consider Mp3Handler::m_nFastSaveTime as a match when deciding if a file was changed (so a transformation can't be applied) struct InvalidInputFile {}; // thrown if the input file was changed so it can no longer be processed }; class IdentityTransformation : public Transformation { public: /*override*/ Result apply(const Mp3Handler&, const TransfConfig&, const std::string& strOrigSrcName, std::string& strTempName); /*override*/ const char* getActionName() const { return getClassName(); } /*override*/ const char* getDescription() const { return QT_TRANSLATE_NOOP("Transformation", "Doesn't actually change the file, but it creates a temporary copy and it reports that it does change it. This is not as meaningless as it might first seem: if the configuration settings indicate some action (i.e. rename, move or erase) to be taken for processed files, then that action will be performed for these files. While the same can be achieved by changing the settings for unprocessed files, this is easier to use when it is executed on a subset of all the files (filtered or selected)."); } static const char* getClassName() { return QT_TRANSLATE_NOOP("Transformation", "No change"); } }; #endif // #ifndef TransformationH MP3Diags-1.2.02/src/MusicBrainzDownloader.h0000644000175000001440000000773311252461427017372 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef MusicBrainzDownloaderH #define MusicBrainzDownloaderH #include "AlbumInfoDownloaderDlgImpl.h" namespace MusicBrainz { struct SearchXmlHandler; struct MusicBrainzAlbumInfo : public WebAlbumInfoBase { MusicBrainzAlbumInfo() : m_nTrackCount(-1) {} //std::string m_strComposer; // !!! no Composer at MusicBrainz; see http://musicbrainz.org/doc/ClassicalMusicFAQ //std::string m_strGenre; // !!! no Genre at MusicBrainz: http://musicbrainz.org/doc/GeneralFAQ#head-6488c5c2fb7503b4ae26f3e234304e07c2b172fb //std::string m_strNotes; // !!! no Notes at MusicBrainz std::string m_strId; int m_nTrackCount; std::string m_strAsin; std::string m_strAmazonLink; /*override*/ void copyTo(AlbumInfo& dest); }; }; class MusicBrainzDownloader : public AlbumInfoDownloaderDlgImpl { Q_OBJECT std::vector m_vAlbums; friend struct MusicBrainz::SearchXmlHandler; void delay(); long long getTime(); // time in milliseconds long long m_nLastReqTime; void clear(); QHttp* m_pImageQHttp; /*override*/ bool initSearch(const std::string& strArtist, const std::string& strAlbum); /*override*/ std::string createQuery(); /*override*/ void loadNextPage(); /*override*/ void requestAlbum(int nAlbum); /*override*/ void requestImage(int nAlbum, int nImage); /*override*/ void reloadGui(); /*override*/ QHttp* getWaitingHttp(); /*override*/ void resetNavigation(); /*override*/ WebAlbumInfoBase& album(int i); /*override*/ int getAlbumCount() const; /*override*/ QXmlDefaultHandler* getSearchXmlHandler(); /*override*/ QXmlDefaultHandler* getAlbumXmlHandler(int nAlbum); /*override*/ const WebAlbumInfoBase* getCrtAlbum() const; // returns 0 if there's no album /*override*/ int getColumnCount() const { return 3; } /*override*/ void saveSize(); /*override*/ char getReplacementChar() const { return ' '; } QString getAmazonText() const; public: MusicBrainzDownloader(QWidget* pParent, SessionSettings& settings, bool bSaveResults); ~MusicBrainzDownloader(); /*$PUBLIC_FUNCTIONS$*/ static const char* SOURCE_NAME; public slots: /*$PUBLIC_SLOTS$*/ protected: /*$PROTECTED_FUNCTIONS$*/ protected slots: /*$PROTECTED_SLOTS$*/ void on_m_pSearchB_clicked(); void onAmazonLinkActivated(const QString&); private: }; #endif // #ifndef MusicBrainzDownloaderH MP3Diags-1.2.02/src/NoteFilterDlgImpl.h0000644000175000001440000001024011252461427016434 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef NoteFilterDlgImplH #define NoteFilterDlgImplH #include #include "ui_NoteFilter.h" #include "DoubleList.h" struct Note; class CommonData; //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== class NoteListElem : public ListElem { /*override*/ std::string getText(int nCol) const; const Note* m_pNote; // doesn't own the pointer CommonData* m_pCommonData; public: NoteListElem(const Note* pNote, CommonData* pCommonData) : m_pNote(pNote), m_pCommonData(pCommonData) {} const Note* getNote() const { return m_pNote; } const CommonData* getCommonData() const { return m_pCommonData; } }; class NoteListPainterBase : public ListPainter { /*override*/ int getColCount() const { return 2; } /*override*/ std::string getColTitle(int nCol) const; /*override*/ int getColWidth(int nCol) const; // positive values are used for fixed widths, while negative ones are for "stretched" /*override*/ int getHdrHeight() const; /*override*/ Qt::Alignment getAlignment(int nCol) const; /*override*/ void getColor(int nIndex, int nColumn, bool bSubList, QColor& bckgColor, QColor& penColor, double& dGradStart, double& dGradEnd) const; mutable std::vector m_vpAvail, m_vpSel; protected: CommonData* m_pCommonData; public: NoteListPainterBase(CommonData* pCommonData, const std::string& strNothingSel) : ListPainter(strNothingSel), m_pCommonData(pCommonData) {} }; //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== class NoteFilterDlgImpl : public QDialog, private Ui::NoteFilterDlg, public NoteListPainterBase { Q_OBJECT void logState(const char* szPlace) const; /*override*/ std::string getTooltip(TooltipKey eTooltipKey) const; /*override*/ void reset(); public: /*$PUBLIC_FUNCTIONS$*/ NoteFilterDlgImpl(CommonData* pCommonData, QWidget *pParent); ~NoteFilterDlgImpl(); public slots: /*$PUBLIC_SLOTS$*/ void on_m_pOkB_clicked(); void on_m_pCancelB_clicked(); void onAvlDoubleClicked(int nRow); void onHelp(); }; #endif // #ifndef NoteFilterDlgImplH MP3Diags-1.2.02/src/Notes.h0000444000175000001440000011041112310542225014167 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef NotesH #define NotesH #include #include #include #include #include #include // for translation #include "SerSupport.h" #include #ifndef Q_MOC_RUN // See: https://bugreports.qt-project.org/browse/QTBUG-22829 #include #endif #ifdef ERR #undef ERR #endif /* Stores a message that is added while parsing a file. Multiple messages may be added, depending on the issues found. Notes are added to an Mp3Handler: 1) while creating (or attempting to create) new streams 2) after the streams were created, addressing the relations between streams (this may include non-existing streams that should have been present). Calling MP3_CHECK(), MP3_THROW() or MP3_NOTE() adds a note to an Mp3Handler. Notes have a "Severity": ERR: Something was seriously wrong and it should be reported to the user. WARNING: There may be some issues, in some cases. SUPPORT: A stream tries to use a feature that is not currently supported. The program needs an update. TRACE: Meant for debugging only. Most operations generate TRACE notes, so that it should be easy to follow the steps that led to an unexpected situation while processing a given file. A note may be attached to a stream. This happens implicitly, by using the "pos" value: if this is -1, the note is non-attached; otherwise it is attached to the stream that contains that address. As a result, it is possible for ERR and SUPPORT notes to be attached to apparently unrelated streams (usually they would be "Unknown" streams). This happens because they belong to streams that can't be constructed, because they are either broken or not currently supported. When the construction fails, "Unknown" streams are most likely to be constructed instead, and the note gets attached to them. Note that not all exceptions thrown on streams' constructors with MP3_CHECK() or MP3_THROW are ERR. Indeed, most are TRACE. This is because the algorithm for identifying the streams simply tries to construct all known streams (for a given position). If the actual stream doesn't match the constructor, a TRACE exception is thrown and the constructor for the next stream type is called. On the other hand, if the stream can be identified as matching by the constructor but has errors preventing the constructor to complete successfully, ERR is the severity that should be used by the note. (And similarly for not supported features, but depending on the particular case, it may be decided to continue loading with that support missing and partial functionality.) SUPPORT notes end up in the UI, so care should be taken to avoid false positives (e.g. if "MPEG 2.5 not supported" is thrown as SUPPORT, we should check that an MPEG 2.5 stream is present, not just a few bits at the beginning"; anyway, for this reason, "MPEG 2.5 not supported" is thrown as TRACE). Since SUPPORT notes are usually attached to "Unknown" streams, the original stream (which couldn't be constructed because of the unsupported feature) should be identifiable from the error message. The same is true for ERR exceptions thrown on the constructors, but it isn't necessary for the other notes (at least for those that are linked to streams). Reformulated: there are 2 situations for exceptions in constructors: 1) obviously not the stream that was expected, no need to report it; use TRACE; 2) the beginning was OK, several checks went OK, but then something went wrong; should be reported, and the type of the stream should be specified in the message; use ERR or SUPPORT; "Detail" is better avoided, because it takes space. It is supposed to provide context-dependent detail for "description", but most of the time the only detail that matters is the position, which is already available. The code that uses Note should call getDetail() first, and if that returns an empty string use getDescription() to present the user with all the information available. */ struct Note { enum Severity { ERR, WARNING, SUPPORT, TRACE }; // !!! the reason "ERR" is used (and not "ERROR") is that "ERROR" is a macro in MSVC enum Category { AUDIO, XING, VBRI, ID3V2, APIC, ID3V230, ID3V240, ID3V1, BROKEN, TRUNCATED, UNKNOWN, LYRICS, APE, MISC, CUSTOM, CATEG_CNT }; // !!! CUSTOM must be just before CATEG_CNT (which is just a counter) static const std::string severityToString(Severity s); struct SharedData // the purpose of this is to make the Note class as small as possible, by storing shared info only once { Severity m_eSeverity; Category m_eCategory; int m_nLabelIndex; // position in a given category int m_nNoteId; // position in the global vector for non-trace notes; -1 for trace bool m_bAllowErase; // needed for the option to remove offending streams; std::string m_strDescription; SharedData(const std::string& strDescription, bool bAllowErase) : m_eSeverity(TRACE), m_eCategory(CUSTOM), m_nLabelIndex(-1), m_nNoteId(-1), m_bAllowErase(bAllowErase), m_strDescription(strDescription) {} private: friend struct Notes; SharedData(Severity eSeverity, Category eCategory, const std::string& strDescription, bool bAllowErase) : m_eSeverity(eSeverity), m_eCategory(eCategory), m_nLabelIndex(-1), m_nNoteId(-1), m_bAllowErase(bAllowErase), m_strDescription(strDescription) {} private: friend class boost::serialization::access; SharedData() {} template void serialize(Archive& ar, const unsigned int nVersion) { if (nVersion > 0) { throw std::runtime_error("invalid version of serialized file"); } ar & m_strDescription; // !!! don't care about other attributes, because after loading, a SharedData object is replaced with one from Notes::getNote(strDescr) } }; Note(const Note&, std::streampos pos, const std::string& strDetail = ""); Note(SharedData& sharedData, std::streampos pos, const std::string& strDetail = ""); // Severity is forced to TRACE ~Note(); private: friend struct Notes; Note(SharedData& sharedData); SharedData* m_pSharedData; // !!! can't be const because Notes needs to set indexes when adding notes to vectors, and also because of serialization public: //id // to sort columns const char* getDescription() const { return m_pSharedData->m_strDescription.c_str(); } std::string getDetail() const { return m_strDetail; } std::streampos getPos() const { return m_pos; } std::string getPosHex() const; // returns an empty string for an invalid position (i.e. one initialized from -1) Severity getSeverity() const { return m_pSharedData->m_eSeverity; } Category getCategory() const { return m_pSharedData->m_eCategory; } int getLabelIndex() const { return m_pSharedData->m_nLabelIndex; } // index in a given category; used for displaying a label int getNoteId() const { return m_pSharedData->m_nNoteId; } // index across categories; used for sorting bool allowErase() const { return m_pSharedData->m_bAllowErase; } //bool operator<(const Note&) const; // error before warning, ... ; alpha by descr ; alpha by detail bool operator==(const Note& other) const; protected: std::streampos m_pos; std::string m_strDetail; //mutable char m_szPosBfr [20]; private: friend class boost::serialization::access; Note(); /*template void serialize(Archive& ar, const unsigned int / *nVersion* /) { }*/ template void save(Archive& ar, const unsigned int nVersion) const; template void load(Archive& ar, const unsigned int nVersion); BOOST_SERIALIZATION_SPLIT_MEMBER() }; /* The order of DECL_NOTE_INFO() in struct Notes is irrelevant. What matters is what happens in initVec(). The current approach is to group together related notes, regardless of their severity. So audio-related notes come first, then Xing, then ID3V2 ... The labels are used for convenience. They may change between releases, and are not used internally for anything (e.g. "ignored" notes are stored as full-text in the config file, so they remain ignored as long as their text doesn't change, even if their label changes.) For a given color / severity the labels should be ordered alphabetically, though (well, as long as there are enough letters; then it depends on what m_nLabelIndex gets mapped to) */ // translation of a note #define noteTr(MSG_ID) QCoreApplication::translate("Notes", Notes::MSG_ID().getDescription()) #define DECL_NOTE_INFO(NAME, SEV, CATEG, MSG, ALLOW_ERASE) static Note& NAME() { static Note::SharedData d (Note::SEV, Note::CATEG, MSG, ALLOW_ERASE); static Note n (d); return n; } struct Notes { Q_DECLARE_TR_FUNCTIONS(Notes) public: static const Note* getNote(const std::string& strDescr); // returns 0 if not found static const Note* getNote(int n); // returns 0 if n is out of range static const Note* getMaster(const Note* p); // asserts there is a master static const Note* getMissingNote(); // to be used by serialization: if a description is no longer found, the note gets replace with a default, "missing", one static void addToDestroyList(Note::SharedData* p) { s_spDestroyList.insert(p); } // while loading from disk, Notes replace the SharedData objects from the disk with instances from getNote(strDescr); after the loading is done, the list (actually a set) must be cleared; static void clearDestroyList(); // destoys the pointers added by addToDestroyList(); to be called after loading is complete; // a list with all known notes static const std::vector& getAllNotes(); static const std::vector& getDefaultIgnoredNoteIds(); // audio // a DECL_NOTE_INFO(twoAudio, ERR, AUDIO, QT_TR_NOOP("Two MPEG audio streams found, but a file should have exactly one."), false) // a DECL_NOTE_INFO(lowQualAudio, WARNING, AUDIO, QT_TR_NOOP("Low quality MPEG audio stream. (What is considered \"low quality\" can be changed in the configuration dialog, under \"Quality thresholds\".)"), false) // b DECL_NOTE_INFO(noAudio, ERR, AUDIO, QT_TR_NOOP("No MPEG audio stream found."), false) // c DECL_NOTE_INFO(vbrUsedForNonMpg1L3, WARNING, AUDIO, QT_TR_NOOP("VBR with audio streams other than MPEG1 Layer III might work incorrectly."), false) // d DECL_NOTE_INFO(incompleteFrameInAudio, ERR, AUDIO, QT_TR_NOOP("Incomplete MPEG frame at the end of an MPEG stream."), false) // e DECL_NOTE_INFO(validFrameDiffVer, ERR, AUDIO, QT_TR_NOOP("Valid frame with a different version found after an MPEG stream."), false) // f DECL_NOTE_INFO(validFrameDiffLayer, ERR, AUDIO, QT_TR_NOOP("Valid frame with a different layer found after an MPEG stream."), false) // g DECL_NOTE_INFO(validFrameDiffMode, ERR, AUDIO, QT_TR_NOOP("Valid frame with a different channel mode found after an MPEG stream."), false) // h DECL_NOTE_INFO(validFrameDiffFreq, ERR, AUDIO, QT_TR_NOOP("Valid frame with a different frequency found after an MPEG stream."), false) // i DECL_NOTE_INFO(validFrameDiffCrc, ERR, AUDIO, QT_TR_NOOP("Valid frame with a different CRC policy found after an MPEG stream."), false) // j DECL_NOTE_INFO(audioTooShort, ERR, AUDIO, QT_TR_NOOP("Invalid MPEG stream. Stream has fewer than 10 frames."), false) // k DECL_NOTE_INFO(diffBitrateInFirstFrame, ERR, AUDIO, QT_TR_NOOP("Invalid MPEG stream. First frame has different bitrate than the rest."), false) // l DECL_NOTE_INFO(noMp3Gain, WARNING, AUDIO, QT_TR_NOOP("No normalization undo information found. The song is probably not normalized by MP3Gain or a similar program. As a result, it may sound too loud or too quiet when compared to songs from other albums."), false) // n DECL_NOTE_INFO(untestedEncoding, SUPPORT, AUDIO, QT_TR_NOOP("Found audio stream in an encoding other than \"MPEG-1 Layer 3\" or \"MPEG-2 Layer 3.\" While MP3 Diags understands such streams, very few tests were run on files containing them (because they are not supposed to be found inside files with the \".mp3\" extension), so there is a bigger chance of something going wrong while processing them."), false) // o // xing // b DECL_NOTE_INFO(twoLame, ERR, XING, QT_TR_NOOP("Two Lame headers found, but a file should have at most one of them."), true) // a DECL_NOTE_INFO(xingAddedByMp3Fixer, WARNING, XING, QT_TR_NOOP("Xing header seems added by Mp3Fixer, which makes the first frame unusable and causes a 16-byte unknown or null stream to be detected next."), false) // b DECL_NOTE_INFO(xingFrameCountMismatch, ERR, XING, QT_TR_NOOP("Frame count mismatch between the Xing header and the audio stream."), false) // c DECL_NOTE_INFO(twoXing, ERR, XING, QT_TR_NOOP("Two Xing headers found, but a file should have at most one of them."), false) // d DECL_NOTE_INFO(xingNotBeforeAudio, ERR, XING, QT_TR_NOOP("The Xing header should be located immediately before the MPEG audio stream."), false) // e DECL_NOTE_INFO(incompatXing, ERR, XING, QT_TR_NOOP("The Xing header should be compatible with the MPEG audio stream, meaning that their MPEG version, layer and frequency must be equal."), false) // f DECL_NOTE_INFO(missingXing, WARNING, XING, QT_TR_NOOP("The MPEG audio stream uses VBR but a Xing header wasn't found. This will confuse some players, which won't be able to display the song duration or to seek."), false) // g // vbri // c DECL_NOTE_INFO(twoVbri, ERR, VBRI, QT_TR_NOOP("Two VBRI headers found, but a file should have at most one of them."), true) // a DECL_NOTE_INFO(vbriFound, WARNING, VBRI, QT_TR_NOOP("VBRI headers aren't well supported by some players. They should be replaced by Xing headers."), true) // b DECL_NOTE_INFO(foundVbriAndXing, WARNING, VBRI, QT_TR_NOOP("VBRI header found alongside Xing header. The VBRI header should probably be removed."), true) // c // id3 v2 // d DECL_NOTE_INFO(id3v2FrameTooShort, ERR, ID3V2, QT_TR_NOOP("Invalid ID3V2 frame. File too short."), false) // a DECL_NOTE_INFO(id3v2InvalidName, ERR, ID3V2, QT_TR_NOOP("Invalid frame name in ID3V2 tag."), true) // b DECL_NOTE_INFO(id3v2IncorrectFlg1, WARNING, ID3V2, QT_TR_NOOP("Flags in the first flag group that are supposed to always be 0 are set to 1. They will be ignored."), true) // c DECL_NOTE_INFO(id3v2IncorrectFlg2, WARNING, ID3V2, QT_TR_NOOP("Flags in the second flag group that are supposed to always be 0 are set to 1. They will be ignored."), true) // d DECL_NOTE_INFO(id3v2TextError, ERR, ID3V2, QT_TR_NOOP("Error decoding the value of a text frame while reading an Id3V2 Stream."), true) // e DECL_NOTE_INFO(id3v2HasLatin1NonAscii, WARNING, ID3V2, QT_TR_NOOP("ID3V2 tag has text frames using Latin-1 encoding that contain characters with a code above 127. While this is valid, those frames may have their content set or displayed incorrectly by software that uses the local code page instead of Latin-1. Conversion to Unicode (UTF16) is recommended."), true) // f DECL_NOTE_INFO(id3v2EmptyTcon, WARNING, ID3V2, QT_TR_NOOP("Empty genre frame (TCON) found."), true) // g DECL_NOTE_INFO(id3v2MultipleFramesWithSameName, WARNING, ID3V2, QT_TR_NOOP("Multiple frame instances found, but only the first one will be used."), true) // h DECL_NOTE_INFO(id3v2PaddingTooLarge, WARNING, ID3V2, QT_TR_NOOP("The padding in the ID3V2 tag is too large, wasting space. (Large padding improves the tag editor saving speed, if fast saving is enabled, so you may want to delay compacting the tag until after you're done with the tag editor.)"), false) // i DECL_NOTE_INFO(id3v2UnsuppVer, SUPPORT, ID3V2, QT_TR_NOOP("Unsupported ID3V2 version."), true) // j DECL_NOTE_INFO(id3v2UnsuppFlag, SUPPORT, ID3V2, QT_TR_NOOP("Unsupported ID3V2 tag. Unsupported flag."), true) // k DECL_NOTE_INFO(id3v2UnsuppFlags1, SUPPORT, ID3V2, QT_TR_NOOP("Unsupported value for Flags1 in ID3V2 frame. (This may also indicate that the file contains garbage where it was supposed to be zero.)"), true) // l DECL_NOTE_INFO(id3v2UnsuppFlags2, SUPPORT, ID3V2, QT_TR_NOOP("Unsupported value for Flags2 in ID3V2 frame. (This may also indicate that the file contains garbage where it was supposed to be zero.)"), true) // n DECL_NOTE_INFO(id3v2DuplicatePopm, SUPPORT, ID3V2, QT_TR_NOOP("Multiple instances of the POPM frame found in ID3V2 tag. The current version discards all the instances except the first when processing this tag."), true) // o DECL_NOTE_INFO(id3v2EmptyTag, WARNING, ID3V2, QT_TR_NOOP("ID3V2 tag contains no frames, which is invalid. This note will disappear once you add track information in the tag editor."), true) //p DECL_NOTE_INFO(id3v2EmptyTextFrame, WARNING, ID3V2, QT_TR_NOOP("ID3V2 tag contains an empty text frame, which is invalid."), true) //q // apic // e DECL_NOTE_INFO(id3v2NoApic, WARNING, APIC, QT_TR_NOOP("ID3V2 tag doesn't have an APIC frame (which is used to store images)."), true) // a DECL_NOTE_INFO(id3v2CouldntLoadPic, WARNING, APIC, QT_TR_NOOP("ID3V2 tag has an APIC frame (which is used to store images), but the image couldn't be loaded."), true) // b DECL_NOTE_INFO(id3v2NotCoverPicture, WARNING, APIC, QT_TR_NOOP("ID3V2 tag has at least one valid APIC frame (which is used to store images), but no frame has a type that is associated with an album cover."), true) // c DECL_NOTE_INFO(id3v2ErrorLoadingApic, WARNING, APIC, QT_TR_NOOP("Error loading image in APIC frame."), true) // d DECL_NOTE_INFO(id3v2ErrorLoadingApicTooShort, WARNING, APIC, QT_TR_NOOP("Error loading image in APIC frame. The frame is too short anyway to have space for an image."), true) // e DECL_NOTE_INFO(id3v2DuplicatePic, ERR, APIC, QT_TR_NOOP("ID3V2 tag has multiple APIC frames with the same picture type."), true) // f DECL_NOTE_INFO(id3v2MultipleApic, WARNING, APIC, QT_TR_NOOP("ID3V2 tag has multiple APIC frames. While this is valid, players usually use only one of them to display an image, discarding the others."), true) // g //DECL_NOTE_INFO(id3v2LinkNotSupported, SUPPORT, APIC, QT_TR_NOOP("ID3V2 tag has an APIC frame (which is used to store images), but it uses a link to an external file, which is not supported."), false) DECL_NOTE_INFO(id3v2UnsupApicTextEnc, SUPPORT, APIC, QT_TR_NOOP("Unsupported text encoding for APIC frame in ID3V2 tag."), true) // h DECL_NOTE_INFO(id3v2LinkInApic, SUPPORT, APIC, QT_TR_NOOP("APIC frame uses a link to a file as a MIME Type, which is not supported."), true) // i DECL_NOTE_INFO(id3v2PictDescrIgnored, SUPPORT, APIC, QT_TR_NOOP("Picture description is ignored in the current version."), true) // j // id3 v2.3.0 // f DECL_NOTE_INFO(noId3V230, WARNING, ID3V230, QT_TR_NOOP("No ID3V2.3.0 tag found, although this is the most popular tag for storing song information."), false) // a DECL_NOTE_INFO(twoId3V230, ERR, ID3V230, QT_TR_NOOP("Two ID3V2.3.0 tags found, but a file should have at most one of them."), true) // b DECL_NOTE_INFO(bothId3V230_V240, WARNING, ID3V230, QT_TR_NOOP("Both ID3V2.3.0 and ID3V2.4.0 tags found, but there should be only one of them."), true) // c DECL_NOTE_INFO(id3v230AfterAudio, ERR, ID3V230, QT_TR_NOOP("The ID3V2.3.0 tag should be the first tag in a file."), true) // d DECL_NOTE_INFO(id3v230UsesUtf8, WARNING, ID3V230, QT_TR_NOOP("ID3V2.3.0 tag contains a text frame encoded as UTF-8, which is valid in ID3V2.4.0 but not in ID3V2.3.0."), true) // e DECL_NOTE_INFO(id3v230UnsuppText, SUPPORT, ID3V230, QT_TR_NOOP("Unsupported value of text frame while reading an Id3V2 Stream."), true) // f DECL_NOTE_INFO(id3v230CantReadFrame, ERR, ID3V230, QT_TR_NOOP("Invalid ID3V2.3.0 frame. Incorrect frame size or file too short."), true) // g // id3 v2.4.0 // g DECL_NOTE_INFO(twoId3V240, ERR, ID3V240, QT_TR_NOOP("Two ID3V2.4.0 tags found, but a file should have at most one of them."), true) // a DECL_NOTE_INFO(id3v240CantReadFrame, ERR, ID3V240, QT_TR_NOOP("Invalid ID3V2.4.0 frame. Incorrect frame size or file too short."), true) // b DECL_NOTE_INFO(id3v240IncorrectSynch, WARNING, ID3V240, QT_TR_NOOP("Invalid ID3V2.4.0 frame. Frame size is supposed to be stored as a synchsafe integer, which uses only 7 bits in a byte, but the size uses all 8 bits, as in ID3V2.3.0. This will confuse some applications"), true) // c DECL_NOTE_INFO(id3v240DeprTyerAndTdrc, WARNING, ID3V240, QT_TR_NOOP("Deprecated TYER frame found in 2.4.0 tag alongside a TDRC frame."), true) // d DECL_NOTE_INFO(id3v240DeprTyer, WARNING, ID3V240, QT_TR_NOOP("Deprecated TYER frame found in 2.4.0 tag. It's supposed to be replaced by a TDRC frame."), true) // e DECL_NOTE_INFO(id3v240DeprTdatAndTdrc, WARNING, ID3V240, QT_TR_NOOP("Deprecated TDAT frame found in 2.4.0 tag alongside a TDRC frame."), true) // f DECL_NOTE_INFO(id3v240DeprTdat, WARNING, ID3V240, QT_TR_NOOP("Deprecated TDAT frame found in 2.4.0 tag. It's supposed to be replaced by a TDRC frame."), true) // g DECL_NOTE_INFO(id3v240IncorrectDli, WARNING, ID3V240, QT_TR_NOOP("Invalid ID3V2.4.0 frame. Mismatched Data length indicator. Frame value is probably incorrect"), true) // h DECL_NOTE_INFO(id3v240IncorrectFrameSynch, WARNING, ID3V240, QT_TR_NOOP("Invalid ID3V2.4.0 frame. Incorrect unsynchronization bit."), true) // i DECL_NOTE_INFO(id3v240UnsuppText, SUPPORT, ID3V240, QT_TR_NOOP("Unsupported value of text frame while reading an Id3V2.4.0 stream. It may be using an unsupported text encoding."), true) // j // id3 v1 // h DECL_NOTE_INFO(onlyId3V1, WARNING, ID3V1, QT_TR_NOOP("The only supported tag found that is capable of storing song information is ID3V1, which has pretty limited capabilities."), false) // a DECL_NOTE_INFO(id3v1BeforeAudio, ERR, ID3V1, QT_TR_NOOP("The ID3V1 tag should be located after the MPEG audio stream."), true) // b DECL_NOTE_INFO(id3v1TooShort, ERR, ID3V1, QT_TR_NOOP("Invalid ID3V1 tag. File too short."), false) // c DECL_NOTE_INFO(twoId3V1, ERR, ID3V1, QT_TR_NOOP("Two ID3V1 tags found, but a file should have at most one of them."), true) // d //DECL_NOTE_INFO(zeroInId3V1, WARNING, ID3V1, QT_TR_NOOP("ID3V1 tag contains characters with the code 0, although this is not allowed by the standard (yet used by some tools)."), false) DECL_NOTE_INFO(mixedPaddingInId3V1, WARNING, ID3V1, QT_TR_NOOP("ID3V1 tag contains fields padded with spaces alongside fields padded with zeroes. The standard only allows zeroes, but some tools use spaces. Even so, zero-padding and space-padding shouldn't be mixed."), true) // e DECL_NOTE_INFO(mixedFieldPaddingInId3V1, WARNING, ID3V1, QT_TR_NOOP("ID3V1 tag contains fields that are padded with spaces mixed with zeroes. The standard only allows zeroes, but some tools use spaces. Even so, one character should be used for padding for the whole tag."), true) // f DECL_NOTE_INFO(id3v1InvalidName, ERR, ID3V1, QT_TR_NOOP("Invalid ID3V1 tag. Invalid characters in Name field."), true) // g DECL_NOTE_INFO(id3v1InvalidArtist, ERR, ID3V1, QT_TR_NOOP("Invalid ID3V1 tag. Invalid characters in Artist field."), true) // h DECL_NOTE_INFO(id3v1InvalidAlbum, ERR, ID3V1, QT_TR_NOOP("Invalid ID3V1 tag. Invalid characters in Album field."), true) // i DECL_NOTE_INFO(id3v1InvalidYear, ERR, ID3V1, QT_TR_NOOP("Invalid ID3V1 tag. Invalid characters in Year field."), true) // j DECL_NOTE_INFO(id3v1InvalidComment, ERR, ID3V1, QT_TR_NOOP("Invalid ID3V1 tag. Invalid characters in Comment field."), true) // k // broken // i DECL_NOTE_INFO(brokenAtTheEnd, ERR, BROKEN, QT_TR_NOOP("Broken stream found."), true) // a DECL_NOTE_INFO(brokenInTheMiddle, ERR, BROKEN, QT_TR_NOOP("Broken stream found. Since other streams follow, it is possible that players and tools will have problems using the file. Removing the stream is recommended."), true) // b // trunc // j DECL_NOTE_INFO(truncAudioWithWholeFile, ERR, TRUNCATED, QT_TR_NOOP("Truncated MPEG stream found. The cause for this seems to be that the file was truncated."), false) // a DECL_NOTE_INFO(truncAudio, ERR, TRUNCATED, QT_TR_NOOP("Truncated MPEG stream found. Since other streams follow, it is possible that players and tools will have problems using the file. Removing the stream or padding it with 0 to reach its declared size is strongly recommended."), false) // b // unknown // k DECL_NOTE_INFO(unknTooShort, WARNING, UNKNOWN, QT_TR_NOOP("Not enough remaining bytes to create an UnknownDataStream."), false) // a DECL_NOTE_INFO(unknownAtTheEnd, ERR, UNKNOWN, QT_TR_NOOP("Unknown stream found."), true) // b DECL_NOTE_INFO(unknownInTheMiddle, ERR, UNKNOWN, QT_TR_NOOP("Unknown stream found. Since other streams follow, it is possible that players and tools will have problems using the file. Removing the stream is recommended."), true) // c DECL_NOTE_INFO(foundNull, WARNING, UNKNOWN, QT_TR_NOOP("File contains null streams."), true) // d // lyrics // l DECL_NOTE_INFO(lyrTooShort, ERR, LYRICS, QT_TR_NOOP("Invalid Lyrics stream tag. File too short."), false) // a DECL_NOTE_INFO(twoLyr, SUPPORT, LYRICS, QT_TR_NOOP("Two Lyrics tags found, but only one is supported."), true) // b // ttt2 see if this is error //DECL_NOTE_INFO(lyricsNotSupported, SUPPORT, LYRICS, QT_TR_NOOP("Lyrics tags cannot be processed in the current version. Some players don't understand them."), false) DECL_NOTE_INFO(invalidLyr, ERR, LYRICS, QT_TR_NOOP("Invalid Lyrics stream tag. Unexpected characters found."), false) // c DECL_NOTE_INFO(duplicateFields, SUPPORT, LYRICS, QT_TR_NOOP("Multiple fields with the same name were found in a Lyrics tag, but only one is supported."), true) // d //DECL_NOTE_INFO(imgInLyrics, SUPPORT, LYRICS, QT_TR_NOOP("Currently images referenced from Lyrics tags are ignored."), true) // (e) DECL_NOTE_INFO(infInLyrics, SUPPORT, LYRICS, QT_TR_NOOP("Currently INF fields in Lyrics tags are not fully supported."), true) // e // ape // n DECL_NOTE_INFO(apeItemTooShort, ERR, APE, QT_TR_NOOP("Invalid Ape Item. File too short."), false) // a DECL_NOTE_INFO(apeItemTooBig, ERR, APE, QT_TR_NOOP("Ape Item seems too big. Although the size may be any 32-bit integer, 256 bytes should be enough in practice. If this note is determined to be incorrect, it will be removed in the future."), false) // b DECL_NOTE_INFO(apeMissingTerminator, ERR, APE, QT_TR_NOOP("Invalid Ape Item. Terminator not found for item name."), false) // c DECL_NOTE_INFO(apeFoundFooter, ERR, APE, QT_TR_NOOP("Invalid Ape tag. Header expected but footer found."), false) // d DECL_NOTE_INFO(apeTooShort, ERR, APE, QT_TR_NOOP("Not an Ape tag. File too short."), false) // e DECL_NOTE_INFO(apeFoundHeader, ERR, APE, QT_TR_NOOP("Invalid Ape tag. Footer expected but header found."), false) // f DECL_NOTE_INFO(apeHdrFtMismatch, ERR, APE, QT_TR_NOOP("Invalid Ape tag. Mismatch between header and footer."), false) // g DECL_NOTE_INFO(twoApe, SUPPORT, APE, QT_TR_NOOP("Two Ape tags found, but only one is supported."), true) // h // ttt2 see if this is error DECL_NOTE_INFO(apeFlagsNotSupported, SUPPORT, APE, QT_TR_NOOP("Ape item flags not supported."), false) // i DECL_NOTE_INFO(apeUnsupported, SUPPORT, APE, QT_TR_NOOP("Unsupported Ape tag. Currently a missing header or footer are not supported."), false) // j // misc // o DECL_NOTE_INFO(fileWasChanged, WARNING, MISC, QT_TR_NOOP("The file seems to have been changed in the (short) time that passed between parsing it and the initial search for pictures. If you think that's not the case, report a bug."), false) // a DECL_NOTE_INFO(noInfoTag, WARNING, MISC, QT_TR_NOOP("No supported tag found that is capable of storing song information."), false) // b DECL_NOTE_INFO(tooManyTraceNotes, WARNING, MISC, QT_TR_NOOP("Too many TRACE notes added. The rest will be discarded."), false) // c DECL_NOTE_INFO(tooManyNotes, WARNING, MISC, QT_TR_NOOP("Too many notes added. The rest will be discarded."), false) // d DECL_NOTE_INFO(tooManyStreams, WARNING, MISC, QT_TR_NOOP("Too many streams found. Aborting processing."), false) // e DECL_NOTE_INFO(unsupportedFound, WARNING, MISC, QT_TR_NOOP("Unsupported stream found. It may be supported in the future if there's a real need for it."), false) // f DECL_NOTE_INFO(rescanningNeeded, WARNING, MISC, QT_TR_NOOP("The file was saved using the \"fast\" option. While this improves the saving speed, it may leave the notes in an inconsistent state, so you should rescan the file."), false) // g struct CompNoteByName // needed for searching { bool operator()(const Note* p1, const Note* p2) const; }; typedef std::set NoteSet; private: //static std::vector s_vpErrNotes; // pointers are to static variables defined in DECL_NOTE_INFO //static std::vector s_vpWarnNotes; // pointers are to static variables defined in DECL_NOTE_INFO //static std::vector s_vpSuppNotes; // pointers are to static variables defined in DECL_NOTE_INFO static std::vector > s_vpNotesByCateg; static std::vector s_vpAllNotes; // pointers are to static variables defined in DECL_NOTE_INFO static NoteSet s_spAllNotes; // pointers are to static variables defined in DECL_NOTE_INFO static void addNote(Note* p); static void initVec(); static std::set s_spDestroyList; // !!! sorted by pointers }; //============================================================================================================ template inline void Note::save(Archive& ar, const unsigned int nVersion) const { if (nVersion > 0) { throw std::runtime_error("invalid version of serialized file"); } ar << m_pSharedData; ar << m_pos; ar << m_strDetail; } template inline void Note::load(Archive& ar, const unsigned int nVersion) { if (nVersion > 0) { throw std::runtime_error("invalid version of serialized file"); } SharedData* pSharedData; ar >> pSharedData; ar >> m_pos; ar >> m_strDetail; Notes::addToDestroyList(pSharedData); const Note* pNote (Notes::getNote(pSharedData->m_strDescription)); m_pSharedData = 0 == pNote ? Notes::getMissingNote()->m_pSharedData : pNote->m_pSharedData; // 2012.02.03 - the test seems pointless: Notes::getNote(const std::string& strDescr) uses getMissingNote() if it cannot find the description } //============================================================================================================ //============================================================================================================ //============================================================================================================ // notes associated with a Mp3Handler (but is used stand-alone too, to pass as a param to some functions and then discard the results) class NoteColl { std::vector m_vpNotes; // owns the pointers int m_nCount; int m_nTraceCount; int m_nMaxTrace; public: NoteColl(int nMaxTrace) : m_nCount(0), m_nTraceCount(0), m_nMaxTrace(nMaxTrace) {} NoteColl() {} // serialization-only constructor ~NoteColl(); void add(Note*); const std::vector& getList() const { return m_vpNotes; } void resetCounter() { m_nCount = 0; } // normally only 200 notes are added and anything following is discarded; this allows to reset the counter void sort(); // sorts by CmpNotePtrById, which is needed for filtering void removeTraceNotes(); bool hasFastSaveWarn() const; void addFastSaveWarn(); void removeNotes(const std::streampos& posFrom, const std::streampos& posTo); // removes notes with addresses in the given range; posFrom is included, but posTo isn't private: friend class boost::serialization::access; template void serialize(Archive& ar, const unsigned int nVersion) { if (nVersion > 0) { throw std::runtime_error("invalid version of serialized file"); } ar & m_vpNotes; // these don't really matter, but they don't take a lot of space and can be useful at debugging ar & m_nCount; ar & m_nTraceCount; ar & m_nMaxTrace; } }; /*struct CmpNotePtrBySevAndLabel { bool operator()(const Note* p1, const Note* p2) const { if (p1->getSeverity() < p2->getSeverity()) { return true; } if (p1->getSeverity() > p2->getSeverity()) { return false; } if (p1->getNoteId() < p2->getNoteId()) { return true; } return false; } static bool equals(const Note* p1, const Note* p2) { if (p1->getSeverity() != p2->getSeverity() || p1->getNoteId() != p2->getNoteId()) { return false; } return true; } };*/ struct CmpNotePtrById // needed for unique notes, where equality doesn't include the position { bool operator()(const Note* p1, const Note* p2) const { if (p1->getNoteId() < p2->getNoteId()) { return true; } return false; } static bool equals(const Note* p1, const Note* p2) { if (p1->getNoteId() != p2->getNoteId()) { return false; } return true; } }; /* struct CmpNotePtrBySevLabelAndPos { bool operator()(const Note* p1, const Note* p2) const { if (p1->getSeverity() < p2->getSeverity()) { return true; } if (p1->getSeverity() > p2->getSeverity()) { return false; } if (p1->getNoteId() < p2->getNoteId()) { return true; } if (p1->getNoteId() > p2->getNoteId()) { return false; } if (p1->getPos() < p2->getPos()) { return true; } return false; } };*/ /*struct CmpNotePtrByPosSevAndLabel { bool operator()(const Note* p1, const Note* p2) const { if (p1->getPos() < p2->getPos()) { return true; } if (p1->getPos() > p2->getPos()) { return false; } if (p1->getSeverity() < p2->getSeverity()) { return true; } if (p1->getSeverity() > p2->getSeverity()) { return false; } if (p1->getNoteId() < p2->getNoteId()) { return true; } return false; } };*/ /* struct CmpNotePtrByIdAndPos { bool operator()(const Note* p1, const Note* p2) const { if (p1->getNoteId() < p2->getNoteId()) { return true; } if (p1->getNoteId() > p2->getNoteId()) { return false; } if (p1->getPos() < p2->getPos()) { return true; } return false; } };*/ struct CmpNotePtrByPosAndId { bool operator()(const Note* p1, const Note* p2) const { if (p1->getPos() < p2->getPos()) { return true; } if (p1->getPos() > p2->getPos()) { return false; } if (p1->getNoteId() < p2->getNoteId()) { return true; } return false; } }; // allows strings to be shared (as pointers) struct StringWrp { std::string s; StringWrp(const std::string& s) : s(s) {} private: friend class boost::serialization::access; StringWrp() {} template void serialize(Archive& ar, const unsigned int nVersion) { if (nVersion > 0) { throw std::runtime_error("invalid version of serialized file"); } ar & s; } }; //BOOST_CLASS_TRACKING(StringWrp, boost::serialization::track_always); //BOOST_CLASS_TRACKING(StringWrp, boost::serialization::track_never); #endif // #ifndef NotesH MP3Diags-1.2.02/src/Widgets.cpp0000644000175000001440000003204411724676665015077 0ustar ciobiusers /*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include #include #include #include "Widgets.h" #include "Helpers.h" #include "CommonData.h" using namespace std; using namespace pearl; /*override*/ void ModifInfoMenu::mousePressEvent(QMouseEvent *pEvent) { m_modif = pEvent->modifiers(); //int x (m_modif); qDebug("modif %d", x); if (pEvent->button() == Qt::RightButton) { m_modif |= Qt::ShiftModifier; } QMenu::mousePressEvent(pEvent); } ModifInfoToolButton::ModifInfoToolButton(QToolButton* pOldBtn) : QToolButton(pOldBtn->parentWidget()) { QWidget* pParent (pOldBtn->parentWidget()); QWidgetList lpSiblings (pParent->findChildren()); setAutoRaise(pOldBtn->autoRaise()); setIcon(pOldBtn->icon()); setMinimumSize(pOldBtn->minimumSize()); setMaximumSize(pOldBtn->maximumSize()); setFocusPolicy(pOldBtn->focusPolicy()); setIconSize(pOldBtn->iconSize()); setToolTip(pOldBtn->toolTip()); //ttt2 set other properties QBoxLayout* pLayout (dynamic_cast(pParent->layout())); //ttt2 see about other layouts CB_ASSERT (0 != pLayout); int nPos (pLayout->indexOf(pOldBtn)); pLayout->insertWidget(nPos, this); //connect(this, SIGNAL(clicked()), that, SLOT(on_x_clicked())); //ttt2 would be nice to take over the signals, but doesn't seem possible delete pOldBtn; } /*override*/ void ModifInfoToolButton::mousePressEvent(QMouseEvent *pEvent) { m_modif = pEvent->modifiers(); //int x (m_modif); qDebug("modif %d", x); QToolButton::mousePressEvent(pEvent); } /*override*/ void ModifInfoToolButton::keyPressEvent(QKeyEvent* pEvent) { m_modif = pEvent->modifiers(); // ttt2 actually this never seems to get triggered; see if it's true (perhaps it matters if the button can get keyboard focus) //int x (m_modif); qDebug("modif %d", x); QToolButton::keyPressEvent(pEvent); } void ModifInfoToolButton::contextMenuEvent(QContextMenuEvent* /*pEvent*/) { //m_modif = pEvent->modifiers(); m_modif = m_modif | Qt::ShiftModifier; //emit clicked(); } int showMessage(QWidget* pParent, QMessageBox::Icon icon, int nDefault, int nEscape, const QString& qstrTitle, const QString& qstrMessage, const QString& qstrButton0, const QString& qstrButton1 /* = ""*/, const QString& qstrButton2 /* = ""*/, const QString& qstrButton3 /* = ""*/) { QStringList l; l << qstrButton0; if (!qstrButton1.isEmpty()) { l << qstrButton1; if (!qstrButton2.isEmpty()) { l << qstrButton2; if (!qstrButton3.isEmpty()) { l << qstrButton3; } } } QMessageBox msgBox (pParent); vector v; for (int i = 0; i < l.size(); ++i) { v.push_back(msgBox.addButton(l[i], QMessageBox::ActionRole)); // ActionRole is meaningless, but had to use something if (cSize(v) - 1 == nEscape) { msgBox.setEscapeButton(v.back()); } if (cSize(v) - 1 == nDefault) { msgBox.setDefaultButton(v.back()); } } msgBox.setText(qstrMessage); msgBox.setIcon(icon); msgBox.setTextFormat(Qt::PlainText); msgBox.setWindowTitle(qstrTitle); msgBox.exec(); return find(v.begin(), v.end(), msgBox.clickedButton()) - v.begin(); } void showWarning(QWidget* pParent, const QString& qstrTitle, const QString& qstrMessage) { showMessage(pParent, QMessageBox::Warning, 0, 0, qstrTitle, qstrMessage, GlobalTranslHlp::tr("O&K")); } void showCritical(QWidget* pParent, const QString& qstrTitle, const QString& qstrMessage) { showMessage(pParent, QMessageBox::Critical, 0, 0, qstrTitle, qstrMessage, GlobalTranslHlp::tr("O&K")); } void showInfo(QWidget* pParent, const QString& qstrTitle, const QString& qstrMessage) { showMessage(pParent, QMessageBox::Information, 0, 0, qstrTitle, qstrMessage, GlobalTranslHlp::tr("O&K")); } CursorOverrider::CursorOverrider(Qt::CursorShape crs /* = Qt::BusyCursor*/) { QApplication::setOverrideCursor(QCursor(crs)); } CursorOverrider::~CursorOverrider() { QApplication::restoreOverrideCursor(); } //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== #if 0 struct ThreadLocalDlgList { virtual void addDlg(QDialog* pDlg) = 0; virtual void removeDlg(QDialog* pDlg) = 0; virtual QDialog* getDlg() = 0; }; ThreadLocalDlgList& getThreadLocalDlgList(); // a unique, global, object struct DlgRef { DlgRef(QDialog* pDlg) : m_pDlg(pDlg) { getThreadLocalDlgList().addDlg(pDlg); } ~DlgRef() { getThreadLocalDlgList().removeDlg(m_pDlg); } private: QDialog* m_pDlg; }; #endif #if 0 struct DlgList { void addDlg(QDialog* pDlg) { m_stpDlg.push(pDlg); } void removeDlg(QDialog* pDlg) { CB_ASSERT (!m_stpDlg.empty()); CB_ASSERT (m_stpDlg.top() == pDlg); m_stpDlg.pop(); } QDialog* getDlg() // may return 0 { if (m_stpDlg.empty()) { return 0; } return m_stpDlg.top(); } ~DlgList() { CB_ASSERT (m_stpDlg.empty()); //ttt 0 see if right, if called from ASSERT } private: stack m_stpDlg; }; class ThreadLocalDlgListImpl : public ThreadLocalDlgList { QThreadStorage m_storage; void addStorageIfNeeded() { if (!m_storage.hasLocalData()) { m_storage.setLocalData(new DlgList()); } } public: /*override*/ void addDlg(QDialog* pDlg) { addStorageIfNeeded(); m_storage.localData()->addDlg(pDlg); } /*override*/ void removeDlg(QDialog* pDlg) { addStorageIfNeeded(); m_storage.localData()->removeDlg(pDlg); } /*override*/ QDialog* getDlg() { addStorageIfNeeded(); return m_storage.localData()->getDlg(); } }; ThreadLocalDlgList& getThreadLocalDlgList() { static ThreadLocalDlgListImpl s_lst; return s_lst; } #endif /*static*/ int HtmlMsg::msg(QWidget* pParent, int nDefault, int nEscape, bool* pbGotTheMessage, int nFlags, const QString& qstrTitle, const QString& qstrMessage, int nWidth, int nHeight, const QString& qstrButton0, const QString& qstrButton1 /* = ""*/, const QString& qstrButton2 /* = ""*/, const QString& qstrButton3 /* = ""*/) { LAST_STEP("HtmlMsg::msg()"); //QDialog dlg (pParent, Qt::Dialog | getNoResizeWndFlags() | (bStayOnTop ? Qt::WindowStaysOnTopHint : Qt::WindowFlags(0))); QDialog dlg (pParent, getDialogWndFlags() | (nFlags & STAY_ON_TOP ? Qt::WindowStaysOnTopHint : Qt::WindowFlags(0))); dlg.setWindowTitle(qstrTitle); dlg.setWindowIcon(QIcon(":/images/logo.svg")); QVBoxLayout* pLayout (new QVBoxLayout(&dlg)); QTextBrowser* pContent (new QTextBrowser(&dlg)); if (nFlags & CRITICAL) { QPalette pal (pContent->palette()); pal.setColor(QPalette::Base, QColor(192, 0, 0)); pal.setColor(QPalette::Text, QColor(255, 255, 0)); pContent->setPalette(pal); QFont fnt (pContent->font()); fnt.setBold(true); pContent->setFont(fnt); } pContent->setOpenExternalLinks(true); pContent->setHtml(qstrMessage + (nFlags & SHOW_SYS_INFO ? "

" + getSystemInfo() + "

" : "")); //ttt2 perhaps use CSS pLayout->addWidget(pContent); QHBoxLayout btnLayout; QCheckBox* pCheck (0); if (0 != pbGotTheMessage) { pCheck = new QCheckBox(tr("I got the message; don't show this again"), &dlg); btnLayout.addWidget(pCheck); } HtmlMsg msg (&dlg, nEscape); btnLayout.addStretch(0); QPushButton* pBtn0 (0); QPushButton* pBtn1 (0); QPushButton* pBtn2 (0); QPushButton* pBtn3 (0); pBtn0 = new QPushButton(qstrButton0, &dlg); (nFlags & VERT_BUTTONS ? (QLayout&)(*pLayout) : (QLayout&)btnLayout).addWidget(pBtn0); QObject::connect(pBtn0, SIGNAL(clicked()), &msg, SLOT(onClick0())); if (0 == nDefault) { pBtn0->setDefault(true); } if (!qstrButton1.isEmpty()) { pBtn1 = new QPushButton(qstrButton1, &dlg); (nFlags & VERT_BUTTONS ? (QLayout&)(*pLayout) : (QLayout&)btnLayout).addWidget(pBtn1); QObject::connect(pBtn1, SIGNAL(clicked()), &msg, SLOT(onClick1())); if (1 == nDefault) { pBtn1->setDefault(true); } if (!qstrButton2.isEmpty()) { pBtn2 = new QPushButton(qstrButton2, &dlg); (nFlags & VERT_BUTTONS ? (QLayout&)(*pLayout) : (QLayout&)btnLayout).addWidget(pBtn2); QObject::connect(pBtn2, SIGNAL(clicked()), &msg, SLOT(onClick2())); if (2 == nDefault) { pBtn2->setDefault(true); } if (!qstrButton3.isEmpty()) { pBtn3 = new QPushButton(qstrButton3, &dlg); (nFlags & VERT_BUTTONS ? (QLayout&)(*pLayout) : (QLayout&)btnLayout).addWidget(pBtn3); QObject::connect(pBtn3, SIGNAL(clicked()), &msg, SLOT(onClick3())); if (3 == nDefault) { pBtn3->setDefault(true); } } } } pLayout->addLayout(&btnLayout); dlg.resize(nWidth, nHeight); dlg.setSizeGripEnabled(true); dlg.exec(); LAST_STEP1("HtmlMsg::msg 2()", 1); if (0 != pbGotTheMessage) { *pbGotTheMessage = pCheck->isChecked(); } return msg.m_nBtn; } void HtmlMsg::onClick0() { m_nBtn = 0; m_pDlg->accept(); } void HtmlMsg::onClick1() { m_nBtn = 1; m_pDlg->accept(); } void HtmlMsg::onClick2() { m_nBtn = 2; m_pDlg->accept(); } void HtmlMsg::onClick3() { m_nBtn = 3; m_pDlg->accept(); } /*override*/ void NoCropHeaderView::paintSection(QPainter* pPainter, const QRect& r, int nLogicalIndex) const { /*{ QHeaderView::paintSection(pPainter, r, nLogicalIndex); return; return; }*/ pPainter->save(); // partial copy from Qt's implementation of QHeaderView (qheaderview.cpp) QStyleOptionHeader opt; initStyleOption(&opt); opt.rect = r; opt.section = nLogicalIndex; int nVisual (visualIndex(nLogicalIndex)); if (count() == 1) opt.position = QStyleOptionHeader::OnlyOneSection; else if (nVisual == 0) opt.position = QStyleOptionHeader::Beginning; else if (nVisual == count() - 1) opt.position = QStyleOptionHeader::End; else opt.position = QStyleOptionHeader::Middle; opt.selectedPosition = QStyleOptionHeader::NotAdjacent; style()->drawControl(QStyle::CE_Header, &opt, pPainter, this); pPainter->restore(); pPainter->save(); pPainter->setFont(getCommonData()->getLabelFont()); /*{ // bold for selected QModelIndexList l (getCommonData()->m_pFilesG->selectionModel()->selection().indexes()); for (QModelIndexList::iterator it = l.begin(); it != l.end(); ++it) { const QModelIndex& ndx (*it); if (ndx.column() == nLogicalIndex) { QFont f (pPainter->font()); f.setWeight(QFont::Bold); pPainter->setFont(f); break; } } }*/ QRect r1 (r); r1.adjust(0, -1, 0, -1); QString qs (model()->headerData(nLogicalIndex, Qt::Vertical).toString()); pPainter->drawText(r1, Qt::AlignCenter, qs); pPainter->restore(); } MP3Diags-1.2.02/src/CommonTypes.h0000644000175000001440000001450411724500035015365 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef CommonTypesH #define CommonTypesH #include #include //#include // ttt2 see why this includes QByteRef, which gives warnings for lots of functions returning "const char" or "const bool", saying that "const" is going to be ignored; perhaps #define something before "//#include "; QtGui and QPixmap avoid this issue, but take longer #include //#include // to avoid some dependencies and shorten the compilation time //ttt2 add more types here struct ImageInfo { enum Compr { INVALID, JPG, PNG }; enum Status { OK, LOADED_NOT_COVER, USES_LINK, ERROR_LOADING, NO_PICTURE_FOUND }; ImageInfo() : m_eCompr(INVALID), m_eStatus(NO_PICTURE_FOUND), m_nWidth(0), m_nHeight(0), m_nImageType(-1) {} //ImageInfo(Compr eCompr, QByteArray compressedImg) : m_eCompr(eCompr), m_eStatus(OK), m_compressedImg(compressedImg) {} ImageInfo(int nImageType, Status eStatus) : m_eCompr(INVALID), m_eStatus(eStatus), m_nWidth(0), m_nHeight(0), m_nImageType(nImageType) {} ImageInfo(int nImageType, Status eStatus, const QImage& pic); ImageInfo(int nImageType, Status eStatus, Compr eCompr, QByteArray compressedImg, int nWidth, int nHeight); bool isNull() const { return m_compressedImg.isEmpty(); } int getWidth() const { return m_nWidth; } int getHeight() const { return m_nHeight; } int getSize() const { return m_compressedImg.size(); } const char* getComprData() const { return m_compressedImg.constData(); } const char* getImageType() const; Status getStatus() const { return m_eStatus; } Compr getCompr() const { return m_eCompr; } // the picture is scaled down, keeping the aspect ratio, if the limits are exceeded; 0 and negative limits are ignored; // if nMaxWidth>0 and nMaxHeight<=0, nMaxHeight has the same value as nMaxWidth; QImage getImage(int nMaxWidth = -1, int nMaxHeight = -1) const; QString getTextDescr(const QString& qstrSep = "\n") const; void showFull(QWidget* pParent) const; //const QByteArray& getCompressedImg() const { return m_compressedImg; } bool operator==(const ImageInfo&) const; static int MAX_IMAGE_SIZE; static void compress(const QImage& origPic, QImage& scaledPic, QByteArray& comprImg); // scales down origPic and stores the pixmap in scaledPic, as well as a compressed version in comprImg; the algorithm coninues until comprImg becomes smaller than MAX_IMAGE_SIZE or until the width and the height of scaledPic get 150 or smaller; no scaling is done if comprImg turns out to be small enough for the original image; static const char* getImageType(int nImageType); static const char* getComprStr(Compr); private: Compr m_eCompr; Status m_eStatus; int m_nWidth, m_nHeight; QByteArray m_compressedImg; // this normally contains a downloaded image, but it may contain a recompressed image, if it got scaled down int m_nImageType; }; struct TrackInfo { TrackInfo() : m_dRating(-1) {} std::string m_strTitle; std::string m_strArtist; std::string m_strPos; std::string m_strComposer; double m_dRating; }; struct AlbumInfo { std::string m_strTitle; //std::string m_strArtist; // in order to show the artist and composer in the grid whatever info is given at album level gets copied to each track, so there's no need for AlbumInfo to store them //std::string m_strComposer; //std::string m_strFormat; // CD, tape, ... std::string m_strGenre; std::string m_strReleased; std::string m_strNotes; enum VarArtists { VA_NOT_SUPP, VA_SINGLE, VA_VARIOUS }; VarArtists m_eVarArtists; std::vector m_vTracks; std::string m_strSourceName; // Discogs, MusicBrainz, ... ; needed by MainFormDlgImpl; ImageInfo m_imageInfo; // a copy of an image in m_pCommonData->m_imageColl; AlbumInfo() : m_eVarArtists(VA_NOT_SUPP) {} }; std::ostream& operator<<(std::ostream&, const AlbumInfo&); enum TextCaseOptions { TC_NONE = -1, TC_LOWER = 0, TC_UPPER, TC_TITLE, TC_SENTENCE }; QString getCaseConv(const QString& s, TextCaseOptions eCase); const char* getCaseAsStr(TextCaseOptions e); struct ExternalToolInfo { std::string m_strName; std::string m_strCommand; enum LaunchOption { DONT_WAIT, WAIT_THEN_CLOSE_WINDOW, WAIT_AND_KEEP_WINDOW_OPEN }; LaunchOption m_eLaunchOption; bool m_bConfirmLaunch; ExternalToolInfo(const std::string& strName, const std::string& strCommand, LaunchOption eLaunchOption, bool bConfirmLaunch) : m_strName(strName), m_strCommand(strCommand), m_eLaunchOption(eLaunchOption), m_bConfirmLaunch(bConfirmLaunch) {} ExternalToolInfo(const std::string& strSerValue); std::string asString(); static QString launchOptionAsTranslatedString(LaunchOption); struct InvalidExternalToolInfo {}; private: static char s_cSeparator; }; #endif // #ifndef CommonTypesH MP3Diags-1.2.02/src/NotesModel.h0000644000175000001440000000616211200236325015157 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef NotesModelH #define NotesModelH #include #include "MultiLineTvDelegate.h" //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== class CommonData; // current notes struct NotesModel : public QAbstractTableModel { Q_OBJECT public: NotesModel(CommonData* pCommonData); /*override*/ int rowCount(const QModelIndex&) const; /*override*/ int columnCount(const QModelIndex&) const; /*override*/ QVariant data(const QModelIndex&, int) const; /*override*/ QVariant headerData(int nSection, Qt::Orientation eOrientation, int nRole = Qt::DisplayRole) const; void matchSelToMain(); void matchSelToStreams(); CommonData* m_pCommonData; void emitLayoutChanged() { emit layoutChanged(); } public slots: void onNotesGSelChanged(); }; //class FilesGDelegate : public QAbstractItemDelegate //class NotesGDelegate : public QItemDelegate class NotesGDelegate : public MultiLineTvDelegate { Q_OBJECT public: //NotesGDelegate(CommonData* pCommonData, QObject* pParent) : QItemDelegate(pParent), m_pCommonData(pCommonData) {} NotesGDelegate(CommonData* pCommonData); /*override*/ void paint(QPainter* pPainter, const QStyleOptionViewItem& option, const QModelIndex& index) const; CommonData* m_pCommonData; }; #endif // #ifndef NotesModelH MP3Diags-1.2.02/src/FileRenamerDlgImpl.h0000644000175000001440000001550012265760240016556 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef FileRenamerDlgImplH #define FileRenamerDlgImplH #include #include #include #include #include #include #include // for translation #include "ui_FileRenamer.h" #include "Helpers.h" #ifdef ERR #undef ERR #endif class CommonData; class QSettings; class FileRenamerDlgImpl; class Mp3Handler; class Renamer; namespace FileRenamer { // current album / filtered handlers; class HndlrListModel : public QAbstractTableModel { Q_OBJECT FileRenamerDlgImpl* m_pFileRenamerDlgImpl; const CommonData* m_pCommonData; const Renamer* m_pRenamer; bool m_bUseCurrentView; /*override*/ Qt::ItemFlags flags(const QModelIndex& index) const; /*override*/ bool setData(const QModelIndex& index, const QVariant& value, int nRole /* = Qt::EditRole*/); public: HndlrListModel(CommonData* pCommonData, FileRenamerDlgImpl* pFileRenamerDlgImpl, bool bUseCurrentView); ~HndlrListModel(); //HndlrListModel(TagEditorDlgImpl* pTagEditorDlgImpl); /*override*/ int rowCount(const QModelIndex&) const; /*override*/ int columnCount(const QModelIndex&) const; /*override*/ QVariant data(const QModelIndex&, int) const; /*override*/ QVariant headerData(int nSection, Qt::Orientation eOrientation, int nRole = Qt::DisplayRole) const; // /*override*/ Qt::ItemFlags flags(const QModelIndex& index) const; const Renamer* getRenamer() const { return m_pRenamer; } void setRenamer(const Renamer*); void setUnratedAsDuplicates(bool bUnratedAsDuplicate); const std::deque getHandlerList() const; // returns either m_pCommonData->getCrtAlbum() or m_pCommonData->getViewHandlers(), based on m_bUseCurrentView void emitLayoutChanged() { emit layoutChanged(); } }; class CurrentAlbumDelegate : public QItemDelegate { Q_OBJECT const HndlrListModel* m_pHndlrListModel; public: CurrentAlbumDelegate(QWidget* pParent, HndlrListModel* pHndlrListModel); /*override*/ void paint(QPainter* pPainter, const QStyleOptionViewItem& option, const QModelIndex& index) const; // /*override*/ QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const; }; struct SequencePattern; class InvalidCharsReplacer; } // namespace FileRenamer class ModifInfoToolButton; class FileRenamerDlgImpl : public QDialog, private Ui::FileRenamerDlg { Q_OBJECT CommonData* m_pCommonData; FileRenamer::HndlrListModel* m_pHndlrListModel; void reloadTable(); void resizeUi(); std::vector m_vstrPatterns; void loadPatterns(); void savePatterns(); void updateButtons(); void createButtons(); void selectPattern(); // selects the appropriate pattern for a new album, based on whether it's VA or SA; sets m_nCrtPattern and checks the right button; assumes m_eState is properly set up; int m_nVaButton, m_nSaButton; int m_nBtnId; int m_nCrtPattern; enum State { SINGLE, SINGLE_ERR, VARIOUS, VARIOUS_ERR, ERR }; // ERR is for when all songs lack ID3V2 State m_eState; bool isSingleArtist() const { return SINGLE == m_eState || SINGLE_ERR == m_eState; } //bool m_bSingleArtist; // if there are a QButtonGroup* m_pButtonGroup; ModifInfoToolButton* m_pModifRenameB; bool m_bUseCurrentView; //std::vector m_pInvalidCharsReplacer; public: Renamer(const std::string& strPattern, const CommonData* pCommonData, bool bUnratedAsDuplicate); ~Renamer(); const std::string& getPattern() const { return m_strPattern; } //const FileRenamer::SequencePattern* getSeq() const { return m_pRoot; } std::string getNewName(const Mp3Handler*) const; bool isSameDir() const { return m_bSameDir; } struct InvalidPattern { Q_DECLARE_TR_FUNCTIONS(InvalidPattern) public: const std::string m_strErr; InvalidPattern(const std::string& strPattern, const QString& strErr) : m_strErr(convStr(tr("Pattern \"%1\" is invalid. %2").arg(strPattern.c_str()).arg(strErr))) {} }; mutable bool m_bUnratedAsDuplicate; mutable std::map m_mValues; // only has entries for custom-modified values }; #endif MP3Diags-1.2.02/src/CommonTypes.cpp0000644000175000001440000004015511724737524015740 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include #include "CommonTypes.h" #include "Helpers.h" #include "FullSizeImgDlg.h" #include "DataStream.h" // for translations #include "Widgets.h" // for translation using namespace std; ImageInfo::ImageInfo(int nImageType, Status eStatus, Compr eCompr, QByteArray compressedImg, int nWidth, int nHeight) : m_eCompr(eCompr), m_eStatus(eStatus), m_nWidth(nWidth), m_nHeight(nHeight), m_compressedImg(compressedImg), m_nImageType(nImageType) { } // the picture is scaled down, keeping the aspect ratio, if the limits are exceeded; 0 and negative limits are ignored; // if nMaxWidth>0 and nMaxHeight<=0, nMaxHeight has the same value as nMaxWidth; QImage ImageInfo::getImage(int nMaxWidth /* = -1*/, int nMaxHeight /* = -1*/) const { CB_ASSERT (NO_PICTURE_FOUND != m_eStatus); if (nMaxHeight <= 0) { nMaxHeight = nMaxWidth; } if (nMaxWidth <= 0) { nMaxWidth = nMaxHeight; } QImage pic; if (USES_LINK == m_eStatus || ERROR_LOADING == m_eStatus || !pic.loadFromData(m_compressedImg)) //ttt2 not sure how loadFromData() handles huge images; { if (nMaxWidth < 0) { nMaxWidth = m_nWidth; } if (nMaxHeight < 0) { nMaxHeight = m_nHeight; } if (nMaxWidth <= 0) { nMaxWidth = 200; } if (nMaxHeight <= 0) { nMaxHeight = 200; } QImage errImg (nMaxWidth, nMaxHeight, QImage::Format_ARGB32); QPainter pntr (&errImg); pntr.fillRect(0, 0, nMaxWidth, nMaxHeight, QColor(255, 128, 128)); pntr.drawRect(0, 0, nMaxWidth - 1, nMaxHeight - 1); //pntr.drawText(5, nMaxHeight/2 + 10, USES_LINK == m_eStatus ? "Link" : (ERROR_LOADING == m_eStatus ? "Error" : "Uncompr error")); pntr.drawText(QRectF(0, 0, nMaxWidth, nMaxHeight), Qt::AlignCenter | Qt::TextWordWrap, USES_LINK == m_eStatus ? "Link" : (ERROR_LOADING == m_eStatus ? "Error" : "Uncompr error")); return errImg; } if (nMaxWidth <= 0 || (pic.width() <= nMaxWidth && pic.height() <= nMaxHeight)) { return pic; } return pic.scaled(nMaxWidth, nMaxHeight, Qt::KeepAspectRatio, Qt::SmoothTransformation); } bool ImageInfo::operator==(const ImageInfo& other) const { return m_eCompr == other.m_eCompr /*&& m_eStatus == other.m_eStatus*/ && m_nWidth == other.m_nWidth && m_compressedImg == other.m_compressedImg; // !!! related to Id3V230StreamWriter::addImage() status is ignored in both places //ttt2 review decision to ignore status } /*static*/ int ImageInfo::MAX_IMAGE_SIZE (100000); ImageInfo::ImageInfo(int nImageType, Status eStatus, const QImage& pic) : m_eCompr(JPG), m_eStatus(eStatus), m_nImageType(nImageType) { QImage scaledPic; compress(pic, scaledPic, m_compressedImg); m_nWidth = scaledPic.width(); m_nHeight = scaledPic.height(); } /*static*/ const char* ImageInfo::getImageType(int nImageType) { switch (nImageType) { case 0x00: return QT_TRANSLATE_NOOP("TagReader", "other"); case 0x01: return QT_TRANSLATE_NOOP("TagReader", "32x32 icon"); case 0x02: return QT_TRANSLATE_NOOP("TagReader", "other file icon"); case 0x03: return QT_TRANSLATE_NOOP("TagReader", "front cover"); case 0x04: return QT_TRANSLATE_NOOP("TagReader", "back cover"); case 0x05: return QT_TRANSLATE_NOOP("TagReader", "leaflet page"); case 0x06: return QT_TRANSLATE_NOOP("TagReader", "media"); case 0x07: return QT_TRANSLATE_NOOP("TagReader", "lead artist"); case 0x08: return QT_TRANSLATE_NOOP("TagReader", "artist"); case 0x09: return QT_TRANSLATE_NOOP("TagReader", "conductor"); case 0x0a: return QT_TRANSLATE_NOOP("TagReader", "band"); case 0x0b: return QT_TRANSLATE_NOOP("TagReader", "composer"); case 0x0c: return QT_TRANSLATE_NOOP("TagReader", "lyricist"); case 0x0d: return QT_TRANSLATE_NOOP("TagReader", "recording location"); case 0x0e: return QT_TRANSLATE_NOOP("TagReader", "during recording"); case 0x0f: return QT_TRANSLATE_NOOP("TagReader", "during performance"); case 0x10: return QT_TRANSLATE_NOOP("TagReader", "screen capture"); //case 0x11: return QT_TRANSLATE_NOOP("TagReader", "a bright coloured fish"); case 0x12: return QT_TRANSLATE_NOOP("TagReader", "illustration"); case 0x13: return QT_TRANSLATE_NOOP("TagReader", "band/artist logotype"); case 0x14: return QT_TRANSLATE_NOOP("TagReader", "publisher/studio logotype"); default: return QT_TRANSLATE_NOOP("TagReader", "unknown"); } } const char* ImageInfo::getImageType() const { return getImageType(m_nImageType); } /*static*/ const char* ImageInfo::getComprStr(Compr eCompr) { switch (eCompr) { case INVALID: return "invalid"; // no translation needed as long as this is only used in XML export case JPG: return "JPEG"; case PNG: return "PNG"; } CB_ASSERT (false); } // scales down origPic and stores the pixmap in scaledPic, as well as a compressed version in comprImg; the algorithm coninues until comprImg becomes smaller than MAX_IMAGE_SIZE or until the width and the height of scaledPic get smaller than 150; no scaling is done if comprImg turns out to be small enough for the original image; /*static*/ void ImageInfo::compress(const QImage& origPic, QImage& scaledPic, QByteArray& comprImg) { const int QUAL (-1); //ttt2 hard-coded //QPixmap scaledImg; int n (max(origPic.width(), origPic.height())); //qDebug("-------------"); for (int i = n;;) { scaledPic = origPic.scaled(i, i, Qt::KeepAspectRatio, Qt::SmoothTransformation); comprImg.clear(); QBuffer bfr (&comprImg); //nWidth = scaledPic.width(); nHeight = scaledPic.height(); scaledPic.save(&bfr, "jpg", QUAL); int nSize (comprImg.size()); //qDebug("width=%d, size=%d", i, nSize); if (nSize <= MAX_IMAGE_SIZE) { break; } //double d (min(4.0/5, 1/sqrt(nSize*1.0/MAX_IMAGE_SIZE))); double d (min(4.0/5, sqrt(MAX_IMAGE_SIZE*1.0/nSize))); // ttt2 review this; the "1.0" is quite wrong in some cases i = int(i*d); if (i <= 150) { break; } } } QString ImageInfo::getTextDescr(const QString& qstrSep /* = "\n"*/) const { QString s; s.sprintf("%dx%d", getWidth(), getHeight()); s += qstrSep + TagReader::tr(getImageType()); return s; } void ImageInfo::showFull(QWidget* pParent) const { FullSizeImgDlg dlg (pParent, *this); dlg.exec(); } ostream& operator<<(ostream& out, const AlbumInfo& inf) { out << "title: \"" << inf.m_strTitle << /*"\", artist: \"" << inf.m_strArtist << "\", composer: \"" << inf.m_strComposer <<*/ /*"\", format: \"" << inf.m_strFormat <<*/ "\", genre: \"" << inf.m_strGenre << "\", released: \"" << inf.m_strReleased << "\", var artists: \"" << int(inf.m_eVarArtists) << "\"\n\nnotes: " << inf.m_strNotes << endl; /*for (int i = 0, n = cSize(inf.m_vstrImageNames); i < n; ++i) { out << inf.m_vstrImageNames[i] << endl; }*/ out << "\ntracks:" << endl; for (int i = 0, n = cSize(inf.m_vTracks); i < n; ++i) { out << "pos: \"" << inf.m_vTracks[i].m_strPos << "\", artist: \"" << inf.m_vTracks[i].m_strArtist << "\", title: \"" << inf.m_vTracks[i].m_strTitle << "\", composer: \"" << inf.m_vTracks[i].m_strComposer << "\"" << endl; } return out; } //===================================================================================================================== //===================================================================================================================== //===================================================================================================================== static const set& getLowerCaseSet() { static set sqstrLowCase; static bool bInit (false); if (!bInit) { bInit = true; const char* aszList[] = { "a","an","the", // from http://avalon-internet.com/Capitalize_an_English_Title/en "about","above","across","after","against","along", //ttt2 English-only //ttt0 see if there's a need for this in other languages; (this is needed for "title case"); also, this should be tied to a different locale than the one the UI is in "amid","among","around","at","before","behind","below", "beneath", "beside","besides","between","beyond","but","by","concerning","despite", "down","during","except","from","in","including", "inside","into","like", "minus","near","notwithstanding","of","off","on", "onto","opposite","out", "outside","over","past","per","plus","regarding","since","through", "throughout","till","to","toward","towards","under","underneath","unless", "unlike","until","up","upon","versus","via","with","within","without", "and","but","for","nor","or","so","yet", "after","although","as","because","if", "lest","than","that","though","when","whereas","while", "also","both","each","either","neither","whether", 0 }; for (const char** p = &aszList[0]; 0 != *p; ++p) { sqstrLowCase.insert(*p); } /* alternative lists/opinions: http://aitech.ac.jp/~ckelly/midi/help/caps.html http://www.cumbrowski.com/CarstenC/articles/20070623_Title_Capitalization_in_the_English_Language.asp http://www.searchenginejournal.com/title-capitalization-in-the-english-language/4882/ http://ezinearticles.com/?Title-Capitalization-In-The-English-Language&id=658201 */ } return sqstrLowCase; } static const map& getFixedCaseMap() { static map mqstrFixedCase; static bool bInit (false); if (!bInit) { bInit = true; const char* aszList[] = { "I","MTV","L.A.", 0 }; for (const char** p = &aszList[0]; 0 != *p; ++p) { mqstrFixedCase[QString(*p).toLower()] = *p; } } return mqstrFixedCase; } static void dropPunct(const QString& s, int& i, int& j) { int n (s.size()); i = 0; j = n; for (; i < n && s[i].isPunct(); ++i) {} for (; i < n && s[n - 1].isPunct(); --n) {} } static QString singleWordFirstLast(const QString& s) { int i, j; dropPunct(s, i, j); if (i == j) { return s; } QString s1 (s.mid(i, j - i).toLower()); if (getFixedCaseMap().count(s1) > 0) { s1 = (*getFixedCaseMap().lower_bound(s1)).second; } else { s1 = s1.toLower(); s1[0] = s1[0].toUpper(); } QString s2 (s); s2.replace(i, j - i, s1); return s2; } static QString singleWordMiddleTitle(const QString& s) { int i, j; dropPunct(s, i, j); if (i == j) { return s; } QString s1 (s.mid(i, j - i).toLower()); if (getFixedCaseMap().count(s1) > 0) { s1 = (*getFixedCaseMap().lower_bound(s1)).second; } else if (getLowerCaseSet().count(s1) > 0) { // !!! nothing, keep lower } else { s1 = s1.toLower(); s1[0] = s1[0].toUpper(); } QString s2 (s); s2.replace(i, j - i, s1); return s2; } static QString singleWordMiddleSentence(const QString& s) { int i, j; dropPunct(s, i, j); if (i == j) { return s; } QString s1 (s.mid(i, j - i).toLower()); if (getFixedCaseMap().count(s1) > 0) { s1 = (*getFixedCaseMap().lower_bound(s1)).second; } QString s2 (s); s2.replace(i, j - i, s1); return s2; } QString getCaseConv(const QString& s, TextCaseOptions eCase) { switch(eCase) { //case TC_NONE: CB_ASSERT (false); case TC_LOWER: return s.toLower(); case TC_UPPER: return s.toUpper(); case TC_TITLE: { QStringList l (s.split(" ", QString::SkipEmptyParts)); int n (l.size()); if (n > 0) { l[0] = singleWordFirstLast(l[0]); } if (n > 1) { l[n - 1] = singleWordFirstLast(l[n - 1]); } for (int i = 1; i < n - 1; ++i) { l[i] = singleWordMiddleTitle(l[i]); } return l.join(" "); } case TC_SENTENCE: { QStringList l (s.split(" ", QString::SkipEmptyParts)); int n (l.size()); if (n > 0) { l[0] = singleWordFirstLast(l[0]); } for (int i = 1; i < n; ++i) { l[i] = singleWordMiddleSentence(l[i]); } return l.join(" "); } default: CB_ASSERT (false); } } const char* getCaseAsStr(TextCaseOptions e) { switch (e) { case TC_NONE: return QT_TRANSLATE_NOOP("TagReader", ""); case TC_LOWER: return QT_TRANSLATE_NOOP("TagReader", "lower case"); case TC_UPPER: return QT_TRANSLATE_NOOP("TagReader", "UPPER CASE"); case TC_TITLE: return QT_TRANSLATE_NOOP("TagReader", "Title Case"); case TC_SENTENCE: return QT_TRANSLATE_NOOP("TagReader", "Sentence case"); default: CB_ASSERT (false); } } /*static*/ char ExternalToolInfo::s_cSeparator = '|'; ExternalToolInfo::ExternalToolInfo(const string& strSerValue) { string::size_type k1 (strSerValue.find(s_cSeparator)); string::size_type k2 (strSerValue.find(s_cSeparator, k1 + 1)); string::size_type k3 (strSerValue.find(s_cSeparator, k2 + 1)); string::size_type k4 (strSerValue.find(s_cSeparator, k3 + 1)); CB_CHECK1(k1 != string::npos && k2 != string::npos && k3 != string::npos && k4 == string::npos, InvalidExternalToolInfo()); m_strName = strSerValue.substr(0, k1); m_strCommand = strSerValue.substr(k1 + 1, k2 - k1 -1); string s (strSerValue.substr(k2 + 1, k3 - k2 - 1)); CB_CHECK1(s.size() == 1 && s >= "0" && s <= "2", InvalidExternalToolInfo()); m_eLaunchOption = (LaunchOption)atoi(s.c_str()); s = strSerValue.substr(k3 + 1); CB_CHECK1(s.size() == 1 && s >= "0" && s <= "1", InvalidExternalToolInfo()); m_bConfirmLaunch = (bool)atoi(s.c_str()); } string ExternalToolInfo::asString() { ostringstream out; out << m_strName << s_cSeparator << m_strCommand << s_cSeparator << (int)m_eLaunchOption << s_cSeparator << (int)m_bConfirmLaunch; return out.str(); } /*static*/ QString ExternalToolInfo::launchOptionAsTranslatedString(LaunchOption eLaunchOption) { switch (eLaunchOption) { case DONT_WAIT: return GlobalTranslHlp::tr("Don't wait"); case WAIT_THEN_CLOSE_WINDOW: return GlobalTranslHlp::tr("Wait for external tool to finish, then close launch window"); case WAIT_AND_KEEP_WINDOW_OPEN: return GlobalTranslHlp::tr("Wait for external tool to finish, then keep launch window open"); default: CB_ASSERT(false); } } MP3Diags-1.2.02/src/Transformation.cpp0000644000175000001440000010027411724340543016457 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include "Transformation.h" #include "Helpers.h" #include "OsFile.h" #include "Mp3Manip.h" #include "CommonData.h" #include "Widgets.h" using namespace std; using namespace pearl; /*static const string& getOsTempDir() { static string s; // ttt3 these static variables are not really thread safe, but in this case it doesn't matter, because they all get called from a single thread (the UI thread) if (s.empty()) { s = convStr(QDir::tempPath()); if (endsWith(s, getPathSepAsStr())) { s.erase(s.size() - 1, 1); } } return s; } */ //ttt2 review all defaults static string getDefaultSrc() { return ""; } //ttt2 see if src dir needs to be revived static string getDefaultUnprocOrig() { return convStr(getTempDir()) + "/mp3diags/unprocOrig"; } static string getDefaultProcessed() { return convStr(getTempDir()) + "/mp3diags/proc"; } static string getDefaultProcOrig() { return convStr(getTempDir() + "/mp3diags/procOrig"); } static string getDefaultTemp() { return convStr(getTempDir() + "/mp3diags/temp"); } static string getDefaultComp() { return convStr(getTempDir() + "/mp3diags/comp"); } /*static TransfConfig::OptionsWrp getDefaultOptions() { TransfConfig::OptionsWrp x; x.m_nVal = 0; x.m_opt.m_bTempCreate = true; x.m_opt.m_bCompCreate = true; x.m_opt.m_nProcessedCreate = 1; // create and always rename, since it is created in the same dir as the source x.m_opt.m_bProcessedUseLabel = true; x.m_opt.m_bProcessedAlwayUseCounter = false; x.m_opt.m_bProcessedUseSeparateDir = false; x.m_opt.m_nUnprocOrigChange = 0; x.m_opt.m_bUnprocOrigUseLabel = false; x.m_opt.m_bUnprocOrigAlwayUseCounter = false; x.m_opt.m_nProcOrigChange = 0; x.m_opt.m_bProcOrigUseLabel = false; x.m_opt.m_bProcOrigAlwayUseCounter = false; return x; }*/ TransfConfig::Options::Options() : //m_nProcOrigChange(5), // move w/o renaming, if doesn't exist; discard if it exists; m_eProcOrigChange(PO_ERASE), // erase m_bProcOrigUseLabel(false), m_bProcOrigAlwayUseCounter(false), m_eUnprocOrigChange(UPO_DONT_CHG), // don't change m_bUnprocOrigUseLabel(false), m_bUnprocOrigAlwayUseCounter(false), m_eProcessedCreate(PR_CREATE_RENAME_IF_USED), // create and rename if the name is in use m_bProcessedUseLabel(true), m_bProcessedAlwayUseCounter(true), m_bProcessedUseSeparateDir(false), m_bTempCreate(false), m_bCompCreate(false), m_bKeepOrigTime(false) { } // returns *this, with some fields changed to match a "backup" configuration; touches many field, but ignores others (m_bProcOrigUseLabel, m_bProcOrigAlwayUseCounter, m_bUnprocOrigUseLabel, m_bUnprocOrigAlwayUseCounter, m_bKeepOrigTime) TransfConfig::Options TransfConfig::Options::asBackup() const { Options opt (*this); opt.m_bTempCreate = false; opt.m_bCompCreate = false; opt.m_eProcOrigChange = PO_MOVE_OR_ERASE; //opt.m_bProcOrigUseLabel = false; //opt.m_bProcOrigAlwayUseCounter = false; opt.m_eUnprocOrigChange = UPO_DONT_CHG; // don't change //opt.m_bUnprocOrigUseLabel = false; //opt.m_bUnprocOrigAlwayUseCounter = false; opt.m_eProcessedCreate = PR_CREATE_RENAME_IF_USED; // create and rename if the name is in use opt.m_bProcessedUseLabel = true; opt.m_bProcessedAlwayUseCounter = true; opt.m_bProcessedUseSeparateDir = false; //opt.m_bKeepOrigTime = false; return opt; } // returns *this, with some fields changed to match a "non-backup" configuration; touches many field, but ignores others (m_bProcOrigUseLabel, m_bProcOrigAlwayUseCounter, m_bUnprocOrigUseLabel, m_bUnprocOrigAlwayUseCounter, m_bKeepOrigTime) TransfConfig::Options TransfConfig::Options::asNonBackup() const { Options opt (*this); opt.m_bTempCreate = false; opt.m_bCompCreate = false; opt.m_eProcOrigChange = PO_ERASE; // move w/o renaming, if doesn't exist; discard if it exists; //opt.m_bProcOrigUseLabel = false; //opt.m_bProcOrigAlwayUseCounter = false; opt.m_eUnprocOrigChange = UPO_DONT_CHG; // don't change //opt.m_bUnprocOrigUseLabel = false; //opt.m_bUnprocOrigAlwayUseCounter = false; opt.m_eProcessedCreate = PR_CREATE_RENAME_IF_USED; // create and rename if the name is in use opt.m_bProcessedUseLabel = true; opt.m_bProcessedAlwayUseCounter = true; opt.m_bProcessedUseSeparateDir = false; //opt.m_bKeepOrigTime = false; return opt; } // like getDefaultOptions() but does backups rather than erase the original file (if a backup exists, the file is deleted, though) /*static* / TransfConfig::Options TransfConfig::getBackupDefaultOptions() { Options opt (getDefaultOptions()); opt.m_nProcOrigChange = 5; // move w/o renaming, if doesn't exist; discard if it exists; return opt; } */ TransfConfig::TransfConfig( const std::string& strSrcDir, const std::string& strProcOrigDir, const std::string& strUnprocOrigDir, const std::string& strProcessedDir, const std::string& strTempDir, const std::string& strCompDir, int nOptions // may be -1 to indicate "default" values ) { m_bInitError = false; m_strSrcDir = "*" == strSrcDir ? getDefaultSrc() : strSrcDir; m_strProcOrigDir = "*" == strProcOrigDir ? getDefaultProcOrig() : strProcOrigDir; m_strUnprocOrigDir = "*" == strUnprocOrigDir ? getDefaultUnprocOrig() : strUnprocOrigDir; m_strProcessedDir = "*" == strProcessedDir ? getDefaultProcessed() : strProcessedDir; m_strTempDir = "*" == strTempDir ? getDefaultTemp() : strTempDir; m_strCompDir = "*" == strCompDir ? getDefaultComp() : strCompDir; if (nOptions >= 0x00040000) { // only the first 18 bits are supposed to be used; if more seem to be used, it is because of a manually entered wrong value or because a change in the bitfield representation; showCritical(getMainForm(), tr("Error"), tr("Invalid value found for file settings. Reverting to default settings.")); nOptions = -1; m_bInitError = true; } if (-1 != nOptions) { m_options.setVal(nOptions); } checkDirName(m_strSrcDir); checkDirName(m_strProcOrigDir); // some of these dirs are allowed to be empty, just like src when comes from the config dialog, empty meaning "/", so that should work for all, probably checkDirName(m_strUnprocOrigDir); checkDirName(m_strProcessedDir); checkDirName(m_strTempDir); checkDirName(m_strCompDir); } TransfConfig::TransfConfig() : m_bInitError(false) { m_strSrcDir = getDefaultSrc(); m_strProcOrigDir = getDefaultProcOrig(); m_strUnprocOrigDir = getDefaultUnprocOrig(); m_strProcessedDir = getDefaultProcessed(); m_strTempDir = getDefaultTemp(); m_strCompDir = getDefaultComp(); } // last '/' goes to dir; last '.' goes to ext (if extension present) void TransfConfig::splitOrigName(const string& strOrigSrcName, std::string& strRelDir, std::string& strBaseName, std::string& strExt) const { //TRACER1A("splitOrigName ", 1); //Tracer t1 (strOrigName); //TRACER1A("splitOrigName ", 1); //TRACER1(strOrigSrcName.c_str(), 2); CB_CHECK1 (beginsWith(strOrigSrcName, m_strSrcDir), IncorrectPath()); //TRACER1A("splitOrigName ", 3); strRelDir = strOrigSrcName.substr(m_strSrcDir.size()); #if defined(WIN32) //CB_CHECK1 (beginsWith(strRelDir, getPathSepAsStr()), IncorrectPath()); //ttt2 see if it's OK to skip this test on Windows; it probably is, because it's more of an assert and the fact that it's not triggered on Linux should be enough #elif defined(__OS2__) //nothing #else CB_CHECK1 (beginsWith(strRelDir, getPathSepAsStr()), IncorrectPath()); #endif string::size_type nLastSep (strRelDir.rfind(getPathSep())); string::size_type nLastPer (strRelDir.rfind('.')); if (nLastPer > nLastSep) { // the file has an extension strExt = strRelDir.substr(nLastPer); strRelDir.erase(nLastPer, string::npos); } strBaseName = strRelDir.substr(nLastSep + 1); strRelDir.erase(nLastSep + 1, string::npos); //TRACER1A("splitOrigName ", 6); } /* static int s_anPositions[] = { 0, 1, 2, 4, 5, 6, 7, 10, 11, 12, 14, 15, 16 }; int TransfConfig::getOptionValue(Option eOption) const { return getOpt(s_anPositions[eOption], s_anPositions[eOption + 1] - s_anPositions[eOption]); }*/ // adds a counter and/or a label, such that a file with the resulting name doesn't exist; /*static*/ string TransfConfig::addLabelAndCounter(const string& s1, const string& s2, const string& strLabel, bool bAlwaysUseCounter, bool bAlwaysRename) { //TRACER1A("addLabelAndCounter ", 1); string strRes; if (!bAlwaysRename) { //TRACER1A("addLabelAndCounter ", 2); strRes = replaceDriveLetter(s1 + s2); //TRACER1A("addLabelAndCounter ", 3); if (!fileExists(strRes)) { //TRACER1A("addLabelAndCounter ", 4); createDirForFile(strRes); //TRACER1A("addLabelAndCounter ", 5); return strRes; } } string strLabel2 (strLabel); if (!strLabel2.empty()) { strLabel2.insert(0, "."); } //TRACER1A("addLabelAndCounter ", 6); if (!bAlwaysUseCounter && !strLabel2.empty()) { //TRACER1A("addLabelAndCounter ", 7); strRes = replaceDriveLetter(s1 + strLabel2 + s2); //TRACER1A("addLabelAndCounter ", 8); if (!fileExists(strRes)) { //TRACER1A("addLabelAndCounter ", 9); createDirForFile(strRes); //TRACER1A("addLabelAndCounter ", 10); return strRes; } } //TRACER1A("addLabelAndCounter ", 11); for (int i = 1; ; ++i) { { ostringstream out; out << "." << setfill('0') << setw(3) << i; //TRACER1A("addLabelAndCounter ", 12); strRes = replaceDriveLetter(s1 + strLabel2 + out.str() + s2); //TRACER1A("addLabelAndCounter ", 13); } //TRACER1A("addLabelAndCounter ", 14); if (!fileExists(strRes)) { //TRACER1A("addLabelAndCounter ", 15); createDirForFile(strRes); //TRACER1A("addLabelAndCounter ", 16); return strRes; } } } // normally makes sure that the name returned doesn't exist, by applying any renaming specified in the params; there's an exception, though: is bAllowDup is true, duplicates are allowed string TransfConfig::getRenamedName(const std::string& strOrigSrcName, const std::string& strNewRootDir, const std::string& strLabel, bool bAlwayUseCounter, bool bAlwaysRename, bool bAllowDup /* = false*/) const // bAllowDup is needed only for the option 5 of m_nProcOrigChange, when ORIG_MOVE_OR_ERASE gets returned { /* steps 1. decide if it should rename, based on bAlwaysRename (merely changing the folder isn't considered renaming) 2. if it should rename: 2.1 add the label (if it's empty, there's nothing to add) 2.2 if bAlwayUseCounter is false and there's a label, try first with that label 2.2.1 if the name doesn't exist, return 2.3 increment the counter until a new name is found (note that addCounter() does all that's needed for 2 */ string strRelDir; string strBaseName; string strExt; //TRACER1A("getRenamedName ", 1); splitOrigName(strOrigSrcName, strRelDir, strBaseName, strExt); string strRes; //TRACER1A("getRenamedName ", 2); if (!bAlwaysRename) { // aside from the new folder, no renaming takes places if this new name isn't in use //TRACER1A("getRenamedName ", 3); strRes = replaceDriveLetter(strNewRootDir + strRelDir + strBaseName + strExt); if (!fileExists(strRes) || bAllowDup) { //TRACER1A("getRenamedName ", 4); createDirForFile(strRes); //TRACER1A("getRenamedName ", 5); return strRes; } } //TRACER1A("getRenamedName ", 6); /*if (!bAlwayUseCounter && !strLabel.empty()) { // now try to use the label but not the counter strRes = strNewRootDir + strRelDir + strBaseName + strExt; if (!fileExists(strRes)) { createDirForFile(strRes); return strRes; } }*/ //out << strNewRootDir << strRelDir << strBaseName << (strLabel.empty() ? "" : ".") << strLabel; string s1 (strNewRootDir + strRelDir + strBaseName); //setfill('0') << setw(3) << i << strExt; return addLabelAndCounter(s1, strExt, strLabel, bAlwayUseCounter, bAlwaysRename); } TransfConfig::OrigFile TransfConfig::getChangedOrigNameHlp(const string& strOrigSrcName, const string& strDestDir, int nChange, bool bUseLabel, bool bAlwayUseCounter, string& strNewName) const { switch (nChange) // !!! nChange must match both ProcOrig and UnprocOrig, since getChangedOrigNameHlp() gets called in both cases { case Options::PO_DONT_CHG: // don't change unprocessed orig file strNewName = strOrigSrcName; return ORIG_DONT_CHANGE; case Options::PO_ERASE: // erase unprocessed orig file strNewName = strOrigSrcName; return ORIG_ERASE; case Options::PO_MOVE_ALWAYS_RENAME: // move unprocessed orig file to strDestDir; always rename strNewName = getRenamedName(strOrigSrcName, strDestDir, bUseLabel ? "orig" : "", bAlwayUseCounter, ALWAYS_RENAME); return ORIG_MOVE; case Options::PO_MOVE_RENAME_IF_USED: // move unprocessed orig file to strDestDir; rename only if name is in use strNewName = getRenamedName(strOrigSrcName, strDestDir, bUseLabel ? "orig" : "", bAlwayUseCounter, RENAME_IF_NEEDED); return ORIG_MOVE; case Options::PO_RENAME_SAME_DIR: // rename in the same dir strNewName = getRenamedName(strOrigSrcName, m_strSrcDir, bUseLabel ? "orig" : "", bAlwayUseCounter, ALWAYS_RENAME); return ORIG_MOVE; case Options::PO_MOVE_OR_ERASE: // move unprocessed orig file to strDestDir if it's not already there; erase if it exists; strNewName = getRenamedName(strOrigSrcName, strDestDir, "", USE_COUNTER_IF_NEEDED, RENAME_IF_NEEDED, ALLOW_DUPLICATES); return ORIG_MOVE_OR_ERASE; default: CB_ASSERT(false); } } TransfConfig::OrigFile TransfConfig::getProcOrigName(string strOrigSrcName, string& strNewName) const { removeSuffix(strOrigSrcName); return getChangedOrigNameHlp(strOrigSrcName, m_strProcOrigDir, m_options.m_eProcOrigChange, m_options.m_bProcOrigUseLabel, m_options.m_bProcOrigAlwayUseCounter, strNewName); } TransfConfig::OrigFile TransfConfig::getUnprocOrigName(string strOrigSrcName, string& strNewName) const { removeSuffix(strOrigSrcName); return getChangedOrigNameHlp(strOrigSrcName, m_strUnprocOrigDir, m_options.m_eUnprocOrigChange, m_options.m_bUnprocOrigUseLabel, m_options.m_bUnprocOrigAlwayUseCounter, strNewName); } void TransfConfig::removeSuffix(string& s) const { /* // !!! this seemed too aggressive; for (;;) { string::size_type nPos (s.rfind(".orig.")); if (string::npos == nPos) { break; } s.erase(nPos, 5); } for (;;) { string::size_type nPos (s.rfind(".proc.")); if (string::npos == nPos) { break; } s.erase(nPos, 5); } for (;;) { int n (cSize(s)); if (!endsWith(s, ".mp3") || n <= 4 + 4) { break; } if ('.' != s[n - 8] || !isdigit(s[n - 7]) || !isdigit(s[n - 6]) || !isdigit(s[n - 5])) { break; } s.erase(n - 8, 4); } */ string::size_type n (s.size()); string::size_type nPos (s.rfind(".orig.")); if (string::npos != nPos && nPos > 0 && nPos == n - 9) { s.erase(n - 9, 5); return; } if (string::npos != nPos && nPos > 0 && nPos == n - 13 && isdigit(s[n - 7]) && isdigit(s[n - 6]) && isdigit(s[n - 5]) && '.' == s[n - 4]) { s.erase(n - 13, 9); return; } nPos = s.rfind(".proc."); if (string::npos != nPos && nPos > 0 && nPos == n - 9) { s.erase(n - 9, 5); return; } if (string::npos != nPos && nPos > 0 && nPos == n - 13 && isdigit(s[n - 7]) && isdigit(s[n - 6]) && isdigit(s[n - 5]) && '.' == s[n - 4]) { s.erase(n - 13, 9); return; } //ttt2 this needs to be revisited if files with extensions other than ".mp3" are accepted } void TransfConfig::testRemoveSuffix() const { string s; const char* p; p = "abc.orig.mp3"; s = p; removeSuffix(s); qDebug("pref: %s => %s", p, s.c_str()); p = ".orig.mp3"; s = p; removeSuffix(s); qDebug("pref: %s => %s", p, s.c_str()); p = ".orig.mp"; s = p; removeSuffix(s); qDebug("pref: %s => %s", p, s.c_str()); p = "ww trty.orig.mp"; s = p; removeSuffix(s); qDebug("pref: %s => %s", p, s.c_str()); p = "abc.orig.003.mp3"; s = p; removeSuffix(s); qDebug("pref: %s => %s", p, s.c_str()); p = ".orig.003.mp3"; s = p; removeSuffix(s); qDebug("pref: %s => %s", p, s.c_str()); p = ".orig.003.mp"; s = p; removeSuffix(s); qDebug("pref: %s => %s", p, s.c_str()); p = "ww trty.orig.003.mp"; s = p; removeSuffix(s); qDebug("pref: %s => %s", p, s.c_str()); p = "abc.orig.03.mp3"; s = p; removeSuffix(s); qDebug("pref: %s => %s", p, s.c_str()); p = ".orig.03.mp3"; s = p; removeSuffix(s); qDebug("pref: %s => %s", p, s.c_str()); p = ".orig.03.mp"; s = p; removeSuffix(s); qDebug("pref: %s => %s", p, s.c_str()); p = "ww trty.orig.03.mp"; s = p; removeSuffix(s); qDebug("pref: %s => %s", p, s.c_str()); qDebug("-------------------------------------"); p = "abc.proc.mp3"; s = p; removeSuffix(s); qDebug("pref: %s => %s", p, s.c_str()); p = ".proc.mp3"; s = p; removeSuffix(s); qDebug("pref: %s => %s", p, s.c_str()); p = ".proc.mp"; s = p; removeSuffix(s); qDebug("pref: %s => %s", p, s.c_str()); p = "ww trty.proc.mp"; s = p; removeSuffix(s); qDebug("pref: %s => %s", p, s.c_str()); p = "abc.proc.003.mp3"; s = p; removeSuffix(s); qDebug("pref: %s => %s", p, s.c_str()); p = ".proc.003.mp3"; s = p; removeSuffix(s); qDebug("pref: %s => %s", p, s.c_str()); p = ".proc.003.mp"; s = p; removeSuffix(s); qDebug("pref: %s => %s", p, s.c_str()); p = "ww trty.proc.003.mp"; s = p; removeSuffix(s); qDebug("pref: %s => %s", p, s.c_str()); p = "abc.proc.03.mp3"; s = p; removeSuffix(s); qDebug("pref: %s => %s", p, s.c_str()); p = ".proc.03.mp3"; s = p; removeSuffix(s); qDebug("pref: %s => %s", p, s.c_str()); p = ".proc.03.mp"; s = p; removeSuffix(s); qDebug("pref: %s => %s", p, s.c_str()); p = "ww trty.proc.03.mp"; s = p; removeSuffix(s); qDebug("pref: %s => %s", p, s.c_str()); qDebug("-------------------------------------"); } TransfConfig::TransfFile TransfConfig::getProcessedName(string strOrigSrcName, std::string& strName) const { //TRACER1A("getProcessedName ", 1); //Tracer t1 (strOrigSrcName); removeSuffix(strOrigSrcName); //TRACER1A("getProcessedName ", 2); //Tracer t2 (strOrigSrcName); switch (m_options.m_eProcessedCreate) { case Options::PR_DONT_CREATE: // don't create proc files { //TRACER1A("getProcessedName ", 3); strName.clear(); return TRANSF_DONT_CREATE; } case Options::PR_CREATE_ALWAYS_RENAME: // create proc files and always rename { //TRACER1A("getProcessedName ", 4); strName = getRenamedName(strOrigSrcName, m_options.m_bProcessedUseSeparateDir ? m_strProcessedDir : m_strSrcDir, m_options.m_bProcessedUseLabel ? "proc" : "", m_options.m_bProcessedAlwayUseCounter, ALWAYS_RENAME); //Tracer t1 (strName); return TRANSF_CREATE; } case Options::PR_CREATE_RENAME_IF_USED: // create proc files and rename if the name is in use { //TRACER1A("getProcessedName ", 5); strName = getRenamedName(strOrigSrcName, m_options.m_bProcessedUseSeparateDir ? m_strProcessedDir : m_strSrcDir, m_options.m_bProcessedUseLabel ? "proc" : "", m_options.m_bProcessedAlwayUseCounter, RENAME_IF_NEEDED); //Tracer t1 (strName); return TRANSF_CREATE; } default: CB_ASSERT(false); } } TransfConfig::TransfFile TransfConfig::getTempName(string strOrigSrcName, const std::string& strOpName, std::string& strName) const { removeSuffix(strOrigSrcName); if (!m_options.m_bTempCreate) { strName = getTempFile(strOrigSrcName); return TRANSF_DONT_CREATE; } string strRelDir; string strBaseName; string strExt; splitOrigName(strOrigSrcName, strRelDir, strBaseName, strExt); //string strRes; { ostringstream out; out << m_strTempDir << strRelDir << strBaseName << ".temp " << strOpName; strName = addLabelAndCounter(out.str(), strExt, "", ALWAYS_USE_COUNTER, ALWAYS_RENAME); return TRANSF_CREATE; } } TransfConfig::TransfFile TransfConfig::getCompNames(string strOrigSrcName, const std::string& strOpName, std::string& strBefore, std::string& strAfter) const { removeSuffix(strOrigSrcName); if (!m_options.m_bCompCreate) { strBefore = getTempFile(strOrigSrcName); strAfter = getTempFile(strOrigSrcName); return TRANSF_DONT_CREATE; } string strRelDir; string strBaseName; string strExt; splitOrigName(strOrigSrcName, strRelDir, strBaseName, strExt); /*{ ostringstream out; //out << m_strCompDir << strRelDir << strBaseName << ".1(before " << strOpName << ")" << strExt; strBefore = out.str(); } { ostringstream out; out << m_strCompDir << strRelDir << strBaseName << ".2(after " << strOpName << ")" << strExt; strAfter = out.str(); } if (!fileExists(strBefore) && !fileExists(strAfter)) { createDirForFile(strAfter); return TRANSF_CREATE; }*/ /*for (int i = 1; ; ++i) { { ostringstream out; //out << m_strCompDir << strRelDir << strBaseName << ".1(before " << strOpName << ")." << setfill('0') << setw(3) << i << strExt; out << m_strCompDir << strRelDir << strBaseName << "." << setfill('0') << setw(3) << i << "a (before " << strOpName << ")" << strExt; strBefore = out.str(); } { ostringstream out; //out << m_strCompDir << strRelDir << strBaseName << ".2(after " << strOpName << ")." << setfill('0') << setw(3) << i << strExt; out << m_strCompDir << strRelDir << strBaseName << "." << setfill('0') << setw(3) << i << "b (after " << strOpName << ")" << strExt; strAfter = out.str(); } if (!fileExists(strBefore) && !fileExists(strAfter)) { createDirForFile(strAfter); return TRANSF_CREATE; } }*/ int i (1); //File Searcher fs; for (;; ++i) { ostringstream out; string strDir (m_strCompDir + strRelDir); out << strBaseName << "." << setfill('0') << setw(3) << i << "*"; string strFile (out.str()); QDir d (convStr(strDir)); QStringList nameFlt; nameFlt << convStr(strFile); if (d.entryList(nameFlt).isEmpty()) { break; } } { ostringstream out; //out << m_strCompDir << strRelDir << strBaseName << ".1(before " << strOpName << ")." << setfill('0') << setw(3) << i << strExt; out << m_strCompDir << strRelDir << strBaseName << "." << setfill('0') << setw(3) << i << "a (before " << strOpName << ")" << strExt; strBefore = out.str(); } { ostringstream out; //out << m_strCompDir << strRelDir << strBaseName << ".2(after " << strOpName << ")." << setfill('0') << setw(3) << i << strExt; out << m_strCompDir << strRelDir << strBaseName << "." << setfill('0') << setw(3) << i << "b (after " << strOpName << ")" << strExt; strAfter = replaceDriveLetter(out.str()); } createDirForFile(strAfter); return TRANSF_CREATE; } TransfConfig::OrigFile TransfConfig::getProcOrigAction() const { switch (m_options.m_eProcOrigChange) { case Options::PO_DONT_CHG: // don't change unprocessed orig file return ORIG_DONT_CHANGE; case Options::PO_ERASE: // erase unprocessed orig file return ORIG_ERASE; case Options::PO_MOVE_ALWAYS_RENAME: // move unprocessed orig file to strDestDir; always rename case Options::PO_MOVE_RENAME_IF_USED: // move unprocessed orig file to strDestDir; rename only if name is in use case Options::PO_RENAME_SAME_DIR: // rename in the same dir return ORIG_MOVE; case Options::PO_MOVE_OR_ERASE: // move if dest doesn't exist; erase if it does; return ORIG_MOVE_OR_ERASE; default: CB_ASSERT(false); } } TransfConfig::OrigFile TransfConfig::getUnprocOrigAction() const { switch (m_options.m_eUnprocOrigChange) { case Options::UPO_DONT_CHG: // don't change unprocessed orig file return ORIG_DONT_CHANGE; case Options::UPO_ERASE: // erase unprocessed orig file return ORIG_ERASE; case Options::UPO_MOVE_ALWAYS_RENAME: // move unprocessed orig file to strDestDir; always rename case Options::UPO_MOVE_RENAME_IF_USED: // move unprocessed orig file to strDestDir; rename only if name is in use case Options::UPO_RENAME_SAME_DIR: // rename in the same dir return ORIG_MOVE; default: CB_ASSERT(false); } } TransfConfig::TransfFile TransfConfig::getProcessedAction() const { switch (m_options.m_eProcessedCreate) { case Options::PR_DONT_CREATE: // don't create proc files return TRANSF_DONT_CREATE; case Options::PR_CREATE_ALWAYS_RENAME: // create proc files and always rename case Options::PR_CREATE_RENAME_IF_USED: // create proc files and rename if the name is in use return TRANSF_CREATE; default: CB_ASSERT(false); } } TransfConfig::TransfFile TransfConfig::getTempAction() const { return m_options.m_bTempCreate ? TRANSF_CREATE : TRANSF_DONT_CREATE; } TransfConfig::TransfFile TransfConfig::getCompAction() const { return m_options.m_bCompCreate ? TRANSF_CREATE : TRANSF_DONT_CREATE; } #if 0 // to what an original file should be renamed, if it was "processed"; // if m_strSrcRootDir and m_strProcRootDir are different, it returns ""; (the original file doesn't get touched); // if m_strSrcRootDir and m_strProcRootDir are equal, it returns a name based on the original, with an ".orig." inserted before the extension, where is a number chosen such that a file with this new name doesn't exist; string TransfConfig::getBackupName(const std::string& strOrigSrcName) const { if (m_strSrcRootDir != m_strProcRootDir) { return ""; } string strRelName; string strExt; splitOrigName(strOrigSrcName, strRelName, strExt); string strRes; for (int i = 1; ; ++i) { { ostringstream out; out << m_strSrcRootDir << strRelName << ".orig." << setfill('0') << setw(3) << i << strExt; strRes = out.str(); } if (!fileExists(strRes)) { createDirForFile(strRes); return strRes; } } } // to what the last "processed" file should be renamed; // if m_strSrcRootDir and m_strProcRootDir are different, it returns strOrigSrcName, moved to m_strProcRootDir; // if m_strSrcRootDir and m_strProcRootDir are equal, it returns strOrigSrcName; // if the name that it would return already exists, it inserts a ".proc." such that a file with this new name doesn't exist; (so if m_strSrcRootDir and m_strProcRootDir are equal, the original file should be renamed before calling this) std::string TransfConfig::getProcName(const std::string& strOrigSrcName) const { string strRelName; string strExt; splitOrigName(strOrigSrcName, strRelName, strExt); if (m_strSrcRootDir == m_strProcRootDir) { if (!fileExists(strOrigSrcName)) { return strOrigSrcName; } // not quite OK; by the time this gets called, strOrigSrcName should have been renamed; well ... } else { string s (m_strProcRootDir + strRelName + strExt); if (!fileExists(s)) { createDirForFile(s); return s; } } string strRes; for (int i = 1; ; ++i) { { ostringstream out; out << m_strProcRootDir << strRelName << ".proc." << setfill('0') << setw(3) << i << strExt; strRes = out.str(); } if (!fileExists(strRes)) { createDirForFile(strRes); return strRes; } } } #endif //=============================================================================================================== //=============================================================================================================== //=============================================================================================================== /*override*/ Transformation::Result IdentityTransformation::apply(const Mp3Handler& h, const TransfConfig& transfConfig, const std::string& strOrigSrcName, std::string& strTempName) { if (h.getName() != strOrigSrcName) { return NOT_CHANGED; } transfConfig.getTempName(strOrigSrcName, getActionName(), strTempName); copyFile(h.getName(), strTempName); return CHANGED_NO_RECALL; } //ttt2 test transformation (and perhaps the other threads) with this: wheat happens if something takes very long and the user presses both pause and resume before the UI checks /* ttt2 warnings + info ID3 V2.3 ID3 V2.4 ID3 V1 APE XING HDR LAME HDR no mp3gain more than 50000 in unknown streams more than 5% in unknown streams Xing TOC incorrect (probably hard to do) ttt2 When removing frames and Xing/Lame is present, adjust header for count and TOC (or remove TOC) */ //ttt2 indiv transformations should not generate duplicates of "original" and "temp" files; this should work only when the orig file is changed (there's no point if the modified file is elsewhere, because changes aren't visible) in the UI //ttt2 perhaps separate tab with actions: "check quality", ... ; or perhaps tab with settings, so the quality is checked the first time //ttt2 there are both "fixes" (pad truncated ...) and "actions" ("remove lyrics", "add picture", "check ID3"); perhaps distinguish between them //ttt2 "check tags" command - goes to musicbrainz and checks all known tags; 2009.03.19 - this doesn't seem to make much sense, given that there are many albums with the same name, ... ; perhaps we should search by track duration too; //ttt2 transform: convert to id3v2: assumes whatever reader order is currently in tag editor and writes to id3v2 if there are differences (including rating); maybe option to convert ID3V2.4.0 reagrdless; doing it manually isn't such a big pain, though; //ttt1 see why some files are read-only on W7 MP3Diags-1.2.02/src/ExternalToolDlgImpl.cpp0000644000175000001440000003142311724622621017341 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include "ExternalToolDlgImpl.h" #include "Widgets.h" #include "StoredSettings.h" #include "CommonData.h" ////#include // ttt remove using namespace std; /* ttt2 random 30-second freeze: norm completed but not detected; window was set to stay open; abort + close sort of worked, but the main window froze and the program had to be killed; on a second run it looked like the UI freeze is not permanent, but lasts 30 seconds (so waiting some more the first time might have unfrozen the app) What seems to happen is this: QProcess loses contact with the actual process (or perhaps the program becomes a zombie or something similar, not really finished but not running anymore either); then 2 things happen: first, waitForFinished() doesn't return for 30 seconds (which is the default timeout, and can be made smaller); then, when closing the norm window there's another 30 seconds freeze, probably caused by the dialog destructor's attempt to destroy the QProcess member (which again tries to kill a dead process) This might be fixed in newer versions of Qt. Might be related to the comment "!!! needed because sometimes" below, which also suggests that the issue is in Qt rather than MP3 Diags Note that this also happens during normal operation, even if "abort" is not pressed. The dialog might fail to detect that normalization is done. If that happens, the solution is to press Abort. ttt2 - perhaps detect that no output is coming from the program, so just assume it's dead; still, the destructor would have to be detached and put on a secondary thread, or just leave a memory leak; (having a timer "clean" a vector with QProcess objects doesn't work, because it would freeze whatever object it's attached to) ttt2 doc: might seem frozen at the end; just press abort and wait for at most a minute. i'm investigating the cause same may happen after pressing abort while the normalization is running */ ExternalToolDlgImpl::ExternalToolDlgImpl(QWidget* pParent, bool bKeepOpen, SessionSettings& settings, const CommonData* pCommonData, const std::string& strCommandName, const char* szHelpFile) : QDialog(pParent, getDialogWndFlags()), Ui::ExternalToolDlg(), m_pProc(0), m_bFinished(false), m_settings(settings), m_pCommonData(pCommonData), m_strCommandName(strCommandName), m_szHelpFile(szHelpFile) { setupUi(this); m_pKeepOpenCkM->setChecked(bKeepOpen); int nWidth, nHeight; m_settings.loadExternalToolSettings(nWidth, nHeight); if (nWidth > 400 && nHeight > 300) { resize(nWidth, nHeight); } setWindowTitle(convStr(strCommandName)); { QAction* p (new QAction(this)); p->setShortcut(QKeySequence("F1")); connect(p, SIGNAL(triggered()), this, SLOT(onHelp())); addAction(p); } } ExternalToolDlgImpl::~ExternalToolDlgImpl() { CursorOverrider crs; delete m_pProc; } void logTransformation(const string& strLogFile, const char* szActionName, const string& strMp3File); /*static*/ void ExternalToolDlgImpl::prepareArgs(const QString& qstrCommand, const QStringList& lFiles, QString& qstrProg, QStringList& lArgs) { if (qstrCommand.contains('"')) { bool bInsideQuotes (false); qstrProg.clear(); QString qstrCrt; for (int i = 0; i < qstrCommand.size(); ++i) { QChar c (qstrCommand[i]); if (' ' == c) { if (bInsideQuotes) { qstrCrt += c; } else { if (!qstrCrt.isEmpty()) { if (qstrProg.isEmpty()) { qstrProg = qstrCrt; } else { lArgs << qstrCrt; } qstrCrt.clear(); } } } else if ('"' == c) { bInsideQuotes = !bInsideQuotes; if (!bInsideQuotes && qstrCrt.isEmpty()) { if (i == qstrCommand.size() - 1 || qstrCommand[i + 1] == ' ') { // add an empty param lArgs << qstrCrt; } } } else { qstrCrt += c; } } if (!qstrCrt.isEmpty()) { if (qstrProg.isEmpty()) { qstrProg = qstrCrt; } else { lArgs << qstrCrt; } qstrCrt.clear(); } //ttt2 maybe: trigger some error if bInsideQuotes is "false" at the end //ttt3 maybe allow <> to be interpreted as 3 params, with the second one empty; currently it is interpreted as 2 non-empty params } else { int k (1); for (; k < qstrCommand.size() && (qstrCommand[k - 1] != ' ' || (qstrCommand[k] != '-' && qstrCommand[k] != '/')); ++k) {} //ttt2 perhaps better: look for spaces from the end and stop when a dir exists from the beginning of the name till the current space qstrProg = qstrCommand.left(k).trimmed(); QString qstrArg (qstrCommand.right(qstrCommand.size() - k).trimmed()); //qDebug("prg <%s> arg <%s>", qstrProg.toUtf8().constData(), qstrArg.toUtf8().constData()); lArgs = qstrArg.split(" ", QString::SkipEmptyParts); // ttt2 perhaps accomodate params that contain spaces, but mp3gain doesn't seem to need them; //QString qstrName (l.front()); //l.removeFirst(); } lArgs << lFiles; } void ExternalToolDlgImpl::run(const QString& qstrProg1, const QStringList& lFiles) //ttt2 in Windows MP3Gain doesn't seem to care about Unicode (well, the GUI version does, but that doesn't help). aacgain doesn't work either; see if there's a good way to deal with this; doc about using short filenames { m_pProc = new QProcess(this); //m_pProc = new QProcess(); // !!! m_pProc is not owned; it will be destroyed connect(m_pProc, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(onFinished())); connect(m_pProc, SIGNAL(readyReadStandardOutput()), this, SLOT(onOutputTxt())); connect(m_pProc, SIGNAL(readyReadStandardError()), this, SLOT(onErrorTxt())); if (m_pCommonData->m_bLogTransf) { for (int i = 0; i < lFiles.size(); ++i) { logTransformation(m_pCommonData->m_strTransfLog, m_strCommandName.c_str(), convStr(lFiles[i])); } } QString qstrProg; QStringList l; prepareArgs(qstrProg1, lFiles, qstrProg, l); m_pProc->start(qstrProg, l); if (!m_pProc->waitForStarted(5000)) { showCritical(this, tr("Error"), tr("Cannot start process. Check that the executable name and the parameters are correct.")); return; } { QAction* p (new QAction(this)); p->setShortcut(QKeySequence(Qt::Key_Escape)); connect(p, SIGNAL(triggered()), this, SLOT(on_m_pAbortB_clicked())); addAction(p); } exec(); m_settings.saveExternalToolSettings(width(), height()); } void ExternalToolDlgImpl::onOutputTxt() { addText(m_pProc->readAllStandardOutput()); //ttt2 perhaps use different colors for std and err, or use 2 separate text boxes } void ExternalToolDlgImpl::onErrorTxt() { //addText("####" + m_pProc->readAllStandardError()); QString s (m_pProc->readAllStandardError().trimmed()); if (s.isEmpty()) { return; } //qDebug("err %s", s.toUtf8().constData()); //inspect(s.toUtf8().constData(), s.size()); int n (s.lastIndexOf("\r")); if (n > 0) { s.remove(0, n + 1); } n = s.lastIndexOf("\n"); if (n > 0) { s.remove(0, n + 1); } //inspect(s.toUtf8().constData(), s.size()); while (!s.isEmpty() && s[0] == ' ') { s.remove(0, 1); } m_pDetailE->setText(s); } void ExternalToolDlgImpl::addText(QString s) { s = s.trimmed(); if (s.isEmpty()) { return; } for (;;) { int n (s.indexOf("\n\n")); if (-1 == n) { break; } s.remove(n, 1); } m_qstrText = (m_qstrText.isEmpty() ? s : m_qstrText + "\n" + s); m_pOutM->setText(m_qstrText); QScrollBar* p (m_pOutM->verticalScrollBar()); if (p->isVisible()) { p->setValue(p->maximum()); } } void ExternalToolDlgImpl::onFinished() { if (m_bFinished) { return; } // !!! needed because sometimes terminating with kill() triggers onFinished() and sometimes it doesn't m_bFinished = true; // !!! doesn't need to destroy m_pProc and QAction, because they will be destroyed anyway when the dialog will be destroyed, which is going to be pretty soon if (m_pKeepOpenCkM->isChecked()) //ttt2 perhaps save in config { addText("=================================="); addText(tr("Finished")); m_pDetailE->setText(""); } else { reject(); } } void ExternalToolDlgImpl::on_m_pCloseB_clicked() { if (!m_bFinished) { showWarning(this, tr("Warning"), tr("Cannot close while \"%1\" is running.").arg(convStr(m_strCommandName))); return; } accept(); } void ExternalToolDlgImpl::on_m_pAbortB_clicked() { qDebug("proc state %d", int(m_pProc->state())); if (m_bFinished) { on_m_pCloseB_clicked(); return; } if (0 == showMessage(this, QMessageBox::Warning, 1, 1, tr("Confirm"), tr("Stopping \"%1\" may leave the files in an inconsistent state or may prevent temporary files from being deleted. Are you sure you want to abort \"%1\"?").arg(convStr(m_strCommandName)), tr("Yes, abort"), tr("Don't abort"))) { CursorOverrider crs; m_pProc->kill(); m_pProc->waitForFinished(5000); onFinished(); } } // !!! Can't allow the top-right close button or the ESC key to close the dialog, because that would kill the thread too and leave everything in an inconsistent state. So the corresponding events are intercepted and "ignore()"d and abort() is called instead /*override*/ void ExternalToolDlgImpl::closeEvent(QCloseEvent* pEvent) { /* Not sure if this should work: from the doc for QDialog: Escape Key If the user presses the Esc key in a dialog, QDialog::reject() will be called. This will cause the window to close: The close event cannot be ignored. ttt2 see Qt::Key_Escape in MainFormDlgImpl for a different approach, decide which is better */ pEvent->ignore(); on_m_pCloseB_clicked(); // on_m_pAbortB_clicked(); } #if 0 /*override*/ void ExternalToolDlgImpl::keyPressEvent(QKeyEvent* pEvent) { //qDebug("key prs %d", pEvent->key()); m_nLastKey = pEvent->key(); pEvent->ignore(); // ttt2 not sure this is the way to do it, but the point is to disable the ESC key } /*override*/ void ExternalToolDlgImpl::keyReleaseEvent(QKeyEvent* pEvent) { //qDebug("key rel %d", pEvent->key()); if (Qt::Key_Escape == pEvent->key()) { on_m_pAbortB_clicked(); } pEvent->ignore(); // ttt2 not sure this is the way to do it, but the point is to disable the ESC key } #endif void ExternalToolDlgImpl::onHelp() { openHelp(m_szHelpFile); } //ttt2 timer in normalizer //ttt2 look at normalized loudness in tracks, maybe warn //ttt1 non-ASCII characters are not shown correctly MP3Diags-1.2.02/src/DoubleListWdg.ui0000644000175000001440000002417111700345153016011 0ustar ciobiusers DoubleListWdg 0 0 641 376 Form 4 0 0 50 false Include elems: Available elems: false QAbstractItemView::SelectRows true 32 16777215 0 0 Qt::Vertical 32 41 0 0 32 32 32 32 Qt::NoFocus < :/images/arrow-left.svg:/images/arrow-left.svg 28 28 true 0 0 32 32 32 32 Qt::NoFocus > :/images/arrow-right.svg:/images/arrow-right.svg 28 28 true Qt::Vertical 32 41 0 0 32 32 32 32 Qt::NoFocus :/images/arrow-left-double.svg:/images/arrow-left-double.svg 28 28 true 0 0 32 32 32 32 Qt::NoFocus >> :/images/arrow-right-double.svg:/images/arrow-right-double.svg 28 28 true Qt::Vertical 32 41 32 32 32 32 Qt::NoFocus ... :/images/reset_settings.svg:/images/reset_settings.svg 28 28 true 32 32 32 32 Qt::NoFocus ... :/images/undo_settings.svg:/images/undo_settings.svg 28 28 true Qt::Vertical 32 41 false QAbstractItemView::SelectRows true MP3Diags-1.2.02/src/MultiLineTvDelegate.h0000644000175000001440000000642511265062600016763 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef MultiLineTvDelegateH #define MultiLineTvDelegateH #include class QTableView; // This helps fix an issue with the default delegate, namely the fact that there is a shight discrepancy between the estimated size returned by sizeHint() and the actual size used when drawing; when a column gets resized, it is quite likely that close to the limit when the number of lines changes the last letters in a word will be changed to "...", because sizeHint() detects that 1 line is enough while paint() wants 2 lines. class MultiLineTvDelegate : public QItemDelegate { Q_OBJECT protected: QTableView* m_pTableView; mutable int m_nLineHeight, m_nAddPerLine; // needed because of Qt bugs in QFontMetrics::boundingRect() that cause incorrect heights to be returned when the text wraps across several lines (e.g. with some fonts an additional pixel must be added for each line to get the correct value) void calibrate(const QFontMetrics&, const QFont&) const; // sets up m_nLine and m_nTotalAdd public: MultiLineTvDelegate(QTableView* pTableView/*, QObject* pParent = 0*/); /*~MultiLineTvDelegate() { printf("destr MultiLineTvDelegate: %p\n", this); }*/ QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const; //void paint(QPainter* pPainter, const QStyleOptionViewItem& option, const QModelIndex& index) const; /*void paint(QPainter* pPainter, const QStyleOptionViewItem& option, const QModelIndex& index) const { //cout << "draw: " << option.rect.width() << "x" << option.rect.height() << endl; int nCol (index.column()); if (0 == nCol) { return QItemDelegate::paint(pPainter, option, index); } }*/ }; #endif // #ifndef MultiLineTvDelegateH MP3Diags-1.2.02/src/About.ui0000644000175000001440000001410111576207177014361 0ustar ciobiusers AboutDlg 0 0 1080 703 About MP3 Diags true 0 0 48 48 :/images/about.svg true 19 75 false true MP3 Diags x.y.z Qt::AlignCenter 0 About 10 20 10 10 QFrame::NoFrame true System info GPL V2 (for the program) LGPL V3 (for the icons) GPL V3 (for the icons) LGPL V2.1 (for Qt) Boost license zlib license 0 Qt::Horizontal 40 20 OK Qt::Horizontal 40 20 m_pOkB MP3Diags-1.2.02/src/Id3V240Stream.h0000644000175000001440000000772111720256551015271 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef Id3V240StreamH #define Id3V240StreamH #include "Id3V2Stream.h" // Frame of an ID3V2.4.0 tag struct Id3V240Frame : public Id3V2Frame { Q_DECLARE_TR_FUNCTIONS(Id3V240Frame) public: Id3V240Frame(NoteColl& notes, std::istream& in, std::streampos pos, bool bHasUnsynch, std::streampos posNext, StringWrp* pFileName); private: bool checkSize(std::istream& in, std::streampos posNext); // since broken applications may use all 8 bits for size, although only 7 should be used, this tries to figure out if the size is correct /*override*/ bool discardOnChange() const; // may return multiple null characters; it's the job of getUtf8String() to deal with them; // chars after the first null are considered comments (or after the second null, for TXXX), so the nulls are replaced with commas, except for those at the end of the string (which are removed) and the first null in TXXX; /*override*/ std::string getUtf8StringImpl() const; /*override*/ int getOffset() const; void load(NoteColl& notes, std::istream& in, std::streampos posNext, bool bHasUnsynch); void load(NoteColl& notes, std::istream& in, std::streampos posNext); private: friend class boost::serialization::access; Id3V240Frame() {} // serialization-only constructor template void serialize(Archive& ar, const unsigned int nVersion) { if (nVersion > 0) { throw std::runtime_error("invalid version of serialized file"); } ar & boost::serialization::base_object(*this); } }; class Id3V240Stream : public Id3V2StreamBase { Q_DECLARE_TR_FUNCTIONS(Id3V240Stream) public: Id3V240Stream(int nIndex, NoteColl& notes, std::istream& in, StringWrp* pFileName, bool bAcceptBroken = false); //typedef typename Id3V2Stream::NotId3V2 NotId3V240; DECL_RD_NAME("ID3V2.4.0") /*override*/ TagTimestamp getTime(bool* pbFrameExists = 0) const; /*override*/ void setTrackTime(const TagTimestamp&) { throw NotSupportedOp(); } /*override*/ SuportLevel getSupport(Feature) const; private: friend class boost::serialization::access; Id3V240Stream() {} // serialization-only constructor template void serialize(Archive& ar, const unsigned int nVersion) { if (nVersion > 0) { throw std::runtime_error("invalid version of serialized file"); } ar & boost::serialization::base_object(*this); } }; #endif // ifndef Id3V240StreamH MP3Diags-1.2.02/src/PaletteDlgImpl.cpp0000644000175000001440000000776111230121556016320 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include "PaletteDlgImpl.h" #include "TagEditorDlgImpl.h" #include "Helpers.h" PaletteDlgImpl::PaletteDlgImpl(CommonData* pCommonData, QWidget* pParent) : QDialog(pParent, getNoResizeWndFlags()), Ui::PaletteDlg(), m_pCommonData(pCommonData) // not a "thread window", but doesn't need resizing anyway { setupUi(this); m_vpColButtons.push_back(m_pCol0B); m_vpColButtons.push_back(m_pCol1B); m_vpColButtons.push_back(m_pCol2B); m_vpColButtons.push_back(m_pCol3B); m_vpColButtons.push_back(m_pCol4B); m_vpColButtons.push_back(m_pCol5B); m_vpColButtons.push_back(m_pCol6B); for (int i = 0; i < cSize(m_vpColButtons); ++i) { setBtnColor(i); } { QAction* p (new QAction(this)); p->setShortcut(QKeySequence("F1")); connect(p, SIGNAL(triggered()), this, SLOT(onHelp())); addAction(p); } } PaletteDlgImpl::~PaletteDlgImpl() { } /*$SPECIALIZATION$*/ void PaletteDlgImpl::on_m_pOkB_clicked() { accept(); } void PaletteDlgImpl::setBtnColor(int n) { /*QPalette pal (m_vpColButtons[n]->palette()); //QPalette pal (m_pCol0B->palette()); pal.setBrush(QPalette::Button, m_pCommonData->m_vTagEdtColors.at(n)); pal.setBrush(QPalette::Window, m_pCommonData->m_vTagEdtColors[n]); //pal.setBrush(QPalette::Midlight, QColor(255, 0, 0)); //pal.setBrush(QPalette::Dark, QColor(255, 0, 0)); //pal.setBrush(QPalette::Mid, QColor(255, 0, 0)); //pal.setBrush(QPalette::Shadow, QColor(255, 0, 0)); m_vpColButtons[n]->setPalette(pal);*/ int f (QApplication::style()->pixelMetric(QStyle::PM_DefaultFrameWidth, 0, m_vpColButtons.at(n)) + 2); //ttt2 hard-coded "2" int w (m_vpColButtons[n]->width() - f), h (m_vpColButtons[n]->height() - f); QPixmap pic (w, h); QPainter pntr (&pic); pntr.fillRect(0, 0, w, h, m_pCommonData->m_vTagEdtColors.at(n)); m_vpColButtons[n]->setIcon(pic); m_vpColButtons[n]->setIconSize(QSize(w, h)); /* QPixmap pic (21, 21); QPainter pntr (&pic); QRect r (0, 0, 21, 21); pntr.fillRect(r, QColor(255, 255, 0)); pntr.drawText(r, Qt::AlignCenter, "i"); m_vpColButtons[n]->setIcon(pic); m_vpColButtons[n]->setIconSize(QSize(21, 21));*/ } void PaletteDlgImpl::onButtonClicked(int n) { QColor c (QColorDialog::getColor(m_pCommonData->m_vTagEdtColors.at(n), this)); if (!c.isValid()) { return; } m_pCommonData->m_vTagEdtColors[n] = c; setBtnColor(n); } void PaletteDlgImpl::onHelp() { //openHelp("index.html"); } MP3Diags-1.2.02/src/main.cpp0000644000175000001440000013120012266016536014372 0ustar ciobiusers/*************************************************************************** * MP3 Diags - diagnosis, repairs and tag editing for MP3 files * * * * Copyright (C) 2009 by Marian Ciobanu * * ciobi@inbox.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ /*************************************************************************** * * * Command-line mode Copyright (C) 2011 by Michael Elsdörfer * * * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include "MainFormDlgImpl.h" #include "SessionEditorDlgImpl.h" #include "SessionsDlgImpl.h" #include "Helpers.h" #include "StoredSettings.h" #include "OsFile.h" #include "Mp3Manip.h" #include "Notes.h" #include "Version.h" #include "Widgets.h" #include "CommonData.h" #include "ConfigDlgImpl.h" #include "Mp3TransformThread.h" #include "Translation.h" //#include "Profiler.h" using namespace std; using namespace Version; namespace po = boost::program_options; GlobalSettings::GlobalSettings() { m_pSettings = new QSettings (getOrganization(), getSettingsAppName()); } GlobalSettings::~GlobalSettings() { delete m_pSettings; } void GlobalSettings::saveSessions(const vector& vstrSess1, const string& strLast, bool bOpenLast, const string& strTempSessTempl, const string& strDirSessTempl, const string& strTranslation, bool bLoadExternalChanges) { vector vstrSess (vstrSess1); if (bLoadExternalChanges) { // add sessions that might have been added by another instance of the program vector vstrSess2; string strLast2, strTempSessTempl2, strDirSessTempl2, strTranslation2; bool bOpenLast2; loadSessions(vstrSess2, strLast2, bOpenLast2, strTempSessTempl2, strDirSessTempl2, strTranslation2); for (int i = 0, n = cSize(vstrSess2); i < n; ++i) { if (vstrSess.end() == find(vstrSess.begin(), vstrSess.end(), vstrSess2[i])) { vstrSess.push_back(vstrSess2[i]); // ttt2 perhaps add a return value to the function, so the caller would know to update the UI; however, what is done here is more important, because it prevents data loss } } } if (!strLast.empty() && vstrSess.end() == find(vstrSess.begin(), vstrSess.end(), strLast)) // e.g. for a new session created using "folder-session" option { vstrSess.push_back(strLast); } int n (cSize(vstrSess)); m_pSettings->setValue("main/lastSession", convStr(strLast)); m_pSettings->setValue("main/openLast", bOpenLast); m_pSettings->remove("main/sessions"); m_pSettings->setValue("main/sessions/count", n); char a [50]; for (int i = 0; i < n; ++i) { sprintf(a, "main/sessions/val%04d", i); m_pSettings->setValue(a, convStr(vstrSess[i])); } m_pSettings->setValue("main/tempSessionTemplate", convStr(strTempSessTempl)); m_pSettings->setValue("main/dirSessionTemplate", convStr(strDirSessTempl)); m_pSettings->setValue("main/translation", convStr(strTranslation)); } void GlobalSettings::loadSessions(vector& vstrSess, string& strLast, bool& bOpenLast, string& strTempSessTempl, string& strDirSessTempl, string& strTranslation) const { vstrSess.clear(); int n (m_pSettings->value("main/sessions/count", 0).toInt()); strLast = convStr(m_pSettings->value("main/lastSession").toString()); bOpenLast = m_pSettings->value("main/openLast", true).toBool(); strTempSessTempl = convStr(m_pSettings->value("main/tempSessionTemplate").toString()); strDirSessTempl = convStr(m_pSettings->value("main/dirSessionTemplate").toString()); char a [50]; for (int i = 0; i < n; ++i) { sprintf(a, "main/sessions/val%04d", i); QString qs (m_pSettings->value(a, "").toString()); if (QFileInfo(qs).isFile()) { string s (convStr(qs)); vstrSess.push_back(s); } } if (!QFileInfo(convStr(strLast)).isFile() || vstrSess.end() == find(vstrSess.begin(), vstrSess.end(), strLast)) { // pick something else if the session got deleted; otherwise there's an assertion failure; strLast = vstrSess.empty() ? "" : vstrSess.back(); // ttt1 generally improve handling of missing sessions (simply erasing them is probably not the best option); } strTranslation = convStr(m_pSettings->value("main/translation", "").toString()); } const QFont& getDefaultFont() { static QFont s_font; return s_font; } //static char* s_pFreeMem (new char[1000000]); static char* s_pFreeMem (new char[10000]); // to test this use "ulimit -v 200000" and uncomment the allocations for char[1000000] below // //ttt2 if the GUI got started, this doesn't seem to get called, regardless of the size of s_pFreeMem void newHandler() { delete[] s_pFreeMem; //puts("inside new handler"); cerr << "inside new handler" << endl; cerr.flush(); throw bad_alloc(); return; } class QMp3DiagsApplication : public QApplication { public: /*override*/ bool notify(QObject* pReceiver, QEvent *pEvent) { try { return QApplication::notify(pReceiver, pEvent); } catch (...) { CB_ASSERT (false); } } QMp3DiagsApplication(int& argc, char** argv) : QApplication(argc, argv) {} }; #ifdef MSVC_QMAKE void visStudioMessageOutput(QtMsgType, const char* szMsg) { OutputDebugStringA(" "); // to stand out from the other messages that get printed OutputDebugStringA(szMsg); OutputDebugStringA("\r\n"); //cerr << szMsg << endl; //showInfo(0, "Debug message", szMsg, QMessageBox::Ok); } #endif namespace { class OptionInfo { const string m_strLongOpt; const string m_strShortOpt; const string m_strFullOpt; const string m_strDescr; public: OptionInfo(const string& strLongOpt, const string& strShortOpt, const string& strDescr) : m_strLongOpt(strLongOpt), m_strShortOpt(strShortOpt), m_strFullOpt(strLongOpt + (strShortOpt.empty() ? "" : "," + strShortOpt)), m_strDescr(strDescr), m_szLongOpt(m_strLongOpt.c_str()), m_szShortOpt(m_strShortOpt.c_str()), m_szFullOpt(m_strFullOpt.c_str()), m_szDescr(m_strDescr.c_str()) {} const char* const m_szLongOpt; const char* const m_szShortOpt; const char* const m_szFullOpt; const char* const m_szDescr; }; OptionInfo s_helpOpt ("help", "h", "Show this help message"); OptionInfo s_uninstOpt ("uninstall", "u", "Uninstall (remove settings)"); OptionInfo s_severityOpt ("severity", "s", "Minimum severity to show (one of error, warning, support); default: warning"); OptionInfo s_inputFileOpt ("input-file", "", "Input file"); OptionInfo s_hiddenFolderSessOpt ("hidden-session", "f", "Creates a new session for the specified folder and stores it inside that folder. The session will be hidden when the program exits."); OptionInfo s_loadedFolderSessOpt ("visible-session", "v", "Creates a new session for the specified folder and stores it inside that folder. The session will be visible in the session list after the program is restarted."); OptionInfo s_tempSessOpt ("temp-session", "t", "Creates a temporary session for the specified folder, which will be deleted when the program exits"); OptionInfo s_transfListOpt ("transf-list", "l", "A number between 1 and 4. Applies the specified custom transformation list to the files and/or folders passed"); //ttt1 use CUSTOM_TRANSF_CNT instead of 4 OptionInfo s_sessionOpt ("session", "w", "Session file name"); OptionInfo s_overrideSess ("", "", ""); // ttt1 maybe implement - for s_folderSessOpt and s_tempSessOpt (and s_inputFileOpt) - session with settings to use instead of the template // replaces the folders of a session file with the given new folder void setFolder(const string& strSessionFile, const string& strFolder) { SessionSettings stg (strSessionFile); vector vstrIncl, vstrExcl; vstrIncl.push_back(strFolder); stg.saveDirs(vstrIncl, vstrExcl); } static const char* const TEMP_SESS ("MP3DiagsTempSession"); struct SessEraser { static void eraseTempSess() { string strErr (eraseFiles(getSepTerminatedDir(convStr(QDir::tempPath())) + TEMP_SESS)); if (!strErr.empty()) { cerr << "Cannot remove file " << strErr << endl; exit(1); } } void hideFolderSession() { if (m_strSessionToHide.empty()) { return; } GlobalSettings st; vector vstrSess; string strLastSession; bool bOpenLast; string strTempSessTempl; string strDirSessTempl; string strTranslation; st.loadSessions(vstrSess, strLastSession, bOpenLast, strTempSessTempl, strDirSessTempl, strTranslation); vector::iterator it (find(vstrSess.begin(), vstrSess.end(), m_strSessionToHide)); if (vstrSess.end() != it) { vstrSess.erase(it); if (strLastSession == m_strSessionToHide) { strLastSession = vstrSess.empty() ? "" : vstrSess.back(); } } st.saveSessions(vstrSess, strLastSession, bOpenLast, strTempSessTempl, strDirSessTempl, strTranslation, GlobalSettings::IGNORE_EXTERNAL_CHANGES); } string m_strSessionToHide; ~SessEraser() { eraseTempSess(); hideFolderSession(); } }; } // namespace //ttt1 in 10.3 loading a session seemed to erase the templates, but couldn't reproduce later string getActiveSession(const po::variables_map& options, int& nSessCnt, bool& bOpenLast, string& strTempSessTempl, string& strDirSessTempl) { string strRes; vector vstrSess; //string strLast; GlobalSettings st; string strTranslation; st.loadSessions(vstrSess, strRes, bOpenLast, strTempSessTempl, strDirSessTempl, strTranslation); nSessCnt = cSize(vstrSess); if (options.count(s_sessionOpt.m_szLongOpt) > 0) { strRes = options[s_sessionOpt.m_szLongOpt].as(); } return strRes; } // http://stackoverflow.com/questions/760323/why-does-my-qt4-5-app-open-a-console-window-under-windows - The option under Visual Studio for setting the subsystem is under Project Settings->Linker->System->SubSystem int guiMain(const po::variables_map& options) { { // by default on Windows the selection is hard to see in the main window, because it's some gray; QPalette pal (QApplication::palette()); pal.setColor(QPalette::Highlight, pal.color(QPalette::Active, QPalette::Highlight)); pal.setColor(QPalette::HighlightedText, pal.color(QPalette::Active, QPalette::HighlightedText)); QApplication::setPalette(pal); } getDefaultFont(); // !!! to initialize the static var string strStartSession; string strLastSession; int nSessCnt; bool bOpenLast; string strTempSessTempl; string strDirSessTempl; //bool bIsTempSess (false); string strTempSession (getSepTerminatedDir(convStr(QDir::tempPath())) + TEMP_SESS + SessionEditorDlgImpl::SESS_EXT); string strFolderSess; bool bHideFolderSess (true); if (options.count(s_hiddenFolderSessOpt.m_szLongOpt) > 0) { strFolderSess = options[s_hiddenFolderSessOpt.m_szLongOpt].as(); } else if (options.count(s_loadedFolderSessOpt.m_szLongOpt) > 0) { strFolderSess = options[s_loadedFolderSessOpt.m_szLongOpt].as(); bHideFolderSess = false; } SessEraser sessEraser; strLastSession = getActiveSession(options, nSessCnt, bOpenLast, strTempSessTempl, strDirSessTempl); if (options.count(s_tempSessOpt.m_szLongOpt) > 0) { SessEraser::eraseTempSess(); strStartSession = strTempSession; if (strTempSessTempl.empty()) { strTempSessTempl = strLastSession; } if (!strTempSessTempl.empty()) { try { copyFile2(strTempSessTempl, strStartSession); } catch (...) { // nothing //ttt2 do more } } string strProcDir (options[s_tempSessOpt.m_szLongOpt].as()); strProcDir = getNonSepTerminatedDir(convStr(QDir(fromNativeSeparators(convStr(strProcDir))).absolutePath())); setFolder(strStartSession, strProcDir); bOpenLast = true; } else if (!strFolderSess.empty()) { string strProcDir = getNonSepTerminatedDir(convStr(QDir(fromNativeSeparators(convStr(strFolderSess))).absolutePath())); //ttt2 test on root if (!dirExists(strProcDir)) { showMessage(0, QMessageBox::Critical, 0, 0, MainFormDlgImpl::tr("Error"), MainFormDlgImpl::tr("Folder \"%1\" doesn't exist. The program will exit ...").arg(convStr(strProcDir)), MainFormDlgImpl::tr("O&K")); return 1; } string strDirName (convStr(QFileInfo(convStr(strProcDir)).fileName())); if (strDirName.empty()) // it's a whole disk drive on Windows, e.g. D:\\ ; { strDirName += strProcDir[0]; strDirName += "-MP3Diags"; } strStartSession = getSepTerminatedDir(strProcDir) + strDirName + SessionEditorDlgImpl::SESS_EXT; if (bHideFolderSess) { sessEraser.m_strSessionToHide = strStartSession; } if (!fileExists(strStartSession)) { if (strDirSessTempl.empty()) { strDirSessTempl = strLastSession; } if (!strDirSessTempl.empty()) { try { copyFile2(strDirSessTempl, strStartSession); setFolder(strStartSession, strProcDir); } catch (...) { // nothing //ttt2 do more } } } ofstream out (strStartSession.c_str(), ios_base::app); if (!out) { showMessage(0, QMessageBox::Critical, 0, 0, MainFormDlgImpl::tr("Error"), MainFormDlgImpl::tr("Cannot write to file \"%1\". The program will exit ...").arg(convStr(strStartSession)), MainFormDlgImpl::tr("O&K")); return 1; } bOpenLast = true; } else if (0 == nSessCnt) { // first run; create a new session and run it SessionEditorDlgImpl dlg (0, "", SessionEditorDlgImpl::FIRST_TIME, ""); // ttt0 detect system locale dlg.setWindowIcon(QIcon(":/images/logo.svg")); strStartSession = dlg.run(); if (strStartSession.empty()) { return 0; } if ("*" == strStartSession) { strStartSession.clear(); } else { vector vstrSess; //vstrSess.push_back(strStartSession); GlobalSettings st; st.saveSessions(vstrSess, strStartSession, dlg.shouldOpenLastSession(), "", "", dlg.getTranslation(), GlobalSettings::LOAD_EXTERNAL_CHANGES); } bOpenLast = true; } else { strStartSession = strLastSession; } bool bOpenSelDlg (strStartSession.empty() || !bOpenLast); try { for (;;) { { QFont fnt; string strNewFont (convStr(fnt.family())); int nNewSize (fnt.pointSize()); fixAppFont(fnt, strNewFont, nNewSize); } if (bOpenSelDlg) { SessionsDlgImpl dlg (0); dlg.setWindowIcon(QIcon(":/images/logo.svg")); strStartSession = dlg.run(); if (strStartSession.empty()) { return 0; } } bOpenSelDlg = true; CB_ASSERT (!strStartSession.empty()); bool bDefaultForVisibleSessBtn (true); //if (strStartSession != strTempSession) { vector vstrSess; bool bOpenLast; string s, s1, s2, s3; GlobalSettings st; st.loadSessions(vstrSess, s, bOpenLast, s1, s2, s3); st.saveSessions(vstrSess, strStartSession, bOpenLast, s1, s2, s3, GlobalSettings::LOAD_EXTERNAL_CHANGES); bDefaultForVisibleSessBtn = (cSize(vstrSess) != 1 || !strFolderSess.empty() || vstrSess.end() != find(vstrSess.begin(), vstrSess.end(), strTempSession)); } { //ttt2 overkill - create a "CommonData" just to read the language setting SessionSettings settings (strStartSession); CommonData commonData(settings, 0, 0, 0, 0, 0, 0, 0, 0, 0, false); settings.loadMiscConfigSettings(&commonData, SessionSettings::DONT_INIT_GUI); TranslatorHandler::getGlobalTranslator().setTranslation(commonData.m_strTranslation); // !!! must be done here, before the MainFormDlgImpl constructor } MainFormDlgImpl mainDlg (strStartSession, bDefaultForVisibleSessBtn); mainDlg.setWindowIcon(QIcon(":/images/logo.svg")); if (bDefaultForVisibleSessBtn) { mainDlg.setWindowTitle(QString(getAppName()) + " - " + convStr(SessionEditorDlgImpl::getTitleName(strStartSession))); } else { mainDlg.setWindowTitle(QString(getAppName())); } if (MainFormDlgImpl::OPEN_SESS_DLG != mainDlg.run()) { return 0; } } } catch (...) // ttt2 for now it doesn't catch many exceptions; it seems that nothing can be done if an exception leaves a slot / event handler, but maybe there are ways around { /*QMessageBox dlg (QMessageBox::Critical, "Error", "Caught generic exception. Exiting ...", QMessageBox::Close, 0, Qt::Dialog | Qt::MSWindowsFixedSizeDialogHint | Qt::WindowStaysOnTopHint); dlg.exec(); qDebug("out - err");*/ CB_ASSERT (false); } /*mainDlg.show(); return app.exec();*/ } namespace { class CmdLineProcessor { QualThresholds m_qualThresholds; int m_nCut; // for relative dirs const vector& m_vstrNames; virtual bool processFile(const string& strFullName, Mp3Handler* mp3Handler) = 0; // returns "true" if there are no problems bool processFile(const string& strFullName) { if (strFullName.size() <= 4) { return true; } QString qs; qs = convStr(strFullName.substr(strFullName.size() - 4)).toLower(); if (qs != ".mp3") // ttt1 unify with test in Mp3ProcThread::scan() { return true; } Mp3Handler* mp3Handler; try { mp3Handler = new Mp3Handler(strFullName, false, m_qualThresholds); } catch (Mp3Handler::FileNotFound) { cout << "File not found: " + toNativeSeparators(strFullName) << endl << endl; return false; } return processFile(strFullName, mp3Handler); } // returns "true" if there are no problems bool processFullName(const string& strFullName) //ttt1 make FileSearcher take wildcards; then this function could take files, directories, and wildcard names (which might match both files and directories); in particular, a FileSearcher might take the name of a specific file, and then it should list it { bool bRes (true); FileSearcher fs (strFullName); while (fs) { if (fs.isFile()) { bRes = processFile(fs.getName()) && bRes; } else if (fs.isDir()) { bRes = processFullName(fs.getName()) && bRes; } else { cout << "Skipping unknown name : \"" << toNativeSeparators(fs.getName()) << "\"" << endl << endl; } fs.findNext(); } return bRes; } // returns "true" if there are no problems bool processName(const string& strName) { string strFullName (convStr(QDir(fromNativeSeparators(convStr(strName))).absolutePath())); //ttt2 test on root m_nCut = endsWith(strFullName, strName) ? cSize(strFullName) - cSize(strName) : 0; //cout << strName << " # " << strFullName << " # " << getNonSepTerminatedDir(strFullName) << endl; if (fileExists(strFullName)) { return processFile(strFullName); } else if (dirExists(strFullName)) { return processFullName(strFullName); } else { cout << "Name not found: \"" << toNativeSeparators(strFullName) << "\"" << endl << endl; } return false; } protected: void setQualThresholds(const QualThresholds& qualThresholds) { m_qualThresholds = qualThresholds; } string getRelativeName(const string& strFullName) { return strFullName.substr(m_nCut); } CmdLineProcessor(const vector& vstrNames) : m_vstrNames(vstrNames) {} virtual ~CmdLineProcessor() {} public: // returns "true" if there are no problems bool run() { bool bAnyFileHasProblems (false); for (int i = 0, n = cSize(m_vstrNames); i < n; ++i) { const string& file (m_vstrNames[i]); bAnyFileHasProblems = processName(file) || bAnyFileHasProblems; //ttt1 make wildcards recognized on Windows (perhaps Linux too but Bash takes care of this; not sure about other shells) } return !bAnyFileHasProblems; } }; class CmdLineAnalyzer : public CmdLineProcessor { Note::Severity m_minLevel; /*override*/ bool processFile(const string& strFullName, Mp3Handler* pmp3Handler) { bool bThisFileHasProblems = false; vector vpNotes (pmp3Handler->getNotes().getList()); // sort by position rather than note ID (notes come sorted by ID from Mp3Handler) sort(vpNotes.begin(), vpNotes.end(), CmpNotePtrByPosAndId()); for (int i = 0, n = cSize(vpNotes); i < n; ++i) { const Note* pNote (vpNotes[i]); // category --include/--exclude options would be nice, too. bool bShowThisNote = (pNote->getSeverity() <= m_minLevel); if (!bShowThisNote) { continue; } if (!bThisFileHasProblems) { bThisFileHasProblems = true; cout << toNativeSeparators(getRelativeName(strFullName)) << endl; } cout << "- " << (Note::severityToString(pNote->getSeverity())) << ": " << pNote->getDescription() << " [" << pNote->getPos() << "]" << endl; } if (bThisFileHasProblems) { cout << endl; } return !bThisFileHasProblems; } public: CmdLineAnalyzer(Note::Severity minLevel, const QualThresholds& qualThresholds, const vector& vstrNames) : CmdLineProcessor(vstrNames), m_minLevel(minLevel) { setQualThresholds(qualThresholds); } }; class TransfListRunner : public CmdLineProcessor { struct Mp3TransformerCli : public Mp3Transformer { Mp3TransformerCli( CommonData* pCommonData, const TransfConfig& transfConfig, const deque& vpHndlr, vector& vpDel, vector& vpAdd, vector& vpTransf) : Mp3Transformer( pCommonData, transfConfig, vpHndlr, vpDel, vpAdd, vpTransf, &cout) { } /*override*/ bool isAborted() { return false; } /*override*/ void checkPause() {} /*override*/ void emitStepChanged(const StrList&, int) {} }; SessionSettings m_settings; CommonData m_commonData; TransfConfig m_transfConfig; deque m_vpHndlr; vector m_vpDel; vector m_vpAdd; vector m_vpTransf; Mp3TransformerCli m_mp3TransformerCli; void loadCustomTransf(int nTransfList) // ttt1 unify with MainFormDlgImpl::loadCustomTransf() - perhaps make it member of SessionSettings { char bfr [50]; sprintf(bfr, "customTransf/set%04d", nTransfList); //vector vstrNames (m_settings.loadCustomTransf(k)); bool bErr; vector vstrNames (m_settings.loadVector(bfr, bErr)); vector v; const vector& u (m_commonData.getAllTransf()); int m (cSize(u)); for (int i = 0, n = cSize(vstrNames); i < n; ++i) { string strName (vstrNames[i]); int j (0); for (; j < m; ++j) { if (u[j]->getActionName() == strName) { v.push_back(j); break; } } if (j == m) { //showWarning(this, "Error setting up custom transformations", "Couldn't find a transformation with the name \"" + convStr(strName) + "\". The program will proceed, but you should review the custom transformations lists."); cerr << "Error setting up custom transformations: " << "Couldn't find a transformation with the name \"" + strName + "\". The program will proceed, but you should review the custom transformations lists." << endl; } } if (v.empty()) { vector > vv (CUSTOM_TRANSF_CNT); initDefaultCustomTransf(nTransfList, vv, &m_commonData); v = vv[nTransfList]; } m_commonData.setCustomTransf(nTransfList, v); } /*override*/ bool processFile(const string& strFullName, Mp3Handler* pmp3Handler) { m_vpHndlr.clear(); m_vpHndlr.push_back(pmp3Handler); m_vpDel.clear(); m_vpAdd.clear(); bool bRes (m_mp3TransformerCli.transform()); if (!bRes) { string strErr (m_mp3TransformerCli.getError()); if (strErr.empty()) { strErr = "Unknown error while processing " + strFullName; } cerr << strErr << endl; } return bRes; } public: TransfListRunner(const vector& vstrNames, int nTransfList, const string& strSessFile) : CmdLineProcessor(vstrNames), m_settings(strSessFile), m_commonData(m_settings, 0, 0, 0, 0, 0, 0, 0, 0, 0, false), m_mp3TransformerCli(&m_commonData, m_transfConfig, m_vpHndlr, m_vpDel, m_vpAdd, m_vpTransf) { m_settings.loadMiscConfigSettings(&m_commonData, SessionSettings::DONT_INIT_GUI); m_settings.loadTransfConfig(m_transfConfig); for (int i = 0; i < CUSTOM_TRANSF_CNT; ++i) { loadCustomTransf(i); } m_vpTransf.clear(); for (int i = 0, n = cSize(m_commonData.getCustomTransf()[nTransfList]); i < n; ++i) { m_vpTransf.push_back(m_commonData.getAllTransf()[m_commonData.getCustomTransf()[nTransfList][i]]); } setQualThresholds(m_commonData.getQualThresholds()); m_commonData.m_strTransfLog = SessionEditorDlgImpl::getLogFileName(strSessFile); } }; void noMessageOutput(QtMsgType, const char*) { } int cmdlineMain(const po::variables_map& options) { /* Notes about Windows, where it doesn't seem possible to output something to the console from a GUI app. - the chosen option is to use redirection, either to a file or through "more" or something similar; this is made more transparent by using MP3DiagsCLI.cmd to redirect to a temp file and then print it - other option would be to open Notepad and populate it using COM/OLE. Not sure how to do it. - other option would be to redirect output to a file and open that file in Notepad. - other option would be to open a Qt window that only has the output from the run and a close button. - other option would be to open a new console and output the results there see also: http://blogs.msdn.com/b/junfeng/archive/2004/02/06/68531.aspx http://www.codeproject.com/KB/cpp/EditBin.aspx http://www.halcyon.com/~ast/dload/guicon.htm */ const vector inputFiles = options[s_inputFileOpt.m_szLongOpt].as< vector >(); Notes::getAllNotes(); // set up proper IDs for the notes, so they can be sorted meaningfully Note::Severity minLevel (Note::WARNING); if (options.count(s_severityOpt.m_szLongOpt) > 0) { try { minLevel = options[s_severityOpt.m_szLongOpt].as(); //ttt2 see how to use default params in cmdlineDesc.add_options() } catch (...) { // nothing } } // In cmdline mode, we want to make sure the user only sees our // carefully crafted messages, and no debug stuff from arbitrary // places in the program. qInstallMsgHandler(noMessageOutput); string strSessFile; { int nSessCnt; bool bOpenLast; string strTempSessTempl; string strDirSessTempl; strSessFile = getActiveSession(options, nSessCnt, bOpenLast, strTempSessTempl, strDirSessTempl); } if (options.count(s_transfListOpt.m_szLongOpt) > 0) { int nTransfList (options[s_transfListOpt.m_szLongOpt].as() - 1); // [1..4] -> [0..3] if (nTransfList < 0 || nTransfList >= CUSTOM_TRANSF_CNT) { cerr << "Transformation list must be a number between 1 and " << CUSTOM_TRANSF_CNT << endl; return 1; } TransfListRunner transfListRunner (inputFiles, nTransfList, strSessFile); return transfListRunner.run() ? 0 : 1; } SessionSettings settings (strSessFile); CommonData commonData (settings, 0, 0, 0, 0, 0, 0, 0, 0, 0, false); settings.loadMiscConfigSettings(&commonData, SessionSettings::DONT_INIT_GUI); //settings.loadTransfConfig(transfConfig); //commonData.m_strTransfLog = SessionEditorDlgImpl::getLogFileName(strSessFile); CmdLineAnalyzer cmdLineAnalyzer (minLevel, commonData.getQualThresholds(), inputFiles); return cmdLineAnalyzer.run() ? 0 : 1; } /* ttt1 CLI-friendly function restructuring: - errors and warnings should be returned in strings / lists and outputting from CLI tools done from a central place */ //static const char* CMD_HELP = "help"; static const char* CMD_HELP_SHORT = "h"; static const char* CMD_HELP_FULL = "help,h"; //#define GEN_CMD(NAME, LNG, SHRT) static const char* CMD_##NAME = #LNG; static const char* CMD_##NAME##_SHORT = #SHRT; static const char* CMD_##NAME##_FULL = "LNG##SHRT"; //GEN_CMD(HELP, help, h); } // namespace // To parse a Note::Severity value from the command line using boost::program_options. static void validate(boost::any& v, vector const& values, Note::Severity*, int) { po::validators::check_first_occurrence(v); const string& s = po::validators::get_single_string(values); if (s.compare("error") == 0) v = Note::ERR; //ttt1 maybe translate else if (s.compare("warning") == 0) v = Note::WARNING; else if (s.compare("support") == 0) v = Note::SUPPORT; //else throw po::validation_error(po::validation_error::invalid_option_value); //else throw po::validation_error("invalid option value"); else throw runtime_error("invalid option value"); } int main(int argc, char *argv[]) { //ifstream_unicode in ("/home/ciobi/cpp/Mp3Utils/mp3diags/trunk/mp3diags/src/2/testä.txt"); /*ifstream_unicode in ("/home/ciobi/cpp/Mp3Utils/mp3diags/trunk/mp3diags/src/2/test_.txt"); string s; getline(in, s); cout << s; return 4;//*/ //char *argv[] = {"aa", "-s", "support", "pppqqq"}; argc = 4; //char *argv[] = {"aa", "pppqqq"}; argc = 2; //char *argv[] = {"aa", "/d/test_mp3/1/tmp2/c pic/vbri assertion.mp3"}; argc = 2; //char *argv1[] = {"aa", "--directory", "/test_mp3/1/tmp2/c pic" }; argc = 3; argv = argv1; //char *argv1[] = {"aa", "--temp-session", "/d/test_mp3/1/tmp2/c pic" }; argc = 3; argv = argv1; //char *argv1[] = {"aa", "--input-file", "/test_mp3/1/tmp2/c pic" }; argc = 3; argv = argv1; //char *argv1[] = {"aa", "--hidden-folder-session", "/d/test_mp3/1/tmp2/c pic" }; argc = 3; argv = argv1; //char *argv1[] = {"aa", "--hidden-folder-session", "/d/test_mp3/1/tmp2/text frames" }; argc = 3; argv = argv1; //char *argv1[] = {"aa", "--loaded-folder-session", "/d/test_mp3/1/tmp2/c pic" }; argc = 3; argv = argv1; //char *argv1[] = {"aa", "--loaded-folder-session", "/d/test_mp3/1/tmp2/text frames" }; argc = 3; argv = argv1; //char *argv1[] = {"aa", "--folder-session", "/usr" }; argc = 3; argv = argv1; //char *argv1[] = {"aa", "--hidden-session", "Q:\\" }; argc = 3; argv = argv1; //DEFINE_PROF_ROOT("mp3diags"); //PROF("root"); /* //locale::global(locale("")); ostringstream o; o << 12345.78; cout << o.str() << endl; printf("%f\n", 12345.78);//*/ void (*nh)() = set_new_handler(newHandler); if (0 != nh) { cerr << "previous new handler: " << (void*)nh << endl; } //for (int i = 0; i < 200; ++i) { new char[1000000]; } #ifdef MSVC_QMAKE qInstallMsgHandler(visStudioMessageOutput); // see http://lists.trolltech.com/qt-interest/2006-10/msg00829.html //OutputDebugStringA("\n\ntest output\n\n\n"); // !!! this only works if actually debugging (started with F5); #endif po::options_description genericDesc ("General options"); genericDesc.add_options() (s_helpOpt.m_szFullOpt, s_helpOpt.m_szDescr) (s_uninstOpt.m_szFullOpt, s_uninstOpt.m_szDescr) (s_sessionOpt.m_szFullOpt, po::value(), s_sessionOpt.m_szDescr) ; po::options_description cmdlineDesc ("Commandline mode"); cmdlineDesc.add_options() //("input-file", po::value >(), "input file") //("severity,s", po::value()->default_value(Note::WARNING), "minimum severity to show (one of error, warning, support); default: warning") //ttt1 see if this can be made to work; it sort of does, but when invoked with "--help" it prints "arg (=1)" rather than "arg (=warning)" (s_severityOpt.m_szFullOpt, po::value(), s_severityOpt.m_szDescr) //("severity,s", "minimum severity to show (one of error, warning, support") (s_transfListOpt.m_szFullOpt, po::value(), s_transfListOpt.m_szDescr) ; po::options_description folderSessDesc ("New, per-folder, session mode"); folderSessDesc.add_options() (s_hiddenFolderSessOpt.m_szFullOpt, po::value(), s_hiddenFolderSessOpt.m_szDescr) (s_loadedFolderSessOpt.m_szFullOpt, po::value(), s_loadedFolderSessOpt.m_szDescr) (s_tempSessOpt.m_szFullOpt, po::value(), s_tempSessOpt.m_szDescr) ; po::options_description hiddenDesc("Hidden options"); hiddenDesc.add_options() (s_inputFileOpt.m_szFullOpt, po::value >(), s_inputFileOpt.m_szDescr) ; po::positional_options_description positionalDesc; positionalDesc.add(s_inputFileOpt.m_szLongOpt, -1); po::options_description fullDesc; fullDesc.add(genericDesc).add(cmdlineDesc).add(hiddenDesc).add(folderSessDesc); po::options_description visibleDesc; visibleDesc.add(genericDesc).add(cmdlineDesc).add(folderSessDesc); po::variables_map options; bool err (false); try { po::command_line_style::style_t style ((po::command_line_style::style_t)( po::command_line_style::unix_style // | po::command_line_style::case_insensitive // | po::command_line_style::allow_long_disguise #ifdef WIN32 | po::command_line_style::allow_slash_for_short #endif )); //po::store(po::command_line_parser(argc, argv).options(fullDesc).positional(positionalDesc).run(), options); po::store(po::command_line_parser(argc, argv).style(style).options(fullDesc).positional(positionalDesc).run(), options); po::notify(options); } catch (const exception& ex) { cerr << ex.what() << endl; err = true; } catch (...)//const po::unknown_option&) { cerr << "unknown exception" << endl; err = true; } if (err || options.count(s_helpOpt.m_szLongOpt) > 0) //ttt1 options "u" and "s" are incompatible; "s" without a file is wrong as well; these should trigger the "usage" message, then exit as well; { cout << "Usage: " << argv[0] << " [OPTION]... [FILE]...\n"; cout << visibleDesc << endl; return 1; } if (options.count(s_uninstOpt.m_szLongOpt) > 0) { QSettings s (getOrganization(), getSettingsAppName()); s.clear(); //ttt2 see if this can actually remove everything, including the getOrganization() dir if it's empty; ShellIntegration::enableHiddenSession(false); ShellIntegration::enableVisibleSession(false); ShellIntegration::enableTempSession(false); return 0; } if (options.count(s_inputFileOpt.m_szLongOpt) > 0) { Q_INIT_RESOURCE(Mp3Diags); // base name of the ".qrc" file QCoreApplication app (argc, argv); // !!! without this Qt file functions don't work correctly, e.g. QFileInfo has problems with file names that contain accents //TranslatorHandler::getGlobalTranslator(); return cmdlineMain(options); } else { //QTranslator translator; //translator.load("mp3diags_cs"); Q_INIT_RESOURCE(Mp3Diags); // base name of the ".qrc" file QMp3DiagsApplication app (argc, argv); TranslatorHandler::getGlobalTranslator(); /*qDebug("lang %s", TranslatorHandler::getGlobalTranslator().getLanguageInfo("ww/mp3diags_cs.qm").c_str()); qDebug("lang %s", TranslatorHandler::getGlobalTranslator().getLanguageInfo("ww/mp3diags_en_US.qm").c_str()); qDebug("lang %s", TranslatorHandler::getGlobalTranslator().getLanguageInfo("ww/mp3diags_ro.qm").c_str()); qDebug("lang %s", TranslatorHandler::getGlobalTranslator().getLanguageInfo("mp3diags_ro.qm").c_str()); qDebug("lang %s", TranslatorHandler::getGlobalTranslator().getLanguageInfo("mp3diags_ro_RO.qm").c_str()); qDebug("lang %s", TranslatorHandler::getGlobalTranslator().getLanguageInfo("_mp3diags_ro_RO.qm").c_str()); qDebug("lang %s", TranslatorHandler::getGlobalTranslator().getLanguageInfo("/qweqw/_mp3diags_ro_RO.qm").c_str());*/ //app.installTranslator(&translator); return guiMain(options); } } //"undefined reference to `qInitResources_application()'" //ttt2 perhaps sign package /* rpmlint: MP3Diags.x86_64: W: no-changelogname-tag MP3Diags.src: W: no-changelogname-tag There is no %changelog tag in your spec file. To insert it, just insert a '%changelog' in your spec file and rebuild it. MP3Diags.src: W: source-or-patch-not-bzipped MP3Diags-0.99.0.1.tar.gz A source archive or patch in your package is not bzipped (doesn't have the .bz2 extension). Files bigger than 100k should be bzip2'ed in order to save space. To bzip2 a patch, use bzip2. To bzip2 a source tarball, use bznew MP3Diags.x86_64: E: summary-ended-with-dot (Badness: 89) Tool for finding and fixing problems in MP3 files. Includes a tagger. MP3Diags.src: E: summary-ended-with-dot (Badness: 89) Tool for finding and fixing problems in MP3 files. Includes a tagger. Summary ends with a dot. ---------------- ??? rpmlint -v ../RPMS/x86_64/MP3Diags-0.99.0.1-1.x86_64.rpm W: MP3Diags untranslated-desktop-file /usr/share/applications/MP3Diags.desktop W: MP3Diags summary-ended-with-dot Tool for finding and fixing problems in MP3 files. Includes a tagger. I: MP3Diags checking E: MP3Diags tag-not-utf8 Summary E: MP3Diags tag-not-utf8 %description E: MP3Diags no-packager-tag E: MP3Diags no-changelogname-tag E: MP3Diags invalid-desktopfile /usr/share/applications/MP3Diags.desktop value "Audio;AudioVideo;AudioVideoEditing" for string list key "Categories" in group "Desktop Entry" does not have a semicolon (';') as trailing character E: MP3Diags filename-not-utf8 /usr/share/icons/hicolor/48x48/apps/MP3Diags.png E: MP3Diags filename-not-utf8 /usr/share/icons/hicolor/40x40/apps/MP3Diags.png E: MP3Diags filename-not-utf8 /usr/share/icons/hicolor/36x36/apps/MP3Diags.png E: MP3Diags filename-not-utf8 /usr/share/icons/hicolor/32x32/apps/MP3Diags.png E: MP3Diags filename-not-utf8 /usr/share/icons/hicolor/24x24/apps/MP3Diags.png E: MP3Diags filename-not-utf8 /usr/share/icons/hicolor/22x22/apps/MP3Diags.png E: MP3Diags filename-not-utf8 /usr/share/icons/hicolor/16x16/apps/MP3Diags.png E: MP3Diags filename-not-utf8 /usr/share/applications/MP3Diags.desktop E: MP3Diags filename-not-utf8 /usr/bin/MP3Diags local build on 10.3: WARNING: Category "Audio" is unknown ! WARNING: it is ignored, until you registered a Category at adrian@suse.de . */ //ttt1 CLI-support: scan some files, create logs, apply some transforms, ... //ttt1 explorer right-click; create a new session vs. add to existing one //ttt0 perhaps look at building Boost serialization, so it can be statically linked //ttt0 config screenshots need "shell" tab //ttt0 "shell" tab has smaller font in screenshots //ttt0 look at running multiple instances concurrently / exit when second starts //use a .pid file in the session dir with its name based on the session name, so multiple instances programs can start for different sessions; if pid matches and it's mp3diags exit and bring the other program up //ttt0 maybe: thread to write to tmp that crt proc is alive and what session it's using; then start new process / bring existing one on top, as needed //ttt1 Windows changelog should use \n\r (probably by replacing "copy /y changelog.txt bin\\changelog.txt" in BuildMp3Diags.hta with something that reads the file line by line //ttt0 does TIFF cover art work? On Windows? //ttt2 non-utf8 file in /d/test_mp3/1/tmp2/crt_test/martin/dj not showing (reason: ListEnumerator::ListEnumerator calls QDir::entryInfoList(), which simply doesn't include the file, probably because its name is not UTF-8) //ttt0 config restructuring: session data: general settings, gui settings, gui controls, files /* ttt0 move to some readme.txt using translations: MP3 Diags automatically loads translations. Translations are stored in .qm files. Several steps are needed to create them: 1) make MP3 Diags aware of the new translation 2) run lupdate to create a .ts file 3) update the .ts file with the actual translation 4) run lcreate to create a .qm file 1) add an entry in the TRANSLATIONS section of src/src.pro 2) to create the .ts files run: lupdate src/src.pro 3) open Qt Linguist and load the .ts file in src/translations translate, save 4) to create the .qm files run: lrelease src/src.pro After it is done, the .ts file should be added to the project's source control. The regular build (in Install.sh or BuildBz2.sh) also creates the .qm files. Testing: the program looks for .qm files in 2 places: - the executable's folder - folder "../share/mp3diags/translations" (or "../share/mp3diags-unstable/translations") relative to the executable's folder */ //ttt0 make clean doesn't remove the .qm file (or anything in "bin") //ttt0 make OSB builds build translations //ttt0 clear global settings, new sess gets started in home, rather than Documents //ttt0 see about adding DLLs MSVCR100.dll and MSVCP100.dll to the MSVC build. They might be needed e.g. on 64bit XP SP3 (see mail 2013.08.18) MP3Diags-1.2.02/src/FileRenamer.ui0000644000175000001440000002201211700277266015473 0ustar ciobiusers FileRenamerDlg 0 0 1003 579 MP3 Diags - File renamer true 20 0 3 0 40 40 40 40 Qt::NoFocus Previous [Ctrl+P] < :/images/go-previous.svg:/images/go-previous.svg 36 36 Ctrl+P true Qt::NoArrow Folder true 40 40 40 40 Qt::NoFocus Next [Ctrl+N] > :/images/go-next.svg:/images/go-next.svg 36 36 Ctrl+N true Qt::NoArrow If this is checked, a copy of the file is created, and both original and copy are kept Keep the original file Mark unrated as duplicates 40 40 40 40 Qt::NoFocus Edit patterns ... :/images/patterns.svg:/images/patterns.svg 36 36 true 40 40 40 40 Qt::NoFocus Rename ... :/images/rename.svg:/images/rename.svg 36 36 true 40 40 40 40 Qt::NoFocus Close ... :/images/dialog-close.svg:/images/dialog-close.svg 36 36 true 0 alb type 0 Qt::Horizontal 40 20 MP3Diags-1.2.02/license.zlib.txt0000644000175000001440000000207511203001660015262 0ustar ciobiusers zlib.h -- interface of the 'zlib' general purpose compression library version 1.2.3, July 18th, 2005 Copyright (C) 1995-2005 Jean-loup Gailly and Mark Adler This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. Jean-loup Gailly jloup@gzip.org Mark Adler madler@alumni.caltech.edu MP3Diags-1.2.02/Build.sh0000755000175000001440000000123412266762315013557 0ustar ciobiusers#!/bin/bash # # Builds MP3 Diags # # Tested on several systems only # ttt1 Quite likely this needs changes to work with other distros and / or versions # # by passing the param "QMAKE_CXX=clang" the project will be compiled with clang ./AdjustMt.sh $MP3_DIAGS_STATIC QMake=qmake if [ -f /etc/fedora-release ] ; then QMake=qmake-qt4 fi if [[ "$1" != "" ]] ; then $QMake "$1" else $QMake fi if [ $? -ne 0 ] ; then exit 1 ; fi make if [ $? -ne 0 ] ; then exit 1 ; fi ./MakeTranslations.sh cp src/translations/*.qm bin BranchSlash=`cat branch.txt` BranchDash=`echo "$BranchSlash" | sed 's#/#-#'` MP3DiagsExe=MP3Diags$BranchDash strip bin/$MP3DiagsExe MP3Diags-1.2.02/desktop/0000755000175000001440000000000012477147651013636 5ustar ciobiusersMP3Diags-1.2.02/desktop/MP3Diags48.png0000644000175000001440000000135611204552354016057 0ustar ciobiusers‰PNG  IHDR00Wù‡sRGB®ÎébKGDnßArS pHYs  šœtIMEÙ1¾¨ÐÉnIDAThÞí™?hSQÆßmÅÁE¥t¨ì  v®JuœÝ¤Ä)F±8‰ƒ“ISPº4SqppwQ±‹ÿ€ÕEÄÁ¦â`«±JßqH´$¯y¯MCÞoËåž{Ï9ß9çò¾€‡‡‡‡Ç6 $›sÒÀlPp08(è×Î:Tg²XVCkkÀðødðYP1x/X0ç>”‚àK[È‚ NI7³ó@ß_K}4Ž2XDºÙ¬ƒù"±ÈÀà‚ÁYÁ(°»ÃUa %jUðØà‘Á½)Xl =‚sÀÝZi ¶&¹­4½®LÂSeá*p 8ÚþêØÑ€ÓÊVN¬–èÞO¸´¿-(Z¹öŒmy­ñ=‹¼'Œëý9¾-}ÝÔ¿Ä 4»(ÊѸgDÙߨ‘Óöh‡óõ/Z™¢•Öÿós Ž QXŸiÊXbâ–ÍVÓXÃ…“7cÙº¤ÅYë$\šÿ?ÆhÇ&' ë3‰YîHÍ&U½cXzˆÊjÜqÛñv²ÙSßÄ>€v5y}£çç&Ò5F£¦U¾g¼ûˆjò|ïxËñšÚOJßÄÝÀô ÿðÝYU×¼±¡½WÍÁÛTµÙ*²ÐopYpÆ`TâV·]¿  žL—`šk£NÒ.3»(¸ét’ÃÚ(ð¸eðÀ`mª®BZf6ûåÜA0 œ03Ø£=qi 﫳]1x |¼0i³g%ØTWÙRidA’öf‚aª*¶N‡kUÕÿ ~•µŠÙs`xeðZν› ‚e<<<<<<âêsáÜIäÎIEND®B`‚MP3Diags-1.2.02/desktop/MP3Diags32.png0000644000175000001440000000110411204550634016036 0ustar ciobiusers‰PNG  IHDR szzôsRGB®ÎébKGDnßArS pHYs  šœtIMEÙ"4þóeîÄIDATXÃíÖ¿jQðßÙlE!D‘  V¦°°±}ÑÖB,ƒ½›WðL^ ¥XˆXY 6 ‚‚Xh Ó¨ (²äÇbfÃdØY·ÈnöƒË̽sæ~ß=÷ÌÜ &8`D} ÃÙäB°¨¸?,`Çp³eø v°ÄO|Ç7|N6‚OXOÞ¯WЏ½:œÀõä*ƒ3YîQ˜•~öSß%ÛRÈ<Ç“^AÜánp39Ìõ&.¯Ù”©!ÑOg_ð¢Ã&¦Ç°Ýu1¿“­vü^®åX2L¿:VEõy-.j1­`¶ÕOj}â~DMæÝêXk?È—ci· ÊL¿ì´š&ü[Z‡Eq£€ý$­ƒþ¶ÇAR¯‰jVÛ£&ûô#ô•´GEúßak8ègÖ×J›D´*göHÐT”»†¤ÃÜH¦b´I¨ÇëÉýž#:ÛÉ-ÌÓYlOÄ>°b'ØRxgX[áa“'¼’\ .ãtr‡¢ð3eÝLU¶0+m›ÉVÐMºÁW¼Ižâñ*¿šÒ=b"Žgæ%\ Î)ŒêæÉÑ(VÕ-É7ð±ô~o“«¼Ÿxÿ þiü}¨ž>*ÕÉIEND®B`‚MP3Diags-1.2.02/desktop/MP3Diags22.png0000644000175000001440000000066511204554046016051 0ustar ciobiusers‰PNG  IHDRÄ´l;sRGB®ÎébKGDnßArS pHYs  šœtIMEÙ.o\{5IDAT8ËíÕÏjSAÇñÏ\.%ˆ¸)î7·]èúlMòöl^ àº¯ ¸pÝ”B7¶Û.²Å’…hNIn‡›ÌwýÂÀ™3w~¿3s‡îùߤ¼ÓåöðÏñ»xˆ?¸Á0¸Æeâ4øÚç¢îðï‚rêuÓ%Tß߇ø”ºŒ‚k.4˜ ÜîDm ZÍ 4ŠYöcû—xÖ–åLHEóàöIg.Îé¥V%ØK­ªŸç È'µO:Õ@¯C¹¨šzœ3[r½Òz®\µ’E†õí™ÛŠmRn*Ÿ†|5©;9Ø[§ÀU¨Ô71‰,GawUð‡—÷’ü /á¿ý¡áT'ØÃ&H¼â9y ÉíŒ7ˆ)gÁy²« ‘ó ®¢!“Œ¢k&‚ .sp‘scÝK¥=UåC©{´Qk£îÇvU:–z]LV9”ÌßÙ+Œ+*µQµðSL6 ·M÷§ésÐà}ƒÀ~®q8°N9ÂIpŒ}ìbk9¥/xJ‚ûänÆÇŸlã'{R d3ñIEND®B`‚MP3Diags-1.2.02/desktop/MP3Diags.desktop0000644000175000001440000000034412477147651016601 0ustar ciobiusers[Desktop Entry] Type=Application Icon=MP3Diags Exec=MP3Diags #DocPath= Comment=MP3 Diags - diagnosis and repair tool Name=MP3 Diags Categories=Audio;AudioVideo;AudioVideoEditing; Keywords=mp3;tags;headers;audio;track;cover;art; MP3Diags-1.2.02/desktop/MP3Diags36.png0000644000175000001440000000107311204551056016046 0ustar ciobiusers‰PNG  IHDR$$ᘘsRGB®ÎébKGDnßArS pHYs  šœtIMEÙ%c¿vÔ»IDATXÃí×½jQÆñß™ŒF¢‚"‹~@ŠFÒˆ7`ãMäœ\‹¬-­m½€¢ 6·TИ&ˆz,fVg'³“1ff÷ògÏ9ïž}g÷æšk®nªwX ¬D–—1ˆœœÃiœÂIœÀ¤ø†¯Å1{ØÅgìà}ä]`„ad;ðb“µ@‹X‹¬a5°‚å˜Cì§>¤bþ²ØÆK<+Æ“Í\ȸ[‘›kXŒ“qš›‡à 5s¯°…Çx²œöª£haÖD†i f|¡çÞ×`VÝÀFX׿}y®¬òçÓÖk& Hš.£z@]á?Ù[7—%ÌFXÿ9šœ«s¯•CÕMµ…=HI0¿£äoûëHû,6å.ë¨MÁ™}eu0mî´kˆ¾©“>ºÍkÚ·AõæÐ´&ß²_Ù$ö=ƒ[‚וŒKªKˆjHakáFô¡€;[ôUè A–Áq„}„ûx8ò‹±j–!¿º)cɬƒš”q1r½x"¹‚K¸€38ŽcÜ—ä;>ám1†xyz/wf®¹þ/ý‚ާ"ηŒIEND®B`‚MP3Diags-1.2.02/desktop/MP3Diags24.png0000644000175000001440000000072211204554233016043 0ustar ciobiusers‰PNG  IHDRàw=øsRGB®ÎébKGDnßArS pHYs  šœtIMEÙ+§_rRIDATHÇíÕ±J\QàoÖ‚…Á"$`‹ha"öÁ¬w· Xù n*;_ÀР .Ø„±!BÅbï±Y×»×k4f¸03œ3ÿÌÏ™Ã#Qt¼L¼ ¦Á <Ã(žâ r´q†_ø‰#ŠØ—Òn‹ã‚ ‰•àj‰ˆ;V˜ f\¹;øØâk4øŽÉ>ª’¦½¬ÏÉ»ªtx[ôOî!XMëw²/¿¿Å.‘•Sf,mÔ¯ÙE4c±›¸‹]¿«ì–6êÝEû¾Ènª®lWIY®¼jOvŸªªˆË²Ý(Q¿‘õ#Iñô”»‹FÏmð=ÁZg2æ©w€ýOgíÄ,ÔyÌaïãQ5Án›p$6ƒ­Äö'N*÷4kµ±<Ïß³x Œ`¸óœãwâGð Ÿ }iµÛ§Oì?ãät¨Ö,¹ÓIEND®B`‚MP3Diags-1.2.02/desktop/MP3Diags40.png0000644000175000001440000000117611204551310016036 0ustar ciobiusers‰PNG  IHDR((Œþ¸msRGB®ÎébKGDnßArS pHYs  šœtIMEÙ'4ƒ„‘«þIDATXÃí˜ÍjÔ`…ŸóÍXi ZZ\¸*V ¢àMHÅ2ˆÛi{ ^‚iŠ î\¹õt%ˆEKÛUa êP°$¯‹d:ét2“‰“ÉTr äïËÉÛ¼IN T©Rg[J3h­R™õ}ÿ†à°hpM0Ô ® Œpš‰<²peø ìÛÀŽÁœûºÍL€ ¸ÜÜ– ®*"Ðñ,»ÚMIoÍìà³û‰€ 8Ü^ –º¼r“©Síí:xïÁ€!ÀsÁ#à¸Àz±(ªì¡àÝxP´€Z5Ô½9ê~Pçä5ƒ{SÐr\QPƒšcÊMúc¦š´ã™½`UõTëñmqÅ÷'ë5¦­Tì6ìò/ÇöósyÀ­ª~<õ«l¿Ê UÁn£4ƃàÓÊ.‹Înç©aº¸Z4@á—¸\š.ϵ‚£l¤‰oWDƒ ó&ªU™´c¯`RÓ$~v­œüP-ïÁ,€íð2I`LËyp Xüˆ'­€Ì:Ë»‚§L«+Ï 6 î fb)+Àb±pð øÀã ø>(¸WéëÀƒ%ÎÝ£ ü&ç^Z¼xàgýõqÞ÷ý9Á`ÁÂ`¿ ˜5¸ ø‘Ñåè} ½%ØšÀà°ì™sÍõ 8,ÿ>•*õ¿ë/€³3iH"ïIEND®B`‚MP3Diags-1.2.02/package/0000755000175000001440000000000012477147652013561 5ustar ciobiusersMP3Diags-1.2.02/package/rpm/0000755000175000001440000000000012477147652014357 5ustar ciobiusersMP3Diags-1.2.02/package/rpm/MP3Diags.spec0000644000175000001440000005323712477147651016613 0ustar ciobiusersSummary: Tool for finding and fixing problems in MP3 files; includes a tagger %define version 1.2.02 %define branch %{nil} %define pkgName MP3Diags # pkgName should be mp3diags, MP3Diags, or whatever else # !!! note that you can't simply comment a "define", as macros get expanded inside comments %define srcBaseName MP3Diags%{branch} # ttt1 perhaps have a binName and a dskName and use some file renaming and sed to control the name of the binary, desktop file, and icons (probably the same as the desktop file) %define translName mp3diags%{branch} Name: %{pkgName}%{branch} Version: %{version} Release: 1 #Conflicts: MP3Diags >= 0.8.0.0 #Provides: MP3Diags Group: Applications/Multimedia Source: %{srcBaseName}-%{version}.tar.gz URL: http://mp3diags.sourceforge.net/ License: http://www.gnu.org/licenses/gpl-2.0.html BuildRoot: %{_tmppath}/%{name}-%{version}-build Packager: Ciobi %if 0%{?suse_version} > 0000 Requires: libqt4-x11 BuildRequires: zlib-devel boost-devel libqt4-devel BuildRequires: update-desktop-files %endif %if 0%{?fedora} || 0%{?fedora_version} Requires: qt-x11 BuildRequires: qt-devel zlib-devel boost-devel boost-devel-static gcc-c++ %endif # this breaks the build for mandriva 2009.1: parseExpressionBoolean returns -1 %if 0%{?mandriva_version} >= 2009 #%if 0%{?mdkversion} >= 200900 BuildRequires: kdelibs4-devel BuildRequires: boost-devel boost-static-devel BuildRequires: zlib-devel Requires: qt4-common %endif # related but probably something else: https://bugzilla.novell.com/show_bug.cgi?id=459337 or https://bugzilla.redhat.com/show_bug.cgi?id=456103 %description Finds problems in MP3 files and helps the user to fix many of them. Looks at both the audio part (VBR info, quality, normalization) and the tags containing track information (ID3.) Has a tag editor, which can download album information (including cover art) from MusicBrainz and Discogs, as well as paste data from the clipboard. Track information can also be extracted from a file's name. Another component is the file renamer, which can rename files based on the fields in their ID3V2 tag (artist, track number, album, genre, ...) %prep %setup -q -n %{srcBaseName}-%{version} %build ./AdjustMt.sh STATIC_SER %if 0%{?suse_version} qmake lrelease src/src.pro %endif %if 0%{?mandriva_version} >= 2009 qmake lrelease src/src.pro %endif %if 0%{?fedora} || 0%{?fedora_version} qmake-qt4 lrelease-qt4 src/src.pro %endif make strip $RPM_BUILD_DIR/%{srcBaseName}-%{version}/bin/%{srcBaseName} %install # ttt1 perhaps look at http://doc.trolltech.com/4.3/qmake-variable-reference.html#installs and use INSTALLS += ... echo BUILD ROOT - %{buildroot}%{_bindir} mkdir -p %{buildroot}%{_bindir} ; install -p -m755 bin/%{srcBaseName} %{buildroot}%{_bindir} #mkdir -p %{buildroot}%{_datadir}/applications ; desktop-file-install --dir %{buildroot}%{_datadir}/applications desktop/%{srcBaseName}.desktop mkdir -p %{buildroot}%{_datadir}/applications ; install -p -m644 desktop/%{srcBaseName}.desktop %{buildroot}%{_datadir}/applications/%{srcBaseName}.desktop mkdir -p %{buildroot}%{_datadir}/icons/hicolor/16x16/apps ; install -p -m644 desktop/%{srcBaseName}16.png %{buildroot}%{_datadir}/icons/hicolor/16x16/apps/%{srcBaseName}.png mkdir -p %{buildroot}%{_datadir}/icons/hicolor/22x22/apps ; install -p -m644 desktop/%{srcBaseName}22.png %{buildroot}%{_datadir}/icons/hicolor/22x22/apps/%{srcBaseName}.png mkdir -p %{buildroot}%{_datadir}/icons/hicolor/24x24/apps ; install -p -m644 desktop/%{srcBaseName}24.png %{buildroot}%{_datadir}/icons/hicolor/24x24/apps/%{srcBaseName}.png mkdir -p %{buildroot}%{_datadir}/icons/hicolor/32x32/apps ; install -p -m644 desktop/%{srcBaseName}32.png %{buildroot}%{_datadir}/icons/hicolor/32x32/apps/%{srcBaseName}.png mkdir -p %{buildroot}%{_datadir}/icons/hicolor/36x36/apps ; install -p -m644 desktop/%{srcBaseName}36.png %{buildroot}%{_datadir}/icons/hicolor/36x36/apps/%{srcBaseName}.png mkdir -p %{buildroot}%{_datadir}/icons/hicolor/40x40/apps ; install -p -m644 desktop/%{srcBaseName}40.png %{buildroot}%{_datadir}/icons/hicolor/40x40/apps/%{srcBaseName}.png mkdir -p %{buildroot}%{_datadir}/icons/hicolor/48x48/apps ; install -p -m644 desktop/%{srcBaseName}48.png %{buildroot}%{_datadir}/icons/hicolor/48x48/apps/%{srcBaseName}.png mkdir -p %{buildroot}/usr/share/%{translName}/translations ; install -p -m644 src/translations/*.qm %{buildroot}/usr/share/%{translName}/translations %if 0%{?suse_version} > 0000 %suse_update_desktop_file -n %{srcBaseName} #echo ================ SUSE ================ SUSE ================ %endif #error with suse_update_desktop_file -in MP3Diags , perhaps try suse_update_desktop_file -n -i MP3Diags %clean rm -rf %{buildroot} %files %defattr(-,root,root) %dir %{_datadir}/icons/hicolor %dir %{_datadir}/icons/hicolor/* %dir %{_datadir}/icons/hicolor/*/* %dir /usr/share/%{translName} %dir /usr/share/%{translName}/translations %{_bindir}/%{srcBaseName} %{_datadir}/applications/%{srcBaseName}.desktop %{_datadir}/icons/hicolor/*/apps/%{srcBaseName}.png /usr/share/%{translName}/translations/*.qm #?datadir (=/usr/share) #/usr/share/applications %changelog * Sun Mar 08 2015 Marian Ciobanu 1.2.02 - integrated changes from 1.3.01: - fixed incorrect message occurring some times when start after a crash - better logging and retries for write errors - added offset to the output created via the command line - fixed crash caused by saving very small images - build fix - disabled Discogs integration * Fri Jan 17 2014 Marian Ciobanu 1.2.01 - 1.2 as the new stable branch - build fix for clang and for Solaris - fixed broken links in documentation and moved alternate downloads from Dropbox to my ISP * Sun Mar 31 2013 Marian Ciobanu 1.2.00 - new version number to support the new "stable" branch * Sat Dec 01 2012 Marian Ciobanu 1.1.21.080 - fixed bug that caused a "%s" to be shown instead of the list of files about to be changed * Sat Oct 20 2012 Marian Ciobanu 1.1.20.077 - Discogs fix * Sun Apr 29 2012 Marian Ciobanu 1.1.19.075 - French translation * Sat Apr 06 2012 Marian Ciobanu 1.1.18.074 - German translation - made code compilable on GCC 4.7 - several translation fixes * Sat Mar 17 2012 Marian Ciobanu 1.1.17.073 - fixed crash introduced in 1.1.16 with translation changes * Sun Mar 11 2012 Marian Ciobanu 1.1.16.072 - made program translatable - added Czech translation * Wed Feb 02 2012 Marian Ciobanu 1.1.12.068 - external tools - possibility of applying transformation lists in CLI mode - "Open containing folder" in the main window - delete files from the main window - close buttons for Gnome 3 - APE remover - non-audio remover - CLI analyzer uses session settings to determine quality thresholds - fixed menu tooltips that disappeared too quickly * Sat Dec 03 2011 Marian Ciobanu 1.1.09.064 - moved to Discogs API V2 - added codepage support when copying ID3V1 to ID3V2 - fixed crash caused by ID3V2.4 tag with invalid flags - fixed splitting of unsupported ID3V2 tags - fixed Xing generation for small bitrates * Sun Aug 28 2011 Marian Ciobanu 1.1.08.062 - made Discogs queries after a Discogs API change - fixed track numbering when querying multi-volume albums from Discogs - made Linux shell integration accept file names containing spaces - added close button for dialogs on Gnome 3 * Sat Jul 30 2011 Marian Ciobanu 1.1.07.061 - shell integration for Linux - replaced MP3Diags with MP3Diags-unstable in places where this wasn't done - restructured the .spec file - added close button on Gnome 3 * Fri Jul 22 2011 Marian Ciobanu 1.1.06.059 - shell integration fixes * Thu Jul 21 2011 Marian Ciobanu 1.1.05.058 - shell integration (Windows only) * Sun Jul 17 2011 Marian Ciobanu 1.1.04.057 - folder-based sessions based on command-line parameters * Wed Jun 15 2011 Marian Ciobanu 1.1.03.056 - integrated command-line mode by Michael Elsdörfer * Mon May 30 2011 Marian Ciobanu 1.1.01.054 - created unstable branch * Sat May 28 2011 Marian Ciobanu 1.0.08.053 - fixed crash when trying to save after error at startup - added branch support to build process * Wed Dec 29 2010 Marian Ciobanu 1.0.07.052 - fixed crash in folder filter dialog - made project compilable on Fedora 13 and 14 * Sun Jul 04 2010 Marian Ciobanu 1.0.06.051 - fixed crash caused by invalid Lyrics tag - fixed assertion failure when opening the folder filter in Windows * Wed Mar 17 2010 Marian Ciobanu 1.0.05.050 - fixed issue #23 (assertion failure) * Sun Mar 07 2010 Marian Ciobanu 1.0.04.049 - fixed crash triggered by invalid frame lengths in ID3V2 - fixed crash that occured when the backup directory couldn't be created * Fri Jan 29 2010 Marian Ciobanu 1.0.03.048 - fixed crash triggered by GEOB frames containing UTF-16 text * Tue Jan 12 2010 Marian Ciobanu 1.0.02.047 - fixed crash triggered by trying to repair broken ID3V2 tags that contain images - fixed crash that occurred when changing file names manually in the file renamer, without defining a pattern - fixed crash caused by unsynchronized ID3V2.4.0 frames that end with 0xff * Fri Dec 11 2009 Marian Ciobanu 1.0.01.046 - fixed crash that occurred when running on Qt 4.6 - fixed crash that occurred when changing song info in the tag editor if songs had images stored as BMP or anything else besides JPG or PNG * Sun Nov 29 2009 Marian Ciobanu 1.0.00.045 - wording changes to reflect non-beta status - pressing CTRL+C when viewing full-size images in the tag editor or in "Tag details" in the main window copies the image to the clipboard - added "Rating" and changed field order in "Tag details" to match the Tag editor * Wed Nov 04 2009 Marian Ciobanu 0.99.06.044 - fixed a crash in folder filter - fixed bug causing non-normalized files having any TXXX frame to appear normalized - case is ignored for file extension, so .Mp3 or .mP3 files are recognized - better support and more consistent handling for TXXX and text frames in ID3V2 - reduced number of locales by eliminating redundant ones - disabled CTRL+A selection in the main window - static link for serialization - added trace details for web downloads * Tue Oct 27 2009 Marian Ciobanu 0.99.06.043 - "Simple view" in file configuration - fixed crash on empty text frames in ID3V2 - generic binaries for Linux - BuildMp3Diags.hta detects VS version - documentation updates * Tue Oct 20 2009 Marian Ciobanu 0.99.06.042 - better support for binary frames in ID2V2.4.0 - fixed bug resulting in crash when files were modified in external tools - fixed bug resulting in crash when renaming files if a filter is applied - fixed bug resulting in crash when going to "Tag details" for files using Unicode in USLT (issue 40) - added option to include styles in Discogs info - added case-change option to the tag editor - better detection and notification for changed files before applying transformations or saving from the tag editor - file renamer allows file names to be changed manually - file renamer can use "duplicate" label for unrated songs - images shown for Lyrics tags - improved case-change transformation - removing images from the tag editor now works even for non-cover images * Fri Oct 09 2009 Marian Ciobanu 0.99.06.041 - brought documentation up to date - added test for ReplayGain info stored inside Id3V2 - failing to read text frames from files no longer causes crashes - fixed bug resulting in crash when 2 ID3V2 tags are present and "Discard invalid ID3V2 data" gets called - XML export now works when names contain double quotes - locale in export dialog - locale lists are now sorted - long text frames are now truncated when shown in the "File info" area - changed names and order for tabs under Config/Files - made transformation options work correctly in MSVC * Wed Sep 30 2009 Marian Ciobanu 0.99.06.040 - "Various Artists" support - all pictures are shown and can be viewed in full size in "Tag details" - all pictures from a file are shown in the tag editor - patterns in the tag editor may now be disabled - export as M3U or XML - better handling of text frames containing null characters - auto-size for the tag editor's "current file" area - better column widths in the tag editor - improved HTA for Windows build * Wed Sep 23 2009 Marian Ciobanu 0.99.05.038 - fixed bug introduced in 0.99.05.037 causing crashes when finding empty ID3V2 frames - new build process for Windows - 4th custom transf list now defaults to a "fix-all" approach * Thu Sep 17 2009 Marian Ciobanu 0.99.05.037 - fixes on right-click - UTF-8 strings recognized in ID3V2.3.0 - fixed bug 35 (assertion failure) - faster tracer - code compilable with VS 2008 (port by Sebastian Schuberth) * Mon Sep 07 2009 Marian Ciobanu 0.99.05.034 - drive labels shown in Windows - mp3gain can be started now if it's in a directory containing spaces - configurable invalid characters and replacement for file renamer - automatic check for new versions - text inside square, curly, and angle brackets removed from web queries - improved tracing code - no longer rescan the files if exiting tag editor without changes - replace non-alphanumeric chars with spaces in web queries (issue 2) - better sorting in the tag editor for albums with unusual track numbers - a default .ini name is generated in most cases - (probably) fixed an assert (not sure because couldn't reproduce it) - tracks without a track number are put at the end in the tag editor - let the user know about reporting support notes and about patterns - better detection of exceptions in threads - exceptions that propagate from slots are now caught - default "actions to be taken" no longer shown when applying transforms - file info for StreamWriter * Wed Sep 02 2009 Marian Ciobanu 0.99.05.033 - fixed crash in Windows when checking a whole drive - fixed crash when saving data from the tag editor - improved trace speed on Windows - fixed potential crash at startup - fixed crash when changing a file that is being used by other program - more details and better formatting in assert messages and trace files - MPEG2 Layer3 streams no longer show Support note - improved temporary file generation, which can result in faster transforms - F1 help now works for the first session dialog - smaller TABs in the "Tag details" area make "Other info" more readable - better HTML paragraph formatting * Sun Aug 23 2009 Marian Ciobanu 0.99.05.032 - restructured crash detector - fixed Windows issue with rectangles being shown instead of letters - Lyrics partial support - warning that may corrupt data - note about how to change selected files - HTML clean up * Wed Aug 19 2009 Marian Ciobanu 0.99.05.031 - crash detection - fixed small memory leak in config dialog - fixed small memory leak in the tag editor - content is shown for GEOB frames - .ID3 files are now loaded in addition to .MP3 - made the counter shown when applying transforms increment on new file(until now it was incremented for each transform) - "Various artists" no longer set as "artist" when downloading track info from MusicBrainz - fixed "current cell" in the tag editor (until now, when dragging the mouse to select several cells, the current cell was wrong, leading to setting values incorrectly - fixed a bug that didn't allow removal of the track number - slightly improved the normalizer, so a "busy" cursor is shown when the connection to the underlying process is lost (the program seems frozen, but it resumes after about 30 seconds) * Tue Jul 28 2009 Marian Ciobanu 0.99.05.030 - fixed a bug that caused the tag editor to reserve more space than needed even if the "fast save" option was turned off - fixed a bug that prevented removal of elements from lists - fixed a bug in the ID3V2 tag writer that prevented "Discard invalid ID3V2 data" and other transformations to properly work with ID3V2.4.0 tags that contain UTF8-encoded strings, resulting in a broken ID3V2.3.0 tag - file renamer now accepts patterns with no directory separators, in which case the renamed files are placed in the source directory - pattern dialogs now show the current line and column - fixed an assertion in the code that determines the file list - added tooltips for all the notes in the main file table - improved speed for "Discard invalid ID3V2 data" when no changes are done - transformation name included in the dialog that shows which file is currently processed - broken ID3V2 streams get removed when saving from the tag editor - the tag editor no longer triggers an assertion failure if non-default settings in the file section of the configuration dialog; (e.g. until now saving from the tag editor while original files weren't deleted resulted in this assertion failure) - fixed a bug in the "Change case for ID3V2 text frames" that resulted in a program crash if some fields were missing from the ID3V2 tag * Sun Jul 26 2009 Marian Ciobanu 0.99.05.029 - fast save in the tag editor - file renamer can work with the list of visible files instead of the current album if the user holds CTRL down when pressing the button - configurable visible transformations - new transform for keeping a single image, as front cover - new transform for removing ID3V1 - button to remove image files - improved paste in tag editor (it is possible to paste to multiple cells or to paste file names copied from file browsers) - configurable max image size - tooltips for the transformations menu - more checks + fixed error reporting in file renamer - "sessions" button visible by default for second and later sessions - directory filter no longer shows some directories that don't make sense - fixed loading images from current dir - fixed a bug in tag editor patterns that prevented patterns ending with a static text from working - fixed some bugs in the directory filter * Sat Jul 18 2009 Marian Ciobanu 0.99.04.026 - non-ASCII file names can now be seen on Windows - tag editor now looks at filter - USLT Lyrics inside ID3V2 are now shown - better alignment for text in note column header on Ubuntu - size grip on most dialogs - F1 help - files changed in the tag editor no longer show up in the main window if a filter is applied and they don't match the filter * Fri Jul 10 2009 Marian Ciobanu 0.99.03.022 - 2-letter labels - gradient grouping of notes - configurable colors - app no longer crashes when files are changed by external tools - tooltips are shown for the column headers in the file table - more consistent font handling * Sun Jul 05 2009 Marian Ciobanu 0.99.02.020 - file renamer now replaces invalid characters in file names - duration is now shown for audio streams - fixed assertion caused by files with too many streams - multiple ID3 stream remover no longer included by default in second list - some changes to the .spec file in the hope it will work on Mandriva * Sun Jul 05 2009 Marian Ciobanu 0.99.02.020 - file renamer now replaces invalid characters in file names - duration is now shown for audio streams - fixed assertion caused by files with too many streams - multiple ID3 stream remover no longer included by default in second list - some changes to the .spec file in the hope it will work on Mandriva * Wed Jun 24 2009 Marian Ciobanu 0.99.02.018 - made -mt suffix default for Boost Serialization * Wed Jun 24 2009 Marian Ciobanu 0.99.02.017 - always use multithreaded libraries (single-threaded ones may lead to crashes) - improved assert dialog; now it has more data, which can be copied and even emailed directly - the tag editor shows a warning in some cases when a user action would result in discarded images * Sun Jun 21 2009 Marian Ciobanu 0.99.02.016 - fixed an assertion failure that was triggered by an unsupported text encoding in APIC - added support for UTF8 text encoding in APIC - made the documentation look slightly better on IE6 * Sat Jun 20 2009 Marian Ciobanu 0.99.02.015 - made compilable on Fedora 11 and added Fedora 11 build - copying missing ID3V1 fields to ID3V2 no longer part of the default custom transformation list 2 * Thu Jun 18 2009 Marian Ciobanu 0.99.02.014 - the tag editor loads albums much faster than before; this is most visible when navigating to the next / previous album * Sun Jun 14 2009 Marian Ciobanu 0.99.02.012 - added support for UTF8 in ID3V240 - added maximize button to most windows in Windows (but could not get this to work with Gnome) - removed "What's this" button from most windows - now the main window shows the session name if more than 1 session was defined - now the main window shows up maximized in Gnome and Windows if it was maximized when it was closed - changed documentation links to point to new site, at SourceForge - minor documentation improvements * Sat Jun 06 2009 Marian Ciobanu 0.99.02.011 - added missing dependency for SVG icons - fixed a bug in "Save as ..." * Thu Jun 04 2009 Marian Ciobanu 0.99.02.010 - fixed a bug that could cause removal of audio data - fixed a bug that prevented single-image-saving from working * Wed Jun 03 2009 Marian Ciobanu 0.99.02.009 - Ubuntu binaries - minor UI tweaks * Mon May 25 2009 Marian Ciobanu 0.99.02.008 - improved font handling - minor UI tweaks * Tue May 19 2009 Marian Ciobanu 0.99.02.007 - initial version MP3Diags-1.2.02/package/rpm/MP3Diags-Mandriva_2009.1.spec0000644000175000001440000005700112477147651021154 0ustar ciobiusersSummary: Tool for finding and fixing problems in MP3 files; includes a tagger %define version 1.2.02 %define branch %{nil} License: http://www.gnu.org/licenses/gpl-2.0.html Group: Applications/Multimedia %define pkgName MP3Diags %define translName mp3diags%{branch} Name: MP3Diags%{branch} #Prefix: /usr #Provides: MP3Diags Release: 1 Source: MP3Diags%{branch}-%{version}.tar.gz URL: http://mp3diags.sourceforge.net/ Version: %{version} BuildRoot: %{_tmppath}/%{name}-%{version}-build Packager: Ciobi #BuildRequires: libqt4-devel #BuildRequires: boost-devel # ??? ttt0 # this breaks the build for mandriva 2009.1: parseExpressionBoolean returns -1 #%if 0%{?mandriva_version} >= 2009 #%if 0%{?mdkversion} >= 200900 BuildRequires: kdelibs4-devel #BuildRequires: libboost1.37.0-devel #BuildRequires: libboost-devel libboost-static-devel-1.38.0 BuildRequires: boost-devel boost-static-devel BuildRequires: zlib-devel Requires: qt4-common #%endif # related but probably something else: https://bugzilla.novell.com/show_bug.cgi?id=459337 or https://bugzilla.redhat.com/show_bug.cgi?id=456103 %description Finds problems in MP3 files and helps the user to fix many of them. Looks at both the audio part (VBR info, quality, normalization) and the tags containing track information (ID3.) Has a tag editor, which can download album information (including cover art) from MusicBrainz and Discogs, as well as paste data from the clipboard. Track information can also be extracted from a file's name. Another component is the file renamer, which can rename files based on the fields in their ID3V2 tag (artist, track number, album, genre, ...) %prep %setup -q %build ./AdjustMt.sh STATIC_SER #%if 0%{?mandriva_version} > 2006 #export PATH=/usr/lib/qt4/bin:$PATH #export QTDIR=%{_prefix}/lib/qt4/ #%endif #%if 0%{?mandriva_version} >= 2009 #export PATH=/usr/lib/qt4/bin:$PATH #export QTDIR=%{_prefix}/lib/qt4/ #ls /usr/lib/qt4/bin #/usr/lib/qt4/bin/qmake qmake #%endif lrelease src/src.pro make strip $RPM_BUILD_DIR/MP3Diags%{branch}-%{version}/bin/MP3Diags%{branch} %install # ttt1 perhaps look at http://doc.trolltech.com/4.3/qmake-variable-reference.html#installs and use INSTALLS += ... echo mkdir $RPM_BUILD_ROOT%{_bindir} mkdir -p $RPM_BUILD_ROOT%{_bindir} cp $RPM_BUILD_DIR/MP3Diags%{branch}-%{version}/bin/MP3Diags%{branch} $RPM_BUILD_ROOT%{_bindir} mkdir -p $RPM_BUILD_ROOT%{_datadir}/applications cp $RPM_BUILD_DIR/MP3Diags%{branch}-%{version}/desktop/MP3Diags%{branch}.desktop $RPM_BUILD_ROOT%{_datadir}/applications mkdir -p $RPM_BUILD_ROOT%{_datadir}/icons/hicolor/16x16/apps cp $RPM_BUILD_DIR/MP3Diags%{branch}-%{version}/desktop/MP3Diags16%{branch}.png $RPM_BUILD_ROOT%{_datadir}/icons/hicolor/16x16/apps/MP3Diags%{branch}.png mkdir -p $RPM_BUILD_ROOT%{_datadir}/icons/hicolor/22x22/apps cp $RPM_BUILD_DIR/MP3Diags%{branch}-%{version}/desktop/MP3Diags22%{branch}.png $RPM_BUILD_ROOT%{_datadir}/icons/hicolor/22x22/apps/MP3Diags%{branch}.png mkdir -p $RPM_BUILD_ROOT%{_datadir}/icons/hicolor/24x24/apps cp $RPM_BUILD_DIR/MP3Diags%{branch}-%{version}/desktop/MP3Diags24%{branch}.png $RPM_BUILD_ROOT%{_datadir}/icons/hicolor/24x24/apps/MP3Diags%{branch}.png mkdir -p $RPM_BUILD_ROOT%{_datadir}/icons/hicolor/32x32/apps cp $RPM_BUILD_DIR/MP3Diags%{branch}-%{version}/desktop/MP3Diags32%{branch}.png $RPM_BUILD_ROOT%{_datadir}/icons/hicolor/32x32/apps/MP3Diags%{branch}.png mkdir -p $RPM_BUILD_ROOT%{_datadir}/icons/hicolor/36x36/apps cp $RPM_BUILD_DIR/MP3Diags%{branch}-%{version}/desktop/MP3Diags36%{branch}.png $RPM_BUILD_ROOT%{_datadir}/icons/hicolor/36x36/apps/MP3Diags%{branch}.png mkdir -p $RPM_BUILD_ROOT%{_datadir}/icons/hicolor/40x40/apps cp $RPM_BUILD_DIR/MP3Diags%{branch}-%{version}/desktop/MP3Diags40%{branch}.png $RPM_BUILD_ROOT%{_datadir}/icons/hicolor/40x40/apps/MP3Diags%{branch}.png mkdir -p $RPM_BUILD_ROOT%{_datadir}/icons/hicolor/48x48/apps cp $RPM_BUILD_DIR/MP3Diags%{branch}-%{version}/desktop/MP3Diags48%{branch}.png $RPM_BUILD_ROOT%{_datadir}/icons/hicolor/48x48/apps/MP3Diags%{branch}.png mkdir -p %{buildroot}/usr/share/%{translName}/translations ; install -p -m644 src/translations/*.qm %{buildroot}/usr/share/%{translName}/translations #mkdir -p $RPM_BUILD_ROOT%{_bindir} #cp $RPM_BUILD_DIR/MP3Diags-%{version}/bin/MP3Diags $RPM_BUILD_ROOT%{_bindir} #mkdir -p $RPM_BUILD_ROOT%{_datadir}/applications #cp $RPM_BUILD_DIR/MP3Diags-%{version}/desktop/MP3Diags.desktop $RPM_BUILD_ROOT%{_datadir}/applications #%dir $RPM_BUILD_ROOT%{_datadir}/icons/hicolor/16x16/apps #cp $RPM_BUILD_DIR/MP3Diags-%{version}/desktop/MP3Diags16.png $RPM_BUILD_ROOT%{_datadir}/icons/hicolor/16x16/apps/MP3Diags.png #%dir $RPM_BUILD_ROOT%{_datadir}/icons/hicolor/22x22/apps #cp $RPM_BUILD_DIR/MP3Diags-%{version}/desktop/MP3Diags22.png $RPM_BUILD_ROOT%{_datadir}/icons/hicolor/22x22/apps/MP3Diags.png #%dir $RPM_BUILD_ROOT%{_datadir}/icons/hicolor/24x24/apps #cp $RPM_BUILD_DIR/MP3Diags-%{version}/desktop/MP3Diags24.png $RPM_BUILD_ROOT%{_datadir}/icons/hicolor/24x24/apps/MP3Diags.png #%dir $RPM_BUILD_ROOT%{_datadir}/icons/hicolor/32x32/apps #cp $RPM_BUILD_DIR/MP3Diags-%{version}/desktop/MP3Diags32.png $RPM_BUILD_ROOT%{_datadir}/icons/hicolor/32x32/apps/MP3Diags.png #%dir $RPM_BUILD_ROOT%{_datadir}/icons/hicolor/36x36/apps #cp $RPM_BUILD_DIR/MP3Diags-%{version}/desktop/MP3Diags36.png $RPM_BUILD_ROOT%{_datadir}/icons/hicolor/36x36/apps/MP3Diags.png #%dir $RPM_BUILD_ROOT%{_datadir}/icons/hicolor/40x40/apps #cp $RPM_BUILD_DIR/MP3Diags-%{version}/desktop/MP3Diags40.png $RPM_BUILD_ROOT%{_datadir}/icons/hicolor/40x40/apps/MP3Diags.png #%dir $RPM_BUILD_ROOT%{_datadir}/icons/hicolor/48x48/apps #cp $RPM_BUILD_DIR/MP3Diags-%{version}/desktop/MP3Diags48.png $RPM_BUILD_ROOT%{_datadir}/icons/hicolor/48x48/apps/MP3Diags.png %clean rm -rf $RPM_BUILD_ROOT %files %defattr(-,root,root) %dir %{_datadir}/icons/hicolor %dir %{_datadir}/icons/hicolor/16x16 %dir %{_datadir}/icons/hicolor/16x16/apps %dir %{_datadir}/icons/hicolor/22x22 %dir %{_datadir}/icons/hicolor/22x22/apps %dir %{_datadir}/icons/hicolor/24x24 %dir %{_datadir}/icons/hicolor/24x24/apps %dir %{_datadir}/icons/hicolor/32x32 %dir %{_datadir}/icons/hicolor/32x32/apps %dir %{_datadir}/icons/hicolor/36x36 %dir %{_datadir}/icons/hicolor/36x36/apps %dir %{_datadir}/icons/hicolor/40x40 %dir %{_datadir}/icons/hicolor/40x40/apps %dir %{_datadir}/icons/hicolor/48x48 %dir %{_datadir}/icons/hicolor/48x48/apps %dir /usr/share/%{translName}/translations %{_bindir}/MP3Diags%{branch} %{_datadir}/applications/MP3Diags%{branch}.desktop %{_datadir}/icons/hicolor/16x16/apps/MP3Diags%{branch}.png %{_datadir}/icons/hicolor/22x22/apps/MP3Diags%{branch}.png %{_datadir}/icons/hicolor/24x24/apps/MP3Diags%{branch}.png %{_datadir}/icons/hicolor/32x32/apps/MP3Diags%{branch}.png %{_datadir}/icons/hicolor/36x36/apps/MP3Diags%{branch}.png %{_datadir}/icons/hicolor/40x40/apps/MP3Diags%{branch}.png %{_datadir}/icons/hicolor/48x48/apps/MP3Diags%{branch}.png /usr/share/%{translName}/translations/*.qm #?datadir (=/usr/share) #/usr/share/applications %changelog * Sun Mar 08 2015 Marian Ciobanu 1.2.02 - integrated changes from 1.3.01: - fixed incorrect message occurring some times when start after a crash - better logging and retries for write errors - added offset to the output created via the command line - fixed crash caused by saving very small images - build fix - disabled Discogs integration * Fri Jan 17 2014 Marian Ciobanu 1.2.01 - 1.2 as the new stable branch - build fix for clang and for Solaris - fixed broken links in documentation and moved alternate downloads from Dropbox to my ISP * Sun Mar 31 2013 Marian Ciobanu 1.2.00 - new version number to support the new "stable" branch * Sat Dec 01 2012 Marian Ciobanu 1.1.21.080 - fixed bug that caused a "%s" to be shown instead of the list of files about to be changed * Sat Oct 20 2012 Marian Ciobanu 1.1.20.077 - Discogs fix * Sun Apr 29 2012 Marian Ciobanu 1.1.19.075 - French translation * Sat Apr 06 2012 Marian Ciobanu 1.1.18.074 - German translation - made code compilable on GCC 4.7 - several translation fixes * Sat Mar 17 2012 Marian Ciobanu 1.1.17.073 - fixed crash introduced in 1.1.16 with translation changes * Sun Mar 11 2012 Marian Ciobanu 1.1.16.072 - made program translatable - added Czech translation * Wed Feb 02 2012 Marian Ciobanu 1.1.12.068 - external tools - possibility of applying transformation lists in CLI mode - "Open containing folder" in the main window - delete files from the main window - close buttons for Gnome 3 - APE remover - non-audio remover - CLI analyzer uses session settings to determine quality thresholds - fixed menu tooltips that disappeared too quickly * Sat Dec 03 2011 Marian Ciobanu 1.1.09.064 - moved to Discogs API V2 - added codepage support when copying ID3V1 to ID3V2 - fixed crash caused by ID3V2.4 tag with invalid flags - fixed splitting of unsupported ID3V2 tags - fixed Xing generation for small bitrates * Sun Aug 28 2011 Marian Ciobanu 1.1.08.062 - made Discogs queries after a Discogs API change - fixed track numbering when querying multi-volume albums from Discogs - made Linux shell integration accept file names containing spaces - added close button for dialogs on Gnome 3 * Sat Jul 30 2011 Marian Ciobanu 1.1.07.061 - shell integration for Linux - replaced MP3Diags with MP3Diags-unstable in places where this wasn't done - restructured the .spec file - added close button on Gnome 3 * Fri Jul 22 2011 Marian Ciobanu 1.1.06.059 - shell integration fixes * Thu Jul 21 2011 Marian Ciobanu 1.1.05.058 - shell integration (Windows only) * Sun Jul 17 2011 Marian Ciobanu 1.1.04.057 - folder-based sessions based on command-line parameters * Wed Jun 15 2011 Marian Ciobanu 1.1.03.056 - integrated command-line mode by Michael Elsdörfer * Mon May 30 2011 Marian Ciobanu 1.1.01.054 - created unstable branch * Sat May 28 2011 Marian Ciobanu 1.0.08.053 - fixed crash when trying to save after error at startup - added branch support to build process * Wed Dec 29 2010 Marian Ciobanu 1.0.07.052 - fixed crash in folder filter dialog - made project compilable on Fedora 13 and 14 * Sun Jul 04 2010 Marian Ciobanu 1.0.06.051 - fixed crash caused by invalid Lyrics tag - fixed assertion failure when opening the folder filter in Windows * Wed Mar 17 2010 Marian Ciobanu 1.0.05.050 - fixed issue #23 (assertion failure) * Sun Mar 07 2010 Marian Ciobanu 1.0.04.049 - fixed crash triggered by invalid frame lengths in ID3V2 - fixed crash that occured when the backup directory couldn't be created * Fri Jan 29 2010 Marian Ciobanu 1.0.03.048 - fixed crash triggered by GEOB frames containing UTF-16 text * Tue Jan 12 2010 Marian Ciobanu 1.0.02.047 - fixed crash triggered by trying to repair broken ID3V2 tags that contain images - fixed crash that occurred when changing file names manually in the file renamer, without defining a pattern - fixed crash caused by unsynchronized ID3V2.4.0 frames that end with 0xff * Fri Dec 11 2009 Marian Ciobanu 1.0.01.046 - fixed crash that occurred when running on Qt 4.6 - fixed crash that occurred when changing song info in the tag editor if songs had images stored as BMP or anything else besides JPG or PNG * Sun Nov 29 2009 Marian Ciobanu 1.0.00.045 - wording changes to reflect non-beta status - pressing CTRL+C when viewing full-size images in the tag editor or in "Tag details" in the main window copies the image to the clipboard - added "Rating" and changed field order in "Tag details" to match the Tag editor * Wed Nov 04 2009 Marian Ciobanu 0.99.06.044 - fixed a crash in folder filter - fixed bug causing non-normalized files having any TXXX frame to appear normalized - case is ignored for file extension, so .Mp3 or .mP3 files are recognized - better support and more consistent handling for TXXX and text frames in ID3V2 - reduced number of locales by eliminating redundant ones - disabled CTRL+A selection in the main window - static link for serialization - added trace details for web downloads * Tue Oct 27 2009 Marian Ciobanu 0.99.06.043 - "Simple view" in file configuration - fixed crash on empty text frames in ID3V2 - generic binaries for Linux - BuildMp3Diags.hta detects VS version - documentation updates * Tue Oct 20 2009 Marian Ciobanu 0.99.06.042 - better support for binary frames in ID2V2.4.0 - fixed bug resulting in crash when files were modified in external tools - fixed bug resulting in crash when renaming files if a filter is applied - fixed bug resulting in crash when going to "Tag details" for files using Unicode in USLT (issue 40) - added option to include styles in Discogs info - added case-change option to the tag editor - better detection and notification for changed files before applying transformations or saving from the tag editor - file renamer allows file names to be changed manually - file renamer can use "duplicate" label for unrated songs - images shown for Lyrics tags - improved case-change transformation - removing images from the tag editor now works even for non-cover images * Fri Oct 09 2009 Marian Ciobanu 0.99.06.041 - brought documentation up to date - added test for ReplayGain info stored inside Id3V2 - failing to read text frames from files no longer causes crashes - fixed bug resulting in crash when 2 ID3V2 tags are present and "Discard invalid ID3V2 data" gets called - XML export now works when names contain double quotes - locale in export dialog - locale lists are now sorted - long text frames are now truncated when shown in the "File info" area - changed names and order for tabs under Config/Files - made transformation options work correctly in MSVC * Wed Sep 30 2009 Marian Ciobanu 0.99.06.040 - "Various Artists" support - all pictures are shown and can be viewed in full size in "Tag details" - all pictures from a file are shown in the tag editor - patterns in the tag editor may now be disabled - export as M3U or XML - better handling of text frames containing null characters - auto-size for the tag editor's "current file" area - better column widths in the tag editor - improved HTA for Windows build * Wed Sep 23 2009 Marian Ciobanu 0.99.05.038 - fixed bug introduced in 0.99.05.037 causing crashes when finding empty ID3V2 frames - new build process for Windows - 4th custom transf list now defaults to a "fix-all" approach * Thu Sep 17 2009 Marian Ciobanu 0.99.05.037 - fixes on right-click - UTF-8 strings recognized in ID3V2.3.0 - fixed bug 35 (assertion failure) - faster tracer - code compilable with VS 2008 (port by Sebastian Schuberth) * Mon Sep 07 2009 Marian Ciobanu 0.99.05.034 - drive labels shown in Windows - mp3gain can be started now if it's in a directory containing spaces - configurable invalid characters and replacement for file renamer - automatic check for new versions - text inside square, curly, and angle brackets removed from web queries - improved tracing code - no longer rescan the files if exiting tag editor without changes - replace non-alphanumeric chars with spaces in web queries (issue 2) - better sorting in the tag editor for albums with unusual track numbers - a default .ini name is generated in most cases - (probably) fixed an assert (not sure because couldn't reproduce it) - tracks without a track number are put at the end in the tag editor - let the user know about reporting support notes and about patterns - better detection of exceptions in threads - exceptions that propagate from slots are now caught - default "actions to be taken" no longer shown when applying transforms - file info for StreamWriter * Wed Sep 02 2009 Marian Ciobanu 0.99.05.033 - fixed crash in Windows when checking a whole drive - fixed crash when saving data from the tag editor - improved trace speed on Windows - fixed potential crash at startup - fixed crash when changing a file that is being used by other program - more details and better formatting in assert messages and trace files - MPEG2 Layer3 streams no longer show Support note - improved temporary file generation, which can result in faster transforms - F1 help now works for the first session dialog - smaller TABs in the "Tag details" area make "Other info" more readable - better HTML paragraph formatting * Sun Aug 23 2009 Marian Ciobanu 0.99.05.032 - restructured crash detector - fixed Windows issue with rectangles being shown instead of letters - Lyrics partial support - warning that may corrupt data - note about how to change selected files - HTML clean up * Wed Aug 19 2009 Marian Ciobanu 0.99.05.031 - crash detection - fixed small memory leak in config dialog - fixed small memory leak in the tag editor - content is shown for GEOB frames - .ID3 files are now loaded in addition to .MP3 - made the counter shown when applying transforms increment on new file(until now it was incremented for each transform) - "Various artists" no longer set as "artist" when downloading track info from MusicBrainz - fixed "current cell" in the tag editor (until now, when dragging the mouse to select several cells, the current cell was wrong, leading to setting values incorrectly - fixed a bug that didn't allow removal of the track number - slightly improved the normalizer, so a "busy" cursor is shown when the connection to the underlying process is lost (the program seems frozen, but it resumes after about 30 seconds) * Tue Jul 28 2009 Marian Ciobanu 0.99.05.030 - fixed a bug that caused the tag editor to reserve more space than needed even if the "fast save" option was turned off - fixed a bug that prevented removal of elements from lists - fixed a bug in the ID3V2 tag writer that prevented "Discard invalid ID3V2 data" and other transformations to properly work with ID3V2.4.0 tags that contain UTF8-encoded strings, resulting in a broken ID3V2.3.0 tag - file renamer now accepts patterns with no directory separators, in which case the renamed files are placed in the source directory - pattern dialogs now show the current line and column - fixed an assertion in the code that determines the file list - added tooltips for all the notes in the main file table - improved speed for "Discard invalid ID3V2 data" when no changes are done - transformation name included in the dialog that shows which file is currently processed - broken ID3V2 streams get removed when saving from the tag editor - the tag editor no longer triggers an assertion failure if non-default settings in the file section of the configuration dialog; (e.g. until now saving from the tag editor while original files weren't deleted resulted in this assertion failure) - fixed a bug in the "Change case for ID3V2 text frames" that resulted in a program crash if some fields were missing from the ID3V2 tag * Sun Jul 26 2009 Marian Ciobanu 0.99.05.029 - fast save in the tag editor - file renamer can work with the list of visible files instead of the current album if the user holds CTRL down when pressing the button - configurable visible transformations - new transform for keeping a single image, as front cover - new transform for removing ID3V1 - button to remove image files - improved paste in tag editor (it is possible to paste to multiple cells or to paste file names copied from file browsers) - configurable max image size - tooltips for the transformations menu - more checks + fixed error reporting in file renamer - "sessions" button visible by default for second and later sessions - directory filter no longer shows some directories that don't make sense - fixed loading images from current dir - fixed a bug in tag editor patterns that prevented patterns ending with a static text from working - fixed some bugs in the directory filter * Sat Jul 18 2009 Marian Ciobanu 0.99.04.026 - non-ASCII file names can now be seen on Windows - tag editor now looks at filter - USLT Lyrics inside ID3V2 are now shown - better alignment for text in note column header on Ubuntu - size grip on most dialogs - F1 help - files changed in the tag editor no longer show up in the main window if a filter is applied and they don't match the filter * Fri Jul 10 2009 Marian Ciobanu 0.99.03.022 - 2-letter labels - gradient grouping of notes - configurable colors - app no longer crashes when files are changed by external tools - tooltips are shown for the column headers in the file table - more consistent font handling * Sun Jul 05 2009 Marian Ciobanu 0.99.02.020 - file renamer now replaces invalid characters in file names - duration is now shown for audio streams - fixed assertion caused by files with too many streams - multiple ID3 stream remover no longer included by default in second list - some changes to the .spec file in the hope it will work on Mandriva * Sun Jul 05 2009 Marian Ciobanu 0.99.02.020 - file renamer now replaces invalid characters in file names - duration is now shown for audio streams - fixed assertion caused by files with too many streams - multiple ID3 stream remover no longer included by default in second list - some changes to the .spec file in the hope it will work on Mandriva * Wed Jun 24 2009 Marian Ciobanu 0.99.02.018 - made -mt suffix default for Boost Serialization * Wed Jun 24 2009 Marian Ciobanu 0.99.02.017 - always use multithreaded libraries (single-threaded ones may lead to crashes) - improved assert dialog; now it has more data, which can be copied and even emailed directly - the tag editor shows a warning in some cases when a user action would result in discarded images * Sun Jun 21 2009 Marian Ciobanu 0.99.02.016 - fixed an assertion failure that was triggered by an unsupported text encoding in APIC - added support for UTF8 text encoding in APIC - made the documentation look slightly better on IE6 * Sat Jun 20 2009 Marian Ciobanu 0.99.02.015 - made compilable on Fedora 11 and added Fedora 11 build - copying missing ID3V1 fields to ID3V2 no longer part of the default custom transformation list 2 * Thu Jun 18 2009 Marian Ciobanu 0.99.02.014 - the tag editor loads albums much faster than before; this is most visible when navigating to the next / previous album * Sun Jun 14 2009 Marian Ciobanu 0.99.02.012 - added support for UTF8 in ID3V240 - added maximize button to most windows in Windows (but could not get this to work with Gnome) - removed "What's this" button from most windows - now the main window shows the session name if more than 1 session was defined - now the main window shows up maximized in Gnome and Windows if it was maximized when it was closed - changed documentation links to point to new site, at SourceForge - minor documentation improvements * Sat Jun 06 2009 Marian Ciobanu 0.99.02.011 - added missing dependency for SVG icons - fixed a bug in "Save as ..." * Thu Jun 04 2009 Marian Ciobanu 0.99.02.010 - fixed a bug that could cause removal of audio data - fixed a bug that prevented single-image-saving from working * Wed Jun 03 2009 Marian Ciobanu 0.99.02.009 - Ubuntu binaries - minor UI tweaks * Mon May 25 2009 Marian Ciobanu 0.99.02.008 - improved font handling - minor UI tweaks * Tue May 19 2009 Marian Ciobanu 0.99.02.007 - initial version MP3Diags-1.2.02/package/deb/0000755000175000001440000000000012477147652014313 5ustar ciobiusersMP3Diags-1.2.02/package/deb/debian.control0000644000175000001440000000164112477147651017140 0ustar ciobiusersSource: mp3diags Section: sound Priority: optional Maintainer: Marian Ciobanu Build-Depends: debhelper (>= 4.1.16), libqt4-dev, zlib1g-dev, libboost-dev, libboost-program-options-dev, libboost-serialization-dev Package: mp3diags Architecture: any Depends: ${shlibs:Depends}, libqt4-svg Description: Tool for finding and fixing problems in MP3 files; includes a tagger Finds problems in MP3 files and helps the user to fix many of them. Looks at both the audio part (VBR info, quality, normalization) and the tags containing track information (ID3.) . Has a tag editor, which can download album information (including cover art) from MusicBrainz and Discogs, as well as paste data from the clipboard. Track information can also be extracted from a file's name. . Another component is the file renamer, which can rename files based on the fields in their ID3V2 tag (artist, track number, album, genre, ...) MP3Diags-1.2.02/package/deb/debian.rules0000644000175000001440000000642412477147651016616 0ustar ciobiusers#!/usr/bin/make -f # Sample debian/rules that uses debhelper. # GNU copyright 1997 to 1999 by Joey Hess. # Uncomment this to turn on verbose mode. #export DH_VERBOSE=1 # This is the debhelper compatibility version to use. export DH_COMPAT=4 CFLAGS = -g ifneq (,$(findstring noopt,$(DEB_BUILD_OPTIONS))) CFLAGS += -O0 else CFLAGS += -O2 endif build: build-stamp build-stamp: dh_testdir # Add here commands to compile the package. mkdir build_dir # pwd # if cd build_dir ; then echo "Changed"; else echo "Some Errors"; fi # pwd # cmake -D CMAKE_BUILD_TYPE="Release" -D NNFW_CONFIG="" -D CMAKE_INSTALL_PREFIX=/usr . # make all ./AdjustMt.sh STATIC_SER qmake make lrelease src/src.pro strip bin/MP3Diags touch build-stamp clean: dh_testdir dh_testroot rm -f build-stamp # Add here commands to clean up after the build process. rm -rf build_dir dh_clean install: build dh_testdir dh_testroot dh_clean -k dh_installdirs # Add here commands to install the package into debian/gentoo. #make install DESTDIR=$(CURDIR)/debian/mp3diags pwd mkdir -p $(CURDIR)/debian/mp3diags/usr/bin cp bin/MP3Diags $(CURDIR)/debian/mp3diags/usr/bin mkdir -p $(CURDIR)/debian/mp3diags/usr/share/applications cp desktop/MP3Diags.desktop $(CURDIR)/debian/mp3diags/usr/share/applications mkdir -p $(CURDIR)/debian/mp3diags/usr/share/icons/hicolor/16x16/apps cp desktop/MP3Diags16.png $(CURDIR)/debian/mp3diags/usr/share/icons/hicolor/16x16/apps/MP3Diags.png mkdir -p $(CURDIR)/debian/mp3diags/usr/share/icons/hicolor/22x22/apps cp desktop/MP3Diags22.png $(CURDIR)/debian/mp3diags/usr/share/icons/hicolor/22x22/apps/MP3Diags.png mkdir -p $(CURDIR)/debian/mp3diags/usr/share/icons/hicolor/24x24/apps cp desktop/MP3Diags24.png $(CURDIR)/debian/mp3diags/usr/share/icons/hicolor/24x24/apps/MP3Diags.png mkdir -p $(CURDIR)/debian/mp3diags/usr/share/icons/hicolor/32x32/apps cp desktop/MP3Diags32.png $(CURDIR)/debian/mp3diags/usr/share/icons/hicolor/32x32/apps/MP3Diags.png mkdir -p $(CURDIR)/debian/mp3diags/usr/share/icons/hicolor/36x36/apps cp desktop/MP3Diags36.png $(CURDIR)/debian/mp3diags/usr/share/icons/hicolor/36x36/apps/MP3Diags.png mkdir -p $(CURDIR)/debian/mp3diags/usr/share/icons/hicolor/40x40/apps cp desktop/MP3Diags40.png $(CURDIR)/debian/mp3diags/usr/share/icons/hicolor/40x40/apps/MP3Diags.png mkdir -p $(CURDIR)/debian/mp3diags/usr/share/icons/hicolor/48x48/apps cp desktop/MP3Diags48.png $(CURDIR)/debian/mp3diags/usr/share/icons/hicolor/48x48/apps/MP3Diags.png mkdir -p $(CURDIR)/debian/mp3diags/usr/share/mp3diags/translations cp src/translations/*.qm $(CURDIR)/debian/mp3diags/usr/share/mp3diags/translations # Build architecture-independent files here. binary-indep: build install # We have nothing to do by default. # Build architecture-dependent files here. binary-arch: build install dh_testdir dh_testroot # dh_installdebconf dh_installdocs dh_installexamples dh_installmenu # dh_installlogrotate # dh_installemacsen # dh_installpam # dh_installmime # dh_installinit dh_installcron dh_installman dh_installinfo # dh_undocumented dh_installchangelogs dh_link dh_strip dh_compress dh_fixperms # dh_makeshlibs dh_installdeb # dh_perl dh_shlibdeps dh_gencontrol dh_md5sums dh_builddeb binary: binary-indep binary-arch .PHONY: build clean binary-indep binary-arch binary install MP3Diags-1.2.02/package/deb/debian.changelog0000644000175000001440000004666712477147651017430 0ustar ciobiusersmp3diags (1.2.02) testing; urgency=low * integrated changes from 1.3.01: * fixed incorrect message occurring some times when start after a crash * better logging and retries for write errors * added offset to the output created via the command line * fixed crash caused by saving very small images * build fix * disabled Discogs integration -- Marian Ciobanu Sun, 08 Mar 2015 22:00:00 +0200 mp3diags (1.2.01) testing; urgency=low * 1.2 as the new stable branch * build fix for clang and for Solaris * fixed broken links in documentation and moved alternate downloads from Dropbox to my ISP -- Marian Ciobanu Fri, 17 Jan 2014 22:00:00 +0200 mp3diags (1.2.00) testing; urgency=low * new version number to support the new "stable" branch -- Marian Ciobanu Sun, 31 Mar 2013 22:00:00 -0800 mp3diags (1.1.21.080) testing; urgency=low * fixed bug that caused a "%s" to be shown instead of the list of files about to be changed -- Marian Ciobanu Sat, 01 Dec 2012 22:00:00 -0800 mp3diags (1.1.20.077) testing; urgency=low * Discogs fix -- Marian Ciobanu Sat, 20 Oct 2012 22:00:00 +0200 mp3diags (1.1.19.075) testing; urgency=low * French translation -- Marian Ciobanu Sun, 29 Apr 2012 22:00:00 +0200 mp3diags (1.1.18.074) testing; urgency=low * German translation * made code compilable on GCC 4.7 * several translation fixes -- Marian Ciobanu Sat, 06 Apr 2012 22:00:00 +0200 mp3diags (1.1.17.073) testing; urgency=low * fixed crash introduced in 1.1.16 with translation changes -- Marian Ciobanu Sat, 17 Mar 2012 22:00:00 +0200 mp3diags (1.1.16.072) testing; urgency=low * made program translatable * added Czech translation -- Marian Ciobanu Sun, 11 Mar 2012 22:00:00 +0200 mp3diags (1.1.12.068) testing; urgency=low * external tools * possibility of applying transformation lists in CLI mode * "Open containing folder" in the main window * delete files from the main window * close buttons for Gnome 3 * APE remover * non-audio remover * CLI analyzer uses session settings to determine quality thresholds * fixed menu tooltips that disappeared too quickly -- Marian Ciobanu Wed, 01 Feb 2012 22:00:00 +0200 mp3diags (1.1.09.064) testing; urgency=low * moved to Discogs API V2 * added codepage support when copying ID3V1 to ID3V2 * fixed crash caused by ID3V2.4 tag with invalid flags * fixed splitting of unsupported ID3V2 tags * fixed Xing generation for small bitrates -- Marian Ciobanu Sat, 03 Dec 2011 22:00:00 +0200 mp3diags (1.1.08.062) testing; urgency=low * made Discogs queries after a Discogs API change * fixed track numbering when querying multi-volume albums from Discogs * made Linux shell integration accept file names containing spaces * added close button for dialogs on Gnome 3 -- Marian Ciobanu Sun, 28 Aug 2011 22:00:00 +0200 mp3diags (1.1.07.061) testing; urgency=low * shell integration for Linux * replaced MP3Diags with MP3Diags-unstable in places where this wasn't done * restructured the .spec file * added close button on Gnome 3 -- Marian Ciobanu Sat, 30 Jul 2011 22:00:00 +0200 mp3diags (1.1.06.059) testing; urgency=low * shell integration fixes -- Marian Ciobanu Fri, 22 Jul 2011 22:00:00 +0200 mp3diags (1.1.05.058) testing; urgency=low * shell integration (Windows only) -- Marian Ciobanu Thu, 21 Jul 2011 22:00:00 +0200 mp3diags (1.1.04.057) testing; urgency=low * folder-based sessions based on command-line parameters -- Marian Ciobanu Sun, 17 Jul 2011 22:00:00 +0200 mp3diags (1.1.03.056) testing; urgency=low * integrated command-line mode by Michael Elsdörfer -- Marian Ciobanu Wed, 15 Jun 2011 22:00:00 +0200 mp3diags (1.1.01.054) testing; urgency=low * created unstable branch -- Marian Ciobanu Mon, 30 May 2011 22:00:00 +0200 mp3diags (1.0.08.053) testing; urgency=low * fixed crash when trying to save after error at startup * added branch support to build process -- Marian Ciobanu Sat, 28 May 2011 22:00:00 +0200 mp3diags (1.0.07.052) testing; urgency=low * fixed crash caused by invalid Lyrics tag * fixed assertion failure when opening the folder filter in Windows -- Marian Ciobanu Wed, 29 Dec 2010 22:00:00 +0200 mp3diags (1.0.06.051) testing; urgency=low * fixed crash caused by invalid Lyrics tag * fixed assertion failure when opening the folder filter in Windows -- Marian Ciobanu Sun, 04 Jul 2010 22:00:00 +0200 mp3diags (1.0.05.050) testing; urgency=low * fixed issue #23 (assertion failure) -- Marian Ciobanu Wed, 17 Mar 2010 22:00:00 +0200 mp3diags (1.0.04.049) testing; urgency=low * fixed crash triggered by invalid frame lengths in ID3V2 * fixed crash that occured when the backup directory couldn't be created -- Marian Ciobanu Sun, 07 Mar 2010 22:00:00 +0200 mp3diags (1.0.03.048) testing; urgency=low * fixed crash triggered by GEOB frames containing UTF-16 text -- Marian Ciobanu Fri, 29 Jan 2010 22:00:00 +0200 mp3diags (1.0.02.047) testing; urgency=low * fixed crash triggered by trying to repair broken ID3V2 tags that contain images * fixed crash that occurred when changing file names manually in the file renamer, without defining a pattern * fixed crash caused by unsynchronized ID3V2.4.0 frames that end with 0xff -- Marian Ciobanu Tue, 12 Jan 2010 22:00:00 +0200 mp3diags (1.0.01.046) testing; urgency=low * fixed crash that occurred when running on Qt 4.6 * fixed crash that occurred when changing song info in the tag editor if songs had images stored as BMP or anything else besides JPG or PNG -- Marian Ciobanu Fri, 11 Dec 2009 22:00:00 +0200 mp3diags (1.0.00.045) testing; urgency=low * wording changes to reflect non-beta status * pressing CTRL+C when viewing full-size images in the tag editor or in "Tag details" in the main window copies the image to the clipboard * added "Rating" and changed field order in "Tag details" to match the Tag editor -- Marian Ciobanu Sun, 29 Nov 2009 22:00:00 +0200 mp3diags (0.99.06.044) testing; urgency=low * fixed a crash in folder filter * fixed bug causing non-normalized files having any TXXX frame to appear normalized * case is ignored for file extension, so .Mp3 or .mP3 files are recognized * better support and more consistent handling for TXXX and text frames in ID3V2 * reduced number of locales by eliminating redundant ones * disabled CTRL+A selection in the main window * static link for serialization * added trace details for web downloads -- Marian Ciobanu Tue, 04 Nov 2009 22:00:00 +0200 mp3diags (0.99.06.043) testing; urgency=low * "Simple view" in file configuration * fixed crash on empty text frames in ID3V2 * generic binaries for Linux * BuildMp3Diags.hta detects VS version * documentation updates -- Marian Ciobanu Tue, 27 Oct 2009 22:00:00 +0200 mp3diags (0.99.06.042) testing; urgency=low * better support for binary frames in ID2V2.4.0 * fixed bug resulting in crash when files were modified in external tools * fixed bug resulting in crash when renaming files if a filter is applied * fixed bug resulting in crash when going to "Tag details" for files using Unicode in USLT (issue 40) * added option to include styles in Discogs info * added case-change option to the tag editor * better detection and notification for changed files before applying transformations or saving from the tag editor * file renamer allows file names to be changed manually * file renamer can use "duplicate" label for unrated songs * images shown for Lyrics tags * improved case-change transformation * removing images from the tag editor now works even for non-cover images -- Marian Ciobanu Tue, 20 Oct 2009 22:00:00 +0200 mp3diags (0.99.06.041) testing; urgency=low * brought documentation up to date * added test for ReplayGain info stored inside Id3V2 * failing to read text frames from files no longer causes crashes * fixed bug resulting in crash when 2 ID3V2 tags are present and "Discard invalid ID3V2 data" gets called * XML export now works when names contain double quotes * locale in export dialog * locale lists are now sorted * long text frames are now truncated when shown in the "File info" area * changed names and order for tabs under Config/Files * made transformation options work correctly in MSVC -- Marian Ciobanu Fri, 09 Oct 2009 22:00:00 +0200 mp3diags (0.99.06.040) testing; urgency=low * "Various Artists" support * all pictures are shown and can be viewed in full size in "Tag details" * all pictures from a file are shown in the tag editor * patterns in the tag editor may now be disabled * export as M3U or XML * better handling of text frames containing null characters * auto-size for the tag editor's "current file" area * better column widths in the tag editor * improved HTA for Windows build -- Marian Ciobanu Wed, 30 Sep 2009 22:00:00 +0200 mp3diags (0.99.05.038) testing; urgency=low * fixed bug introduced in 0.99.05.037 causing crashes when finding empty ID3V2 frames * new build process for Windows * 4th custom transf list now defaults to a "fix-all" approach -- Marian Ciobanu Wed, 23 Sep 2009 22:00:00 +0200 mp3diags (0.99.05.037) testing; urgency=low * fixes on right-click * UTF-8 strings recognized in ID3V2.3.0 * fixed bug 35 (assertion failure) * faster tracer * code compilable with VS 2008 (port by Sebastian Schuberth) -- Marian Ciobanu Thu, 17 Sep 2009 22:00:00 +0200 mp3diags (0.99.05.034) testing; urgency=low * drive labels shown in Windows * mp3gain can be started now if it's in a directory containing spaces * configurable invalid characters and replacement for file renamer * automatic check for new versions * text inside square, curly, and angle brackets removed from web queries * improved tracing code * no longer rescan the files if exiting tag editor without changes * replace non-alphanumeric chars with spaces in web queries (issue 2) * better sorting in the tag editor for albums with unusual track numbers * a default .ini name is generated in most cases * (probably) fixed an assert (not sure because couldn't reproduce it) * tracks without a track number are put at the end in the tag editor * let the user know about reporting support notes and about patterns * better detection of exceptions in threads * exceptions that propagate from slots are now caught * default "actions to be taken" no longer shown when applying transforms * file info for StreamWriter -- Marian Ciobanu Mon, 07 Sep 2009 22:00:00 +0200 mp3diags (0.99.05.033) testing; urgency=low * fixed crash in Windows when checking a whole drive * fixed crash when saving data from the tag editor * improved trace speed on Windows * fixed potential crash at startup * fixed crash when changing a file that is being used by other program * more details and better formatting in assert messages and trace files * MPEG2 Layer3 streams no longer show Support note * improved temporary file generation, which can result in faster transforms * F1 help now works for the first session dialog * smaller TABs in the "Tag details" area make "Other info" more readable * better HTML paragraph formatting -- Marian Ciobanu Wed, 02 Sep 2009 22:00:00 +0200 mp3diags (0.99.05.032) testing; urgency=low * restructured crash detector * fixed Windows issue with rectangles being shown instead of letters * Lyrics partial support * warning that may corrupt data * note about how to change selected files * HTML clean up -- Marian Ciobanu Sun, 23 Aug 2009 22:00:00 +0200 mp3diags (0.99.05.031) testing; urgency=low * crash detection * fixed small memory leak in config dialog * fixed small memory leak in the tag editor * content is shown for GEOB frames * .ID3 files are now loaded in addition to .MP3 * made the counter shown when applying transforms increment on new file (until now it was incremented for each transform) * "Various artists" no longer set as "artist" when downloading track info from MusicBrainz * fixed "current cell" in the tag editor (until now, when dragging the mouse to select several cells, the current cell was wrong, leading to setting values incorrectly * fixed a bug that didn't allow removal of the track number * slightly improved the normalizer, so a "busy" cursor is shown when the connection to the underlying process is lost (the program seems frozen, but it resumes after about 30 seconds) -- Marian Ciobanu Wed, 19 Aug 2009 22:00:00 +0200 mp3diags (0.99.05.030) testing; urgency=low * fixed a bug that caused the tag editor to reserve more space than needed even if the "fast save" option was turned off * fixed a bug that prevented removal of elements from lists * fixed a bug in the ID3V2 tag writer that prevented "Discard invalid ID3V2 data" and other transformations to properly work with ID3V2.4.0 tags that contain UTF8-encoded strings, resulting in a broken ID3V2.3.0 tag * file renamer now accepts patterns with no directory separators, in which case the renamed files are placed in the source directory * pattern dialogs now show the current line and column * fixed an assertion in the code that determines the file list * added tooltips for all the notes in the main file table * improved speed for "Discard invalid ID3V2 data" when no changes are done * transformation name included in the dialog that shows which file is currently processed * broken ID3V2 streams get removed when saving from the tag editor * the tag editor no longer triggers an assertion failure if non-default settings in the file section of the configuration dialog; (e.g. until now saving from the tag editor while original files weren't deleted resulted in this assertion failure) * fixed a bug in the "Change case for ID3V2 text frames" that resulted in a program crash if some fields were missing from the ID3V2 tag -- Marian Ciobanu Tue, 28 Jul 2009 22:00:00 +0200 mp3diags (0.99.05.029) testing; urgency=low * fast save in the tag editor * file renamer can work with the list of visible files instead of the current album if the user holds CTRL down when pressing the button * configurable visible transformations * new transform for keeping a single image, as front cover * new transform for removing ID3V1 * button to remove image files * improved paste in tag editor (it is possible to paste to multiple cells or to paste file names copied from file browsers) * configurable max image size * tooltips for the transformations menu * more checks + fixed error reporting in file renamer * "sessions" button visible by default for second and later sessions * directory filter no longer shows some directories that don't make sense * fixed loading images from current dir * fixed a bug in tag editor patterns that prevented patterns ending with a static text from working * fixed some bugs in the directory filter -- Marian Ciobanu Sun, 26 Jul 2009 22:00:00 +0200 mp3diags (0.99.04.026) testing; urgency=low * non-ASCII file names can now be seen on Windows * tag editor now looks at filter * USLT Lyrics inside ID3V2 are now shown * better alignment for text in note column header on Ubuntu * size grip on most dialogs * F1 help * files changed in the tag editor no longer show up in the main window if a filter is applied and they don't match the filter -- Marian Ciobanu Sat, 18 Jul 2009 22:00:00 +0200 mp3diags (0.99.03.022) testing; urgency=low * 2-letter labels * gradient grouping of notes * configurable colors * app no longer crashes when files are changed by external tools * tooltips are shown for the column headers in the file table * more consistent font handling -- Marian Ciobanu Fri, 10 Jul 2009 22:00:00 +0200 mp3diags (0.99.02.020) testing; urgency=low * file renamer now replaces invalid characters in file names * duration is now shown for audio streams * fixed assertion caused by files with too many streams * multiple ID3 stream remover no longer included by default in second list -- Marian Ciobanu Sun, 05 Jul 2009 22:00:00 +0200 mp3diags (0.99.02.018) testing; urgency=low * made -mt suffix default for Boost Serialization -- Marian Ciobanu Wed, 24 Jun 2009 22:00:00 +0200 mp3diags (0.99.02.017) testing; urgency=low * always use multithreaded Boost libraries (single-threaded ones may lead to crashes) * improved assert dialog; now it has more data, which can be copied and even emailed directly * the tag editor shows a warning in some cases when a user action would result in discarded images -- Marian Ciobanu Wed, 24 Jun 2009 15:00:00 +0200 mp3diags (0.99.02.016) testing; urgency=low * fixed an assertion failure that was triggered by an unsupported text encoding in APIC * added support for UTF8 text encoding in APIC * made the documentation look slightly better on IE6 -- Marian Ciobanu Sun, 21 Jun 2009 10:00:00 +0200 mp3diags (0.99.02.015) testing; urgency=low * made compilable on Fedora 11 and added Fedora 11 build * copying missing ID3V1 fields to ID3V2 no longer part of the default custom transformation list 2 -- Marian Ciobanu Sat, 20 Jun 2009 10:00:00 +0200 mp3diags (0.99.02.014) testing; urgency=low * the tag editor loads albums much faster than before; this is most visible when navigating to the next / previous album -- Marian Ciobanu Thu, 18 Jun 2009 10:00:00 +0200 mp3diags (0.99.02.012) testing; urgency=low * added support for UTF8 in ID3V240 * added maximize button to most windows in Windows (but could not get this to work with Gnome) * removed "What's this" button from most windows * now the main window shows the session name if more than 1 session was defined * now the main window shows up maximized in Gnome and Windows if it was maximized when it was closed * changed documentation links to point to new site, at SourceForge * minor documentation improvements -- Marian Ciobanu Sun, 14 Jun 2009 10:00:00 +0200 mp3diags (0.99.02.011) testing; urgency=low * added missing dependency for SVG icons * fixed a bug in "Save as ..." -- Marian Ciobanu Sat, 06 Jun 2009 10:00:00 +0200 mp3diags (0.99.02.010) testing; urgency=low * fixed a bug that could cause removal of audio data * fixed a bug that prevented single-image-saving from working -- Marian Ciobanu Thu, 04 Jun 2009 10:00:00 +0200 mp3diags (0.99.02.009) testing; urgency=low * Ubuntu binaries * minor UI tweaks -- Marian Ciobanu Wed, 03 Jun 2009 10:00:00 +0200 mp3diags (0.99.02.008) testing; urgency=low * improved font handling * minor UI tweaks -- Marian Ciobanu Mon, 25 May 2009 10:00:00 +0200 mp3diags (0.99.02.007) testing; urgency=low * initial version -- Marian Ciobanu Tue, 19 May 2009 10:00:00 +0200 MP3Diags-1.2.02/package/deb/MP3Diags.dsc0000644000175000001440000000061312477147651016354 0ustar ciobiusersFormat: 1.0 Source: mp3diags Version: 1.2.02 Binary: mp3diags Maintainer: Marian Ciobanu Architecture: any Build-Depends: debhelper (>= 4.1.16), g++, libqt4-dev, zlib1g-dev, libboost-dev, libboost-program-options-dev, libboost-serialization-dev Files: d57283ebb8157ae919762c58419353c8 133282 MP3Diags-1.2.02.tar.gz 2fecf324a32123b08cefc0f047bca5ee 63176 MP3Diags.diff.tar.gz MP3Diags-1.2.02/BuildMp3Diags.hta0000644000175000001440000006171212477147652015262 0ustar ciobiusers MP3 Diags build configuration
Boost Serialization .lib file - release
Toolchain / IDECompiler binary folderQt root folderBoost root folderBoost Serialization .lib or .a file - releaseZlib include folderZlib .lib file
Maximum number of folders remembered:


Notes:
  1. The purpose of this utility is to allow builds to be made easily for various combinations of compiler and libraries, as well as generate project files that can be used from IDEs.
  2. First you need to choose your compiler / IDE and configure the folders and libraries. For a given folder there is a toolchain and a set of libraries that can be configured. Others should be set up in another folder.
  3. Folders are populated with invalid values by default. Usually it should be easy to correct them, provided that you have installed the required libraries (Qt, Boost Serialization, and zlib.) If you already configured a folder for the same toolchain, you can copy the settings to the current folder.
  4. Note that the Qt root folder is for the Qt library, not for Qt Creator..
  5. After configuring the folders you need to "set up" the project. Then you can either build it, by clicking on the corresponding button, or open it in an IDE.
  6. To open MP3 Diags with Qt Creator: set up the project, start Qt Creator and open mp3diags.pro. If it offers to import existing build settings, you should decline; otherwise you'll only be able to build the debug version.
  7. To open MP3 Diags with Visual C++ (Express): first set up the project. Then, if you have a 2008 or 2010 version, you can double-click on mp3diags.sln in Windows Explorer. If you have something else, double-clicking on src\MP3DiagsWindows.vcproj will probably work, as well as opening either src\MP3DiagsWindows.vcproj or mp3diags.sln from the IDE; however, these haven't been tested.
  8. MinGW uses the "release" Boost serialization in both debug and and release version, while the Visual C++ debug version uses the "debug" serialization. There is little reason to debug the serialization code, but by design that's how Boost works under Visual C++. (Note that you should specify the release library in "Boost .lib file" regardless of what you want to build. The generated .vcproj file will have correct settings for both "debug" and "release" configurations.)
  9. The settings are stored in the file Up to 10 folders are remembered by default (the maximum is 100.) You can use them to keep several versions of the project, perhaps including some local changes or different libraries.
  10. The settings are saved automatically only if you set up or build the project. Otherwise you need to save them explicitely.