Discman
Loading...
Searching...
No Matches
cd_ripper.cc
Go to the documentation of this file.
1
6
7#define STB_IMAGE_IMPLEMENTATION
8#include "cd_ripper.h"
9
10CDRipper::CDRipper(CDDrive& drive, const DiscDB::Disc& disc, const std::string& albumArtURL, const std::string& mediaRoot)
11 : _drive(drive)
12 , _disc(disc)
13 , _thread(nullptr)
14 , _track(0)
15 , _progress(0)
16 , _media_root(mediaRoot)
17 , _album_art_url(albumArtURL)
18 , _album_art_image(nullptr)
21
22 _dispatcher.connect(sigc::mem_fun(*this, &CDRipper::on_notification));
23 _dispatcher_done.connect(sigc::mem_fun(*this, &CDRipper::on_done_notification));
24}
25
27 if (_album_art_image) {
28 av_free(_album_art_image);
29 }
30 if (_thread) {
31 _thread->join();
32 delete _thread;
33 }
34}
35
36std::string CDRipper::make_safe(const std::string& str) const {
37 std::string result(str);
38
39 const std::string illegal = "<>:\"/\\|?*";
40
41 std::ranges::replace_if(result, [&](unsigned char c) { return illegal.find(c) != std::string::npos || std::iscntrl(c); }, '_');
42
43 return result;
44}
45
49
53
55 if (_media_root.empty()) {
56 throw NoMedia();
57 }
58
61 + "/Music"
62 + "/" + make_safe(_disc.artist())
63 + "/" + make_safe(_disc.title());
64
65 std::filesystem::create_directories(_output_dir);
66}
67
69 const AVCodec* codec = avcodec_find_encoder(AV_CODEC_ID_AAC);
70
71 AVPacket* packet = av_packet_alloc();
72
73 AVChannelLayout ch_layout = AV_CHANNEL_LAYOUT_STEREO;
74
75 SwrContext* swr_ctx = nullptr;
76
77 swr_alloc_set_opts2(&swr_ctx,
78 &ch_layout,
79 AV_SAMPLE_FMT_FLTP,
80 44100,
81 &ch_layout,
82 AV_SAMPLE_FMT_S16,
83 44100,
84 0,
85 nullptr);
86
87 if (swr_init(swr_ctx) < 0) {
88 swr_free(&swr_ctx);
89 throw RipperErrorException("Failed to initialize resampler");
90 }
91
92 *rip_ctx = (struct CDRipper::RipContext) {
93 .fmt_ctx = nullptr,
94 .st = nullptr,
95 .codec = codec,
96 .c = nullptr,
97 .frame = nullptr,
98 .packet = packet,
99 .swr_ctx = swr_ctx
100 };
101
102
103 cURLpp::Cleanup cleanup;
104 cURLpp::Easy easyhandle;
105
106 std::stringstream ss;
107 easyhandle.setOpt(cURLpp::Options::Url(_album_art_url));
108 easyhandle.setOpt(cURLpp::Options::WriteStream(&ss));
109 easyhandle.perform();
110
111 std::string contentType;
112 cURLpp::infos::ContentType::get(easyhandle, contentType);
113
114 if (contentType == "image/jpeg") {
115 _album_art_image_codec = AV_CODEC_ID_MJPEG;
116 } else if (contentType == "image/png") {
117 _album_art_image_codec = AV_CODEC_ID_PNG;
118 }
119
120 std::string s = ss.str();
121
122 _album_art_image = (uint8_t*)av_malloc(s.size());
123 memcpy(_album_art_image, s.c_str(), s.size());
124
125 _album_art_image_size = s.size();
126
127 int width, height;
128
129 unsigned char* stb_image = stbi_load_from_memory(
132 &width,
133 &height,
134 nullptr,
135 0
136 );
137
138 stbi_image_free(stb_image);
139
140 _album_art_image_dims = std::make_pair(width, height);
141}
142
143void CDRipper::do_rip(CDRipper::RipContext* rip_ctx, bool continuous) {
144 start_file(rip_ctx);
145
146 uint16_t* frame_input = new uint16_t[rip_ctx->c->frame_size*2];
147
148 while (true) {
149 for (int i = 0; i < rip_ctx->c->frame_size; i++) {
150 frame_input[(i*2)+0] = consume();
151 frame_input[(i*2)+1] = consume();
152
153 if (_track != _drive.track() || _drive.done()) {
154 rip_ctx->frame->nb_samples = i + 1;
155 break;
156 }
157 }
158
159 int ret = swr_convert(rip_ctx->swr_ctx,
160 rip_ctx->frame->data,
161 rip_ctx->frame->nb_samples,
162 reinterpret_cast<const uint8_t* const *>(&frame_input),
163 rip_ctx->frame->nb_samples);
164 if (ret < 0) {
165 throw RipperErrorException("Error performing resample");
166 }
167
168 ret = avcodec_send_frame(rip_ctx->c, rip_ctx->frame);
169 if (ret < 0) {
170 throw RipperErrorException("Could not send frame to encoder");
171 }
172
173 while (ret >= 0) {
174 ret = avcodec_receive_packet(rip_ctx->c, rip_ctx->packet);
175 if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
176 break;
177 } else if (ret < 0) {
178 throw RipperErrorException("Error encoding audio frame");
179 return;
180 }
181
182 av_interleaved_write_frame(rip_ctx->fmt_ctx, rip_ctx->packet);
183
184 unsigned int progress = std::ceil(_drive.progress() * 100);
185 if (_progress < progress) {
186 _progress = progress;
187 _dispatcher.emit();
188 }
189 }
190
191 if (_track != _drive.track() || _drive.done()) {
192
193 ret = avcodec_send_frame(rip_ctx->c, nullptr);
194 if (ret < 0) {
195 throw RipperErrorException("Could not send frame to encoder");
196 }
197
198 while (ret >= 0) {
199 ret = avcodec_receive_packet(rip_ctx->c, rip_ctx->packet);
200 if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
201 break;
202 } else if (ret < 0) {
203 throw RipperErrorException("Error encoding audio frame");
204 }
205
206 av_interleaved_write_frame(rip_ctx->fmt_ctx, rip_ctx->packet);
207 }
208
209 end_file(rip_ctx);
210
211 _track++;
212 _progress = 0;
213
214 if (continuous) {
215 if (_drive.done()) {
216 break;
217 }
218
219 start_file(rip_ctx);
220
221 delete [] frame_input;
222
223 frame_input = new uint16_t[rip_ctx->c->frame_size*2];
224 } else {
225 break;
226 }
227 }
228 }
229
230 delete [] frame_input;
231}
232
234 swr_free(&rip_ctx->swr_ctx);
235 av_packet_free(&rip_ctx->packet);
236}
237
239 AVCodecContext* c = avcodec_alloc_context3(rip_ctx->codec);
240
241 c->ch_layout = AV_CHANNEL_LAYOUT_STEREO;
242 c->sample_rate = 44100;
243 c->sample_fmt = AV_SAMPLE_FMT_FLTP;
244 c->bit_rate = 128000;
245 c->time_base = {1, 44100};
246 c->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
247
248 avcodec_open2(c, rip_ctx->codec, nullptr);
249
250 rip_ctx->c = c;
251
252 AVFrame* frame = av_frame_alloc();
253
254 frame->nb_samples = c->frame_size;
255 frame->format = c->sample_fmt;
256 frame->ch_layout = c->ch_layout;
257
258 int ret = av_frame_get_buffer(frame, 0);
259 if (ret < 0) {
260 throw RipperErrorException("Could not allocate audio data buffers");
261 }
262
263 rip_ctx->frame = frame;
264
265 std::stringstream ss;
266 ss << std::setw(2) << std::setfill('0') << _track;
267
268 _output_filename = ss.str()
269 + " " + _disc.tracks()[_track - 1].title()
270 + ".m4a";
271
273
274 std::string output_filename = _output_dir + "/" + _output_filename;
275
276 rip_ctx->fmt_ctx = nullptr;
277 avformat_alloc_output_context2(&rip_ctx->fmt_ctx, nullptr, "ipod", output_filename.c_str());
278
279 tag_file(rip_ctx);
280
281 rip_ctx->st = avformat_new_stream(rip_ctx->fmt_ctx, rip_ctx->codec);
282 rip_ctx->st->time_base = {1, 44100};
283
284 avcodec_parameters_from_context(rip_ctx->st->codecpar, rip_ctx->c);
285
286 rip_ctx->sta = avformat_new_stream(rip_ctx->fmt_ctx, nullptr);
287 rip_ctx->sta->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
288 rip_ctx->sta->codecpar->codec_id = _album_art_image_codec;
289 rip_ctx->sta->codecpar->width = _album_art_image_dims.first;
290 rip_ctx->sta->codecpar->height = _album_art_image_dims.second;
291 rip_ctx->sta->disposition |= AV_DISPOSITION_ATTACHED_PIC;
292
293 avio_open(&rip_ctx->fmt_ctx->pb, output_filename.c_str(), AVIO_FLAG_WRITE);
294 ret = avformat_write_header(rip_ctx->fmt_ctx, nullptr);
295
296 if (ret < 0) {
297 throw RipperErrorException("Could not write file header");
298 }
299
300 add_file_art(rip_ctx);
301}
302
304 av_write_trailer(rip_ctx->fmt_ctx);
305 avio_closep(&rip_ctx->fmt_ctx->pb);
306 avformat_free_context(rip_ctx->fmt_ctx);
307 av_frame_free(&rip_ctx->frame);
308 avcodec_free_context(&rip_ctx->c);
309}
310
313
314 if (_thread) {
315 _thread->join();
316 delete _thread;
317 }
318
319 _thread = new std::thread(static_cast<void(CDRipper::*)(void)>(&CDRipper::rip_helper), this);
320}
321
323 _track = 1;
324 _progress = 0;
325
326 _dispatcher.emit();
327
328 _drive.track(_track);
329
330 RipContext rip_ctx;
331
332 start_rip(&rip_ctx);
333 do_rip(&rip_ctx);
334 end_rip(&rip_ctx);
335
336 _dispatcher_done.emit();
337}
338
339void CDRipper::rip(const int track) {
341
342 if (_thread) {
343 _thread->join();
344 delete _thread;
345 }
346
347 _thread = new std::thread(static_cast<void(CDRipper::*)(const int)>(&CDRipper::rip_helper), this, track);
348}
349
350void CDRipper::rip_helper(const int track) {
351 _track = track;
352 _progress = 0;
353
354 _dispatcher.emit();
355
356 _drive.track(_track);
357
358 RipContext rip_ctx;
359
360 start_rip(&rip_ctx);
361 do_rip(&rip_ctx, false);
362 end_rip(&rip_ctx);
363
364 _dispatcher_done.emit();
365}
366
370
374
376 std::stringstream ss;
377 ss << _disc.year();
378
379 av_dict_set(&rip_ctx->fmt_ctx->metadata, "title", _disc.tracks()[_track - 1].title().c_str(), 0);
380 av_dict_set(&rip_ctx->fmt_ctx->metadata, "artist", _disc.artist().c_str(), 0);
381 av_dict_set(&rip_ctx->fmt_ctx->metadata, "album", _disc.title().c_str(), 0);
382 av_dict_set(&rip_ctx->fmt_ctx->metadata, "date", ss.str().c_str(), 0);
383 av_dict_set(&rip_ctx->fmt_ctx->metadata, "genre", _disc.genre().c_str(), 0);
384
385}
386
388 uint8_t* newImage = (uint8_t*)av_memdup(_album_art_image, _album_art_image_size);
389
390 AVPacket* pkt = av_packet_alloc();
391 int ret = av_packet_from_data(pkt, _album_art_image, _album_art_image_size);
392
393 if (ret != 0) {
394 throw RipperErrorException("Could not allocate packet buffer");
395 }
396
397 pkt->stream_index = rip_ctx->sta->index;
398 pkt->flags |= AV_PKT_FLAG_KEY;
399 av_interleaved_write_frame(rip_ctx->fmt_ctx, pkt);
400
401 av_packet_free(&pkt);
402
403 _album_art_image = newImage;
404}
Abstracts the host's disc drive.
Definition cd_drive.h:39
Glib::Dispatcher _dispatcher
Used by _thread to safely emit _sig_track_progress.
Definition cd_ripper.h:109
int _album_art_image_size
The size of _album_art_image.
Definition cd_ripper.h:118
const DiscDB::Disc & _disc
The fully populated DiscDB::Disc describing the album.
Definition cd_ripper.h:107
std::string _output_filename
The output filename.
Definition cd_ripper.h:115
std::string _output_dir
The path to the album folder on the removable volume.
Definition cd_ripper.h:114
~CDRipper()
CDRipper destructor.
Definition cd_ripper.cc:26
sig_done _sig_done
Emiited when the rip (whole disc or single track) is done.
Definition cd_ripper.h:104
std::string _album_art_url
The URL of the album art currently shown.
Definition cd_ripper.h:116
void rip()
Rip the entire disc.
Definition cd_ripper.cc:311
Glib::Dispatcher _dispatcher_done
Used by _thread to safely emit _sig_done.
Definition cd_ripper.h:110
void end_rip(RipContext *rip_ctx)
Ends the ripping process.
Definition cd_ripper.cc:233
CDRipper(CDDrive &drive, const DiscDB::Disc &disc, const std::string &albumArtURL, const std::string &mediaRoot)
CDRipper constructor.
Definition cd_ripper.cc:10
std::string make_safe(const std::string &str) const
Used to sanitize folder and file names.
Definition cd_ripper.cc:36
unsigned int _progress
The current percentage completion ripping the current track.
Definition cd_ripper.h:112
void add_file_art(RipContext *rip_ctx)
Adds _album_art_image to the output file.
Definition cd_ripper.cc:387
void on_done_notification()
Called when _dispatcher_done is notified.
Definition cd_ripper.cc:50
void on_notification()
Called when _dispatcher is notified.
Definition cd_ripper.cc:46
CDDrive & _drive
The parent CDDrive.
Definition cd_ripper.h:106
void start_file(RipContext *rip_ctx)
Creates the ouput file, tags it and writes the M4A header.
Definition cd_ripper.cc:238
sigc::signal< void(void)> sig_done
"Rip done" signal type.
Definition cd_ripper.h:67
unsigned int _track
The 1-based index of the track currently being ripped.
Definition cd_ripper.h:111
sigc::signal< void(unsigned int, unsigned int)> sig_track_progress
"Track progress made" signal type.
Definition cd_ripper.h:64
void ensure_output_dir()
Verifies _media_root exists and sets _output_dir.
Definition cd_ripper.cc:54
AVCodecID _album_art_image_codec
Reflects whether the _album_art_image is JPEG or PNG.
Definition cd_ripper.h:119
uint8_t * _album_art_image
Raw bytes received from the _album_art_url.
Definition cd_ripper.h:117
void do_rip(RipContext *rip_ctx, bool continuous=true)
The core ripping method interacting directly with ffmpeg.
Definition cd_ripper.cc:143
sig_done signal_done()
Getter for ::_signal_done.
Definition cd_ripper.cc:371
sig_track_progress _sig_track_progress
Emitted when the percentage progress ripping a track has increased.
Definition cd_ripper.h:103
void rip_helper()
Private helper for rip().
Definition cd_ripper.cc:322
std::thread * _thread
Used to execute the ripping process.
Definition cd_ripper.h:108
void end_file(RipContext *rip_ctx)
Frees resources in the ripping context specific to the output file.
Definition cd_ripper.cc:303
void start_rip(RipContext *rip_ctx)
Begins the ripping process.
Definition cd_ripper.cc:68
sig_track_progress signal_track_progress()
Getter for ::_signal_track_progress.
Definition cd_ripper.cc:367
std::string _media_root
The path to the root of the removable volume.
Definition cd_ripper.h:113
std::pair< int, int > _album_art_image_dims
The dimensions of _album_art_image in pixels.
Definition cd_ripper.h:120
void tag_file(RipContext *rip_ctx)
Adds textual metadata (album and track information) to the output file.
Definition cd_ripper.cc:375
virtual void producer(Producer< int16_t > *const producer)
Definition consumer.h:42
int16_t consume() const
Definition consumer.h:48
Thrown when there is no removable volume to rip to.
Definition cd_ripper.h:44
Contains resources that are shared by private helper functions.
Definition cd_ripper.h:92
AVFrame * frame
The unit of input to ffmpeg.
Definition cd_ripper.h:98
AVStream * sta
Video stream for album art.
Definition cd_ripper.h:95
AVFormatContext * fmt_ctx
Format (file) context.
Definition cd_ripper.h:93
const AVCodec * codec
The M4A codec.
Definition cd_ripper.h:96
AVPacket * packet
The unit of output from ffmpeg.
Definition cd_ripper.h:99
AVCodecContext * c
The codec context.
Definition cd_ripper.h:97
AVStream * st
Audio stream.
Definition cd_ripper.h:94
SwrContext * swr_ctx
Context for resampling from int16_t to float.
Definition cd_ripper.h:100
Thrown when an error occurs mid-rip.
Definition cd_ripper.h:51