FFmpegfs Fuse Multi Media Filesystem 2.16
fuseops.cc
Go to the documentation of this file.
1/*
2 * Copyright (C) 2006-2008 David Collett
3 * Copyright (C) 2008-2012 K. Henriksson
4 * Copyright (C) 2017-2024 FFmpeg support by Norbert Schlia (nschlia@oblivion-software.de)
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
19 *
20 * On Debian systems, the complete text of the GNU General Public License
21 * Version 3 can be found in `/usr/share/common-licenses/GPL-3'.
22 */
23
36#include "transcode.h"
37#include "cache_maintenance.h"
38#include "logging.h"
39#ifdef USE_LIBVCD
40#include "vcdparser.h"
41#endif // USE_LIBVCD
42#ifdef USE_LIBDVD
43#include "dvdparser.h"
44#endif // USE_LIBDVD
45#ifdef USE_LIBBLURAY
46#include "blurayparser.h"
47#endif // USE_LIBBLURAY
48#include "cuesheetparser.h"
49#include "thread_pool.h"
50#include "buffer.h"
51#include "cache_entry.h"
52
53#include <dirent.h>
54#include <list>
55#include <csignal>
56#include <cstring>
57
58typedef std::map<const std::string, VIRTUALFILE> FILENAME_MAP;
59typedef std::map<const std::string, VIRTUALFILE> RFILENAME_MAP;
61static void init_stat(struct stat *stbuf, size_t fsize, time_t ftime, bool directory);
62static LPVIRTUALFILE make_file(void *buf, fuse_fill_dir_t filler, VIRTUALTYPE type, const std::string & origpath, const std::string & filename, size_t fsize, time_t ftime = time(nullptr), int flags = VIRTUALFLAG_NONE);
63static void prepare_script();
64static bool is_passthrough(const std::string & ext);
65static bool virtual_name(std::string *virtualpath, const std::string &origpath = "", const FFmpegfs_Format **current_format = nullptr);
66static FILENAME_MAP::const_iterator find_prefix(const FILENAME_MAP & map, const std::string & search_for);
67static void stat_to_dir(struct stat *stbuf);
68static void flags_to_dir(int *flags);
69static void insert(const VIRTUALFILE & virtualfile);
70static int get_source_properties(const std::string & origpath, LPVIRTUALFILE virtualfile);
71static int make_hls_fileset(void * buf, fuse_fill_dir_t filler, const std::string & origpath, LPVIRTUALFILE virtualfile);
72static int kick_next(LPVIRTUALFILE virtualfile);
73static void sighandler(int signum);
74static std::string get_number(const char *path, uint32_t *value);
75static size_t guess_format_idx(const std::string & filepath);
76static int parse_file(LPVIRTUALFILE newvirtualfile);
77static const FFmpegfs_Format * get_format(LPVIRTUALFILE newvirtualfile);
78static int selector(const struct dirent * de);
79static int scandir(const char *dirp, std::vector<struct dirent> * _namelist, int (*selector) (const struct dirent *), int (*cmp) (const struct dirent **, const struct dirent **));
80
81static int ffmpegfs_readlink(const char *path, char *buf, size_t size);
82static int ffmpegfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi);
83static int ffmpegfs_getattr(const char *path, struct stat *stbuf);
84static int ffmpegfs_fgetattr(const char *path, struct stat * stbuf, struct fuse_file_info *fi);
85static int ffmpegfs_open(const char *path, struct fuse_file_info *fi);
86static int ffmpegfs_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi);
87static int ffmpegfs_statfs(const char *path, struct statvfs *stbuf);
88static int ffmpegfs_release(const char *path, struct fuse_file_info *fi);
89static void * ffmpegfs_init(struct fuse_conn_info *conn);
90static void ffmpegfs_destroy(__attribute__((unused)) void * p);
91
94static std::vector<char> script_file;
96static struct sigaction oldHandler;
100fuse_operations ffmpegfs_ops;
102std::unique_ptr<thread_pool> tp;
109static bool is_passthrough(const std::string & ext)
110{
115 static const std::set<std::string, comp> passthrough_set =
116 {
117 "AA",
118 "ACR", // Dicom/ACR/IMA file format for medical images
119 "AI", // PostScript Formats (Ghostscript required)
120 "ANI", // Animated Cursor
121 "ARW", // Digital camera RAW formats (Adobe, Epson, Nikon, Minolta, Olympus, Fuji, Kodak, Sony, Pentax, Sigma)
122 "AWD", // Artweaver format
123 "B3D", // BodyPaint 3D format
124 "BMP", // Windows Bitmap
125 "CAM", // Casio digital camera format (JPG version only)
126 "CEL",
127 "CGM", // CAD Formats (Shareware PlugIns)
128 "CIN", // Digital Picture Exchange/Cineon Format
129 "CLP", // Windows Clipboard
130 "CPT", // CorelDraw Photopaint format (CPT version 6 only)
131 "CR2", // Canon RAW format
132 "CRW", // Canon RAW format
133 "CUR", // Animated Cursor
134 "DCM", // Dicom/ACR/IMA file format for medical images
135 "DCR", // Digital camera RAW formats (Adobe, Epson, Nikon, Minolta, Olympus, Fuji, Kodak, Sony, Pentax, Sigma)
136 "DCX", // Multipage PCX format
137 "DDS", // Direct Draw Surface format
138 "DIB", // Windows Bitmap
139 "DJVU", // DjVu File Format
140 "DNG", // Digital camera RAW formats (Adobe, Epson, Nikon, Minolta, Olympus, Fuji, Kodak, Sony, Pentax, Sigma)
141 "DPX", // Digital Picture Exchange/Cineon Format
142 "DWG", // CAD Formats (Shareware PlugIns)
143 "DXF", // Drawing Interchange Format, CAD format
144 "ECW", // Enhanced Compressed Wavelet
145 "EEF", // Digital camera RAW formats (Adobe, Epson, Nikon, Minolta, Olympus, Fuji, Kodak, Sony, Pentax, Sigma)
146 "EMF", // Enhanced Metafile Format
147 "EPS", // PostScript Formats (Ghostscript required)
148 "EXR", // EXR format
149 "FITS", // Flexible Image Transport System
150 "FLI",
151 "FLIF", // Free Lossless Image format
152 "FPX", // FlashPix format
153 "G3", // Group 3 Facsimile Apparatus format
154 "GIF", // Graphics Interchange Format
155 "HDP", // JPEG-XR/Microsoft HD Photo format
156 "HDR", // High Dynamic Range format
157 "HEIC", // High Efficiency Image format
158 "HPGL", // CAD Formats (Shareware PlugIns)
159 "HRZ",
160 "ICL", // Icon Library formats
161 "ICO", // Windows Icon
162 "ICS", // Image Cytometry Standard format
163 "IFF", // Interchange File Format
164 "IMA", // Dicom/ACR/IMA file format for medical images
165 "IMG", // GEM Raster format
166 "IW44", // DjVu File Format
167 "J2K", // JPEG 2000 format
168 "JLS", // JPEG-LS, JPEG Lossless
169 "JNG", // Multiple Network Graphics
170 "JP2", // JPEG 2000 format
171 "JPC", // JPEG 2000 format
172 "JPEG", // Joint Photographic Experts Group
173 "JPG", // Joint Photographic Experts Group
174 "JPM", // JPEG2000/Part6, LuraDocument.jpm
175 "JXR", // JPEG-XR/Microsoft HD Photo format
176 "KDC", // Kodak digital camera format
177 "LBM", // Interchange File Format
178 "M3U8", // Apple HTTP Live Streaming
179 "MIFF",
180 "MNG", // Multiple Network Graphics
181 "MRC", // MRC format
182 "MrSID", // LizardTech's SID Wavelet format
183 "MRW", // Digital camera RAW formats (Adobe, Epson, Nikon, Minolta, Olympus, Fuji, Kodak, Sony, Pentax, Sigma)
184 "NEF", // Digital camera RAW formats (Adobe, Epson, Nikon, Minolta, Olympus, Fuji, Kodak, Sony, Pentax, Sigma)
185 "NRW", // Digital camera RAW formats (Adobe, Epson, Nikon, Minolta, Olympus, Fuji, Kodak, Sony, Pentax, Sigma)
186 "ORF", // Digital camera RAW formats (Adobe, Epson, Nikon, Minolta, Olympus, Fuji, Kodak, Sony, Pentax, Sigma)
187 "PBM", // Portable Bitmap format
188 "PCD", // Kodak Photo CD
189 "PCX", // PC Paintbrush format from ZSoft Corporation
190 "PDF", // PostScript Formats (Ghostscript required)
191 "PDF", // Portable Document format
192 "PDN", // Paint.NET file format
193 "PEF", // Digital camera RAW formats (Adobe, Epson, Nikon, Minolta, Olympus, Fuji, Kodak, Sony, Pentax, Sigma)
194 "PGM", // Portable Greymap format
195 "PICT", // Macintosh PICT format
196 "PIX",
197 "PNG", // Portable Network Graphics
198 "PNM",
199 "PPM", // Portable Pixelmap format
200 "PS", // PostScript Formats (Ghostscript required)
201 "PSD", // Adobe PhotoShop format
202 "PSP", // Paint Shop Pro format
203 "PVR", // DreamCast Texture format
204 "QTIF", // Macintosh PICT format
205 "RAF", // Digital camera RAW formats (Adobe, Epson, Nikon, Minolta, Olympus, Fuji, Kodak, Sony, Pentax, Sigma)
206 "RAS", // Sun Raster format
207 "RAW", // Raw (binary) data
208 "RGB", // Silicon Graphics format
209 "RLE", // Utah RLE format
210 "RW2", // Digital camera RAW formats (Adobe, Epson, Nikon, Minolta, Olympus, Fuji, Kodak, Sony, Pentax, Sigma)
211 "SFF", // Structured Fax File
212 "SFW", // Seattle Film Works format
213 "SGI", // Silicon Graphics format
214 "SID", // LizardTech's SID Wavelet format
215 "SIF", // SIF format
216 "SRF", // Digital camera RAW formats (Adobe, Epson, Nikon, Minolta, Olympus, Fuji, Kodak, Sony, Pentax, Sigma)
217 "SUN", // Sun Raster format
218 "Sunras",
219 "SVG", // CAD Formats (Shareware PlugIns)
220 "TGA", // Truevision Advanced Raster Graphics Adapter (TARGA)
221 "TIF", // Tagged Image File Format
222 "TIFF",
223 "TTF", // True Type Font
224 "TXT", // Text (ASCII) File (as image)
225 "VTF", // Valve Texture format
226 "WAD", // WAD3 Game format
227 "WAL", // Quake 2 textures
228 "WBC", // Webshots formats
229 "WBZ", // Webshots formats
230 "WBMP", // WAP Bitmap format
231 "WDP", // JPEG-XR/Microsoft HD Photo format
232 "WebP", // Weppy file format
233 "WMF", // Windows Metafile Format
234 "WSQ", // Wavelet Scaler Quantization format
235 "X",
236 "X3F", // Digital camera RAW formats (Adobe, Epson, Nikon, Minolta, Olympus, Fuji, Kodak, Sony, Pentax, Sigma)
237 "XBM", // X11 Bitmap
238 "XCF", // GIMP file format
239 "XPM",
240 "XWD",
241 "YUV" // Raw (binary) data
242 };
243
244 return (passthrough_set.find(ext) != passthrough_set.cend());
245}
246
248{
249 std::memset(&ffmpegfs_ops, 0, sizeof(fuse_operations));
260}
261
269static int ffmpegfs_readlink(const char *path, char *buf, size_t size)
270{
271 std::string origpath;
272 std::string transcoded;
273 ssize_t len;
274
275 Logging::trace(path, "readlink");
276
277 append_basepath(&origpath, path);
278 find_original(&origpath);
279
280 len = readlink(origpath.c_str(), buf, size - 2);
281 if (len != -1)
282 {
283 buf[len] = '\0';
284
285 transcoded = buf;
286 virtual_name(&transcoded, origpath);
287
288 buf[0] = '\0';
289 strncat(buf, transcoded.c_str(), size);
290
291 errno = 0; // Just to make sure - reset any error
292 }
293
294 return -errno;
295}
296
304static int ffmpegfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t /*offset*/, struct fuse_file_info * /*fi*/)
305{
306 std::string origpath;
307
308 Logging::trace(path, "readdir");
309
310 append_basepath(&origpath, path);
311 append_sep(&origpath);
312
313 // Add a virtual script if enabled
315 {
316 LPVIRTUALFILE virtualfile = make_file(buf, filler, VIRTUALTYPE::SCRIPT, origpath, params.m_scriptfile, script_file.size());
317 virtualfile->m_file_contents = script_file;
318 }
319
320 LPVIRTUALFILE virtualfile = find_original(origpath);
321
322 if (virtualfile == nullptr)
323 {
324#if defined(USE_LIBBLURAY) || defined(USE_LIBDVD) || defined(USE_LIBVCD)
325 int res;
326#endif
327
328#ifdef USE_LIBVCD
329 res = check_vcd(origpath, buf, filler);
330 if (res != 0)
331 {
332 // Found VCD or error reading VCD
333 return (res >= 0 ? 0 : res);
334 }
335#endif // USE_LIBVCD
336#ifdef USE_LIBDVD
337 res = check_dvd(origpath, buf, filler);
338 if (res != 0)
339 {
340 // Found DVD or error reading DVD
341 return (res >= 0 ? 0 : res);
342 }
343#endif // USE_LIBDVD
344#ifdef USE_LIBBLURAY
345 res = check_bluray(origpath, buf, filler);
346 if (res != 0)
347 {
348 // Found Blu-ray or error reading Blu-ray
349 return (res >= 0 ? 0 : res);
350 }
351#endif // USE_LIBBLURAY
352 }
353
354 if (virtualfile == nullptr || !(virtualfile->m_flags & VIRTUALFLAG_FILESET))
355 {
356 DIR *dp = opendir(origpath.c_str());
357 if (dp != nullptr)
358 {
359 try
360 {
361 std::map<const std::string, struct stat> files;
362
363 // Read directory contents
364 for (struct dirent *de = readdir(dp); de != nullptr; de = readdir(dp))
365 {
366 struct stat stbuf;
367
368 if (lstat((origpath + de->d_name).c_str(), &stbuf) == -1)
369 {
370 // Should actually not happen, file listed by readdir, so it should exist
371 throw ENOENT;
372 }
373
374 files.insert({ de->d_name, stbuf });
375 }
376
377 // Process files
378 for (auto& [key, value] : files)
379 {
380 std::string origname(key);
381
382 if (is_blocked(origname))
383 {
384 continue;
385 }
386
387 std::string origfile;
388 std::string filename(key);
389 struct stat & stbuf = value;
390 int flags = 0;
391
392 origfile = origpath + origname;
393
394 std::string origext;
395 find_ext(&origext, filename);
396
397 if (S_ISREG(stbuf.st_mode) || S_ISLNK(stbuf.st_mode))
398 {
399 const FFmpegfs_Format *current_format = nullptr;
400
401 // Check if file can be transcoded
402 if (virtual_name(&filename, origpath, &current_format))
403 {
404 if (current_format->video_codec() == AV_CODEC_ID_NONE)
405 {
406 LPVIRTUALFILE newvirtualfile = find_file_from_orig(origfile);
407
408 if (newvirtualfile == nullptr)
409 {
410 // Should never happen, we've just created this entry in virtual_name...
411 Logging::error(origfile, "INTERNAL ERROR: ffmpegfs_readdir()! newvirtualfile is NULL.");
412 throw EINVAL;
413 }
414
415 // If target supports no video, we need to do some extra work and checkF
416 // the input file to actually have an audio stream. If not, hide the file,
417 // makes no sense to transcode anyway.
418 if (!newvirtualfile->m_has_audio)
419 {
420 Logging::debug(origfile, "Unable to transcode. The source has no audio stream, but the target just supports audio.");
421 flags |= VIRTUALFLAG_HIDDEN;
422 }
423 }
424
425 if (!(flags & VIRTUALFLAG_HIDDEN))
426 {
427 if (check_cuesheet(origfile, buf, filler) < 0)
428 {
429 throw EINVAL;
430 }
431 }
432
433 std::string newext;
434 find_ext(&newext, filename);
435
436 if (!current_format->is_multiformat())
437 {
438 if (origext != newext || params.m_recodesame == RECODESAME::YES)
439 {
440 insert_file(VIRTUALTYPE::DISK, origpath + filename, origfile, &stbuf, flags);
441 }
442 else
443 {
444 insert_file(VIRTUALTYPE::DISK, origpath + filename, origfile, &stbuf, flags | VIRTUALFLAG_PASSTHROUGH);
445 }
446 }
447 else
448 {
449 // Change file to directory for the frame set
450 stat_to_dir(&stbuf);
451 flags_to_dir(&flags);
452
453 filename = origname; // Restore original name
454
455 insert_file(VIRTUALTYPE::DISK, origfile, &stbuf, flags);
456 }
457 }
458 }
459
460 if (!(flags & VIRTUALFLAG_HIDDEN))
461 {
462 if (add_fuse_entry(buf, filler, filename, &stbuf, 0))
463 {
464 break;
465 }
466 }
467 }
468
469 closedir(dp);
470
471 errno = 0; // Just to make sure - reset any error
472 }
473 catch (int _errno)
474 {
475 closedir(dp);
476
477 errno = _errno;
478 }
479 }
480 }
481 else
482 {
483 try
484 {
486 {
487 add_dotdot(buf, filler, &virtualfile->m_st, 0);
488 }
489
490 const FFmpegfs_Format *ffmpegfs_format = params.current_format(virtualfile);
491
492 if (ffmpegfs_format->is_frameset())
493 {
494 // Generate set of all frames
495 if (!virtualfile->m_video_frame_count)
496 {
497 int res = get_source_properties(origpath, virtualfile);
498 if (res < 0)
499 {
500 throw EINVAL;
501 }
502 }
503
504 //Logging::debug(origpath, "readdir: Creating frame set of %1 frames. %2", virtualfile->m_video_frame_count, virtualfile->m_origfile);
505
506 for (uint32_t frame_no = 1; frame_no <= virtualfile->m_video_frame_count; frame_no++)
507 {
508 make_file(buf, filler, virtualfile->m_type, origpath, make_filename(frame_no, params.current_format(virtualfile)->fileext()), virtualfile->m_predicted_size, virtualfile->m_st.st_ctime, VIRTUALFLAG_FRAME);
509 }
510 }
511 else if (ffmpegfs_format->is_hls())
512 {
513 int res = make_hls_fileset(buf, filler, origpath, virtualfile);
514 if (res < 0)
515 {
516 throw EINVAL;
517 }
518 }
519 else if (/*virtualfile != nullptr && */virtualfile->m_flags & VIRTUALFLAG_CUESHEET)
520 {
521 // Fill in list for cue sheet
522 load_path(origpath, nullptr, buf, filler);
523 }
524
525 errno = 0; // Just to make sure - reset any error
526 }
527 catch (int _errno)
528 {
529 errno = _errno;
530 }
531 }
532
533 return -errno;
534}
535
542static int ffmpegfs_getattr(const char *path, struct stat *stbuf)
543{
544 Logging::trace(path, "getattr");
545
546 if (is_blocked(path))
547 {
548 errno = ENOENT;
549 return -errno;
550 }
551
552 std::string origpath;
553
554 append_basepath(&origpath, path);
555
556 LPVIRTUALFILE virtualfile = find_original(&origpath);
557 VIRTUALTYPE type = (virtualfile != nullptr) ? virtualfile->m_type : VIRTUALTYPE::DISK;
558 int flags = (virtualfile != nullptr) ? virtualfile->m_flags : VIRTUALFLAG_NONE;
559
560 if (virtualfile != nullptr && (virtualfile->m_flags & VIRTUALFLAG_HIDDEN))
561 {
562 errno = ENOENT;
563 return -errno;
564 }
565
566 if (virtualfile == nullptr && lstat(origpath.c_str(), stbuf) == 0)
567 {
568 // File was not yet created as virtual file, but physically exists
569 const FFmpegfs_Format *current_format = nullptr;
570 std::string filename(origpath);
571
572 if (S_ISREG(stbuf->st_mode) && virtual_name(&filename, "", &current_format))
573 {
574 if (!current_format->is_multiformat())
575 {
576 // Regular files
577 Logging::trace(origpath, "getattr: Existing file.");
578 errno = 0;
579 return 0;
580 }
581 else
582 {
583 Logging::trace(origpath, "getattr: Creating frame set directory of file.");
584
585 flags |= VIRTUALFLAG_FILESET;
586
587 if (current_format->is_frameset())
588 {
589 flags |= VIRTUALFLAG_FRAME;
590 }
591 else if (current_format->is_hls())
592 {
593 flags |= VIRTUALFLAG_HLS;
594 }
595 }
596 }
597 else
598 {
599 // Pass-through for regular files
600 Logging::trace(origpath, "getattr: Treating existing file/directory as passthrough.");
601 errno = 0;
602 return 0;
603 }
604 }
605 else if ((flags & VIRTUALFLAG_PASSTHROUGH) && lstat(origpath.c_str(), stbuf) == 0)
606 {
607 // File physically exists and is marked as passthrough
608 Logging::debug(origpath, "getattr: File not recoded because --recodesame=NO.");
609 errno = 0;
610 return 0;
611 }
612 else
613 {
614 // Not really an error.
615 errno = 0;
616 }
617
618 // This is a virtual file
619 bool no_check = false;
620
621 switch (type)
622 {
624 {
625 // Use stored status
626 mempcpy(stbuf, &virtualfile->m_st, sizeof(struct stat));
627 errno = 0;
628 break;
629 }
630#ifdef USE_LIBVCD
631 case VIRTUALTYPE::VCD:
632#endif // USE_LIBVCD
633#ifdef USE_LIBDVD
634 case VIRTUALTYPE::DVD:
635#endif // USE_LIBDVD
636#ifdef USE_LIBBLURAY
638#endif // USE_LIBBLURAY
639 {
640 // Use stored status
641 mempcpy(stbuf, &virtualfile->m_st, sizeof(struct stat));
642 no_check = true; // FILETYPE already known, no need to check again.
644 }
646 {
647 if (virtualfile != nullptr && (flags & (VIRTUALFLAG_FRAME | VIRTUALFLAG_HLS | VIRTUALFLAG_DIRECTORY | VIRTUALFLAG_CUESHEET)))
648 {
649 mempcpy(stbuf, &virtualfile->m_st, sizeof(struct stat));
650
651 errno = 0; // Just to make sure - reset any error
652 break;
653 }
654
655 if (virtualfile == nullptr || !(virtualfile->m_flags & VIRTUALFLAG_FILESET))
656 {
657 if (!no_check && lstat(origpath.c_str(), stbuf) == -1)
658 {
659 // If file does not exist here we can assume it's some sort of virtual file: Regular, DVD, S/VCD, cue sheet track
660 int error = -errno;
661
662 virtualfile = find_original(&origpath);
663
664 if (virtualfile == nullptr)
665 {
666 std::string pathonly(origpath);
667 int res = 0;
668
669 remove_filename(&pathonly);
670
671#ifdef USE_LIBVCD
672 //if (res <= 0) Not necessary here, will always be true
673 {
674 // Returns -errno or number or titles on VCD
675 res = check_vcd(pathonly);
676 }
677#endif // USE_LIBVCD
678#ifdef USE_LIBDVD
679 if (res <= 0)
680 {
681 // Returns -errno or number or titles on DVD
682 res = check_dvd(pathonly);
683 }
684#endif // USE_LIBDVD
685#ifdef USE_LIBBLURAY
686 if (res <= 0)
687 {
688 // Returns -errno or number or titles on Blu-ray
689 res = check_bluray(pathonly);
690 }
691#endif // USE_LIBBLURAY
692 if (res <= 0)
693 {
694 if (check_ext(TRACKDIR, origpath))
695 {
696 std::string origfile(origpath);
697
698 remove_ext(&origfile); // remove TRACKDIR extension to get original media file name
699
700 Logging::trace(origfile, "getattr: Checking for cue sheet.");
701
702 if (virtual_name(&origfile))
703 {
704 // Returns -errno or number or titles in cue sheet
705 res = check_cuesheet(origfile);
706 }
707 }
708 }
709
710 if (ffmpeg_format[FORMAT::VIDEO].is_frameset())
711 {
712 LPVIRTUALFILE parent_file = find_parent(origpath);
713
714 if (parent_file != nullptr && (parent_file->m_flags & VIRTUALFLAG_DIRECTORY) && (parent_file->m_flags & VIRTUALFLAG_FILESET))
715 {
716 // Generate set of all frames
717 if (!parent_file->m_video_frame_count)
718 {
719 int res2 = get_source_properties(origpath, parent_file);
720 if (res2 < 0)
721 {
722 return res2;
723 }
724 }
725
726 for (uint32_t frame_no = 1; frame_no <= parent_file->m_video_frame_count; frame_no++)
727 {
728 make_file(nullptr, nullptr, parent_file->m_type, parent_file->m_destfile + "/", make_filename(frame_no, params.current_format(parent_file)->fileext()), parent_file->m_predicted_size, parent_file->m_st.st_ctime, VIRTUALFLAG_FRAME);
729 }
730
731 LPVIRTUALFILE virtualfile2 = find_original(origpath);
732 if (virtualfile2 == nullptr)
733 {
734 // File does not exist
735 return -ENOENT;
736 }
737
738 mempcpy(stbuf, &virtualfile2->m_st, sizeof(struct stat));
739
740 // Clear errors
741 errno = 0;
742
743 return 0;
744 }
745 }
746 else if (ffmpeg_format[FORMAT::VIDEO].is_hls())
747 {
748 LPVIRTUALFILE parent_file = find_parent(origpath);
749
750 if (parent_file != nullptr && (parent_file->m_flags & VIRTUALFLAG_DIRECTORY) && (parent_file->m_flags & VIRTUALFLAG_FILESET))
751 {
752 if (!parent_file->m_video_frame_count) //***< @todo HLS format: Do audio files source properties get checked over and over?
753 {
754 int res2 = get_source_properties(origpath, parent_file);
755 if (res2 < 0)
756 {
757 return res2;
758 }
759 }
760
761 make_hls_fileset(nullptr, nullptr, parent_file->m_destfile + "/", parent_file);
762
763 LPVIRTUALFILE virtualfile2 = find_original(origpath);
764 if (virtualfile2 == nullptr)
765 {
766 // File does not exist
767 return -ENOENT;
768 }
769
770 mempcpy(stbuf, &virtualfile2->m_st, sizeof(struct stat));
771
772 // Clear errors
773 errno = 0;
774
775 return 0;
776 }
777 }
778
779 if (res <= 0)
780 {
781 // No Blu-ray/DVD/VCD found or error reading disk
782 return (!res ? error : res);
783 }
784 }
785
786 virtualfile = find_original(&origpath);
787
788 if (virtualfile == nullptr)
789 {
790 // Not a DVD/VCD/Blu-ray file or cue sheet track
791 return -ENOENT;
792 }
793
794 mempcpy(stbuf, &virtualfile->m_st, sizeof(struct stat));
795 }
796
797 if (flags & VIRTUALFLAG_FILESET)
798 {
799 int flags2 = 0;
800
801 // Change file to virtual directory for the frame set. Keep permissions.
802 stat_to_dir(stbuf);
803 flags_to_dir(&flags2);
804
805 append_sep(&origpath);
806
807 insert_file(type, origpath, stbuf, flags2);
808 }
809 else if (S_ISREG(stbuf->st_mode))
810 {
811 // Get size for resulting output file from regular file, otherwise it's a symbolic link or a virtual frame set.
812 if (virtualfile == nullptr)
813 {
814 // We should not never end here - report bad file number.
815 return -EBADF;
816 }
817
818 if (!transcoder_cached_filesize(virtualfile, stbuf))
819 {
820 Cache_Entry* cache_entry = transcoder_new(virtualfile, false);
821 if (cache_entry == nullptr)
822 {
823 return -errno;
824 }
825
826 stat_set_size(stbuf, transcoder_get_size(cache_entry));
827
828 transcoder_delete(cache_entry);
829 }
830 }
831
832 errno = 0; // Just to make sure - reset any error
833 }
834 else // if (virtualfile != nullptr && (virtualfile->m_flags & VIRTUALFLAG_DIRECTORY))
835 {
836 // Frame set, simply report stat.
837 mempcpy(stbuf, &virtualfile->m_st, sizeof(struct stat));
838 errno = 0;
839 }
840 break;
841 }
842 // We should never come here but this shuts up a warning
845 {
846 break;
847 }
848 }
849
850 return 0;
851}
852
860static int ffmpegfs_fgetattr(const char *path, struct stat * stbuf, struct fuse_file_info *fi)
861{
862 std::string origpath;
863
864 Logging::trace(path, "fgetattr");
865
866 errno = 0;
867
868 append_basepath(&origpath, path);
869
870 LPCVIRTUALFILE virtualfile = find_original(&origpath);
871
872 if (virtualfile != nullptr && (virtualfile->m_flags & VIRTUALFLAG_HIDDEN))
873 {
874 errno = ENOENT;
875 return -errno;
876 }
877
878 if ((virtualfile == nullptr || (virtualfile->m_flags & VIRTUALFLAG_PASSTHROUGH)) && lstat(origpath.c_str(), stbuf) == 0)
879 {
880 // passthrough for regular files
881 errno = 0;
882 return 0;
883 }
884 else
885 {
886 // Not really an error.
887 errno = 0;
888 }
889
890 // This is a virtual file
891
892 bool no_check = false;
893
894 switch (virtualfile->m_type)
895 {
896#ifdef USE_LIBVCD
897 case VIRTUALTYPE::VCD:
898#endif // USE_LIBVCD
899#ifdef USE_LIBDVD
900 case VIRTUALTYPE::DVD:
901#endif // USE_LIBDVD
902#ifdef USE_LIBBLURAY
904#endif // USE_LIBBLURAY
905 {
906 // Use stored status
907 mempcpy(stbuf, &virtualfile->m_st, sizeof(struct stat));
908 no_check = true;
910 }
912 {
914 {
915 mempcpy(stbuf, &virtualfile->m_st, sizeof(struct stat));
916 }
917 else
918 {
919 if (!no_check)
920 {
921 if (lstat(origpath.c_str(), stbuf) == -1)
922 {
923 return -errno;
924 }
925 }
926
927 // Get size for resulting output file from regular file, otherwise it's a symbolic link.
928 if (S_ISREG(stbuf->st_mode))
929 {
930 Cache_Entry* cache_entry = reinterpret_cast<Cache_Entry*>(fi->fh);
931
932 if (cache_entry == nullptr)
933 {
934 Logging::error(path, "fgetattr: Tried to stat unopen file.");
935 errno = EBADF;
936 return -errno;
937 }
938
939 uint32_t segment_no = 0;
940
941 stat_set_size(stbuf, transcoder_buffer_watermark(cache_entry, segment_no));
942 }
943 }
944
945 errno = 0; // Just to make sure - reset any error
946
947 break;
948 }
950 {
951 mempcpy(stbuf, &virtualfile->m_st, sizeof(struct stat));
952 break;
953 }
954 // We should never come here but this shuts up a warning
957 {
958 break;
959 }
960 }
961
962 return 0;
963}
964
971static int ffmpegfs_open(const char *path, struct fuse_file_info *fi)
972{
973 std::string origpath;
974
975 Logging::trace(path, "open");
976
977 append_basepath(&origpath, path);
978
979 LPVIRTUALFILE virtualfile = find_original(&origpath);
980
981 if (virtualfile == nullptr || (virtualfile->m_flags & VIRTUALFLAG_PASSTHROUGH))
982 {
983 int fd = open(origpath.c_str(), fi->flags);
984 if (fd != -1)
985 {
986 close(fd);
987 // File is real and can be opened.
988 errno = 0;
989 }
990 else
991 {
992 if (errno == ENOENT)
993 {
994 // File does not exist? We should never end up here...
995 errno = EINVAL;
996 }
997
998 // If file does exist, but can't be opened, return error.
999 }
1000
1001 return -errno;
1002 }
1003
1004 // This is a virtual file
1005 kick_next(virtualfile);
1006
1007 switch (virtualfile->m_type)
1008 {
1010 {
1011 errno = 0;
1012 break;
1013 }
1014#ifdef USE_LIBVCD
1015 case VIRTUALTYPE::VCD:
1016#endif // USE_LIBVCD
1017#ifdef USE_LIBDVD
1018 case VIRTUALTYPE::DVD:
1019#endif // USE_LIBDVD
1020#ifdef USE_LIBBLURAY
1022#endif // USE_LIBBLURAY
1023 case VIRTUALTYPE::DISK:
1024 {
1025 if (virtualfile->m_flags & (VIRTUALFLAG_FRAME | VIRTUALFLAG_HLS))
1026 {
1027 LPVIRTUALFILE parent_file = find_parent(origpath);
1028
1029 if (parent_file != nullptr)
1030 {
1031 Cache_Entry* cache_entry;
1032
1033 cache_entry = transcoder_new(parent_file, true);
1034 if (cache_entry == nullptr)
1035 {
1036 return -errno;
1037 }
1038
1039 // Store transcoder in the fuse_file_info structure.
1040 fi->fh = reinterpret_cast<uintptr_t>(cache_entry);
1041 // Need this because we do not know the exact size in advance.
1042 fi->direct_io = 1;
1043 //fi->keep_cache = 1;
1044
1045 // Clear errors
1046 errno = 0;
1047 }
1048 }
1049 else if (!(virtualfile->m_flags & VIRTUALFLAG_FILESET))
1050 {
1051 Cache_Entry* cache_entry;
1052
1053 cache_entry = transcoder_new(virtualfile, true);
1054 if (cache_entry == nullptr)
1055 {
1056 return -errno;
1057 }
1058
1059 // Store transcoder in the fuse_file_info structure.
1060 fi->fh = reinterpret_cast<uintptr_t>(cache_entry);
1061 // Need this because we do not know the exact size in advance.
1062 fi->direct_io = 1;
1063 //fi->keep_cache = 1;
1064
1065 // Clear errors
1066 errno = 0;
1067 }
1068 break;
1069 }
1070 // We should never come here but this shuts up a warning
1073 {
1074 break;
1075 }
1076 }
1077
1078 return 0;
1079}
1080
1090static int ffmpegfs_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi)
1091{
1092 std::string origpath;
1093 size_t locoffset = static_cast<size_t>(offset); // Cast OK: offset can never be < 0.
1094 int bytes_read = 0;
1095
1096 Logging::trace(path, "read: Reading %1 bytes from offset %2 to %3.", size, locoffset, size + locoffset);
1097
1098 append_basepath(&origpath, path);
1099
1100 LPVIRTUALFILE virtualfile = find_original(&origpath);
1101
1102 if (virtualfile == nullptr || (virtualfile->m_flags & VIRTUALFLAG_PASSTHROUGH))
1103 {
1104 int fd = open(origpath.c_str(), O_RDONLY);
1105 if (fd != -1)
1106 {
1107 // If this is a real file, pass the call through.
1108 bytes_read = static_cast<int>(pread(fd, buf, size, offset));
1109 close(fd);
1110 if (bytes_read >= 0)
1111 {
1112 return bytes_read;
1113 }
1114 else
1115 {
1116 return -errno;
1117 }
1118 }
1119 else if (errno != ENOENT)
1120 {
1121 // File does exist, but can't be opened.
1122 return -errno;
1123 }
1124 else
1125 {
1126 // File does not exist, and this is fine.
1127 errno = 0;
1128 }
1129 }
1130
1131 // This is a virtual file
1132 bool success = true;
1133
1134 if (virtualfile == nullptr)
1135 {
1136 errno = EINVAL;
1137 Logging::error(origpath.c_str(), "read: INTERNAL ERROR: ffmpegfs_read()! virtualfile == NULL");
1138 return -errno;
1139 }
1140
1141 switch (virtualfile->m_type)
1142 {
1144 {
1145 if (locoffset >= virtualfile->m_file_contents.size())
1146 {
1147 bytes_read = 0;
1148 break;
1149 }
1150
1151 size_t bytes = size;
1152 if (locoffset + bytes > virtualfile->m_file_contents.size())
1153 {
1154 bytes = virtualfile->m_file_contents.size() - locoffset;
1155 }
1156
1157 if (bytes)
1158 {
1159 std::memcpy(buf, &virtualfile->m_file_contents[locoffset], bytes);
1160 }
1161
1162 bytes_read = static_cast<int>(bytes);
1163 break;
1164 }
1165#ifdef USE_LIBVCD
1166 case VIRTUALTYPE::VCD:
1167#endif // USE_LIBVCD
1168#ifdef USE_LIBDVD
1169 case VIRTUALTYPE::DVD:
1170#endif // USE_LIBDVD
1171#ifdef USE_LIBBLURAY
1173#endif // USE_LIBBLURAY
1174 case VIRTUALTYPE::DISK:
1175 {
1176 if (virtualfile->m_flags & VIRTUALFLAG_FRAME)
1177 {
1178 Cache_Entry* cache_entry;
1179
1180 cache_entry = reinterpret_cast<Cache_Entry*>(fi->fh);
1181
1182 if (cache_entry == nullptr)
1183 {
1184 if (errno)
1185 {
1186 Logging::error(origpath.c_str(), "read: Tried to read from unopen file: (%1) %2", errno, strerror(errno));
1187 }
1188 return -errno;
1189 }
1190
1191 uint32_t frame_no = 0;
1192 std::string filename = get_number(path, &frame_no);
1193 if (!frame_no)
1194 {
1195 errno = EINVAL;
1196 Logging::error(origpath.c_str(), "read: Unable to deduct frame no. from file name (%1): (%2) %3", filename.c_str(), errno, strerror(errno));
1197 return -errno;
1198 }
1199
1200 success = transcoder_read_frame(cache_entry, buf, locoffset, size, frame_no, &bytes_read, virtualfile);
1201 }
1202 else if (!(virtualfile->m_flags & VIRTUALFLAG_FILESET))
1203 {
1204 Cache_Entry* cache_entry;
1205
1206 cache_entry = reinterpret_cast<Cache_Entry*>(fi->fh);
1207
1208 if (cache_entry == nullptr)
1209 {
1210 if (errno)
1211 {
1212 Logging::error(origpath.c_str(), "read: Tried to read from unopen file: (%1) %2", errno, strerror(errno));
1213 }
1214 return -errno;
1215 }
1216
1217 uint32_t segment_no = 0;
1218
1219 if (virtualfile->m_flags & VIRTUALFLAG_HLS)
1220 {
1221 std::string filename = get_number(path, &segment_no);
1222 if (!segment_no)
1223 {
1224 errno = EINVAL;
1225 Logging::error(origpath.c_str(), "read: Unable to deduct segment no. from file name (%1): (%2) %3", filename.c_str(), errno, strerror(errno));
1226 return -errno;
1227 }
1228 }
1229
1230 success = transcoder_read(cache_entry, buf, locoffset, size, &bytes_read, segment_no);
1231 }
1232 break;
1233 }
1236 {
1237 break;
1238 }
1239 }
1240
1241 if (success)
1242 {
1243 if (bytes_read)
1244 {
1245 Logging::trace(path, "read: Read %1 bytes from offset %2 to %3.", bytes_read, locoffset, static_cast<size_t>(bytes_read) + locoffset);
1246 }
1247 else
1248 {
1249 Logging::trace(path, "read: Read output file to EOF.");
1250 }
1251
1252 return bytes_read;
1253 }
1254 else
1255 {
1256 return -errno;
1257 }
1258}
1259
1266static int ffmpegfs_statfs(const char *path, struct statvfs *stbuf)
1267{
1268 std::string origpath;
1269
1270 Logging::trace(path, "statfs");
1271
1272 append_basepath(&origpath, path);
1273
1274 // passthrough for regular files
1275 if (!origpath.empty() && statvfs(origpath.c_str(), stbuf) == 0)
1276 {
1277 errno = 0; // Just to make sure - reset any error
1278
1279 return 0;
1280 }
1281 else
1282 {
1283 // Not really an error.
1284 errno = 0;
1285 }
1286
1287 find_original(&origpath);
1288
1289 statvfs(origpath.c_str(), stbuf);
1290
1291 errno = 0; // Just to make sure - reset any error
1292
1293 return 0;
1294}
1295
1302static int ffmpegfs_release(const char *path, struct fuse_file_info *fi)
1303{
1304 Cache_Entry* cache_entry = reinterpret_cast<Cache_Entry*>(fi->fh);
1305
1306 Logging::trace(path, "release");
1307
1308 if (cache_entry != nullptr)
1309 {
1310 uint32_t segment_no = 0;
1311
1312 if (cache_entry->virtualfile()->m_flags & VIRTUALFLAG_HLS)
1313 {
1314 std::string filename = get_number(path, &segment_no);
1315 if (!segment_no)
1316 {
1317 errno = EINVAL;
1318 Logging::error(path, "release: Unable to deduct segment no. from file name (%1): (%2) %3", filename.c_str(), errno, strerror(errno));
1319 }
1320 else
1321 {
1322 cache_entry->m_buffer->close_file(segment_no - 1, CACHE_FLAG_RO);
1323 }
1324 }
1325 transcoder_delete(cache_entry);
1326 }
1327
1328 return 0;
1329}
1330
1336static void *ffmpegfs_init(struct fuse_conn_info *conn)
1337{
1338 Logging::info(nullptr, "%1 V%2 initialising.", PACKAGE_NAME, FFMPEFS_VERSION);
1339 Logging::info(nullptr, "Mapping '%1' to '%2'.", params.m_basepath.c_str(), params.m_mountpath.c_str());
1340 if (docker_client)
1341 {
1342 Logging::info(nullptr, "Running inside Docker.");
1343 }
1344
1345 struct sigaction sa;
1346 std::memset(&sa, 0, sizeof(sa));
1347 sigemptyset(&sa.sa_mask);
1348 sigaddset(&sa.sa_mask, SIGINT);
1349 sa.sa_handler = sighandler;
1350 sigaction(SIGINT, &sa, &oldHandler);
1351
1352 // We need synchronous reads.
1353 conn->async_read = 0;
1354 // conn->async_read = 1;
1355 // conn->want |= FUSE_CAP_ASYNC_READ;
1356 // conn->want |= FUSE_CAP_SPLICE_READ;
1357
1359 {
1361 {
1362 exit(1);
1363 }
1364 }
1365
1367 {
1369 }
1370
1371 if (tp == nullptr)
1372 {
1373 tp = std::make_unique<thread_pool>(params.m_max_threads);
1374 }
1375
1376 tp->init();
1377
1378 return nullptr;
1379}
1380
1385static void ffmpegfs_destroy(__attribute__((unused)) void * p)
1386{
1387 Logging::info(nullptr, "%1 V%2 terminating.", PACKAGE_NAME, FFMPEFS_VERSION);
1388 std::printf("%s V%s terminating\n", PACKAGE_NAME, FFMPEFS_VERSION);
1389
1391
1394
1395 if (tp != nullptr)
1396 {
1397 tp->tear_down();
1398 tp.reset();
1399 }
1400
1401 script_file.clear();
1402
1403 Logging::info(nullptr, "%1 V%2 terminated.", PACKAGE_NAME, FFMPEFS_VERSION);
1404}
1405
1412static int get_source_properties(const std::string & origpath, LPVIRTUALFILE virtualfile)
1413{
1414 Cache_Entry* cache_entry = transcoder_new(virtualfile, false);
1415 if (cache_entry == nullptr)
1416 {
1417 return -errno;
1418 }
1419
1420 transcoder_delete(cache_entry);
1421
1422 Logging::debug(origpath, "Duration: %1 Frames: %2 Segments: %3", virtualfile->m_duration, virtualfile->m_video_frame_count, virtualfile->get_segment_count());
1423
1424 return 0;
1425}
1426
1434static void init_stat(struct stat * stbuf, size_t fsize, time_t ftime, bool directory)
1435{
1436 std::memset(stbuf, 0, sizeof(struct stat));
1437
1438 stbuf->st_mode = DEFFILEMODE; //S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
1439 if (directory)
1440 {
1441 stbuf->st_mode |= S_IFDIR | S_IXUSR | S_IXGRP | S_IXOTH;
1442 stbuf->st_nlink = 2;
1443 }
1444 else
1445 {
1446 stbuf->st_mode |= S_IFREG;
1447 stbuf->st_nlink = 1;
1448 }
1449
1450 stat_set_size(stbuf, fsize);
1451
1452 // Set current user as owner
1453 stbuf->st_uid = getuid();
1454 stbuf->st_gid = getgid();
1455
1456 // Use current date/time
1457 stbuf->st_atime = stbuf->st_mtime = stbuf->st_ctime = ftime;
1458}
1459
1472static LPVIRTUALFILE make_file(void *buf, fuse_fill_dir_t filler, VIRTUALTYPE type, const std::string & origpath, const std::string & filename, size_t fsize, time_t ftime, int flags)
1473{
1474 struct stat stbuf;
1475
1476 init_stat(&stbuf, fsize, ftime, false);
1477
1478 if (add_fuse_entry(buf, filler, filename, &stbuf, 0))
1479 {
1480 return nullptr;
1481 }
1482
1483 return insert_file(type, origpath + filename, &stbuf, flags);
1484}
1485
1489static void prepare_script()
1490{
1491 std::string scriptsource;
1492
1493 exepath(&scriptsource);
1494 scriptsource += params.m_scriptsource;
1495
1496 Logging::debug(scriptsource, "Reading virtual script source.");
1497
1498 FILE *fpi = fopen(scriptsource.c_str(), "rt");
1499 if (fpi == nullptr)
1500 {
1501 Logging::warning(scriptsource, "File open failed. Disabling script: (%1) %2", errno, strerror(errno));
1502 params.m_enablescript = false;
1503 }
1504 else
1505 {
1506 struct stat stbuf;
1507 if (fstat(fileno(fpi), &stbuf) == -1)
1508 {
1509 Logging::warning(scriptsource, "File could not be accessed. Disabling script: (%1) %2", errno, strerror(errno));
1510 params.m_enablescript = false;
1511 }
1512 else
1513 {
1514 script_file.resize(static_cast<size_t>(stbuf.st_size));
1515
1516 if (fread(&script_file[0], 1, static_cast<size_t>(stbuf.st_size), fpi) != static_cast<size_t>(stbuf.st_size))
1517 {
1518 Logging::warning(scriptsource, "File could not be read. Disabling script: (%1) %2", errno, strerror(errno));
1519 params.m_enablescript = false;
1520 }
1521 else
1522 {
1523 Logging::trace(scriptsource, "Read %1 bytes of script file.", script_file.size());
1524 }
1525 }
1526
1527 fclose(fpi);
1528 }
1529}
1530
1538static bool virtual_name(std::string * virtualpath, const std::string & origpath /*= ""*/, const FFmpegfs_Format **current_format /*= nullptr*/)
1539{
1540 std::string ext;
1541
1542 if (current_format != nullptr)
1543 {
1544 *current_format = nullptr;
1545 }
1546
1547 if (!find_ext(&ext, *virtualpath) || is_passthrough(ext))
1548 {
1549 return false;
1550 }
1551
1552 if (!is_selected(ext))
1553 {
1554 return false;
1555 }
1556
1557 VIRTUALFILE newvirtualfile;
1558
1559 newvirtualfile.m_origfile = origpath + *virtualpath;
1560
1561 const FFmpegfs_Format *ffmpegfs_format = get_format(&newvirtualfile);
1562
1563 if (ffmpegfs_format != nullptr)
1564 {
1566 {
1567 // Old filename scheme, creates duplicates
1568 replace_ext(virtualpath, ffmpegfs_format->fileext());
1569 }
1570 else
1571 {
1572 // New name scheme
1573 append_ext(virtualpath, ffmpegfs_format->fileext());
1574 }
1575
1576 newvirtualfile.m_destfile = origpath + *virtualpath;
1577 newvirtualfile.m_virtfile = params.m_mountpath + *virtualpath;
1578
1579 if (lstat(newvirtualfile.m_destfile.c_str(), &newvirtualfile.m_st) == 0)
1580 {
1582 {
1583 newvirtualfile.m_flags |= VIRTUALFLAG_PASSTHROUGH;
1584 }
1585
1586 if (ffmpegfs_format->is_multiformat())
1587 {
1588 // Change file to directory for the frame set
1589 // Change file to virtual directory for the frame set. Keep permissions.
1590 stat_to_dir(&newvirtualfile.m_st);
1591 flags_to_dir(&newvirtualfile.m_flags);
1592 }
1593 }
1594
1595 insert(newvirtualfile);
1596
1597 if (current_format != nullptr)
1598 {
1599 *current_format = ffmpegfs_format;
1600 }
1601
1602 return true;
1603 }
1604
1605 return false;
1606}
1607
1614static FILENAME_MAP::const_iterator find_prefix(const FILENAME_MAP & map, const std::string & search_for)
1615{
1616 FILENAME_MAP::const_iterator it = map.lower_bound(search_for);
1617 if (it != map.cend())
1618 {
1619 const std::string & key = it->first;
1620 if (key.compare(0, search_for.size(), search_for) == 0) // Really a prefix?
1621 {
1622 return it;
1623 }
1624 }
1625 return map.cend();
1626}
1627
1632static void insert(const VIRTUALFILE & virtualfile)
1633{
1634 filenames.insert(make_pair(virtualfile.m_destfile, virtualfile));
1635 rfilenames.insert(make_pair(virtualfile.m_origfile, virtualfile));
1636}
1637
1638LPVIRTUALFILE insert_file(VIRTUALTYPE type, const std::string & virtfile, const struct stat * stbuf, int flags)
1639{
1640 return insert_file(type, virtfile, virtfile, stbuf, flags);
1641}
1642
1643LPVIRTUALFILE insert_file(VIRTUALTYPE type, const std::string & virtfile, const std::string & origfile, const struct stat * stbuf, int flags)
1644{
1645 std::string sanitised_virtfile(sanitise_filepath(virtfile));
1646
1647 FILENAME_MAP::iterator it = filenames.find(sanitised_virtfile);
1648
1649 if (it == filenames.cend())
1650 {
1651 // Create new
1652 std::string sanitised_origfile(sanitise_filepath(origfile));
1653 VIRTUALFILE virtualfile;
1654
1655 std::memcpy(&virtualfile.m_st, stbuf, sizeof(struct stat));
1656
1657 virtualfile.m_type = type;
1658 virtualfile.m_flags = flags;
1659 virtualfile.m_format_idx = guess_format_idx(sanitised_origfile); // Make a guess, will be finalised later
1660 virtualfile.m_destfile = sanitised_virtfile;
1661 virtualfile.m_origfile = sanitised_origfile;
1662 virtualfile.m_virtfile = sanitised_origfile;
1663 //virtualfile.m_predicted_size = static_cast<size_t>(stbuf->st_size);
1664
1666
1667 insert(virtualfile);
1668
1669 it = filenames.find(sanitised_virtfile);
1670 }
1671
1672 return &it->second;
1673}
1674
1679static void stat_to_dir(struct stat *stbuf)
1680{
1681 stbuf->st_mode &= ~static_cast<mode_t>(S_IFREG | S_IFLNK);
1682 stbuf->st_mode |= S_IFDIR;
1683 if (stbuf->st_mode & S_IRWXU)
1684 {
1685 stbuf->st_mode |= S_IXUSR; // Add user execute bit if user has read or write access
1686 }
1687 if (stbuf->st_mode & S_IRWXG)
1688 {
1689 stbuf->st_mode |= S_IXGRP; // Add group execute bit if group has read or write access
1690 }
1691 if (stbuf->st_mode & S_IRWXO)
1692 {
1693 stbuf->st_mode |= S_IXOTH; // Add other execute bit if other has read or write access
1694 }
1695 stbuf->st_nlink = 2;
1696 stbuf->st_size = stbuf->st_blksize;
1697}
1698
1703static void flags_to_dir(int *flags)
1704{
1706
1707 if (ffmpeg_format[FORMAT::VIDEO].is_frameset())
1708 {
1709 *flags |= VIRTUALFLAG_FRAME;
1710 }
1711 else if (ffmpeg_format[FORMAT::VIDEO].is_hls())
1712 {
1713 *flags |= VIRTUALFLAG_HLS;
1714 }
1715}
1716
1717LPVIRTUALFILE insert_dir(VIRTUALTYPE type, const std::string & virtdir, const struct stat * stbuf, int flags)
1718{
1719 struct stat stbufdir;
1720
1721 std::memcpy(&stbufdir, stbuf, sizeof(stbufdir));
1722
1723 // Change file to directory for the frame set
1724 // Change file to virtual directory for the frame set. Keep permissions.
1725 stat_to_dir(&stbufdir);
1726 flags_to_dir(&flags);
1727
1728 std::string path(virtdir);
1729 append_sep(&path);
1730
1731 return insert_file(type, path, &stbufdir, flags);
1732}
1733
1734LPVIRTUALFILE find_file(const std::string & virtfile)
1735{
1736 FILENAME_MAP::iterator it = filenames.find(sanitise_filepath(virtfile));
1737
1738 errno = 0;
1739
1740 return (it != filenames.end() ? &it->second : nullptr);
1741}
1742
1743LPVIRTUALFILE find_file_from_orig(const std::string &origfile)
1744{
1745 RFILENAME_MAP::iterator it = rfilenames.find(sanitise_filepath(origfile));
1746
1747 errno = 0;
1748
1749 return (it != rfilenames.end() ? &it->second : nullptr);
1750}
1751
1752bool check_path(const std::string & path)
1753{
1754 FILENAME_MAP::const_iterator it = find_prefix(filenames, path);
1755
1756 return (it != filenames.cend());
1757}
1758
1759int load_path(const std::string & path, const struct stat *statbuf, void *buf, fuse_fill_dir_t filler)
1760{
1761 if (buf == nullptr)
1762 {
1763 // We can't add anything here if buf == nullptr
1764 return 0;
1765 }
1766
1767 int title_count = 0;
1768
1769 for (FILENAME_MAP::iterator it = filenames.lower_bound(path); it != filenames.end(); ++it)
1770 {
1771 std::string virtfilepath = it->first;
1772 LPVIRTUALFILE virtualfile = &it->second;
1773
1774 if (
1775 #ifdef USE_LIBVCD
1776 (virtualfile->m_type != VIRTUALTYPE::VCD) &&
1777 #endif // USE_LIBVCD
1778 #ifdef USE_LIBDVD
1779 (virtualfile->m_type != VIRTUALTYPE::DVD) &&
1780 #endif // USE_LIBDVD
1781 #ifdef USE_LIBBLURAY
1782 (virtualfile->m_type != VIRTUALTYPE::BLURAY) &&
1783 #endif // USE_LIBBLURAY
1784 !(virtualfile->m_flags & VIRTUALFLAG_CUESHEET)
1785 )
1786 {
1787 continue;
1788 }
1789
1790 remove_filename(&virtfilepath);
1791 if (virtfilepath == path) // Really a prefix?
1792 {
1793 struct stat stbuf;
1794 std::string destfile(virtualfile->m_destfile);
1795
1796 if (virtualfile->m_flags & VIRTUALFLAG_DIRECTORY)
1797 {
1798 // Is a directory, no need to translate the file name, just drop terminating separator
1799 remove_sep(&destfile);
1800 }
1801 remove_path(&destfile);
1802
1803 title_count++;
1804
1805 std::string cachefile;
1806
1807 Buffer::make_cachefile_name(&cachefile, virtualfile->m_destfile, params.current_format(virtualfile)->fileext(), false);
1808
1809 struct stat stbuf2;
1810 if (!lstat(cachefile.c_str(), &stbuf2))
1811 {
1812 // Cache file exists, use cache file size here
1813
1814 stat_set_size(&virtualfile->m_st, static_cast<size_t>(stbuf2.st_size));
1815
1816 std::memcpy(&stbuf, &virtualfile->m_st, sizeof(struct stat));
1817 }
1818 else
1819 {
1820 if (statbuf == nullptr)
1821 {
1822 std::memcpy(&stbuf, &virtualfile->m_st, sizeof(struct stat));
1823 }
1824 else
1825 {
1826 std::memcpy(&stbuf, statbuf, sizeof(struct stat));
1827
1828 stat_set_size(&stbuf, static_cast<size_t>(virtualfile->m_st.st_size));
1829 }
1830 }
1831
1832 if (add_fuse_entry(buf, filler, destfile, &stbuf, 0))
1833 {
1834 // break;
1835 }
1836 }
1837 }
1838
1839 return title_count;
1840}
1841
1850static int selector(const struct dirent * de)
1851{
1852 if (de->d_type & (DT_REG | DT_LNK))
1853 {
1854 return (av_guess_format(nullptr, de->d_name, nullptr) != nullptr);
1855 }
1856 else
1857 {
1858 return 0;
1859 }
1860}
1861
1874static int scandir(const char *dirp, std::vector<struct dirent> * _namelist, int (*selector) (const struct dirent *), int (*cmp) (const struct dirent **, const struct dirent **))
1875{
1876 struct dirent **namelist;
1877 int count = scandir(dirp, &namelist, selector, cmp); // cppcheck-suppress [nullPointer, ctunullpointer]
1878
1879 _namelist->clear();
1880
1881 if (count != -1)
1882 {
1883 for (int n = 0; n < count; n++)
1884 {
1885 _namelist->push_back(*namelist[n]);
1886 free(namelist[n]);
1887 }
1888 free(namelist);
1889 }
1890
1891 return count;
1892}
1893
1894LPVIRTUALFILE find_original(const std::string & origpath)
1895{
1896 std::string buffer(origpath);
1897 return find_original(&buffer);
1898}
1899
1900LPVIRTUALFILE find_original(std::string * filepath)
1901{
1902 sanitise_filepath(filepath);
1903
1904 LPVIRTUALFILE virtualfile = find_file(*filepath);
1905
1906 errno = 0;
1907
1908 if (virtualfile != nullptr)
1909 {
1910 *filepath = virtualfile->m_origfile;
1911 return virtualfile;
1912 }
1913 else
1914 {
1915 // Fallback to old method (required if file accessed directly)
1916 std::string ext;
1917 if (!ffmpeg_format[FORMAT::VIDEO].is_hls() && find_ext(&ext, *filepath) && (strcasecmp(ext, ffmpeg_format[FORMAT::VIDEO].fileext()) == 0 || (params.smart_transcode() && strcasecmp(ext, ffmpeg_format[FORMAT::AUDIO].fileext()) == 0)))
1918 {
1919 std::string dir(*filepath);
1920 std::string searchexp(*filepath);
1921 std::string origfile;
1922 std::vector<struct dirent> namelist;
1923 struct stat stbuf;
1924 int count;
1925 int found = 0;
1926
1927 remove_filename(&dir);
1928 origfile = dir;
1929
1930 // cppcheck-suppress nullPointer
1931 count = scandir(dir.c_str(), &namelist, selector, nullptr);
1932 if (count == -1)
1933 {
1934 if (errno != ENOTDIR) // If not a directory, simply ignore error
1935 {
1936 Logging::error(dir, "Error scanning directory: (%1) %2", errno, strerror(errno));
1937 }
1938 return nullptr;
1939 }
1940
1941 remove_path(&searchexp);
1942 remove_ext(&searchexp);
1943
1944 for (size_t n = 0; n < static_cast<size_t>(count); n++)
1945 {
1946 if (!strcmp(namelist[n].d_name, searchexp.c_str()))
1947 {
1948 append_filename(&origfile, namelist[n].d_name);
1949 sanitise_filepath(&origfile);
1950 found = 1;
1951 break;
1952 }
1953 }
1954
1955 if (found && lstat(origfile.c_str(), &stbuf) == 0)
1956 {
1957 // The original file exists
1958 LPVIRTUALFILE virtualfile2;
1959
1960 if (*filepath != origfile)
1961 {
1962 virtualfile2 = insert_file(VIRTUALTYPE::DISK, *filepath, origfile, &stbuf);
1963 *filepath = origfile;
1964 }
1965 else
1966 {
1967 virtualfile2 = insert_file(VIRTUALTYPE::DISK, origfile, &stbuf, VIRTUALFLAG_PASSTHROUGH);
1968 }
1969 return virtualfile2;
1970 }
1971 else
1972 {
1973 // File does not exist; this is a virtual file, not an error
1974 errno = 0;
1975 }
1976 }
1977 }
1978 // Source file exists with no supported extension, keep path
1979 return nullptr;
1980}
1981
1982LPVIRTUALFILE find_parent(const std::string & origpath)
1983{
1984 std::string filepath(origpath);
1985
1986 remove_filename(&filepath);
1987 remove_sep(&filepath);
1988
1989 return find_original(&filepath);
1990}
1991
2000static int make_hls_fileset(void * buf, fuse_fill_dir_t filler, const std::string & origpath, LPVIRTUALFILE virtualfile)
2001{
2002 // Generate set of TS segment files and necessary M3U lists
2003
2004 if (!virtualfile->get_segment_count())
2005 {
2006 int res = get_source_properties(origpath, virtualfile);
2007 if (res < 0)
2008 {
2009 return res;
2010 }
2011 }
2012
2013 if (virtualfile->get_segment_count())
2014 {
2015 std::string master_contents;
2016 std::string index_0_av_contents;
2017
2018 // Examples...
2019 //"#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1250,RESOLUTION=720x406,CODECS= \"avc1.77.30, mp4a.40.2 \",CLOSED-CAPTIONS=NONE\n"
2020 //"index_0_av.m3u8\n";
2021 //"#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=2647000,RESOLUTION=1280x720,CODECS= \"avc1.77.30, mp4a.40.2 \",CLOSED-CAPTIONS=NONE\n"
2022 //"index_1_av.m3u8\n"
2023 //"#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=922000,RESOLUTION=640x360,CODECS= \"avc1.77.30, mp4a.40.2 \",CLOSED-CAPTIONS=NONE\n"
2024 //"index_2_av.m3u8\n"
2025 //"#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=448000,RESOLUTION=384x216,CODECS= \"avc1.66.30, mp4a.40.2 \",CLOSED-CAPTIONS=NONE\n"
2026 //"index_3_av.m3u8\n"
2027 //"#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=61000,CODECS= \"mp4a.40.2 \",CLOSED-CAPTIONS=NONE\n"
2028 //"index_3_a.m3u8\n";
2029
2030 master_contents = "#EXTM3U\n"
2031 "#EXT-X-STREAM-INF:PROGRAM-ID=1\n"
2032 "index_0_av.m3u8\n";
2033
2034 strsprintf(&index_0_av_contents, "#EXTM3U\n"
2035 "#EXT-X-TARGETDURATION:%i\n"
2036 "#EXT-X-ALLOW-CACHE:YES\n"
2037 "#EXT-X-PLAYLIST-TYPE:VOD\n"
2038 "#EXT-X-VERSION:3\n"
2039 "#EXT-X-MEDIA-SEQUENCE:1\n", static_cast<int32_t>(params.m_segment_duration / AV_TIME_BASE));
2040
2041 int64_t remaining_duration = virtualfile->m_duration % params.m_segment_duration;
2042 size_t segment_size = virtualfile->m_predicted_size / virtualfile->get_segment_count();
2043
2044 for (uint32_t file_no = 1; file_no <= virtualfile->get_segment_count(); file_no++)
2045 {
2046 std::string buffer;
2047 std::string segment_name = make_filename(file_no, params.current_format(virtualfile)->fileext());
2048
2049 struct stat stbuf;
2050 std::string cachefile;
2051 std::string _origpath(origpath);
2052 remove_sep(&_origpath);
2053
2054 std::string filename(_origpath);
2055
2056 filename.append(".");
2057 filename.append(segment_name);
2058
2059 Buffer::make_cachefile_name(&cachefile, filename, params.current_format(virtualfile)->fileext(), false);
2060
2061 if (!lstat(cachefile.c_str(), &stbuf))
2062 {
2063 make_file(buf, filler, virtualfile->m_type, origpath, segment_name, static_cast<size_t>(stbuf.st_size), virtualfile->m_st.st_ctime, VIRTUALFLAG_HLS);
2064 }
2065 else
2066 {
2067 make_file(buf, filler, virtualfile->m_type, origpath, segment_name, segment_size, virtualfile->m_st.st_ctime, VIRTUALFLAG_HLS);
2068 }
2069
2070 if (file_no < virtualfile->get_segment_count())
2071 {
2072 strsprintf(&buffer, "#EXTINF:%.3f,\n", static_cast<double>(params.m_segment_duration) / AV_TIME_BASE);
2073 }
2074 else
2075 {
2076 strsprintf(&buffer, "#EXTINF:%.3f,\n", static_cast<double>(remaining_duration) / AV_TIME_BASE);
2077 }
2078
2079 index_0_av_contents += buffer;
2080 index_0_av_contents += segment_name;
2081 index_0_av_contents += "\n";
2082 }
2083
2084 index_0_av_contents += "#EXT-X-ENDLIST\n";
2085
2086 LPVIRTUALFILE child_file;
2087 child_file = make_file(buf, filler, VIRTUALTYPE::SCRIPT, origpath, "master.m3u8", master_contents.size(), virtualfile->m_st.st_ctime);
2088 std::copy(master_contents.begin(), master_contents.end(), std::back_inserter(child_file->m_file_contents));
2089
2090 child_file = make_file(buf, filler, VIRTUALTYPE::SCRIPT, origpath, "index_0_av.m3u8", index_0_av_contents.size(), virtualfile->m_st.st_ctime, VIRTUALFLAG_NONE);
2091 std::copy(index_0_av_contents.begin(), index_0_av_contents.end(), std::back_inserter(child_file->m_file_contents));
2092
2093 {
2094 // Demo code adapted from: https://github.com/video-dev/hls.js/
2095 std::string hls_html;
2096
2097 hls_html =
2098 "<html>\n"
2099 "\n"
2100 "<head>\n"
2101 " <title>HLS Demo</title>\n"
2102 " <script src=\"https://cdn.jsdelivr.net/npm/hls.js@latest\"></script>\n"
2103 " <meta charset=\"utf-8\">\n"
2104 "</head>\n"
2105 "\n"
2106 "<body>\n"
2107 " <center>\n"
2108 " <h1>Hls.js demo - basic usage</h1>\n"
2109 " <video height=\"600\" id=\"video\" controls></video>\n"
2110 " </center>\n"
2111 " <script>\n"
2112 " var video = document.getElementById(\"video\");\n"
2113 " var videoSrc = \"index_0_av.m3u8\";\n"
2114 " if (Hls.isSupported()) {\n"
2115 " var hls = new Hls();\n"
2116 " hls.loadSource(videoSrc);\n"
2117 " hls.attachMedia(video);\n"
2118 " hls.on(Hls.Events.MANIFEST_PARSED, function() {\n"
2119 " video.play();\n"
2120 " });\n"
2121 " }\n"
2122 " // hls.js is not supported on platforms that do not have Media Source Extensions (MSE) enabled.\n"
2123 " // When the browser has built-in HLS support (check using `canPlayType`), we can provide an HLS manifest (i.e. .m3u8 URL) directly to the video element through the `src` property.\n"
2124 " // This is using the built-in support of the plain video element, without using hls.js.\n"
2125 " // Note: it would be more normal to wait on the 'canplay' event below, but on Safari (where you are most likely to find built-in HLS support), the video.src URL must be on the user-driven\n"
2126 " // white-list before a 'canplay' event will be emitted; the last video event that can be reliably listened to when the URL is not on the white-list is 'loadedmetadata'.\n"
2127 " else if (video.canPlayType(\"application/vnd.apple.mpegurl\")) {\n"
2128 " video.src = videoSrc;\n"
2129 " video.addEventListener(\"loadedmetadata\", function() {\n"
2130 " video.play();\n"
2131 " });\n"
2132 " }\n"
2133 " </script>\n"
2134 "</body>\n"
2135 "\n"
2136 "</html>\n";
2137
2138 child_file = make_file(buf, filler, VIRTUALTYPE::SCRIPT, origpath, "hls.html", hls_html.size(), virtualfile->m_st.st_ctime, VIRTUALFLAG_NONE);
2139 std::copy(hls_html.begin(), hls_html.end(), std::back_inserter(child_file->m_file_contents));
2140 }
2141 }
2142 return 0;
2143}
2144
2156static int kick_next(LPVIRTUALFILE virtualfile)
2157{
2158 if (virtualfile == nullptr)
2159 {
2160 // Not OK, should not happen
2161 return -EINVAL;
2162 }
2163
2164 if (virtualfile->m_cuesheet_track.m_nextfile == nullptr)
2165 {
2166 // No next file
2167 return 0;
2168 }
2169
2170 LPVIRTUALFILE nextvirtualfile = virtualfile->m_cuesheet_track.m_nextfile;
2171
2172 Logging::debug(virtualfile->m_destfile, "Preparing next file: %1", nextvirtualfile->m_destfile.c_str());
2173
2174 Cache_Entry* cache_entry = transcoder_new(nextvirtualfile, true);
2175 if (cache_entry == nullptr)
2176 {
2177 return -errno;
2178 }
2179
2180 transcoder_delete(cache_entry);
2181
2182 return 0;
2183}
2184
2194static void sighandler(int signum)
2195{
2196 if (signum == SIGINT)
2197 {
2198 Logging::warning(nullptr, "Caught SIGINT, shutting down now...");
2199 // Make our threads terminate now
2201 // Restore fuse's handler
2202 sigaction(SIGINT, &oldHandler, nullptr);
2203 // Dispatch to fuse's handler
2204 raise(SIGINT);
2205 }
2206}
2207
2214static std::string get_number(const char *path, uint32_t *value)
2215{
2216 std::string filename(path);
2217
2218 // Get frame number
2219 remove_path(&filename);
2220
2221 *value = static_cast<uint32_t>(std::stoi(filename)); // Extract frame or segment number. May be more fancy in the future. Currently just get number from filename part.
2222
2223 return filename;
2224}
2225
2231static size_t guess_format_idx(const std::string & filepath)
2232{
2233 const AVOutputFormat* oformat = ::av_guess_format(nullptr, filepath.c_str(), nullptr);
2234
2235 if (oformat != nullptr)
2236 {
2237 if (!params.smart_transcode())
2238 {
2239 // Not smart encoding: use first format (video file)
2240 return 0;
2241 }
2242 else
2243 {
2244 // Smart transcoding
2245 if (ffmpeg_format[FORMAT::VIDEO].video_codec() != AV_CODEC_ID_NONE && oformat->video_codec != AV_CODEC_ID_NONE && !is_album_art(oformat->video_codec))
2246 {
2247 // Is a video: use first format (video file)
2248 return 0;
2249 }
2250 else if (ffmpeg_format[FORMAT::AUDIO].audio_codec() != AV_CODEC_ID_NONE && oformat->audio_codec != AV_CODEC_ID_NONE)
2251 {
2252 // For audio only, use second format (audio only file)
2253 return 1;
2254 }
2255 }
2256 }
2257
2258 return 0;
2259}
2260
2266static int parse_file(LPVIRTUALFILE newvirtualfile)
2267{
2268 AVFormatContext *format_ctx = nullptr;
2269 int res;
2270
2271 try
2272 {
2273 Logging::debug(newvirtualfile->m_origfile, "Creating a new format context and parsing the file.");
2274
2275 res = avformat_open_input(&format_ctx, newvirtualfile->m_origfile.c_str(), nullptr, nullptr);
2276 if (res)
2277 {
2278 Logging::trace(newvirtualfile->m_origfile, "No parseable file: %1", ffmpeg_geterror(res).c_str());
2279 throw res;
2280 }
2281
2282 res = avformat_find_stream_info(format_ctx, nullptr);
2283 if (res < 0)
2284 {
2285 Logging::error(newvirtualfile->m_origfile, "Cannot find stream information: %1", ffmpeg_geterror(res).c_str());
2286 throw res;
2287 }
2288
2289 // Check for an embedded cue sheet
2290 AVDictionaryEntry *tag = av_dict_get(format_ctx->metadata, "CUESHEET", nullptr, AV_DICT_IGNORE_SUFFIX);
2291 if (tag != nullptr)
2292 {
2293 // Found cue sheet
2294 Logging::trace(newvirtualfile->m_origfile, "Found an embedded cue sheet.");
2295
2296 newvirtualfile->m_cuesheet = tag->value;
2297 newvirtualfile->m_cuesheet += "\r\n"; // cue_parse_string() reports syntax error if string does not end with newline
2298 replace_all(&newvirtualfile->m_cuesheet, "\r\n", "\n"); // Convert all to unix
2299 }
2300
2301 // Check for audio/video/subtitles
2302 int ret = get_audio_props(format_ctx, &newvirtualfile->m_channels, &newvirtualfile->m_sample_rate);
2303 if (ret < 0)
2304 {
2305 if (ret != AVERROR_STREAM_NOT_FOUND) // Not an error, no audio is OK
2306 {
2307 Logging::error(newvirtualfile->m_origfile, "Could not find audio stream in input file (error '%1').", ffmpeg_geterror(ret).c_str());
2308 }
2309 }
2310 else
2311 {
2312 newvirtualfile->m_has_audio = true;
2313 }
2314
2315 newvirtualfile->m_duration = format_ctx->duration;
2316
2317 struct stat stbuf;
2318 if (lstat(newvirtualfile->m_origfile.c_str(), &stbuf) == 0)
2319 {
2320 std::memcpy(&newvirtualfile->m_st, &stbuf, sizeof(stbuf));
2321 }
2322
2323 for (unsigned int stream_idx = 0; stream_idx < format_ctx->nb_streams; stream_idx++)
2324 {
2325 switch (format_ctx->streams[stream_idx]->codecpar->codec_type)
2326 {
2327 case AVMEDIA_TYPE_VIDEO:
2328 {
2329 if (!is_album_art(format_ctx->streams[stream_idx]->codecpar->codec_id))
2330 {
2331 newvirtualfile->m_has_video = true;
2332 }
2333 break;
2334 }
2335 case AVMEDIA_TYPE_SUBTITLE:
2336 {
2337 newvirtualfile->m_has_subtitle = true;
2338 break;
2339 }
2340 default:
2341 {
2342 break;
2343 }
2344 }
2345 }
2346 }
2347 catch (int _res)
2348 {
2349 res = _res;
2350 }
2351
2352 if (format_ctx != nullptr)
2353 {
2354 avformat_close_input(&format_ctx);
2355 }
2356
2357 return res;
2358}
2359
2365static const FFmpegfs_Format * get_format(LPVIRTUALFILE newvirtualfile)
2366{
2367 LPVIRTUALFILE virtualfile = find_file_from_orig(newvirtualfile->m_origfile);
2368
2369 if (virtualfile != nullptr)
2370 {
2371 // We already know the file!
2372 return params.current_format(virtualfile);
2373 }
2374
2375 if (parse_file(newvirtualfile) < 0)
2376 {
2377 return nullptr;
2378 }
2379
2380 Logging::trace(newvirtualfile->m_origfile, "Audio: %1 Video: %2 Subtitles: %3", newvirtualfile->m_has_audio, newvirtualfile->m_has_video, newvirtualfile->m_has_subtitle);
2381
2382 if (!params.smart_transcode())
2383 {
2384 // Not smart encoding: use first format (video file)
2385 newvirtualfile->m_format_idx = 0;
2387 }
2388 else
2389 {
2390 if (newvirtualfile->m_has_video)
2391 {
2392 newvirtualfile->m_format_idx = 0;
2394 }
2395
2396 if (newvirtualfile->m_has_audio)
2397 {
2398 newvirtualfile->m_format_idx = 1;
2400 }
2401 }
2402
2403 return nullptr;
2404}
2405
2406int add_fuse_entry(void *buf, fuse_fill_dir_t filler, const std::string & name, const struct stat *stbuf, off_t off)
2407{
2408 if (buf == nullptr || filler == nullptr)
2409 {
2410 return 0;
2411 }
2412
2413 return filler(buf, name.c_str(), stbuf, off);
2414}
2415
2416int add_dotdot(void *buf, fuse_fill_dir_t filler, const struct stat *stbuf, off_t off)
2417{
2418 struct stat *stbuf2 = nullptr;
2419 struct stat stbuf3;
2420
2421 if (stbuf != nullptr)
2422 {
2423 stbuf2 = &stbuf3;
2424 init_stat(stbuf2, 0, stbuf->st_ctime, true);
2425 }
2426
2427 add_fuse_entry(buf, filler, ".", stbuf2, off);
2428 add_fuse_entry(buf, filler, "..", stbuf2, off);
2429
2430 return 0;
2431}
int check_bluray(const std::string &path, void *buf, fuse_fill_dir_t filler)
Get number of titles on Blu-ray.
Blu-ray parser.
Buffer class.
#define CACHE_FLAG_RO
Mark cache file read-only.
Definition: buffer.h:49
Cache entry
bool stop_cache_maintenance()
Stop cache maintenance timer.
bool start_cache_maintenance(time_t interval)
Start cache maintenance timer.
Cache maintenance
static const std::string & make_cachefile_name(std::string *cachefile, const std::string &filename, const std::string &fileext, bool is_idx)
Make up a cache file name, including the full path.
Definition: buffer.cc:975
The Cache_Entry class.
Definition: cache_entry.h:49
LPVIRTUALFILE virtualfile()
Get the underlying VIRTUALFILE object.
Definition: cache_entry.cc:420
std::unique_ptr< Buffer > m_buffer
Buffer object.
Definition: cache_entry.h:271
The FFmpegfs_Format class.
Definition: ffmpeg_utils.h:388
bool is_hls() const
Check for HLS format.
bool is_frameset() const
Check for an export frame format.
bool is_multiformat() const
Check if this is some sort of multi file format (any of the following: is_frameset() or is_hls()).
AVCodecID video_codec() const
Get video codec_id.
const std::string & fileext() const
Get file extension.
static void warning(const T filename, const std::string &format_string, Args &&...args)
Write warning level log entry.
Definition: logging.h:220
static void debug(const T filename, const std::string &format_string, Args &&...args)
Write debug level log entry.
Definition: logging.h:182
static void trace(const T filename, const std::string &format_string, Args &&...args)
Write trace level log entry.
Definition: logging.h:163
static void info(const T filename, const std::string &format_string, Args &&...args)
Write info level log entry.
Definition: logging.h:201
static void error(const T filename, const std::string &format_string, Args &&...args)
Write error level log entry.
Definition: logging.h:239
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
int check_dvd(const std::string &path, void *buf, fuse_fill_dir_t filler)
Get number of titles on DVD.
Definition: dvdparser.cc:550
DVD parser.
#define FALLTHROUGH_INTENDED
Allow fallthrough in case statements for GCC.
Definition: ffmpeg_compat.h:42
const std::string & remove_path(std::string *filepath)
Remove path from filename. Handy basename alternative.
void exepath(std::string *path)
Path to FFmpegfs binary.
const std::string & append_filename(std::string *path, const std::string &filename)
Add filename to path, including / after the path if required.
const std::string & remove_filename(std::string *filepath)
Remove filename from path. Handy dirname alternative.
bool check_ext(const std::string &ext, const std::string &filename)
Check if filename has a certain extension. The check is case sensitive.
bool is_blocked(const std::string &filename)
Check if filename should be hidden from output path.
std::string sanitise_filepath(std::string *filepath)
Sanitise file name. Calls realpath() to remove duplicate // or resolve ../.. etc. Changes the path in...
bool is_selected(const std::string &ext)
Find extension in include list, if existing.
int strcasecmp(const std::string &s1, const std::string &s2)
strcasecmp() equivalent for std::string.
bool is_album_art(AVCodecID codec_id, const AVRational *frame_rate)
Minimal check if codec is an album art. Requires frame_rate to decide whether this is a video stream ...
std::string make_filename(uint32_t file_no, const std::string &fileext)
Make a file name from file number and file extension.
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 & remove_sep(std::string *path)
Remove / from the path.
const std::string & remove_ext(std::string *filepath)
Remove extension from filename.
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 append_basepath(std::string *origpath, const char *path)
Translate file names from FUSE to the original absolute path.
void stat_set_size(struct stat *st, size_t size)
Properly fill in all size related members in stat struct.
int get_audio_props(AVFormatContext *format_ctx, int *channels, int *samplerate)
Get first audio stream.
std::string replace_all(std::string str, const std::string &from, const std::string &to)
Same as std::string replace(), but replaces all occurrences.
bool replace_start(std::string *str, const std::string &from, const std::string &to)
Replace start of string from "from" to "to".
bool find_ext(std::string *ext, const std::string &filename)
Find extension in filename, if existing.
std::string ffmpeg_geterror(int errnum)
Get FFmpeg error string for errnum. Internally calls av_strerror().
@ YES
Always recode to same format.
@ NO
Never recode to same format.
#define FFMPEFS_VERSION
FFmpegfs version number.
Definition: ffmpeg_utils.h:42
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
@ AUDIO
FFmpegfs_Format info, 1: audio file.
Definition: ffmpeg_utils.h:518
FFMPEGFS_PARAMS params
FFmpegfs command line parameters.
Definition: ffmpegfs.cc:74
FFMPEGFS_FORMAT_ARR ffmpeg_format
Two FFmpegfs_Format infos, 0: video file, 1: audio file.
Definition: ffmpegfs.cc:73
void transcoder_free()
Free transcoder.
Definition: transcode.cc:251
VIRTUALTYPE
Virtual file types enum.
Definition: fileio.h:92
@ BUFFER
Buffer file.
@ DVD
DVD file.
@ DISK
Regular disk file to transcode.
@ BLURAY
Blu-ray disk file.
@ SCRIPT
Virtual script.
@ PASSTHROUGH
passthrough file, not used
@ VCD
Video CD file.
#define VIRTUALFLAG_CUESHEET
File is part of a set of cue sheet tracks or the directory.
Definition: fileio.h:117
#define VIRTUALFLAG_HIDDEN
File is not transcodable or should otherwise show in listings.
Definition: fileio.h:118
VIRTUALFILE const * LPCVIRTUALFILE
Pointer to const version of VIRTUALFILE.
Definition: fileio.h:254
#define VIRTUALFLAG_FILESET
File is file set (images, HLS)
Definition: fileio.h:114
#define VIRTUALFLAG_HLS
File is part of a set of HLS transport stream (ts) files.
Definition: fileio.h:116
#define VIRTUALFLAG_DIRECTORY
File is a virtual directory.
Definition: fileio.h:113
#define VIRTUALFLAG_NONE
No flags.
Definition: fileio.h:111
#define VIRTUALFLAG_FRAME
File is part of a set of frames.
Definition: fileio.h:115
#define VIRTUALFLAG_PASSTHROUGH
passthrough file, not used
Definition: fileio.h:112
static size_t guess_format_idx(const std::string &filepath)
Try to guess the format index (audio or video) for a file.
Definition: fuseops.cc:2231
LPVIRTUALFILE find_file(const std::string &virtfile)
Find file in cache.
Definition: fuseops.cc:1734
static int ffmpegfs_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi)
Read data from an open file.
Definition: fuseops.cc:1090
static int ffmpegfs_fgetattr(const char *path, struct stat *stbuf, struct fuse_file_info *fi)
Get attributes from an open file.
Definition: fuseops.cc:860
LPVIRTUALFILE find_parent(const std::string &origpath)
Given the destination (post-transcode) file name, determine the parent of the file to be transcoded.
Definition: fuseops.cc:1982
LPVIRTUALFILE insert_dir(VIRTUALTYPE type, const std::string &virtdir, const struct stat *stbuf, int flags)
Add new virtual directory to the internal list. If the file already exists, it will be updated.
Definition: fuseops.cc:1717
int add_dotdot(void *buf, fuse_fill_dir_t filler, const struct stat *stbuf, off_t off)
Make dot and double dot entries for a virtual directory.
Definition: fuseops.cc:2416
static int parse_file(LPVIRTUALFILE newvirtualfile)
Open file with FFmpeg API and parse for streams and cue sheet.
Definition: fuseops.cc:2266
fuse_operations ffmpegfs_ops
FUSE file system operations.
Definition: fuseops.cc:100
std::map< const std::string, VIRTUALFILE > RFILENAME_MAP
Map source file names to virtual file objects.
Definition: fuseops.cc:59
static RFILENAME_MAP rfilenames
Reverse map virtual files to real files.
Definition: fuseops.cc:93
static int ffmpegfs_release(const char *path, struct fuse_file_info *fi)
Release an open file.
Definition: fuseops.cc:1302
static std::vector< char > script_file
Buffer for the virtual script if enabled.
Definition: fuseops.cc:94
bool docker_client
True if running inside a Docker container.
Definition: fuseops.cc:98
int add_fuse_entry(void *buf, fuse_fill_dir_t filler, const std::string &name, const struct stat *stbuf, off_t off)
Wrapper to the Fuse filler function.
Definition: fuseops.cc:2406
static void init_stat(struct stat *stbuf, size_t fsize, time_t ftime, bool directory)
Initialise a stat structure.
Definition: fuseops.cc:1434
static FILENAME_MAP::const_iterator find_prefix(const FILENAME_MAP &map, const std::string &search_for)
Find mapped file by prefix. Normally used to find a path.
Definition: fuseops.cc:1614
static int ffmpegfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi)
Read directory.
Definition: fuseops.cc:304
static int selector(const struct dirent *de)
Filter function used for scandir.
Definition: fuseops.cc:1850
static bool virtual_name(std::string *virtualpath, const std::string &origpath="", const FFmpegfs_Format **current_format=nullptr)
Convert file name from source to destination name.
Definition: fuseops.cc:1538
static struct sigaction oldHandler
Saves old SIGINT handler to restore on shutdown.
Definition: fuseops.cc:96
int load_path(const std::string &path, const struct stat *statbuf, void *buf, fuse_fill_dir_t filler)
Load a path with virtual files for FUSE.
Definition: fuseops.cc:1759
static void stat_to_dir(struct stat *stbuf)
Convert stbuf to directory.
Definition: fuseops.cc:1679
static int ffmpegfs_open(const char *path, struct fuse_file_info *fi)
File open operation.
Definition: fuseops.cc:971
static int ffmpegfs_getattr(const char *path, struct stat *stbuf)
Get file attributes.
Definition: fuseops.cc:542
static bool is_passthrough(const std::string &ext)
Check if a file should be treated passthrough, i.e. bitmaps etc.
Definition: fuseops.cc:109
static void sighandler(int signum)
Replacement SIGINT handler.
Definition: fuseops.cc:2194
LPVIRTUALFILE find_original(const std::string &origpath)
Given the destination (post-transcode) file name, determine the parent of the file to be transcoded.
Definition: fuseops.cc:1894
static void ffmpegfs_destroy(__attribute__((unused)) void *p)
Clean up filesystem.
Definition: fuseops.cc:1385
static int get_source_properties(const std::string &origpath, LPVIRTUALFILE virtualfile)
Calculate the video frame count.
Definition: fuseops.cc:1412
static int scandir(const char *dirp, std::vector< struct dirent > *_namelist, int(*selector)(const struct dirent *), int(*cmp)(const struct dirent **, const struct dirent **))
Scans the directory dirp Works exactly like the scandir(3) function, the only difference is that it r...
Definition: fuseops.cc:1874
static int make_hls_fileset(void *buf, fuse_fill_dir_t filler, const std::string &origpath, LPVIRTUALFILE virtualfile)
Build a virtual HLS file set.
Definition: fuseops.cc:2000
static int ffmpegfs_readlink(const char *path, char *buf, size_t size)
Read the target of a symbolic link.
Definition: fuseops.cc:269
static void * ffmpegfs_init(struct fuse_conn_info *conn)
Initialise filesystem.
Definition: fuseops.cc:1336
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
static int ffmpegfs_statfs(const char *path, struct statvfs *stbuf)
Get file system statistics.
Definition: fuseops.cc:1266
LPVIRTUALFILE find_file_from_orig(const std::string &origfile)
Look for the file in the cache.
Definition: fuseops.cc:1743
static std::string get_number(const char *path, uint32_t *value)
Extract the number for a file name.
Definition: fuseops.cc:2214
LPVIRTUALFILE insert_file(VIRTUALTYPE type, const std::string &virtfile, const struct stat *stbuf, int flags)
Add new virtual file to internal list.
Definition: fuseops.cc:1638
static FILENAME_MAP filenames
Map files to virtual files.
Definition: fuseops.cc:92
static void prepare_script()
Read the virtual script file into memory and store in buffer.
Definition: fuseops.cc:1489
static int kick_next(LPVIRTUALFILE virtualfile)
Give next song in cuesheet list a kick start Starts transcoding of the next song on the cuesheet list...
Definition: fuseops.cc:2156
static void insert(const VIRTUALFILE &virtualfile)
Insert virtualfile into list.
Definition: fuseops.cc:1632
std::map< const std::string, VIRTUALFILE > FILENAME_MAP
Map virtual file names to virtual file objects.
Definition: fuseops.cc:58
static const FFmpegfs_Format * get_format(LPVIRTUALFILE newvirtualfile)
Get FFmpegfs_Format for the file.
Definition: fuseops.cc:2365
void init_fuse_ops()
Initialise FUSE operation structure.
Definition: fuseops.cc:247
std::unique_ptr< thread_pool > tp
Thread pool object.
Definition: fuseops.cc:102
static LPVIRTUALFILE make_file(void *buf, fuse_fill_dir_t filler, VIRTUALTYPE type, const std::string &origpath, const std::string &filename, size_t fsize, time_t ftime=time(nullptr), int flags=VIRTUALFLAG_NONE)
Make a virtual file.
Definition: fuseops.cc:1472
static void flags_to_dir(int *flags)
Convert flags to directory.
Definition: fuseops.cc:1703
Provide various log facilities to stderr, disk or syslog.
std::string m_scriptsource
Source script.
Definition: ffmpegfs.h:234
time_t m_cache_maintenance
Prune timer interval.
Definition: ffmpegfs.h:251
bool smart_transcode() const
Check for smart transcode mode.
Definition: ffmpegfs.cc:230
int m_oldnamescheme
Use old output name scheme, can create duplicate filenames.
Definition: ffmpegfs.h:258
const FFmpegfs_Format * current_format(LPCVIRTUALFILE virtualfile) const
Get FFmpegfs_Format for a virtual file.
Definition: ffmpegfs.cc:235
int64_t m_segment_duration
Duration of one HLS segment file, in AV_TIME_BASE fractional seconds.
Definition: ffmpegfs.h:217
RECODESAME m_recodesame
Recode to same format options.
Definition: ffmpegfs.h:203
unsigned int m_max_threads
Max. number of recoder threads.
Definition: ffmpegfs.h:254
std::string m_mountpath
Mount path: Files from m_mountpath will be mapped to this directory.
Definition: ffmpegfs.h:197
int m_enablescript
Enable virtual script.
Definition: ffmpegfs.h:232
std::string m_scriptfile
Script name.
Definition: ffmpegfs.h:233
std::string m_basepath
Base path: Files from this directory (including all sub directories) will be mapped to m_mountpath.
Definition: ffmpegfs.h:196
VIRTUALFILE * m_nextfile
Next (probable) file to be played. Used for cuesheet lists.
Definition: fileio.h:241
Virtual file definition.
Definition: fileio.h:123
int m_sample_rate
Audio sample rate - Filled in for the DVD/Blu-ray directory.
Definition: fileio.h:247
std::string m_destfile
Name and path of destination file.
Definition: fileio.h:150
size_t m_predicted_size
Use this as the size instead of computing it over and over.
Definition: fileio.h:157
VIRTUALTYPE m_type
Type of this virtual file.
Definition: fileio.h:146
bool m_has_audio
True if file has an audio track.
Definition: fileio.h:160
std::string m_origfile
Sanitised name and path of original file.
Definition: fileio.h:152
int m_flags
One of the VIRTUALFLAG_* flags.
Definition: fileio.h:147
int m_channels
Audio channels - Filled in for the DVD/Blu-ray directory.
Definition: fileio.h:246
std::string m_virtfile
Name and path of virtual file.
Definition: fileio.h:151
bool m_has_video
True if file has a video track.
Definition: fileio.h:161
struct stat m_st
stat structure with size etc.
Definition: fileio.h:153
bool m_has_subtitle
True if file has a subtitle track.
Definition: fileio.h:162
std::vector< char > m_file_contents
Buffer for virtual files.
Definition: fileio.h:164
std::string m_cuesheet
Cue sheet file contents for physical file.
Definition: fileio.h:243
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.
uint32_t get_segment_count() const
Number of HLS segments in set.
Definition: fileio.cc:45
uint32_t m_video_frame_count
Number of frames in video or 0 if not a video.
Definition: fileio.h:158
int64_t m_duration
Track/chapter duration, in AV_TIME_BASE fractional seconds.
Definition: fileio.h:156
Thread pool class implementation.
Cache_Entry * transcoder_new(LPVIRTUALFILE virtualfile, bool begin_transcode)
Allocate and initialise the transcoder.
Definition: transcode.cc:353
bool transcoder_read(Cache_Entry *cache_entry, char *buff, size_t offset, size_t len, int *bytes_read, uint32_t segment_no)
Read some bytes from the internal buffer and into the given buffer.
Definition: transcode.cc:469
void transcoder_exit()
Exit transcoding.
Definition: transcode.cc:738
size_t transcoder_get_size(Cache_Entry *cache_entry)
Return size of output file, as computed by encoder.
Definition: transcode.cc:723
bool transcoder_read_frame(Cache_Entry *cache_entry, char *buff, size_t offset, size_t len, uint32_t frame_no, int *bytes_read, LPVIRTUALFILE virtualfile)
Read one image frame from the internal buffer and into the given buffer.
Definition: transcode.cc:603
size_t transcoder_buffer_watermark(Cache_Entry *cache_entry, uint32_t segment_no)
Return the current watermark of the file while transcoding.
Definition: transcode.cc:728
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
void transcoder_delete(Cache_Entry *cache_entry)
Free the cache entry structure.
Definition: transcode.cc:718
File transcoder interface (for use with by FUSE)
int check_vcd(const std::string &path, void *buf, fuse_fill_dir_t filler)
Get number of chapters on S/VCD.
Definition: vcdparser.cc:159
Video CD and Super Video CD parser.