FFmpegfs Fuse Multi Media Filesystem 2.16
cuesheetparser.cc
Go to the documentation of this file.
1/*
2 * Copyright (C) 2017-2024 Norbert Schlia (nschlia@oblivion-software.de)
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
17 *
18 * On Debian systems, the complete text of the GNU General Public License
19 * Version 3 can be found in `/usr/share/common-licenses/GPL-3'.
20 */
21
32#include "ffmpegfs.h"
33#include "cuesheetparser.h"
34#include "transcode.h"
35#include "logging.h"
36
37#include <libcue.h>
38
39#define FPS (75)
40#define VAL_OR_EMPTY(val) ((val) != nullptr ? (val) : "")
41
97static bool create_cuesheet_virtualfile(LPCVIRTUALFILE virtualfile, Track *track, int titleno, const std::string & path, const struct stat * statbuf, int trackcount, int trackno, const std::string &aperformer, const std::string & album, const std::string & genre, const std::string & date, int64_t *remainingduration, LPVIRTUALFILE *lastfile);
98static int parse_cuesheet_file(LPCVIRTUALFILE virtualfile, const std::string & cuesheet, const struct stat *statbuf, void *buf, fuse_fill_dir_t filler);
99static int parse_cuesheet_text(LPCVIRTUALFILE virtualfile, void *buf, fuse_fill_dir_t filler);
100static int parse_cuesheet(LPCVIRTUALFILE virtualfile, const std::string & cuesheet, Cd *cd, const struct stat *statbuf, void *buf, fuse_fill_dir_t filler);
101
119static bool create_cuesheet_virtualfile(LPCVIRTUALFILE virtualfile, Track *track, int titleno, const std::string & path, const struct stat * statbuf, int trackcount, int trackno, const std::string & aperformer, const std::string & album, const std::string & genre, const std::string & date, int64_t *remainingduration, LPVIRTUALFILE *lastfile)
120{
121 Cdtext *cuesheetcdtext = track_get_cdtext(track);
122 if (cuesheetcdtext == nullptr)
123 {
124 Logging::error(virtualfile->m_origfile, "The track CD text could not be extracted from the cue sheet.");
125 errno = EIO;
126 return false;
127 }
128
129 std::string performer = VAL_OR_EMPTY(cdtext_get(PTI_PERFORMER, cuesheetcdtext));
130 std::string title = VAL_OR_EMPTY(cdtext_get(PTI_TITLE, cuesheetcdtext));
131
132 if (performer.empty())
133 {
134 // If track performer is empty, try album performer.
135 performer = aperformer;
136 }
137
138 int64_t start = AV_TIME_BASE * static_cast<int64_t>(track_get_start(track)) / FPS;
139 int64_t length = track_get_length(track);
140 int64_t duration;
141
142 if (length > -1)
143 {
144 duration = AV_TIME_BASE * length / FPS;
145 *remainingduration -= duration;
146 }
147 else
148 {
149 // Length of
150 duration = *remainingduration;
151 }
152
153 std::string virtfilename;
154
155 strsprintf(&virtfilename, "%02d. %s - %s [%s].%s",
156 titleno,
157 performer.c_str(),
158 title.c_str(),
159 replace_all(format_duration(duration), ":", "-").c_str(),
160 ffmpeg_format[virtualfile->m_format_idx].fileext().c_str());
161
162 // Filenames can't contain '/' in POSIX etc.
163 std::replace(virtfilename.begin(), virtfilename.end(), '/', '-');
164
165 LPVIRTUALFILE newvirtualfile = nullptr;
166 if (!ffmpeg_format[FORMAT::VIDEO].is_multiformat())
167 {
168 newvirtualfile = insert_file(VIRTUALTYPE::DISK, path + virtfilename, virtualfile->m_origfile, statbuf, VIRTUALFLAG_CUESHEET);
169 }
170 else
171 {
172 newvirtualfile = insert_dir(VIRTUALTYPE::DISK, path + virtfilename, statbuf, VIRTUALFLAG_CUESHEET);
173 }
174
175 if (newvirtualfile == nullptr)
176 {
177 Logging::error(path, "Failed to create virtual path: %1", (path + virtfilename).c_str());
178 errno = EIO;
179 return false;
180 }
181
182 // We do not add the file to fuse here because it's in a sub directory.
183 // Will be done later on request by load_path()
184
185 newvirtualfile->m_format_idx = virtualfile->m_format_idx; // Store the correct index (audio) in m_format_idx
186
187 if (!transcoder_cached_filesize(newvirtualfile, &newvirtualfile->m_st))
188 {
189 BITRATE video_bit_rate = 0;
190 BITRATE audio_bit_rate = 0;
191
192 int width = 0;
193 int height = 0;
194 AVRational framerate = { 0, 0 };
195 bool interleaved = false;
196
197 newvirtualfile->m_duration = duration;
198 newvirtualfile->m_cuesheet_track.m_duration = duration;
199 newvirtualfile->m_cuesheet_track.m_start = start;
200 newvirtualfile->m_cuesheet_track.m_tracktotal = trackcount;
201 newvirtualfile->m_cuesheet_track.m_trackno = trackno;
202 newvirtualfile->m_cuesheet_track.m_artist = performer;
203 newvirtualfile->m_cuesheet_track.m_title = title;
204 newvirtualfile->m_cuesheet_track.m_album = album;
205 newvirtualfile->m_cuesheet_track.m_genre = genre;
206 newvirtualfile->m_cuesheet_track.m_date = date;
207 if (*lastfile != nullptr)
208 {
209 (*lastfile)->m_cuesheet_track.m_nextfile = newvirtualfile;
210 }
211 *lastfile = newvirtualfile;
212
213 transcoder_set_filesize(newvirtualfile, duration, audio_bit_rate, virtualfile->m_channels, virtualfile->m_sample_rate, AV_SAMPLE_FMT_NONE, video_bit_rate, width, height, interleaved, framerate);
214
215 stat_set_size(&newvirtualfile->m_st, newvirtualfile->m_predicted_size);
216 }
217
218 return true;
219}
220
230static int parse_cuesheet_file(LPCVIRTUALFILE virtualfile, const std::string & cuesheet, const struct stat *statbuf, void *buf, fuse_fill_dir_t filler)
231{
232 // Check for cue sheet
233 std::string text;
234 int res = read_file(cuesheet, text);
235 if (res >= 0)
236 {
237 return -res;
238 }
239
240 // Found cue sheet
241 Logging::trace(cuesheet, "Found an external cue sheet file.");
242
243 return parse_cuesheet(virtualfile, cuesheet, cue_parse_string(text.c_str()), statbuf, buf, filler);
244}
245
253static int parse_cuesheet_text(LPCVIRTUALFILE virtualfile, void *buf, fuse_fill_dir_t filler)
254{
255 // Found cue sheet
256 Logging::trace(virtualfile->m_origfile, "Found an embedded cue sheet file.");
257
258 return parse_cuesheet(virtualfile, virtualfile->m_origfile, cue_parse_string(virtualfile->m_cuesheet.c_str()), &virtualfile->m_st, buf, filler);
259}
260
271static int parse_cuesheet(LPCVIRTUALFILE virtualfile, const std::string & cuesheet, Cd *cd, const struct stat *statbuf, void *buf, fuse_fill_dir_t filler)
272{
273 int res = 0;
274
275 try
276 {
277 if (cd == nullptr)
278 {
279 Logging::error(cuesheet, "The cue sheet could not be parsed.");
280 throw AVERROR(EIO);
281 }
282
283 Rem *rem = cd_get_rem(cd);
284 if (rem == nullptr)
285 {
286 Logging::error(cuesheet, "Unable to parse remarks from the cue sheet.");
287 throw AVERROR(EIO);
288 }
289
290 Cdtext *cdtext = cd_get_cdtext(cd);
291 if (cdtext == nullptr)
292 {
293 Logging::error(cuesheet, "The CD text could not be extracted from the cue sheet.");
294 throw AVERROR(EIO);
295 }
296
297 std::string performer = VAL_OR_EMPTY(cdtext_get(PTI_PERFORMER, cdtext));
298 std::string album = VAL_OR_EMPTY(cdtext_get(PTI_TITLE, cdtext));
299 std::string genre = VAL_OR_EMPTY(cdtext_get(PTI_GENRE, cdtext));
300 std::string date = VAL_OR_EMPTY(rem_get(REM_DATE, rem));
301
302 int trackcount = static_cast<int>(cd_get_ntrack(cd));
303 if (trackcount)
304 {
305 LPVIRTUALFILE insertedvirtualfile = nullptr;
306 std::string subbdir(virtualfile->m_origfile);
307
308 append_ext(&subbdir, TRACKDIR);
309
310 std::string dirname(subbdir);
311
312 append_sep(&subbdir);
313 remove_path(&dirname);
314
315 insertedvirtualfile = insert_dir(VIRTUALTYPE::DISK, subbdir, statbuf, VIRTUALFLAG_CUESHEET);
316
317 if (insertedvirtualfile == nullptr)
318 {
319 Logging::error(cuesheet, "Failed to create virtual path: %1", subbdir.c_str());
320 errno = EIO;
321 return -errno;
322 }
323
324 if (buf != nullptr && filler(buf, dirname.c_str(), &insertedvirtualfile->m_st, 0))
325 {
326 // break;
327 }
328
329 std::string path(virtualfile->m_origfile);
330
331 remove_filename(&path);
332
333 LPVIRTUALFILE lastfile = nullptr;
334 int64_t remainingduration = virtualfile->m_duration;
335
336 for (int trackno = 1; trackno <= trackcount; trackno++)
337 {
338 Track *track = cd_get_track(cd, trackno);
339 if (track == nullptr)
340 {
341 Logging::error(cuesheet, "Track no. %1 could not be obtained from the cue sheet.", trackno);
342 errno = EIO;
343 throw -errno;
344 }
345
346 if (!create_cuesheet_virtualfile(virtualfile, track, trackno, path + dirname + "/", statbuf, trackcount, trackno, performer, album, genre, date, &remainingduration, &lastfile))
347 {
348 throw -errno;
349 }
350 }
351 }
352
353 res = trackcount;
354 }
355 catch (int _res)
356 {
357 res = _res;
358 }
359
360 if (cd != nullptr)
361 {
362 cd_delete(cd);
363 }
364
365 return res;
366}
367
368int check_cuesheet(const std::string & filename, void *buf, fuse_fill_dir_t filler)
369{
370 std::string trackdir(filename); // Tracks directory (with extra TRACKDIR extension)
371 std::string cuesheet(filename); // Cue sheet name (original name, extension replaced by .cue)
372 struct stat stbuf;
373 int res = 0;
374
375 append_ext(&trackdir, TRACKDIR); // Need to add TRACKDIR to file name
376 append_sep(&trackdir);
377 replace_ext(&cuesheet, "cue"); // Get the cue sheet file name by replacing the extension with .cue
378
379 try
380 {
381 LPCVIRTUALFILE virtualfile = find_file_from_orig(filename);
382
383 if (virtualfile == nullptr)
384 {
385 // Should never happen...
386 Logging::error(filename, "INTERNAL ERROR: check_cuesheet()! virtualfile is NULL.");
387 errno = EINVAL;
388 throw -errno;
389 }
390
391 if (stat(filename.c_str(), &stbuf) == -1)
392 {
393 // Media file does not exist, can be ignored silently
394 throw 0;
395 }
396
397 if (stat(cuesheet.c_str(), &stbuf) != -1)
398 {
399 // Cue sheet file exists, preferrably use its contents
400 if (!check_path(trackdir))
401 {
402 // Not a virtual directory
403 res = parse_cuesheet_file(virtualfile, cuesheet, &stbuf, buf, filler);
404
405 Logging::trace(cuesheet, "%1 titles were discovered.", res);
406 }
407 else
408 {
409 // Obviously a virtual directory, simply add it
410 std::string dirname(trackdir);
411
412 remove_path(&dirname);
413
414 LPCVIRTUALFILE virtualdir = find_file(trackdir);
415
416 if (virtualdir == nullptr)
417 {
418 Logging::error(filename, "INTERNAL ERROR: check_cuesheet()! virtualdir is NULL.");
419 errno = EIO;
420 throw -errno;
421 }
422
423 if (buf != nullptr && filler(buf, dirname.c_str(), &virtualdir->m_st, 0))
424 {
425 // break;
426 }
427
428 res = 0;
429 }
430 }
431 else if (!virtualfile->m_cuesheet.empty())
432 {
433 // No cue sheet file, but there is one embedded in media file
434 res = parse_cuesheet_text(virtualfile, buf, filler);
435 }
436 }
437 catch (int _res)
438 {
439 res = _res;
440 }
441 return res;
442}
static void trace(const T filename, const std::string &format_string, Args &&...args)
Write trace level log entry.
Definition: logging.h:163
static void error(const T filename, const std::string &format_string, Args &&...args)
Write error level log entry.
Definition: logging.h:239
static bool create_cuesheet_virtualfile(LPCVIRTUALFILE virtualfile, Track *track, int titleno, const std::string &path, const struct stat *statbuf, int trackcount, int trackno, const std::string &aperformer, const std::string &album, const std::string &genre, const std::string &date, int64_t *remainingduration, LPVIRTUALFILE *lastfile)
Cuesheet structure Structure see https://en.wikipedia.org/wiki/Cue_sheet_(computing) Real life exam...
static int parse_cuesheet_text(LPCVIRTUALFILE virtualfile, void *buf, fuse_fill_dir_t filler)
Parse a cue sheet and build virtual set of files.
static int parse_cuesheet_file(LPCVIRTUALFILE virtualfile, const std::string &cuesheet, const struct stat *statbuf, void *buf, fuse_fill_dir_t filler)
Parse a cue sheet file and build virtual set of files.
static int parse_cuesheet(LPCVIRTUALFILE virtualfile, const std::string &cuesheet, Cd *cd, const struct stat *statbuf, void *buf, fuse_fill_dir_t filler)
Parse a cue sheet and build virtual set of files.
#define VAL_OR_EMPTY(val)
#define FPS
int check_cuesheet(const std::string &filename, void *buf, fuse_fill_dir_t filler)
Get number of titles in cue sheet.
Clue sheet parser.
#define TRACKDIR
const std::string & remove_path(std::string *filepath)
Remove path from filename. Handy basename alternative.
std::string format_duration(int64_t value, uint32_t fracs)
Format a time in format HH:MM:SS.fract.
int read_file(const std::string &path, std::string &result)
Read text file and return in UTF-8 format, no matter in which encoding the input file is....
const std::string & remove_filename(std::string *filepath)
Remove filename from path. Handy dirname alternative.
const std::string & replace_ext(std::string *filepath, const std::string &ext)
Replace extension in filename, taking into account that there might not be an extension already.
const std::string & append_ext(std::string *filepath, const std::string &ext)
Append extension to filename. If ext is the same as.
const std::string & append_sep(std::string *path)
Add / to the path if required.
void stat_set_size(struct stat *st, size_t size)
Properly fill in all size related members in stat struct.
std::string replace_all(std::string str, const std::string &from, const std::string &to)
Same as std::string replace(), but replaces all occurrences.
#define BITRATE
For FFmpeg bit rate is an int.
Definition: ffmpeg_utils.h:145
const std::string & strsprintf(std::string *str, const std::string &format, Args ... args)
Format a std::string sprintf-like.
Definition: ffmpeg_utils.h:737
@ VIDEO
FFmpegfs_Format info, 0: video file.
Definition: ffmpeg_utils.h:517
FFMPEGFS_FORMAT_ARR ffmpeg_format
Two FFmpegfs_Format infos, 0: video file, 1: audio file.
Definition: ffmpegfs.cc:73
Main include for FFmpegfs project.
LPVIRTUALFILE find_file(const std::string &virtfile)
Find file in cache.
Definition: fuseops.cc:1734
LPVIRTUALFILE insert_dir(VIRTUALTYPE type, const std::string &virtdir, const struct stat *stbuf, int flags=VIRTUALFLAG_NONE)
Add new virtual directory to the internal list. If the file already exists, it will be updated.
Definition: fuseops.cc:1717
bool check_path(const std::string &path)
Check if the path has already been parsed. Only useful if for DVD, Blu-ray or VCD where it is guarant...
Definition: fuseops.cc:1752
LPVIRTUALFILE find_file_from_orig(const std::string &origfile)
Look for the file in the cache.
Definition: fuseops.cc:1743
LPVIRTUALFILE insert_file(VIRTUALTYPE type, const std::string &virtfile, const struct stat *stbuf, int flags=VIRTUALFLAG_NONE)
Add new virtual file to internal list.
Definition: fuseops.cc:1638
@ DISK
Regular disk file to transcode.
#define VIRTUALFLAG_CUESHEET
File is part of a set of cue sheet tracks or the directory.
Definition: fileio.h:117
VIRTUALFILE const * LPCVIRTUALFILE
Pointer to const version of VIRTUALFILE.
Definition: fileio.h:254
Provide various log facilities to stderr, disk or syslog.
std::string m_artist
Track artist.
Definition: fileio.h:232
int64_t m_duration
Track/chapter duration, in AV_TIME_BASE fractional seconds.
Definition: fileio.h:239
std::string m_album
Album title.
Definition: fileio.h:234
std::string m_title
Track title.
Definition: fileio.h:233
int64_t m_start
Track start time, in AV_TIME_BASE fractional seconds.
Definition: fileio.h:238
int m_trackno
Track number.
Definition: fileio.h:231
int m_tracktotal
Total number of tracks in cue sheet.
Definition: fileio.h:230
std::string m_genre
Album genre.
Definition: fileio.h:235
std::string m_date
Publishing date.
Definition: fileio.h:236
Virtual file definition.
Definition: fileio.h:123
size_t m_predicted_size
Use this as the size instead of computing it over and over.
Definition: fileio.h:157
int m_channels
Audio channels - Filled in for the DVD/Blu-ray directory.
Definition: fileio.h:246
struct stat m_st
stat structure with size etc.
Definition: fileio.h:153
size_t m_format_idx
Index into params.format[] array.
Definition: fileio.h:149
struct VIRTUALFILE::CUESHEET_TRACK m_cuesheet_track
Cue sheet data for track.
int64_t m_duration
Track/chapter duration, in AV_TIME_BASE fractional seconds.
Definition: fileio.h:156
bool transcoder_set_filesize(LPVIRTUALFILE virtualfile, int64_t duration, BITRATE audio_bit_rate, int channels, int sample_rate, AVSampleFormat sample_format, BITRATE video_bit_rate, int width, int height, bool interleaved, const AVRational &framerate)
Set the file size.
Definition: transcode.cc:288
bool transcoder_cached_filesize(LPVIRTUALFILE virtualfile, struct stat *stbuf)
Simply get encoded file size (do not create the whole encoder/decoder objects)
Definition: transcode.cc:261
File transcoder interface (for use with by FUSE)