• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

palfrey / tagpy / 3794187600

pending completion
3794187600

Pull #13

github

GitHub
Merge 054e0a64b into 54db83f05
Pull Request #13: Test on all the Python versions we support

931 of 1215 relevant lines covered (76.63%)

76.4 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.97
/src/wrapper/id3.cpp
1
// Copyright (c) 2006-2008 Andreas Kloeckner
2
//
3
// Permission is hereby granted, free of charge, to any person obtaining a copy
4
// of this software and associated documentation files (the "Software"), to
5
// deal in the Software without restriction, including without limitation the
6
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7
// sell copies of the Software, and to permit persons to whom the Software is
8
// furnished to do so, subject to the following conditions:
9
//
10
// The above copyright notice and this permission notice shall be included in
11
// all copies or substantial portions of the Software.
12
//
13
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
19
// IN THE SOFTWARE.
20

21

22

23

24
#include <taglib/id3v1genres.h>
25
#include <taglib/id3v1tag.h>
26
#include <taglib/id3v2tag.h>
27
#include <taglib/id3v2header.h>
28
#include <taglib/id3v2extendedheader.h>
29
#include <taglib/id3v2footer.h>
30
#include <taglib/mpegfile.h>
31
#include <taglib/attachedpictureframe.h>
32
#include <taglib/commentsframe.h>
33
#include <taglib/relativevolumeframe.h>
34
#include <taglib/textidentificationframe.h>
35
#include <taglib/uniquefileidentifierframe.h>
36
#include <taglib/unknownframe.h>
37
#include <taglib/unsynchronizedlyricsframe.h>
38
#include <taglib/apetag.h>
39

40
#include "common.hpp"
41

42

43

44

