1 /***************************************************************************
2 * Copyright (C) 2008-2013 by Andrzej Rybczak *
3 * electricityispower@gmail.com *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
19 ***************************************************************************/
27 #include <vorbisfile.h>
28 #include <textidentificationframe.h>
30 #include <xiphcomment.h>
32 enum class CopyResult
{ Success
, NoArtist
, AlbumArtistAlreadyInPlace
};
34 bool is_framelist_empty(const TagLib::ID3v2::FrameList
&list
)
36 for (auto it
= list
.begin(); it
!= list
.end(); ++it
)
37 if ((*it
)->toString() != TagLib::String::null
)
42 CopyResult
copy_album_artist(TagLib::ID3v2::Tag
*tag
)
44 typedef TagLib::ID3v2::TextIdentificationFrame TextFrame
;
46 TagLib::ByteVector album_artist
= "TPE2";
47 if (!is_framelist_empty(tag
->frameList(album_artist
)))
48 return CopyResult::AlbumArtistAlreadyInPlace
;
50 auto artists
= tag
->frameList("TPE1");
51 if (artists
.isEmpty())
52 return CopyResult::NoArtist
;
54 for (auto it
= artists
.begin(); it
!= artists
.end(); ++it
)
56 // this cast should always succeed.
57 auto &textIt
= dynamic_cast<TextFrame
&>(**it
);
58 auto frame
= new TextFrame(album_artist
, TagLib::String::UTF8
);
59 frame
->setText(textIt
.fieldList());
63 return CopyResult::Success
;
66 CopyResult
copy_album_artist(TagLib::Ogg::XiphComment
*tag
)
68 if (tag
->contains("ALBUM ARTIST") || tag
->contains("ALBUMARTIST"))
69 return CopyResult::AlbumArtistAlreadyInPlace
;
71 auto artists
= tag
->fieldListMap()["ARTIST"];
72 if (artists
.isEmpty())
73 return CopyResult::NoArtist
;
75 for (auto it
= artists
.begin(); it
!= artists
.end(); ++it
)
76 tag
->addField("ALBUMARTIST", *it
, false);
78 return CopyResult::Success
;
81 void convert(int n
, char **files
, bool dry_run
)
85 std::cout
<< "No files to convert, exiting.\n";
89 std::cout
<< "Dry run mode enabled, pretending to modify files.\n";
91 for (int i
= 0; i
< n
; ++i
)
93 std::cout
<< "Modifying " << files
[i
] << "... ";
95 TagLib::FileRef
f(files
[i
]);
99 if (auto mp3_f
= dynamic_cast<TagLib::MPEG::File
*>(f
.file()))
101 result
= copy_album_artist(mp3_f
->ID3v2Tag(true));
103 else if (auto ogg_f
= dynamic_cast<TagLib::Ogg::Vorbis::File
*>(f
.file()))
105 result
= copy_album_artist(ogg_f
->tag());
107 else if (auto flac_f
= dynamic_cast<TagLib::FLAC::File
*>(f
.file()))
109 result
= copy_album_artist(flac_f
->xiphComment(true));
113 std::cout
<< "Not mp3/ogg/flac file, skipping.\n";
119 case CopyResult::Success
:
122 std::cout
<< "Done.\n";
124 case CopyResult::NoArtist
:
125 std::cout
<< "Artist not found, skipping.\n";
127 case CopyResult::AlbumArtistAlreadyInPlace
:
128 std::cout
<< "AlbumArtist is already there, skipping.\n";
133 std::cout
<< "Could not open file, skipping.\n";
137 int main(int argc
, char **argv
)
141 std::cout
<< "This little script copies Artist tag (if present) to\n";
142 std::cout
<< "AlbumArtist (if not present) for given mp3/ogg/flac files.\n";
144 std::cout
<< "Usage: " << argv
[0] << " [--dry-run] files\n";
146 std::cout
<< "Note: to run it recursively for all your files, you can use:\n";
147 std::cout
<< "$ find DIRECTORY \\( -name \"*.flac\" -o -name \"*.mp3\" -o -name \"*.ogg\" \\) -exec ./artist_to_albumartist [--dry-run] {} \\;\n";
151 bool dry_run
= !strcmp(argv
[1], "--dry-run");
152 convert(argc
-1-dry_run
, &argv
[1+dry_run
], dry_run
);