45
namespace
46
{
47
  // -------------------------------------------------------------
48
  // ID3v2
49
  // -------------------------------------------------------------
50
  struct id3v2_FrameWrap : ID3v2::Frame, wrapper<ID3v2::Frame>
51
  {
52
      String toString() const { return this->get_override("toString")(); }
53
    protected: // maintain constructability
54
      id3v2_FrameWrap(const ByteVector &data) : ID3v2::Frame(data) { }
55
      // In docs, but not in code:
56
      /* id3v2_FrameWrap(ID3v2::Header *h) : ID3v2::Frame(h) { } */
57
  };
58

59
  void id3v2_Tag_addFrame(ID3v2::Tag &t, ID3v2::Frame *f)
4✔
60
  {
61
    ID3v2::Frame *f_clone = ID3v2::FrameFactory::instance()->createFrame(f->render());
4✔
62
    t.addFrame(f_clone);
4✔
63
  }
4✔
64

65
  object id3v2_rvf_channels(const ID3v2::RelativeVolumeFrame &rvf)
×
66
  {
67
    List<ID3v2::RelativeVolumeFrame::ChannelType> l = rvf.channels();
×
68
    return make_list(l.begin(), l.end());
×
69
  }
×
70

71
  #define MF_OL(MF, MIN, MAX) \
72
  BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(MF##_overloads, MF, MIN, MAX);
73

74
  MF_OL(createFrame, 1, 2);
4✔
75
  MF_OL(volumeAdjustmentIndex, 0, 1);
4✔
76
  MF_OL(volumeAdjustment, 0, 1);
4✔
77
  MF_OL(peakVolume, 0, 1);
4✔
78
  MF_OL(setVolumeAdjustmentIndex, 1, 2);
4✔
79
  MF_OL(setVolumeAdjustment, 1, 2);
4✔
80
  MF_OL(setPeakVolume, 1, 2);
4✔
81

82
  #if (TAGPY_TAGLIB_HEX_VERSION >= 0x10800)
83
    MF_OL(render, 0, 1)
4✔
84
  #endif
85

86
  // -------------------------------------------------------------
87
  // MPEG
88
  // -------------------------------------------------------------
89
  #if (TAGPY_TAGLIB_HEX_VERSION >= 0x10800)
90
    MF_OL(save, 0, 3)
4✔
91
  #else
92
    MF_OL(save, 0, 2)
93
  #endif
94
  MF_OL(ID3v1Tag, 0, 1)
4✔
95
  MF_OL(ID3v2Tag, 0, 1)
4✔
96
  MF_OL(APETag, 0, 1)
4✔
97
  MF_OL(strip, 0, 1)
4✔
98
}
99

100

101

102

103
void exposeID3()
4✔
104
{
105
  // -------------------------------------------------------------
106
  // ID3v1
107
  // -------------------------------------------------------------
108
  def("id3v1_genre", ID3v1::genre);
4✔
109

110
  class_<ID3v1::Tag, boost::noncopyable, bases<Tag> >
4✔
111
    ("id3v1_Tag")
112
    .def("render", &ID3v1::Tag::render)
4✔
113
    ;
114

115
  // -------------------------------------------------------------
116
  // ID3v2
117
  // -------------------------------------------------------------
118
  exposeMap<ByteVector, ID3v2::FrameList>("id3v2_FrameListMap");
4✔
119
  exposePointerList<ID3v2::Frame>("id3v2_FrameList");
4✔
120

121
  {
4✔
122
    typedef ID3v2::FrameFactory cl;
4✔
123

124
    ID3v2::Frame *(ID3v2::FrameFactory::*cf1)(const ByteVector &, bool) const
4✔
125
      = &cl::createFrame;
126
    ID3v2::Frame *(ID3v2::FrameFactory::*cf2)(const ByteVector &, TagLib::uint) const
4✔
127
      = &cl::createFrame;
128

129
    class_<ID3v2::FrameFactory, boost::noncopyable>
4✔
130
      ("id3v2_FrameFactory", no_init)
131
      .def("createFrame", cf1, return_value_policy<manage_new_object>())
4✔
132
      .def("createFrame", cf2, createFrame_overloads()[return_value_policy<manage_new_object>()])
4✔
133
      .def("instance", &cl::instance,
4✔
134
          return_value_policy<reference_existing_object>())
4✔
135
      .staticmethod("instance")
4✔
136

137
      .DEF_SIMPLE_METHOD(defaultTextEncoding)
4✔
138
      .DEF_SIMPLE_METHOD(setDefaultTextEncoding)
4✔
139
      ;
140
  }
141

142
  {
4✔
143
    typedef ID3v2::Frame cl;
4✔
144
    class_<id3v2_FrameWrap, boost::noncopyable>("id3v2_Frame", no_init)
4✔
145
      .DEF_SIMPLE_METHOD(frameID)
4✔
146
      .DEF_SIMPLE_METHOD(size)
4✔
147
      .DEF_SIMPLE_METHOD(setData)
4✔
148
      .DEF_SIMPLE_METHOD(setText)
4✔
149
      .def("toString", pure_virtual(&ID3v2::Frame::toString))
4✔
150
      .DEF_SIMPLE_METHOD(render)
4✔
151

152
      .def("headerSize",
4✔
153
           (TagLib::uint (*)())
154
           &ID3v2::Frame::headerSize)
155
      .def("headerSize",
4✔
156
           (TagLib::uint (*)(TagLib::uint))
157
           &ID3v2::Frame::headerSize)
158
      // MISSING: textDelimiter
159
      ;
160
  }
161

162
  {
4✔
163
    typedef ID3v2::Header cl;
4✔
164
    class_<cl, boost::noncopyable>
4✔
165
      ("id3v2_Header")
166
    // MISSING: second constructor
167
      .DEF_SIMPLE_METHOD(majorVersion)
4✔
168
      .DEF_SIMPLE_METHOD(revisionNumber)
4✔
169
      .DEF_SIMPLE_METHOD(extendedHeader)
4✔
170
      .DEF_SIMPLE_METHOD(experimentalIndicator)
4✔
171
      .DEF_SIMPLE_METHOD(footerPresent)
4✔
172
      .DEF_SIMPLE_METHOD(tagSize)
4✔
173
      .DEF_SIMPLE_METHOD(completeTagSize)
4✔
174
      .DEF_SIMPLE_METHOD(setTagSize)
4✔
175
      .DEF_SIMPLE_METHOD(setData)
4✔
176
      .DEF_SIMPLE_METHOD(render)
4✔
177
      .DEF_SIMPLE_METHOD(size)
4✔
178
      .staticmethod("size")
4✔
179
      .DEF_SIMPLE_METHOD(fileIdentifier)
4✔
180
      .staticmethod("fileIdentifier")
4✔
181
      ;
182
  }
183

184
  {
4✔
185
    typedef ID3v2::ExtendedHeader cl;
4✔
186
    class_<cl, boost::noncopyable>
4✔
187
      ("id3v2_ExtendedHeader", no_init)
188
      .DEF_SIMPLE_METHOD(size)
4✔
189
      .DEF_SIMPLE_METHOD(setData)
4✔
190
      ;
191
  }
192

193
  {
4✔
194
    typedef ID3v2::Footer cl;
4✔
195
    class_<cl, boost::noncopyable>
4✔
196
      ("id3v2_Footer", no_init)
197
      .DEF_SIMPLE_METHOD(render)
4✔
198
      .DEF_SIMPLE_METHOD(size)
4✔
199
      .staticmethod("size")
4✔
200
      ;
201
  }
202

203
  {
4✔
204
    typedef ID3v2::Tag cl;
4✔
205
    const ID3v2::FrameList &(cl::*fl1)(const ByteVector &) const =
4✔
206
      &cl::frameList;
207
    const ID3v2::FrameList &(cl::*fl2)() const =
4✔
208
      &cl::frameList;
209

210
    class_<cl, boost::noncopyable, bases<Tag> >("id3v2_Tag")
4✔
211
      .def("header", &ID3v2::Tag::header, return_internal_reference<>())
4✔
212
      .def("extendedHeader", &ID3v2::Tag::extendedHeader, return_internal_reference<>())
4✔
213
      .def("footer", &ID3v2::Tag::footer, return_internal_reference<>())
4✔
214

215
      .def("frameListMap", &ID3v2::Tag::frameListMap, return_internal_reference<>())
4✔
216
      .def("frameList", fl1, return_internal_reference<>())
4✔
217
      .def("frameList", fl2, return_internal_reference<>())
8✔
218

219
      .def("addFrame", id3v2_Tag_addFrame)
4✔
220
      .DEF_SIMPLE_METHOD(removeFrame)
4✔
221
      .DEF_SIMPLE_METHOD(removeFrames)
4✔
222

223
      #if (TAGPY_TAGLIB_HEX_VERSION >= 0x10800)
224
        // Commented out following comment at:
225
        // https://github.com/inducer/tagpy/commit/fb6d9a95f8ed1b0f347a82569a13e60a75c7e6d6
226
        // .DEF_OVERLOADED_METHOD(render, ByteVector (cl::*)() const)
227
        .DEF_OVERLOADED_METHOD(render, ByteVector (cl::*)(int) const)
4✔
228
      #else
229
        .def("render", (ByteVector (cl::*)() const) &cl::render)
230
      #endif
231
      ;
232
  }
233

234

235
  // -------------------------------------------------------------
236
  // ID3v2 frame types
237
  // -------------------------------------------------------------
238
  {
4✔
239
    typedef TagLib::ID3v2::AttachedPictureFrame scope;
4✔
240
    enum_<ID3v2::AttachedPictureFrame::Type>("id3v2_AttachedPictureFrame_Type")
4✔
241
      .ENUM_VALUE(Other)
4✔
242
      .ENUM_VALUE(FileIcon)
4✔
243
      .ENUM_VALUE(OtherFileIcon)
4✔
244
      .ENUM_VALUE(FrontCover)
4✔
245
      .ENUM_VALUE(BackCover)
4✔
246
      .ENUM_VALUE(LeafletPage)
4✔
247
      .ENUM_VALUE(Media)
4✔
248
      .ENUM_VALUE(LeadArtist)
4✔
249
      .ENUM_VALUE(Artist)
4✔
250
      .ENUM_VALUE(Conductor)
4✔
251
      .ENUM_VALUE(Band)
4✔
252
      .ENUM_VALUE(Composer)
4✔
253
      .ENUM_VALUE(Lyricist)
4✔
254
      .ENUM_VALUE(RecordingLocation)
4✔
255
      .ENUM_VALUE(DuringRecording)
4✔
256
      .ENUM_VALUE(DuringPerformance)
4✔
257
      .ENUM_VALUE(MovieScreenCapture)
4✔
258
      .ENUM_VALUE(ColouredFish)
4✔
259
      .ENUM_VALUE(Illustration)
4✔
260
      .ENUM_VALUE(BandLogo)
4✔
261
      .ENUM_VALUE(PublisherLogo)
4✔
262
      ;
263
  }
264

265
  {
4✔
266
    typedef ID3v2::AttachedPictureFrame cl;
4✔
267
    class_<cl, bases<ID3v2::Frame>, boost::noncopyable>
4✔
268
      ("id3v2_AttachedPictureFrame", init<optional<const ByteVector &> >())
4✔
269
      .DEF_SIMPLE_METHOD(textEncoding)
4✔
270
      .DEF_SIMPLE_METHOD(setTextEncoding)
4✔
271
      .DEF_SIMPLE_METHOD(mimeType)
4✔
272
      .DEF_SIMPLE_METHOD(setMimeType)
4✔
273
      .DEF_SIMPLE_METHOD(type)
4✔
274
      .DEF_SIMPLE_METHOD(setType)
4✔
275
      .DEF_SIMPLE_METHOD(description)
4✔
276
      .DEF_SIMPLE_METHOD(setDescription)
4✔
277
      .DEF_SIMPLE_METHOD(picture)
4✔
278
      .DEF_SIMPLE_METHOD(setPicture)
4✔
279
      ;
280
  }
281

282
  {
4✔
283
    typedef ID3v2::CommentsFrame cl;
4✔
284
    class_<cl, bases<ID3v2::Frame>, boost::noncopyable>
4✔
285
      ("id3v2_CommentsFrame", init<optional<const ByteVector &> >())
4✔
286
      .def(init<String::Type>())
8✔
287
      .DEF_SIMPLE_METHOD(language)
4✔
288
      .DEF_SIMPLE_METHOD(setLanguage)
4✔
289
      .DEF_SIMPLE_METHOD(description)
4✔
290
      .DEF_SIMPLE_METHOD(setDescription)
4✔
291
      .DEF_SIMPLE_METHOD(textEncoding)
4✔
292
      .DEF_SIMPLE_METHOD(setTextEncoding)
4✔
293
      ;
294
  }
295

296
  {
4✔
297
    typedef ID3v2::RelativeVolumeFrame::PeakVolume cl;
4✔
298
    class_<cl>
4✔
299
      ("id3v2_PeakVolume")
300
      .def_readwrite("bitsRepresentingPeak", &cl::bitsRepresentingPeak)
4✔
301
      .def_readwrite("peakVolume", &cl::peakVolume)
8✔
302
      ;
303
  }
304

305
  {
4✔
306
    typedef TagLib::ID3v2::RelativeVolumeFrame scope;
4✔
307
    enum_<ID3v2::RelativeVolumeFrame::ChannelType>("id3v2_RelativeVolumeFrame_ChannelType")
4✔
308
      .ENUM_VALUE(Other)
4✔
309
      .ENUM_VALUE(MasterVolume)
4✔
310
      .ENUM_VALUE(FrontRight)
4✔
311
      .ENUM_VALUE(FrontLeft)
4✔
312
      .ENUM_VALUE(BackRight)
4✔
313
      .ENUM_VALUE(BackLeft)
4✔
314
      .ENUM_VALUE(FrontCentre)
4✔
315
      .ENUM_VALUE(BackCentre)
4✔
316
      .ENUM_VALUE(Subwoofer)
4✔
317
      ;
318
  }
319

320
  {
4✔
321
    typedef ID3v2::RelativeVolumeFrame cl;
4✔
322
    class_<cl, bases<ID3v2::Frame>, boost::noncopyable>
4✔
323
      ("id3v2_RelativeVolumeFrame", init<const ByteVector &>())
4✔
324
      // MISSING: Empty constructor, gives symbol errors
325
      .def("channels", id3v2_rvf_channels)
4✔
326
      .DEF_SIMPLE_METHOD(setChannelType)
4✔
327
      .DEF_OVERLOADED_METHOD(volumeAdjustmentIndex, short (cl::*)(cl::ChannelType) const)
4✔
328
      .DEF_OVERLOADED_METHOD(setVolumeAdjustmentIndex, void (cl::*)(short, cl::ChannelType))
4✔
329
      .DEF_OVERLOADED_METHOD(volumeAdjustment, float (cl::*)(cl::ChannelType) const)
4✔
330
      .DEF_OVERLOADED_METHOD(setVolumeAdjustment, void (cl::*)(float, cl::ChannelType))
4✔
331
      .DEF_OVERLOADED_METHOD(peakVolume, cl::PeakVolume (cl::*)(cl::ChannelType) const)
4✔
332
      .DEF_OVERLOADED_METHOD(setPeakVolume, void (cl::*)(const cl::PeakVolume &, cl::ChannelType))
4✔
333
      ;
334
  }
335

336
  {
4✔
337
    typedef ID3v2::TextIdentificationFrame cl;
4✔
338
    class_<cl, bases<ID3v2::Frame>, boost::noncopyable>
4✔
339
      ("id3v2_TextIdentificationFrame", init<const ByteVector &, optional<String::Type> >())
4✔
340
      .def("setText", (void (cl::*)(const String &)) &cl::setText)
4✔
341
      .def("setText", (void (cl::*)(const StringList &)) &cl::setText)
4✔
342
      .DEF_SIMPLE_METHOD(textEncoding)
4✔
343
      .DEF_SIMPLE_METHOD(setTextEncoding)
4✔
344
      .DEF_SIMPLE_METHOD(fieldList)
4✔
345
      ;
346
  }
347

348
  {
4✔
349
    typedef ID3v2::UnsynchronizedLyricsFrame cl;
4✔
350
    class_<cl, bases<ID3v2::Frame>, boost::noncopyable>
4✔
351
      ("id3v2_UnsynchronizedLyricsFrame", init<optional<const ByteVector &> >())
4✔
352
      .def(init<String::Type>())
8✔
353
      .DEF_SIMPLE_METHOD(language)
4✔
354
      .DEF_SIMPLE_METHOD(setLanguage)
4✔
355
      .DEF_SIMPLE_METHOD(description)
4✔
356
      .DEF_SIMPLE_METHOD(setDescription)
4✔
357
      .DEF_SIMPLE_METHOD(textEncoding)
4✔
358
      .DEF_SIMPLE_METHOD(setTextEncoding)
4✔
359
      ;
360
  }
361

362
  {
4✔
363
    typedef ID3v2::UserTextIdentificationFrame cl;
4✔
364
    class_<cl, bases<ID3v2::TextIdentificationFrame>, boost::noncopyable>
4✔
365
      ("id3v2_UserTextIdentificationFrame", init<const ByteVector &>())
4✔
366
      .def(init<optional<String::Type> >())
8✔
367
      .DEF_SIMPLE_METHOD(description)
4✔
368
      .DEF_SIMPLE_METHOD(setDescription)
4✔
369
      .DEF_SIMPLE_METHOD(fieldList)
4✔
370
      ;
371
  }
372

373
  {
4✔
374
    typedef ID3v2::UniqueFileIdentifierFrame cl;
4✔
375
    class_<cl, bases<ID3v2::Frame>, boost::noncopyable>
4✔
376
      ("id3v2_UniqueFileIdentifierFrame", init<const ByteVector &>())
4✔
377
      .def(init<const String &, const ByteVector &>())
8✔
378
      .DEF_SIMPLE_METHOD(owner)
4✔
379
      .DEF_SIMPLE_METHOD(setOwner)
4✔
380
      .DEF_SIMPLE_METHOD(identifier)
4✔
381
      .DEF_SIMPLE_METHOD(setIdentifier)
4✔
382
      ;
383
  }
384

385
  {
4✔
386
    typedef ID3v2::UnknownFrame cl;
4✔
387
    class_<cl, bases<ID3v2::Frame>, boost::noncopyable>
4✔
388
      ("id3v2_UnknownFrame", init<const ByteVector &>())
4✔
389
      .DEF_SIMPLE_METHOD(data)
4✔
390
      ;
391
  }
392

393
  // -------------------------------------------------------------
394
  // MPEG
395
  // -------------------------------------------------------------
396
  enum_<MPEG::File::TagTypes>("mpeg_TagTypes")
4✔
397
    .value("NoTags", MPEG::File::NoTags)
4✔
398
    .value("ID3v1", MPEG::File::ID3v1)
4✔
399
    .value("ID3v2", MPEG::File::ID3v2)
4✔
400
    .value("APE", MPEG::File::APE)
4✔
401
    .value("AllTags", MPEG::File::AllTags)
4✔
402
    ;
403

404
  {
4✔
405
    typedef MPEG::Properties cl;
4✔
406
    class_<cl, bases<AudioProperties>, boost::noncopyable>
4✔
407
      ("mpeg_Properties",
408
       //init<File *, AudioProperties::ReadStyle>()
409
       no_init
410
       )
411
      .ADD_RO_PROPERTY(layer)
4✔
412
      // .ADD_RO_PROPERTY(protectionEnabled) (not implemented in TagLib 1.4)
413
      // .ADD_RO_PROPERTY(channelMode) (depends on ChannelMode type)
414
      .ADD_RO_PROPERTY(isCopyrighted)
4✔
415
      .ADD_RO_PROPERTY(isOriginal)
4✔
416
      ;
417
  }
418

419
  {
4✔
420
    typedef MPEG::File cl;
4✔
421
    class_<MPEG::File, bases<File>, boost::noncopyable>
4✔
422
      ("mpeg_File",
423
       init<const char *, optional<bool, AudioProperties::ReadStyle> >())
4✔
424
      .def(init<const char *, ID3v2::FrameFactory *, optional<bool, AudioProperties::ReadStyle> >())
4✔
425
      .def("save",
4✔
426
           #if (TAGPY_TAGLIB_HEX_VERSION >= 0x10800)
427
             (bool (MPEG::File::*)(int, bool, int))
428
           #else
429
             (bool (MPEG::File::*)(int, bool))
430
           #endif
431
           &cl::save,
432
           save_overloads())
×
433
      .def("ID3v1Tag",
4✔
434
           (ID3v1::Tag *(MPEG::File::*)(bool))
435
           &cl::ID3v1Tag,
436
           ID3v1Tag_overloads()[return_internal_reference<>()])
4✔
437
      .def("ID3v2Tag",
4✔
438
           (ID3v2::Tag *(MPEG::File::*)(bool))
439
           &cl::ID3v2Tag,
440
           ID3v2Tag_overloads()[return_internal_reference<>()])
4✔
441
      .def("APETag",
4✔
442
           (APE::Tag *(cl::*)(bool)) &cl::APETag,
443
           APETag_overloads()[return_internal_reference<>()])
4✔
444
      .def("strip",
8✔
445
           (bool (cl::*)(int)) &cl::strip,
446
           strip_overloads())
8✔
447
      .DEF_SIMPLE_METHOD(setID3v2FrameFactory)
4✔
448
      .DEF_SIMPLE_METHOD(firstFrameOffset)
4✔
449
      .DEF_SIMPLE_METHOD(nextFrameOffset)
4✔
450
      .DEF_SIMPLE_METHOD(previousFrameOffset)
4✔
451
      .DEF_SIMPLE_METHOD(lastFrameOffset)
4✔
452
      ;
453
  }
454

455
  // MISSING: Header, XingHeader
456
}
4✔
457

458

459

460

461
// EMACS-FORMAT-TAG
462
//
463
// Local Variables:
464
// mode: C++
465
// eval: (c-set-style "stroustrup")
466
// eval: (c-set-offset 'access-label -2)
467
// eval: (c-set-offset 'inclass '++)
468
// c-basic-offset: 2
469
// tab-width: 8
470
// End:
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc