FFmpegfs Fuse Multi Media Filesystem 2.19
Loading...
Searching...
No Matches
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-2026 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, enum fuse_readdir_flags flags);
83static int ffmpegfs_getattr(const char *path, struct stat *stbuf, fuse_file_info *fi);
84//static 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, fuse_config *cfg);
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));
251// ffmpegfs_ops.fgetattr = ffmpegfs_fgetattr;
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
307static int ffmpegfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi, enum fuse_readdir_flags flags)
308{
309 (void) offset;
310 (void) fi;
311 (void) flags;
312 std::string origpath;
313
314 Logging::trace(path, "readdir");
315
316 append_basepath(&origpath, path);
317 append_sep(&origpath);
318
319 // Add a virtual script if enabled
321 {
322 LPVIRTUALFILE virtualfile = make_file(buf, filler, VIRTUALTYPE::SCRIPT, origpath, params.m_scriptfile, script_file.size());
323 virtualfile->m_file_contents = script_file;
324 }
325
326 LPVIRTUALFILE virtualfile = find_original(origpath);
327
328 if (virtualfile == nullptr)
329 {
330#if defined(USE_LIBBLURAY) || defined(USE_LIBDVD) || defined(USE_LIBVCD)
331 int res;
332#endif
333
334#ifdef USE_LIBVCD
335 res = check_vcd(origpath, buf, filler);
336 if (res != 0)
337 {
338 // Found VCD or error reading VCD
339 return (res >= 0 ? 0 : res);
340 }
341#endif // USE_LIBVCD
342#ifdef USE_LIBDVD
343 res = check_dvd(origpath, buf, filler);
344 if (res != 0)
345 {
346 // Found DVD or error reading DVD
347 return (res >= 0 ? 0 : res);
348 }
349#endif // USE_LIBDVD
350#ifdef USE_LIBBLURAY
351 res = check_bluray(origpath, buf, filler);
352 if (res != 0)
353 {
354 // Found Blu-ray or error reading Blu-ray
355 return (res >= 0 ? 0 : res);
356 }
357#endif // USE_LIBBLURAY
358 }
359
360 if (virtualfile == nullptr || !(virtualfile->m_flags & VIRTUALFLAG_FILESET))
361 {
362 DIR *dp = opendir(origpath.c_str());
363 if (dp != nullptr)
364 {
365 try
366 {
367 std::map<const std::string, struct stat> files;
368
369 // Read directory contents
370 for (struct dirent *de = readdir(dp); de != nullptr; de = readdir(dp))
371 {
372 struct stat stbuf;
373
374 if (lstat((origpath + de->d_name).c_str(), &stbuf) == -1)
375 {
376 // Should actually not happen, file listed by readdir, so it should exist
377 throw ENOENT;
378 }
379
380 files.insert({ de->d_name, stbuf });
381 }
382
383 // Process files
384 for (auto& [key, value] : files)
385 {
386 std::string origname(key);
387
388 if (is_blocked(origname))
389 {
390 continue;
391 }
392
393 std::string origfile;
394 std::string filename(key);
395 struct stat & stbuf = value;
396 int flags = 0;
397
398 origfile = origpath + origname;
399
400 std::string origext;
401 find_ext(&origext, filename);
402
403 if (S_ISREG(stbuf.st_mode) || S_ISLNK(stbuf.st_mode))
404 {
405 const FFmpegfs_Format *current_format = nullptr;
406
407 // Check if file can be transcoded
408 if (virtual_name(&filename, origpath, &current_format))
409 {
410 if (current_format->video_codec() == AV_CODEC_ID_NONE)
411 {
412 LPVIRTUALFILE newvirtualfile = find_file_from_orig(origfile);
413
414 if (newvirtualfile == nullptr)
415 {
416 // Should never happen, we've just created this entry in virtual_name...
417 Logging::error(origfile, "INTERNAL ERROR: ffmpegfs_readdir()! newvirtualfile is NULL.");
418 throw EINVAL;
419 }
420
421 // If target supports no video, we need to do some extra work and checkF
422 // the input file to actually have an audio stream. If not, hide the file,
423 // makes no sense to transcode anyway.
424 if (!newvirtualfile->m_has_audio)
425 {
426 Logging::debug(origfile, "Unable to transcode. The source has no audio stream, but the target just supports audio.");
427 flags |= VIRTUALFLAG_HIDDEN;
428 }
429 }
430
431 if (!(flags & VIRTUALFLAG_HIDDEN))
432 {
433 if (check_cuesheet(origfile, buf, filler) < 0)
434 {
435 throw EINVAL;
436 }
437 }
438
439 std::string newext;
440 find_ext(&newext, filename);
441
442 if (!current_format->is_multiformat())
443 {
444 if (origext != newext || params.m_recodesame == RECODESAME::YES)
445 {
446 insert_file(VIRTUALTYPE::DISK, origpath + filename, origfile, &stbuf, flags);
447 }
448 else
449 {
450 insert_file(VIRTUALTYPE::DISK, origpath + filename, origfile, &stbuf, flags | VIRTUALFLAG_PASSTHROUGH);
451 }
452 }
453 else
454 {
455 // Change file to directory for the frame set
456 stat_to_dir(&stbuf);
457 flags_to_dir(&flags);
458
459 filename = origname; // Restore original name
460
461 insert_file(VIRTUALTYPE::DISK, origfile, &stbuf, flags);
462 }
463 }
464 }
465
466 if (!(flags & VIRTUALFLAG_HIDDEN))
467 {
468 if (add_fuse_entry(buf, filler, filename, &stbuf, 0))
469 {
470 break;
471 }
472 }
473 }
474
475 closedir(dp);
476
477 errno = 0; // Just to make sure - reset any error
478 }
479 catch (int _errno)
480 {
481 closedir(dp);
482
483 errno = _errno;
484 }
485 }
486 }
487 else
488 {
489 try
490 {
492 {
493 add_dotdot(buf, filler, &virtualfile->m_st, 0);
494 }
495
496 const FFmpegfs_Format *ffmpegfs_format = params.current_format(virtualfile);
497
498 if (ffmpegfs_format->is_frameset())
499 {
500 // Generate set of all frames
501 if (!virtualfile->m_video_frame_count)
502 {
503 int res = get_source_properties(origpath, virtualfile);
504 if (res < 0)
505 {
506 throw EINVAL;
507 }
508 }
509
510 //Logging::debug(origpath, "readdir: Creating frame set of %1 frames. %2", virtualfile->m_video_frame_count, virtualfile->m_origfile);
511
512 for (uint32_t frame_no = 1; frame_no <= virtualfile->m_video_frame_count; frame_no++)
513 {
514 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);
515 }
516 }
517 else if (ffmpegfs_format->is_hls())
518 {
519 int res = make_hls_fileset(buf, filler, origpath, virtualfile);
520 if (res < 0)
521 {
522 throw EINVAL;
523 }
524 }
525 else if (/*virtualfile != nullptr && */virtualfile->m_flags & VIRTUALFLAG_CUESHEET)
526 {
527 // Fill in list for cue sheet
528 load_path(origpath, nullptr, buf, filler);
529 }
530
531 errno = 0; // Just to make sure - reset any error
532 }
533 catch (int _errno)
534 {
535 errno = _errno;
536 }
537 }
538
539 return -errno;
540}
541
548static int ffmpegfs_getattr(const char *path, struct stat *stbuf, struct fuse_file_info * /*fi*/)
549{
550 Logging::trace(path, "getattr");
551
552 if (is_blocked(path))
553 {
554 errno = ENOENT;
555 return -errno;
556 }
557
558 std::string origpath;
559
560 append_basepath(&origpath, path);
561
562 LPVIRTUALFILE virtualfile = find_original(&origpath);
563 VIRTUALTYPE type = (virtualfile != nullptr) ? virtualfile->m_type : VIRTUALTYPE::DISK;
564 int flags = (virtualfile != nullptr) ? virtualfile->m_flags : VIRTUALFLAG_NONE;
565
566 if (virtualfile != nullptr && (virtualfile->m_flags & VIRTUALFLAG_HIDDEN))
567 {
568 errno = ENOENT;
569 return -errno;
570 }
571
572 if (virtualfile == nullptr && lstat(origpath.c_str(), stbuf) == 0)
573 {
574 // File was not yet created as virtual file, but physically exists
575 const FFmpegfs_Format *current_format = nullptr;
576 std::string filename(origpath);
577
578 if (S_ISREG(stbuf->st_mode) && virtual_name(&filename, "", &current_format))
579 {
580 if (!current_format->is_multiformat())
581 {
582 // Regular files
583 Logging::trace(origpath, "getattr: Existing file.");
584 errno = 0;
585 return 0;
586 }
587 else
588 {
589 Logging::trace(origpath, "getattr: Creating frame set directory of file.");
590
591 flags |= VIRTUALFLAG_FILESET;
592
593 if (current_format->is_frameset())
594 {
595 flags |= VIRTUALFLAG_FRAME;
596 }
597 else if (current_format->is_hls())
598 {
599 flags |= VIRTUALFLAG_HLS;
600 }
601 }
602 }
603 else
604 {
605 // Pass-through for regular files
606 Logging::trace(origpath, "getattr: Treating existing file/directory as passthrough.");
607 errno = 0;
608 return 0;
609 }
610 }
611 else if ((flags & VIRTUALFLAG_PASSTHROUGH) && lstat(origpath.c_str(), stbuf) == 0)
612 {
613 // File physically exists and is marked as passthrough
614 Logging::debug(origpath, "getattr: File not recoded because --recodesame=NO.");
615 errno = 0;
616 return 0;
617 }
618 else
619 {
620 // Not really an error.
621 errno = 0;
622 }
623
624 // This is a virtual file
625 bool no_check = false;
626
627 switch (type)
628 {
630 {
631 // Use stored status
632 mempcpy(stbuf, &virtualfile->m_st, sizeof(struct stat));
633 errno = 0;
634 break;
635 }
636#ifdef USE_LIBVCD
637 case VIRTUALTYPE::VCD:
638#endif // USE_LIBVCD
639#ifdef USE_LIBDVD
640 case VIRTUALTYPE::DVD:
641#endif // USE_LIBDVD
642#ifdef USE_LIBBLURAY
644#endif // USE_LIBBLURAY
645 {
646 // Use stored status
647 mempcpy(stbuf, &virtualfile->m_st, sizeof(struct stat));
648 no_check = true; // FILETYPE already known, no need to check again.
650 }
652 {
653 if (virtualfile != nullptr && (flags & (VIRTUALFLAG_FRAME | VIRTUALFLAG_HLS | VIRTUALFLAG_DIRECTORY | VIRTUALFLAG_CUESHEET)))
654 {
655 mempcpy(stbuf, &virtualfile->m_st, sizeof(struct stat));
656
657 errno = 0; // Just to make sure - reset any error
658 break;
659 }
660
661 if (virtualfile == nullptr || !(virtualfile->m_flags & VIRTUALFLAG_FILESET))
662 {
663 if (!no_check && lstat(origpath.c_str(), stbuf) == -1)
664 {
665 // If file does not exist here we can assume it's some sort of virtual file: Regular, DVD, S/VCD, cue sheet track
666 int error = -errno;
667
668 virtualfile = find_original(&origpath);
669
670 if (virtualfile == nullptr)
671 {
672 std::string pathonly(origpath);
673 int res = 0;
674
675 remove_filename(&pathonly);
676
677#ifdef USE_LIBVCD
678 //if (res <= 0) Not necessary here, will always be true
679 {
680 // Returns -errno or number or titles on VCD
681 res = check_vcd(pathonly);
682 }
683#endif // USE_LIBVCD
684#ifdef USE_LIBDVD
685 if (res <= 0)
686 {
687 // Returns -errno or number or titles on DVD
688 res = check_dvd(pathonly);
689 }
690#endif // USE_LIBDVD
691#ifdef USE_LIBBLURAY
692 if (res <= 0)
693 {
694 // Returns -errno or number or titles on Blu-ray
695 res = check_bluray(pathonly);
696 }
697#endif // USE_LIBBLURAY
698 if (res <= 0)
699 {
700 if (check_ext(TRACKDIR, origpath))
701 {
702 std::string origfile(origpath);
703
704 remove_ext(&origfile); // remove TRACKDIR extension to get original media file name
705
706 Logging::trace(origfile, "getattr: Checking for cue sheet.");
707
708 if (virtual_name(&origfile))
709 {
710 // Returns -errno or number or titles in cue sheet
711 res = check_cuesheet(origfile);
712 }
713 }
714 }
715
716 if (ffmpeg_format[FORMAT::VIDEO].is_frameset())
717 {
718 LPVIRTUALFILE parent_file = find_parent(origpath);
719
720 if (parent_file != nullptr && (parent_file->m_flags & VIRTUALFLAG_DIRECTORY) && (parent_file->m_flags & VIRTUALFLAG_FILESET))
721 {
722 // Generate set of all frames
723 if (!parent_file->m_video_frame_count)
724 {
725 int res2 = get_source_properties(origpath, parent_file);
726 if (res2 < 0)
727 {
728 return res2;
729 }
730 }
731
732 for (uint32_t frame_no = 1; frame_no <= parent_file->m_video_frame_count; frame_no++)
733 {
734 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);
735 }
736
737 LPVIRTUALFILE virtualfile2 = find_original(origpath);
738 if (virtualfile2 == nullptr)
739 {
740 // File does not exist
741 return -ENOENT;
742 }
743
744 mempcpy(stbuf, &virtualfile2->m_st, sizeof(struct stat));
745
746 // Clear errors
747 errno = 0;
748
749 return 0;
750 }
751 }
752 else if (ffmpeg_format[FORMAT::VIDEO].is_hls())
753 {
754 LPVIRTUALFILE parent_file = find_parent(origpath);
755
756 if (parent_file != nullptr && (parent_file->m_flags & VIRTUALFLAG_DIRECTORY) && (parent_file->m_flags & VIRTUALFLAG_FILESET))
757 {
758 if (!parent_file->m_video_frame_count) //***< @todo HLS format: Do audio files source properties get checked over and over?
759 {
760 int res2 = get_source_properties(origpath, parent_file);
761 if (res2 < 0)
762 {
763 return res2;
764 }
765 }
766
767 make_hls_fileset(nullptr, nullptr, parent_file->m_destfile + "/", parent_file);
768
769 LPVIRTUALFILE virtualfile2 = find_original(origpath);
770 if (virtualfile2 == nullptr)
771 {
772 // File does not exist
773 return -ENOENT;
774 }
775
776 mempcpy(stbuf, &virtualfile2->m_st, sizeof(struct stat));
777
778 // Clear errors
779 errno = 0;
780
781 return 0;
782 }
783 }
784
785 if (res <= 0)
786 {
787 // No Blu-ray/DVD/VCD found or error reading disk
788 return (!res ? error : res);
789 }
790 }
791
792 virtualfile = find_original(&origpath);
793
794 if (virtualfile == nullptr)
795 {
796 // Not a DVD/VCD/Blu-ray file or cue sheet track
797 return -ENOENT;
798 }
799
800 mempcpy(stbuf, &virtualfile->m_st, sizeof(struct stat));
801 }
802
803 if (flags & VIRTUALFLAG_FILESET)
804 {
805 int flags2 = 0;
806
807 // Change file to virtual directory for the frame set. Keep permissions.
808 stat_to_dir(stbuf);
809 flags_to_dir(&flags2);
810
811 append_sep(&origpath);
812
813 insert_file(type, origpath, stbuf, flags2);
814 }
815 else if (S_ISREG(stbuf->st_mode))
816 {
817 // Get size for resulting output file from regular file, otherwise it's a symbolic link or a virtual frame set.
818 if (virtualfile == nullptr)
819 {
820 // We should not never end here - report bad file number.
821 return -EBADF;
822 }
823
824 if (!transcoder_cached_filesize(virtualfile, stbuf))
825 {
826 Cache_Entry* cache_entry = transcoder_new(virtualfile, false);
827 if (cache_entry == nullptr)
828 {
829 return -errno;
830 }
831
832 stat_set_size(stbuf, transcoder_get_size(cache_entry));
833
834 transcoder_delete(cache_entry);
835 }
836 }
837
838 errno = 0; // Just to make sure - reset any error
839 }
840 else // if (virtualfile != nullptr && (virtualfile->m_flags & VIRTUALFLAG_DIRECTORY))
841 {
842 // Frame set, simply report stat.
843 mempcpy(stbuf, &virtualfile->m_st, sizeof(struct stat));
844 errno = 0;
845 }
846 break;
847 }
848 // We should never come here but this shuts up a warning
851 {
852 break;
853 }
854 }
855
856 return 0;
857}
858
859/*
860 * @brief Get attributes from an open file.
861 * @param[in] path Virtual path to inspect.
862 * @param[out] stbuf Buffer receiving the attributes.
863 * @param[in] fi FUSE file information.
864 * @return On success, returns 0. On error, returns -errno.
865 */
866//static int ffmpegfs_fgetattr(const char *path, struct stat * stbuf, struct fuse_file_info *fi)
867//{
868// std::string origpath;
869
870// Logging::trace(path, "fgetattr");
871
872// errno = 0;
873
874// append_basepath(&origpath, path);
875
876// LPCVIRTUALFILE virtualfile = find_original(&origpath);
877
878// if (virtualfile != nullptr && (virtualfile->m_flags & VIRTUALFLAG_HIDDEN))
879// {
880// errno = ENOENT;
881// return -errno;
882// }
883
884// if ((virtualfile == nullptr || (virtualfile->m_flags & VIRTUALFLAG_PASSTHROUGH)) && lstat(origpath.c_str(), stbuf) == 0)
885// {
886// // passthrough for regular files
887// errno = 0;
888// return 0;
889// }
890// else
891// {
892// // Not really an error.
893// errno = 0;
894// }
895
896// // This is a virtual file
897
898// bool no_check = false;
899
900// switch (virtualfile->m_type)
901// {
902//#ifdef USE_LIBVCD
903// case VIRTUALTYPE::VCD:
904//#endif // USE_LIBVCD
905//#ifdef USE_LIBDVD
906// case VIRTUALTYPE::DVD:
907//#endif // USE_LIBDVD
908//#ifdef USE_LIBBLURAY
909// case VIRTUALTYPE::BLURAY:
910//#endif // USE_LIBBLURAY
911// {
912// // Use stored status
913// mempcpy(stbuf, &virtualfile->m_st, sizeof(struct stat));
914// no_check = true;
915// FALLTHROUGH_INTENDED;
916// }
917// case VIRTUALTYPE::DISK:
918// {
919// if (virtualfile->m_flags & (VIRTUALFLAG_FILESET | VIRTUALFLAG_FRAME | VIRTUALFLAG_HLS | VIRTUALFLAG_DIRECTORY))
920// {
921// mempcpy(stbuf, &virtualfile->m_st, sizeof(struct stat));
922// }
923// else
924// {
925// if (!no_check)
926// {
927// if (lstat(origpath.c_str(), stbuf) == -1)
928// {
929// return -errno;
930// }
931// }
932
933// // Get size for resulting output file from regular file, otherwise it's a symbolic link.
934// if (S_ISREG(stbuf->st_mode))
935// {
936// Cache_Entry* cache_entry = reinterpret_cast<Cache_Entry*>(fi->fh);
937
938// if (cache_entry == nullptr)
939// {
940// Logging::error(path, "fgetattr: Tried to stat unopen file.");
941// errno = EBADF;
942// return -errno;
943// }
944
945// uint32_t segment_no = 0;
946
947// stat_set_size(stbuf, transcoder_buffer_watermark(cache_entry, segment_no));
948// }
949// }
950
951// errno = 0; // Just to make sure - reset any error
952
953// break;
954// }
955// case VIRTUALTYPE::SCRIPT:
956// {
957// mempcpy(stbuf, &virtualfile->m_st, sizeof(struct stat));
958// break;
959// }
960// // We should never come here but this shuts up a warning
961// case VIRTUALTYPE::PASSTHROUGH:
962// case VIRTUALTYPE::BUFFER:
963// {
964// break;
965// }
966// }
967
968// return 0;
969//}
970
977static int ffmpegfs_open(const char *path, struct fuse_file_info *fi)
978{
979 std::string origpath;
980
981 Logging::trace(path, "open");
982
983 append_basepath(&origpath, path);
984
985 LPVIRTUALFILE virtualfile = find_original(&origpath);
986
987 if (virtualfile == nullptr || (virtualfile->m_flags & VIRTUALFLAG_PASSTHROUGH))
988 {
989 int fd = open(origpath.c_str(), fi->flags);
990 if (fd != -1)
991 {
992 close(fd);
993 // File is real and can be opened.
994 errno = 0;
995 }
996 else
997 {
998 if (errno == ENOENT)
999 {
1000 // File does not exist? We should never end up here...
1001 errno = EINVAL;
1002 }
1003
1004 // If file does exist, but can't be opened, return error.
1005 }
1006
1007 return -errno;
1008 }
1009
1010 // This is a virtual file
1011 kick_next(virtualfile);
1012
1013 switch (virtualfile->m_type)
1014 {
1016 {
1017 errno = 0;
1018 break;
1019 }
1020#ifdef USE_LIBVCD
1021 case VIRTUALTYPE::VCD:
1022#endif // USE_LIBVCD
1023#ifdef USE_LIBDVD
1024 case VIRTUALTYPE::DVD:
1025#endif // USE_LIBDVD
1026#ifdef USE_LIBBLURAY
1028#endif // USE_LIBBLURAY
1029 case VIRTUALTYPE::DISK:
1030 {
1031 if (virtualfile->m_flags & (VIRTUALFLAG_FRAME | VIRTUALFLAG_HLS))
1032 {
1033 LPVIRTUALFILE parent_file = find_parent(origpath);
1034
1035 if (parent_file != nullptr)
1036 {
1037 Cache_Entry* cache_entry;
1038
1039 // HLS segments and frame-set files are opened through their
1040 // parent cache entry. The exact segment/frame number is only
1041 // known in ffmpegfs_read(), so do not start transcoding here.
1042 // Starting here would always begin at segment/frame 1 and only
1043 // seek afterwards.
1044 cache_entry = transcoder_new(parent_file, false);
1045 if (cache_entry == nullptr)
1046 {
1047 return -errno;
1048 }
1049
1050 // Store transcoder in the fuse_file_info structure.
1051 fi->fh = reinterpret_cast<uintptr_t>(cache_entry);
1052 // Need this because we do not know the exact size in advance.
1053 fi->direct_io = 1;
1054 //fi->keep_cache = 1;
1055
1056 // Clear errors
1057 errno = 0;
1058 }
1059 }
1060 else if (!(virtualfile->m_flags & VIRTUALFLAG_FILESET))
1061 {
1062 Cache_Entry* cache_entry;
1063
1064 cache_entry = transcoder_new(virtualfile, true);
1065 if (cache_entry == nullptr)
1066 {
1067 return -errno;
1068 }
1069
1070 // Store transcoder in the fuse_file_info structure.
1071 fi->fh = reinterpret_cast<uintptr_t>(cache_entry);
1072 // Need this because we do not know the exact size in advance.
1073 fi->direct_io = 1;
1074 //fi->keep_cache = 1;
1075
1076 // Clear errors
1077 errno = 0;
1078 }
1079 break;
1080 }
1081 // We should never come here but this shuts up a warning
1084 {
1085 break;
1086 }
1087 }
1088
1089 return 0;
1090}
1091
1101static int ffmpegfs_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi)
1102{
1103 std::string origpath;
1104 size_t locoffset = static_cast<size_t>(offset); // Cast OK: offset can never be < 0.
1105 int bytes_read = 0;
1106
1107 Logging::trace(path, "read: Reading %1 bytes from offset %2 to %3.", size, locoffset, size + locoffset);
1108
1109 append_basepath(&origpath, path);
1110
1111 LPVIRTUALFILE virtualfile = find_original(&origpath);
1112
1113 if (virtualfile == nullptr || (virtualfile->m_flags & VIRTUALFLAG_PASSTHROUGH))
1114 {
1115 int fd = open(origpath.c_str(), O_RDONLY);
1116 if (fd != -1)
1117 {
1118 // If this is a real file, pass the call through.
1119 bytes_read = static_cast<int>(pread(fd, buf, size, offset));
1120 close(fd);
1121 if (bytes_read >= 0)
1122 {
1123 return bytes_read;
1124 }
1125 else
1126 {
1127 return -errno;
1128 }
1129 }
1130 else if (errno != ENOENT)
1131 {
1132 // File does exist, but can't be opened.
1133 return -errno;
1134 }
1135 else
1136 {
1137 // File does not exist, and this is fine.
1138 errno = 0;
1139 }
1140 }
1141
1142 // This is a virtual file
1143 bool success = true;
1144
1145 if (virtualfile == nullptr)
1146 {
1147 errno = EINVAL;
1148 Logging::error(origpath.c_str(), "read: INTERNAL ERROR: ffmpegfs_read()! virtualfile == NULL");
1149 return -errno;
1150 }
1151
1152 switch (virtualfile->m_type)
1153 {
1155 {
1156 if (locoffset >= virtualfile->m_file_contents.size())
1157 {
1158 bytes_read = 0;
1159 break;
1160 }
1161
1162 size_t bytes = size;
1163 if (locoffset + bytes > virtualfile->m_file_contents.size())
1164 {
1165 bytes = virtualfile->m_file_contents.size() - locoffset;
1166 }
1167
1168 if (bytes)
1169 {
1170 std::memcpy(buf, &virtualfile->m_file_contents[locoffset], bytes);
1171 }
1172
1173 bytes_read = static_cast<int>(bytes);
1174 break;
1175 }
1176#ifdef USE_LIBVCD
1177 case VIRTUALTYPE::VCD:
1178#endif // USE_LIBVCD
1179#ifdef USE_LIBDVD
1180 case VIRTUALTYPE::DVD:
1181#endif // USE_LIBDVD
1182#ifdef USE_LIBBLURAY
1184#endif // USE_LIBBLURAY
1185 case VIRTUALTYPE::DISK:
1186 {
1187 if (virtualfile->m_flags & VIRTUALFLAG_FRAME)
1188 {
1189 Cache_Entry* cache_entry;
1190
1191 cache_entry = reinterpret_cast<Cache_Entry*>(fi->fh);
1192
1193 if (cache_entry == nullptr)
1194 {
1195 if (errno)
1196 {
1197 Logging::error(origpath.c_str(), "read: Tried to read from unopen file: (%1) %2", errno, strerror(errno));
1198 }
1199 return -errno;
1200 }
1201
1202 uint32_t frame_no = 0;
1203 std::string filename = get_number(path, &frame_no);
1204 if (!frame_no)
1205 {
1206 errno = EINVAL;
1207 Logging::error(origpath.c_str(), "read: Unable to deduct frame no. from file name (%1): (%2) %3", filename.c_str(), errno, strerror(errno));
1208 return -errno;
1209 }
1210
1211 success = transcoder_read_frame(cache_entry, buf, locoffset, size, frame_no, &bytes_read, virtualfile);
1212 }
1213 else if (!(virtualfile->m_flags & VIRTUALFLAG_FILESET))
1214 {
1215 Cache_Entry* cache_entry;
1216
1217 cache_entry = reinterpret_cast<Cache_Entry*>(fi->fh);
1218
1219 if (cache_entry == nullptr)
1220 {
1221 if (errno)
1222 {
1223 Logging::error(origpath.c_str(), "read: Tried to read from unopen file: (%1) %2", errno, strerror(errno));
1224 }
1225 return -errno;
1226 }
1227
1228 uint32_t segment_no = 0;
1229
1230 if (virtualfile->m_flags & VIRTUALFLAG_HLS)
1231 {
1232 std::string filename = get_number(path, &segment_no);
1233 if (!segment_no)
1234 {
1235 errno = EINVAL;
1236 Logging::error(origpath.c_str(), "read: Unable to deduct segment no. from file name (%1): (%2) %3", filename.c_str(), errno, strerror(errno));
1237 return -errno;
1238 }
1239 }
1240
1241 success = transcoder_read(cache_entry, buf, locoffset, size, &bytes_read, segment_no);
1242 }
1243 break;
1244 }
1247 {
1248 break;
1249 }
1250 }
1251
1252 if (success)
1253 {
1254 if (bytes_read)
1255 {
1256 Logging::trace(path, "read: Read %1 bytes from offset %2 to %3.", bytes_read, locoffset, static_cast<size_t>(bytes_read) + locoffset);
1257 }
1258 else
1259 {
1260 Logging::trace(path, "read: Read output file to EOF.");
1261 }
1262
1263 return bytes_read;
1264 }
1265 else
1266 {
1267 return -errno;
1268 }
1269}
1270
1277static int ffmpegfs_statfs(const char *path, struct statvfs *stbuf)
1278{
1279 std::string origpath;
1280
1281 Logging::trace(path, "statfs");
1282
1283 append_basepath(&origpath, path);
1284
1285 // passthrough for regular files
1286 if (!origpath.empty() && statvfs(origpath.c_str(), stbuf) == 0)
1287 {
1288 errno = 0; // Just to make sure - reset any error
1289
1290 return 0;
1291 }
1292 else
1293 {
1294 // Not really an error.
1295 errno = 0;
1296 }
1297
1298 find_original(&origpath);
1299
1300 statvfs(origpath.c_str(), stbuf);
1301
1302 errno = 0; // Just to make sure - reset any error
1303
1304 return 0;
1305}
1306
1313static int ffmpegfs_release(const char *path, struct fuse_file_info *fi)
1314{
1315 Cache_Entry* cache_entry = reinterpret_cast<Cache_Entry*>(fi->fh);
1316
1317 Logging::trace(path, "release");
1318
1319 if (cache_entry != nullptr)
1320 {
1321 uint32_t segment_no = 0;
1322
1323 if (cache_entry->virtualfile()->m_flags & VIRTUALFLAG_HLS)
1324 {
1325 std::string filename = get_number(path, &segment_no);
1326 if (!segment_no)
1327 {
1328 errno = EINVAL;
1329 Logging::error(path, "release: Unable to deduct segment no. from file name (%1): (%2) %3", filename.c_str(), errno, strerror(errno));
1330 }
1331 else
1332 {
1333 cache_entry->m_buffer->close_file(segment_no - 1, CACHE_FLAG_RO);
1334 }
1335 }
1336 transcoder_delete(cache_entry);
1337 }
1338
1339 return 0;
1340}
1341
1348static void *ffmpegfs_init(struct fuse_conn_info *conn, struct fuse_config *cfg)
1349{
1350 (void) conn;
1351 (void) cfg;
1352 Logging::info(nullptr, "%1 V%2 initialising.", PACKAGE_NAME, FFMPEFS_VERSION);
1353 Logging::info(nullptr, "Mapping '%1' to '%2'.", params.m_basepath.c_str(), params.m_mountpath.c_str());
1354 if (docker_client)
1355 {
1356 Logging::info(nullptr, "Running inside Docker.");
1357 }
1358
1359 struct sigaction sa;
1360 std::memset(&sa, 0, sizeof(sa));
1361 sigemptyset(&sa.sa_mask);
1362 sigaddset(&sa.sa_mask, SIGINT);
1363 sa.sa_handler = sighandler;
1364 sigaction(SIGINT, &sa, &oldHandler);
1365
1366 // We need synchronous reads.
1367 //conn->async_read = 0;
1368 //conn->async_read = 1;
1369 //conn->want |= FUSE_CAP_ASYNC_READ;
1370 //conn->want |= FUSE_CAP_SPLICE_READ;
1371
1373 {
1375 {
1376 exit(1);
1377 }
1378 }
1379
1381 {
1383 }
1384
1385 if (tp == nullptr)
1386 {
1387 tp = std::make_unique<thread_pool>(params.m_max_threads);
1388 }
1389
1390 tp->init();
1391
1392 return nullptr;
1393}
1394
1399static void ffmpegfs_destroy(__attribute__((unused)) void * p)
1400{
1401 Logging::info(nullptr, "%1 V%2 terminating.", PACKAGE_NAME, FFMPEFS_VERSION);
1402 std::printf("%s V%s terminating\n", PACKAGE_NAME, FFMPEFS_VERSION);
1403
1405
1408
1409 if (tp != nullptr)
1410 {
1411 tp->tear_down();
1412 tp.reset();
1413 }
1414
1415 script_file.clear();
1416
1417 Logging::info(nullptr, "%1 V%2 terminated.", PACKAGE_NAME, FFMPEFS_VERSION);
1418}
1419
1426static int get_source_properties(const std::string & origpath, LPVIRTUALFILE virtualfile)
1427{
1428 Cache_Entry* cache_entry = transcoder_new(virtualfile, false);
1429 if (cache_entry == nullptr)
1430 {
1431 return -errno;
1432 }
1433
1434 transcoder_delete(cache_entry);
1435
1436 Logging::debug(origpath, "Duration: %1 Frames: %2 Segments: %3", virtualfile->m_duration, virtualfile->m_video_frame_count, virtualfile->get_segment_count());
1437
1438 return 0;
1439}
1440
1448static void init_stat(struct stat * stbuf, size_t fsize, time_t ftime, bool directory)
1449{
1450 std::memset(stbuf, 0, sizeof(struct stat));
1451
1452 stbuf->st_mode = DEFFILEMODE; //S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
1453 if (directory)
1454 {
1455 stbuf->st_mode |= S_IFDIR | S_IXUSR | S_IXGRP | S_IXOTH;
1456 stbuf->st_nlink = 2;
1457 }
1458 else
1459 {
1460 stbuf->st_mode |= S_IFREG;
1461 stbuf->st_nlink = 1;
1462 }
1463
1464 stat_set_size(stbuf, fsize);
1465
1466 // Set current user as owner
1467 stbuf->st_uid = getuid();
1468 stbuf->st_gid = getgid();
1469
1470 // Use current date/time
1471 stbuf->st_atime = stbuf->st_mtime = stbuf->st_ctime = ftime;
1472}
1473
1486static 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)
1487{
1488 struct stat stbuf;
1489
1490 init_stat(&stbuf, fsize, ftime, false);
1491
1492 if (add_fuse_entry(buf, filler, filename, &stbuf, 0))
1493 {
1494 return nullptr;
1495 }
1496
1497 return insert_file(type, origpath + filename, &stbuf, flags);
1498}
1499
1503static void prepare_script()
1504{
1505 std::string scriptsource;
1506
1507 exepath(&scriptsource);
1508 scriptsource += params.m_scriptsource;
1509
1510 Logging::debug(scriptsource, "Reading virtual script source.");
1511
1512 FILE *fpi = fopen(scriptsource.c_str(), "rt");
1513 if (fpi == nullptr)
1514 {
1515 Logging::warning(scriptsource, "File open failed. Disabling script: (%1) %2", errno, strerror(errno));
1516 params.m_enablescript = false;
1517 }
1518 else
1519 {
1520 struct stat stbuf;
1521 if (fstat(fileno(fpi), &stbuf) == -1)
1522 {
1523 Logging::warning(scriptsource, "File could not be accessed. Disabling script: (%1) %2", errno, strerror(errno));
1524 params.m_enablescript = false;
1525 }
1526 else
1527 {
1528 script_file.resize(static_cast<size_t>(stbuf.st_size));
1529
1530 if (fread(&script_file[0], 1, static_cast<size_t>(stbuf.st_size), fpi) != static_cast<size_t>(stbuf.st_size))
1531 {
1532 Logging::warning(scriptsource, "File could not be read. Disabling script: (%1) %2", errno, strerror(errno));
1533 params.m_enablescript = false;
1534 }
1535 else
1536 {
1537 Logging::trace(scriptsource, "Read %1 bytes of script file.", script_file.size());
1538 }
1539 }
1540
1541 fclose(fpi);
1542 }
1543}
1544
1552static bool virtual_name(std::string * virtualpath, const std::string & origpath /*= ""*/, const FFmpegfs_Format **current_format /*= nullptr*/)
1553{
1554 std::string ext;
1555
1556 if (current_format != nullptr)
1557 {
1558 *current_format = nullptr;
1559 }
1560
1561 if (!find_ext(&ext, *virtualpath) || is_passthrough(ext))
1562 {
1563 return false;
1564 }
1565
1566 if (!is_selected(ext))
1567 {
1568 return false;
1569 }
1570
1571 VIRTUALFILE newvirtualfile;
1572
1573 newvirtualfile.m_origfile = origpath + *virtualpath;
1574
1575 const FFmpegfs_Format *ffmpegfs_format = get_format(&newvirtualfile);
1576
1577 if (ffmpegfs_format != nullptr)
1578 {
1580 {
1581 // Old filename scheme, creates duplicates
1582 replace_ext(virtualpath, ffmpegfs_format->fileext());
1583 }
1584 else
1585 {
1586 // New name scheme
1587 append_ext(virtualpath, ffmpegfs_format->fileext());
1588 }
1589
1590 newvirtualfile.m_destfile = origpath + *virtualpath;
1591 newvirtualfile.m_virtfile = params.m_mountpath + *virtualpath;
1592
1593 if (lstat(newvirtualfile.m_destfile.c_str(), &newvirtualfile.m_st) == 0)
1594 {
1596 {
1597 newvirtualfile.m_flags |= VIRTUALFLAG_PASSTHROUGH;
1598 }
1599
1600 if (ffmpegfs_format->is_multiformat())
1601 {
1602 // Change file to directory for the frame set
1603 // Change file to virtual directory for the frame set. Keep permissions.
1604 stat_to_dir(&newvirtualfile.m_st);
1605 flags_to_dir(&newvirtualfile.m_flags);
1606 }
1607 }
1608
1609 insert(newvirtualfile);
1610
1611 if (current_format != nullptr)
1612 {
1613 *current_format = ffmpegfs_format;
1614 }
1615
1616 return true;
1617 }
1618
1619 return false;
1620}
1621
1628static FILENAME_MAP::const_iterator find_prefix(const FILENAME_MAP & map, const std::string & search_for)
1629{
1630 FILENAME_MAP::const_iterator it = map.lower_bound(search_for);
1631 if (it != map.cend())
1632 {
1633 const std::string & key = it->first;
1634 if (key.compare(0, search_for.size(), search_for) == 0) // Really a prefix?
1635 {
1636 return it;
1637 }
1638 }
1639 return map.cend();
1640}
1641
1646static void insert(const VIRTUALFILE & virtualfile)
1647{
1648 filenames.emplace(virtualfile.m_destfile, virtualfile);
1649 rfilenames.emplace(virtualfile.m_origfile, virtualfile);
1650}
1651
1652LPVIRTUALFILE insert_file(VIRTUALTYPE type, const std::string & virtfile, const struct stat * stbuf, int flags)
1653{
1654 return insert_file(type, virtfile, virtfile, stbuf, flags);
1655}
1656
1657LPVIRTUALFILE insert_file(VIRTUALTYPE type, const std::string & virtfile, const std::string & origfile, const struct stat * stbuf, int flags)
1658{
1659 std::string sanitised_virtfile(sanitise_filepath(virtfile));
1660
1661 FILENAME_MAP::iterator it = filenames.find(sanitised_virtfile);
1662
1663 if (it == filenames.cend())
1664 {
1665 // Create new
1666 std::string sanitised_origfile(sanitise_filepath(origfile));
1667 VIRTUALFILE virtualfile;
1668
1669 std::memcpy(&virtualfile.m_st, stbuf, sizeof(struct stat));
1670
1671 virtualfile.m_type = type;
1672 virtualfile.m_flags = flags;
1673 virtualfile.m_format_idx = guess_format_idx(sanitised_origfile); // Make a guess, will be finalised later
1674 virtualfile.m_destfile = sanitised_virtfile;
1675 virtualfile.m_origfile = sanitised_origfile;
1676 virtualfile.m_virtfile = sanitised_origfile;
1677 //virtualfile.m_predicted_size = static_cast<size_t>(stbuf->st_size);
1678
1680
1681 insert(virtualfile);
1682
1683 it = filenames.find(sanitised_virtfile);
1684 }
1685
1686 return &it->second;
1687}
1688
1693static void stat_to_dir(struct stat *stbuf)
1694{
1695 stbuf->st_mode &= ~static_cast<mode_t>(S_IFREG | S_IFLNK);
1696 stbuf->st_mode |= S_IFDIR;
1697 if (stbuf->st_mode & S_IRWXU)
1698 {
1699 stbuf->st_mode |= S_IXUSR; // Add user execute bit if user has read or write access
1700 }
1701 if (stbuf->st_mode & S_IRWXG)
1702 {
1703 stbuf->st_mode |= S_IXGRP; // Add group execute bit if group has read or write access
1704 }
1705 if (stbuf->st_mode & S_IRWXO)
1706 {
1707 stbuf->st_mode |= S_IXOTH; // Add other execute bit if other has read or write access
1708 }
1709 stbuf->st_nlink = 2;
1710 stbuf->st_size = stbuf->st_blksize;
1711}
1712
1717static void flags_to_dir(int *flags)
1718{
1720
1721 if (ffmpeg_format[FORMAT::VIDEO].is_frameset())
1722 {
1723 *flags |= VIRTUALFLAG_FRAME;
1724 }
1725 else if (ffmpeg_format[FORMAT::VIDEO].is_hls())
1726 {
1727 *flags |= VIRTUALFLAG_HLS;
1728 }
1729}
1730
1731LPVIRTUALFILE insert_dir(VIRTUALTYPE type, const std::string & virtdir, const struct stat * stbuf, int flags)
1732{
1733 struct stat stbufdir;
1734
1735 std::memcpy(&stbufdir, stbuf, sizeof(stbufdir));
1736
1737 // Change file to directory for the frame set
1738 // Change file to virtual directory for the frame set. Keep permissions.
1739 stat_to_dir(&stbufdir);
1740 flags_to_dir(&flags);
1741
1742 std::string path(virtdir);
1743 append_sep(&path);
1744
1745 return insert_file(type, path, &stbufdir, flags);
1746}
1747
1748LPVIRTUALFILE find_file(const std::string & virtfile)
1749{
1750 FILENAME_MAP::iterator it = filenames.find(sanitise_filepath(virtfile));
1751
1752 errno = 0;
1753
1754 return (it != filenames.end() ? &it->second : nullptr);
1755}
1756
1757LPVIRTUALFILE find_file_from_orig(const std::string &origfile)
1758{
1759 RFILENAME_MAP::iterator it = rfilenames.find(sanitise_filepath(origfile));
1760
1761 errno = 0;
1762
1763 return (it != rfilenames.end() ? &it->second : nullptr);
1764}
1765
1766bool check_path(const std::string & path)
1767{
1768 FILENAME_MAP::const_iterator it = find_prefix(filenames, path);
1769
1770 return (it != filenames.cend());
1771}
1772
1773int load_path(const std::string & path, const struct stat *statbuf, void *buf, fuse_fill_dir_t filler)
1774{
1775 if (buf == nullptr)
1776 {
1777 // We can't add anything here if buf == nullptr
1778 return 0;
1779 }
1780
1781 int title_count = 0;
1782
1783 for (FILENAME_MAP::iterator it = filenames.lower_bound(path); it != filenames.end(); ++it)
1784 {
1785 std::string virtfilepath = it->first;
1786 LPVIRTUALFILE virtualfile = &it->second;
1787
1788 if (
1789 #ifdef USE_LIBVCD
1790 (virtualfile->m_type != VIRTUALTYPE::VCD) &&
1791 #endif // USE_LIBVCD
1792 #ifdef USE_LIBDVD
1793 (virtualfile->m_type != VIRTUALTYPE::DVD) &&
1794 #endif // USE_LIBDVD
1795 #ifdef USE_LIBBLURAY
1796 (virtualfile->m_type != VIRTUALTYPE::BLURAY) &&
1797 #endif // USE_LIBBLURAY
1798 !(virtualfile->m_flags & VIRTUALFLAG_CUESHEET)
1799 )
1800 {
1801 continue;
1802 }
1803
1804 remove_filename(&virtfilepath);
1805 if (virtfilepath == path) // Really a prefix?
1806 {
1807 struct stat stbuf;
1808 std::string destfile(virtualfile->m_destfile);
1809
1810 if (virtualfile->m_flags & VIRTUALFLAG_DIRECTORY)
1811 {
1812 // Is a directory, no need to translate the file name, just drop terminating separator
1813 remove_sep(&destfile);
1814 }
1815 remove_path(&destfile);
1816
1817 title_count++;
1818
1819 std::string cachefile;
1820
1821 Buffer::make_cachefile_name(&cachefile, virtualfile->m_destfile, params.current_format(virtualfile)->fileext(), false);
1822
1823 struct stat stbuf2;
1824 if (!lstat(cachefile.c_str(), &stbuf2))
1825 {
1826 // Cache file exists, use cache file size here
1827
1828 stat_set_size(&virtualfile->m_st, static_cast<size_t>(stbuf2.st_size));
1829
1830 std::memcpy(&stbuf, &virtualfile->m_st, sizeof(struct stat));
1831 }
1832 else
1833 {
1834 if (statbuf == nullptr)
1835 {
1836 std::memcpy(&stbuf, &virtualfile->m_st, sizeof(struct stat));
1837 }
1838 else
1839 {
1840 std::memcpy(&stbuf, statbuf, sizeof(struct stat));
1841
1842 stat_set_size(&stbuf, static_cast<size_t>(virtualfile->m_st.st_size));
1843 }
1844 }
1845
1846 if (add_fuse_entry(buf, filler, destfile, &stbuf, 0))
1847 {
1848 // break;
1849 }
1850 }
1851 }
1852
1853 return title_count;
1854}
1855
1864static int selector(const struct dirent * de)
1865{
1866 if (de->d_type & (DT_REG | DT_LNK))
1867 {
1868 return (av_guess_format(nullptr, de->d_name, nullptr) != nullptr);
1869 }
1870 else
1871 {
1872 return 0;
1873 }
1874}
1875
1888static int scandir(const char *dirp, std::vector<struct dirent> * _namelist, int (*selector) (const struct dirent *), int (*cmp) (const struct dirent **, const struct dirent **))
1889{
1890 struct dirent **namelist;
1891 int count = scandir(dirp, &namelist, selector, cmp); // cppcheck-suppress [nullPointer, ctunullpointer]
1892
1893 _namelist->clear();
1894
1895 if (count != -1)
1896 {
1897 for (int n = 0; n < count; n++)
1898 {
1899 _namelist->push_back(*namelist[n]);
1900 free(namelist[n]);
1901 }
1902 free(namelist);
1903 }
1904
1905 return count;
1906}
1907
1908LPVIRTUALFILE find_original(const std::string & origpath)
1909{
1910 std::string buffer(origpath);
1911 return find_original(&buffer);
1912}
1913
1914LPVIRTUALFILE find_original(std::string * filepath)
1915{
1916 sanitise_filepath(filepath);
1917
1918 LPVIRTUALFILE virtualfile = find_file(*filepath);
1919
1920 errno = 0;
1921
1922 if (virtualfile != nullptr)
1923 {
1924 *filepath = virtualfile->m_origfile;
1925 return virtualfile;
1926 }
1927 else
1928 {
1929 // Fallback to old method (required if file accessed directly)
1930 std::string ext;
1931 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)))
1932 {
1933 std::string dir(*filepath);
1934 std::string searchexp(*filepath);
1935 std::string origfile;
1936 std::vector<struct dirent> namelist;
1937 struct stat stbuf;
1938 int count;
1939 int found = 0;
1940
1941 remove_filename(&dir);
1942 origfile = dir;
1943
1944 // cppcheck-suppress nullPointer
1945 count = scandir(dir.c_str(), &namelist, selector, nullptr);
1946 if (count == -1)
1947 {
1948 if (errno != ENOTDIR) // If not a directory, simply ignore error
1949 {
1950 Logging::error(dir, "Error scanning directory: (%1) %2", errno, strerror(errno));
1951 }
1952 return nullptr;
1953 }
1954
1955 remove_path(&searchexp);
1956 remove_ext(&searchexp);
1957
1958 for (size_t n = 0; n < static_cast<size_t>(count); n++)
1959 {
1960 if (!strcmp(namelist[n].d_name, searchexp.c_str()))
1961 {
1962 append_filename(&origfile, namelist[n].d_name);
1963 sanitise_filepath(&origfile);
1964 found = 1;
1965 break;
1966 }
1967 }
1968
1969 if (found && lstat(origfile.c_str(), &stbuf) == 0)
1970 {
1971 // The original file exists
1972 LPVIRTUALFILE virtualfile2;
1973
1974 if (*filepath != origfile)
1975 {
1976 virtualfile2 = insert_file(VIRTUALTYPE::DISK, *filepath, origfile, &stbuf);
1977 *filepath = origfile;
1978 }
1979 else
1980 {
1981 virtualfile2 = insert_file(VIRTUALTYPE::DISK, origfile, &stbuf, VIRTUALFLAG_PASSTHROUGH);
1982 }
1983 return virtualfile2;
1984 }
1985 else
1986 {
1987 // File does not exist; this is a virtual file, not an error
1988 errno = 0;
1989 }
1990 }
1991 }
1992 // Source file exists with no supported extension, keep path
1993 return nullptr;
1994}
1995
1996LPVIRTUALFILE find_parent(const std::string & origpath)
1997{
1998 std::string filepath(origpath);
1999
2000 remove_filename(&filepath);
2001 remove_sep(&filepath);
2002
2003 return find_original(&filepath);
2004}
2005
2014static int make_hls_fileset(void * buf, fuse_fill_dir_t filler, const std::string & origpath, LPVIRTUALFILE virtualfile)
2015{
2016 // Generate set of TS segment files and necessary M3U lists
2017
2018 if (!virtualfile->get_segment_count())
2019 {
2020 int res = get_source_properties(origpath, virtualfile);
2021 if (res < 0)
2022 {
2023 return res;
2024 }
2025 }
2026
2027 if (virtualfile->get_segment_count())
2028 {
2029 std::string master_contents;
2030 std::string index_0_av_contents;
2031
2032 // Examples...
2033 //"#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1250,RESOLUTION=720x406,CODECS= \"avc1.77.30, mp4a.40.2 \",CLOSED-CAPTIONS=NONE\n"
2034 //"index_0_av.m3u8\n";
2035 //"#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=2647000,RESOLUTION=1280x720,CODECS= \"avc1.77.30, mp4a.40.2 \",CLOSED-CAPTIONS=NONE\n"
2036 //"index_1_av.m3u8\n"
2037 //"#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=922000,RESOLUTION=640x360,CODECS= \"avc1.77.30, mp4a.40.2 \",CLOSED-CAPTIONS=NONE\n"
2038 //"index_2_av.m3u8\n"
2039 //"#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=448000,RESOLUTION=384x216,CODECS= \"avc1.66.30, mp4a.40.2 \",CLOSED-CAPTIONS=NONE\n"
2040 //"index_3_av.m3u8\n"
2041 //"#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=61000,CODECS= \"mp4a.40.2 \",CLOSED-CAPTIONS=NONE\n"
2042 //"index_3_a.m3u8\n";
2043
2044 master_contents = "#EXTM3U\n"
2045 "#EXT-X-STREAM-INF:PROGRAM-ID=1\n"
2046 "index_0_av.m3u8\n";
2047
2048 strsprintf(&index_0_av_contents, "#EXTM3U\n"
2049 "#EXT-X-TARGETDURATION:%i\n"
2050 "#EXT-X-ALLOW-CACHE:YES\n"
2051 "#EXT-X-PLAYLIST-TYPE:VOD\n"
2052 "#EXT-X-VERSION:3\n"
2053 "#EXT-X-MEDIA-SEQUENCE:1\n", static_cast<int32_t>(params.m_segment_duration / AV_TIME_BASE));
2054
2055 int64_t remaining_duration = virtualfile->m_duration % params.m_segment_duration;
2056 size_t segment_size = virtualfile->m_predicted_size / virtualfile->get_segment_count();
2057
2058 for (uint32_t file_no = 1; file_no <= virtualfile->get_segment_count(); file_no++)
2059 {
2060 std::string buffer;
2061 std::string segment_name = make_filename(file_no, params.current_format(virtualfile)->fileext());
2062
2063 struct stat stbuf;
2064 std::string cachefile;
2065 std::string _origpath(origpath);
2066 remove_sep(&_origpath);
2067
2068 std::string filename(_origpath);
2069
2070 filename.append(".");
2071 filename.append(segment_name);
2072
2073 Buffer::make_cachefile_name(&cachefile, filename, params.current_format(virtualfile)->fileext(), false);
2074
2075 if (!lstat(cachefile.c_str(), &stbuf))
2076 {
2077 make_file(buf, filler, virtualfile->m_type, origpath, segment_name, static_cast<size_t>(stbuf.st_size), virtualfile->m_st.st_ctime, VIRTUALFLAG_HLS);
2078 }
2079 else
2080 {
2081 make_file(buf, filler, virtualfile->m_type, origpath, segment_name, segment_size, virtualfile->m_st.st_ctime, VIRTUALFLAG_HLS);
2082 }
2083
2084 if (file_no < virtualfile->get_segment_count())
2085 {
2086 strsprintf(&buffer, "#EXTINF:%.3f,\n", static_cast<double>(params.m_segment_duration) / AV_TIME_BASE);
2087 }
2088 else
2089 {
2090 strsprintf(&buffer, "#EXTINF:%.3f,\n", static_cast<double>(remaining_duration) / AV_TIME_BASE);
2091 }
2092
2093 index_0_av_contents += buffer;
2094 index_0_av_contents += segment_name;
2095 index_0_av_contents += "\n";
2096 }
2097
2098 index_0_av_contents += "#EXT-X-ENDLIST\n";
2099
2100 LPVIRTUALFILE child_file;
2101 child_file = make_file(buf, filler, VIRTUALTYPE::SCRIPT, origpath, "master.m3u8", master_contents.size(), virtualfile->m_st.st_ctime);
2102 std::copy(master_contents.begin(), master_contents.end(), std::back_inserter(child_file->m_file_contents));
2103
2104 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);
2105 std::copy(index_0_av_contents.begin(), index_0_av_contents.end(), std::back_inserter(child_file->m_file_contents));
2106
2107 {
2108 // Demo code adapted from: https://github.com/video-dev/hls.js/
2109 std::string hls_html;
2110
2111 hls_html =
2112 "<html>\n"
2113 "\n"
2114 "<head>\n"
2115 " <title>HLS Demo</title>\n"
2116 " <script src=\"https://cdn.jsdelivr.net/npm/hls.js@latest\"></script>\n"
2117 " <meta charset=\"utf-8\">\n"
2118 "</head>\n"
2119 "\n"
2120 "<body>\n"
2121 " <center>\n"
2122 " <h1>Hls.js demo - basic usage</h1>\n"
2123 " <video height=\"600\" id=\"video\" controls></video>\n"
2124 " </center>\n"
2125 " <script>\n"
2126 " var video = document.getElementById(\"video\");\n"
2127 " var videoSrc = \"index_0_av.m3u8\";\n"
2128 " if (Hls.isSupported()) {\n"
2129 " var hls = new Hls();\n"
2130 " hls.loadSource(videoSrc);\n"
2131 " hls.attachMedia(video);\n"
2132 " hls.on(Hls.Events.MANIFEST_PARSED, function() {\n"
2133 " video.play();\n"
2134 " });\n"
2135 " }\n"
2136 " // hls.js is not supported on platforms that do not have Media Source Extensions (MSE) enabled.\n"
2137 " // 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"
2138 " // This is using the built-in support of the plain video element, without using hls.js.\n"
2139 " // 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"
2140 " // 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"
2141 " else if (video.canPlayType(\"application/vnd.apple.mpegurl\")) {\n"
2142 " video.src = videoSrc;\n"
2143 " video.addEventListener(\"loadedmetadata\", function() {\n"
2144 " video.play();\n"
2145 " });\n"
2146 " }\n"
2147 " </script>\n"
2148 "</body>\n"
2149 "\n"
2150 "</html>\n";
2151
2152 child_file = make_file(buf, filler, VIRTUALTYPE::SCRIPT, origpath, "hls.html", hls_html.size(), virtualfile->m_st.st_ctime, VIRTUALFLAG_NONE);
2153 std::copy(hls_html.begin(), hls_html.end(), std::back_inserter(child_file->m_file_contents));
2154 }
2155 }
2156 return 0;
2157}
2158
2170static int kick_next(LPVIRTUALFILE virtualfile)
2171{
2172 if (virtualfile == nullptr)
2173 {
2174 // Not OK, should not happen
2175 return -EINVAL;
2176 }
2177
2178 if (virtualfile->m_cuesheet_track.m_nextfile == nullptr)
2179 {
2180 // No next file
2181 return 0;
2182 }
2183
2184 LPVIRTUALFILE nextvirtualfile = virtualfile->m_cuesheet_track.m_nextfile;
2185
2186 Logging::debug(virtualfile->m_destfile, "Preparing next file: %1", nextvirtualfile->m_destfile.c_str());
2187
2188 Cache_Entry* cache_entry = transcoder_new(nextvirtualfile, true);
2189 if (cache_entry == nullptr)
2190 {
2191 return -errno;
2192 }
2193
2194 transcoder_delete(cache_entry);
2195
2196 return 0;
2197}
2198
2208static void sighandler(int signum)
2209{
2210 if (signum == SIGINT)
2211 {
2212 Logging::warning(nullptr, "Caught SIGINT, shutting down now...");
2213 // Make our threads terminate now
2215 // Restore fuse's handler
2216 sigaction(SIGINT, &oldHandler, nullptr);
2217 // Dispatch to fuse's handler
2218 raise(SIGINT);
2219 }
2220}
2221
2228static std::string get_number(const char *path, uint32_t *value)
2229{
2230 std::string filename(path);
2231
2232 // Get frame number
2233 remove_path(&filename);
2234
2235 *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.
2236
2237 return filename;
2238}
2239
2245static size_t guess_format_idx(const std::string & filepath)
2246{
2247 const AVOutputFormat* oformat = ::av_guess_format(nullptr, filepath.c_str(), nullptr);
2248
2249 if (oformat != nullptr)
2250 {
2251 if (!params.smart_transcode())
2252 {
2253 // Not smart encoding: use first format (video file)
2254 return 0;
2255 }
2256 else
2257 {
2258 // Smart transcoding
2259 if (ffmpeg_format[FORMAT::VIDEO].video_codec() != AV_CODEC_ID_NONE && oformat->video_codec != AV_CODEC_ID_NONE && !is_album_art(oformat->video_codec))
2260 {
2261 // Is a video: use first format (video file)
2262 return 0;
2263 }
2264 else if (ffmpeg_format[FORMAT::AUDIO].audio_codec() != AV_CODEC_ID_NONE && oformat->audio_codec != AV_CODEC_ID_NONE)
2265 {
2266 // For audio only, use second format (audio only file)
2267 return 1;
2268 }
2269 }
2270 }
2271
2272 return 0;
2273}
2274
2280static int parse_file(LPVIRTUALFILE newvirtualfile)
2281{
2282 AVFormatContext *format_ctx = nullptr;
2283 int res;
2284
2285 try
2286 {
2287 Logging::debug(newvirtualfile->m_origfile, "Creating a new format context and parsing the file.");
2288
2289 res = avformat_open_input(&format_ctx, newvirtualfile->m_origfile.c_str(), nullptr, nullptr);
2290 if (res)
2291 {
2292 Logging::trace(newvirtualfile->m_origfile, "No parseable file: %1", ffmpeg_geterror(res).c_str());
2293 throw res;
2294 }
2295
2296 res = avformat_find_stream_info(format_ctx, nullptr);
2297 if (res < 0)
2298 {
2299 Logging::error(newvirtualfile->m_origfile, "Cannot find stream information: %1", ffmpeg_geterror(res).c_str());
2300 throw res;
2301 }
2302
2303 // Check for an embedded cue sheet
2304 AVDictionaryEntry *tag = av_dict_get(format_ctx->metadata, "CUESHEET", nullptr, AV_DICT_IGNORE_SUFFIX);
2305 if (tag != nullptr)
2306 {
2307 // Found cue sheet
2308 Logging::trace(newvirtualfile->m_origfile, "Found an embedded cue sheet.");
2309
2310 newvirtualfile->m_cuesheet = tag->value;
2311 newvirtualfile->m_cuesheet += "\r\n"; // cue_parse_string() reports syntax error if string does not end with newline
2312 replace_all(&newvirtualfile->m_cuesheet, "\r\n", "\n"); // Convert all to unix
2313 }
2314
2315 // Check for audio/video/subtitles
2316 int ret = get_audio_props(format_ctx, &newvirtualfile->m_channels, &newvirtualfile->m_sample_rate);
2317 if (ret < 0)
2318 {
2319 if (ret != AVERROR_STREAM_NOT_FOUND) // Not an error, no audio is OK
2320 {
2321 Logging::error(newvirtualfile->m_origfile, "Could not find audio stream in input file (error '%1').", ffmpeg_geterror(ret).c_str());
2322 }
2323 }
2324 else
2325 {
2326 newvirtualfile->m_has_audio = true;
2327 }
2328
2329 newvirtualfile->m_duration = format_ctx->duration;
2330
2331 struct stat stbuf;
2332 if (lstat(newvirtualfile->m_origfile.c_str(), &stbuf) == 0)
2333 {
2334 std::memcpy(&newvirtualfile->m_st, &stbuf, sizeof(stbuf));
2335 }
2336
2337 for (unsigned int stream_idx = 0; stream_idx < format_ctx->nb_streams; stream_idx++)
2338 {
2339 switch (format_ctx->streams[stream_idx]->codecpar->codec_type)
2340 {
2341 case AVMEDIA_TYPE_VIDEO:
2342 {
2343 if (!is_album_art(format_ctx->streams[stream_idx]->codecpar->codec_id))
2344 {
2345 newvirtualfile->m_has_video = true;
2346 }
2347 break;
2348 }
2349 case AVMEDIA_TYPE_SUBTITLE:
2350 {
2351 newvirtualfile->m_has_subtitle = true;
2352 break;
2353 }
2354 default:
2355 {
2356 break;
2357 }
2358 }
2359 }
2360 }
2361 catch (int _res)
2362 {
2363 res = _res;
2364 }
2365
2366 if (format_ctx != nullptr)
2367 {
2368 avformat_close_input(&format_ctx);
2369 }
2370
2371 return res;
2372}
2373
2379static const FFmpegfs_Format * get_format(LPVIRTUALFILE newvirtualfile)
2380{
2381 LPVIRTUALFILE virtualfile = find_file_from_orig(newvirtualfile->m_origfile);
2382
2383 if (virtualfile != nullptr)
2384 {
2385 // We already know the file!
2386 return params.current_format(virtualfile);
2387 }
2388
2389 if (parse_file(newvirtualfile) < 0)
2390 {
2391 return nullptr;
2392 }
2393
2394 Logging::trace(newvirtualfile->m_origfile, "Audio: %1 Video: %2 Subtitles: %3", newvirtualfile->m_has_audio, newvirtualfile->m_has_video, newvirtualfile->m_has_subtitle);
2395
2396 if (!params.smart_transcode())
2397 {
2398 // Not smart encoding: use first format (video file)
2399 newvirtualfile->m_format_idx = 0;
2400 return &ffmpeg_format[FORMAT::VIDEO];
2401 }
2402 else
2403 {
2404 if (newvirtualfile->m_has_video)
2405 {
2406 newvirtualfile->m_format_idx = 0;
2407 return &ffmpeg_format[FORMAT::VIDEO];
2408 }
2409
2410 if (newvirtualfile->m_has_audio)
2411 {
2412 newvirtualfile->m_format_idx = 1;
2413 return &ffmpeg_format[FORMAT::AUDIO];
2414 }
2415 }
2416
2417 return nullptr;
2418}
2419
2420int add_fuse_entry(void *buf, fuse_fill_dir_t filler, const std::string & name, const struct stat *stbuf, off_t /*off*/)
2421{
2422 if (buf == nullptr || filler == nullptr)
2423 {
2424 return 0;
2425 }
2426
2427 // Issue #173: FUSE_FILL_DIR_PLUS erstmal NICHT nutzen
2428 return filler(buf, name.c_str(), stbuf, 0, static_cast<fuse_fill_dir_flags>(0));
2429}
2430
2431int add_dotdot(void *buf, fuse_fill_dir_t filler, const struct stat *stbuf, off_t off)
2432{
2433 struct stat *stbuf2 = nullptr;
2434 struct stat stbuf3;
2435
2436 if (stbuf != nullptr)
2437 {
2438 stbuf2 = &stbuf3;
2439 init_stat(stbuf2, 0, stbuf->st_ctime, true);
2440 }
2441
2442 add_fuse_entry(buf, filler, ".", stbuf2, off);
2443 add_fuse_entry(buf, filler, "..", stbuf2, off);
2444
2445 return 0;
2446}
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:1073
The Cache_Entry class.
Definition cache_entry.h:49
LPVIRTUALFILE virtualfile()
Get the underlying VIRTUALFILE object.
std::unique_ptr< Buffer > m_buffer
Buffer object.
The FFmpegfs_Format class.
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.
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.
const std::string & strsprintf(std::string *str, const std::string &format, Args ... args)
Format a std::string sprintf-like.
FFMPEGFS_PARAMS params
FFmpegfs command line parameters.
Definition ffmpegfs.cc:81
FFMPEGFS_FORMAT_ARR ffmpeg_format
Two FFmpegfs_Format infos, 0: video file, 1: audio file.
Definition ffmpegfs.cc:80
void transcoder_free()
Free transcoder.
Definition transcode.cc:392
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
#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:2245
LPVIRTUALFILE find_file(const std::string &virtfile)
Find file in cache.
Definition fuseops.cc:1748
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:1101
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:1996
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:1731
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:2431
static int parse_file(LPVIRTUALFILE newvirtualfile)
Open file with FFmpeg API and parse for streams and cue sheet.
Definition fuseops.cc:2280
fuse_operations ffmpegfs_ops
FUSE file system operations.
Definition fuseops.cc:100
int add_fuse_entry(void *buf, fuse_fill_dir_t filler, const std::string &name, const struct stat *stbuf, off_t)
Wrapper to the Fuse filler function.
Definition fuseops.cc:2420
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:1313
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
static void * ffmpegfs_init(struct fuse_conn_info *conn, fuse_config *cfg)
Initialise the filesystem.
Definition fuseops.cc:1348
static void init_stat(struct stat *stbuf, size_t fsize, time_t ftime, bool directory)
Initialise a stat structure.
Definition fuseops.cc:1448
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:1628
static int selector(const struct dirent *de)
Filter function used for scandir.
Definition fuseops.cc:1864
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:1552
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:1773
static void stat_to_dir(struct stat *stbuf)
Convert stbuf to directory.
Definition fuseops.cc:1693
static int ffmpegfs_open(const char *path, struct fuse_file_info *fi)
Open a virtual or passthrough file.
Definition fuseops.cc:977
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:2208
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:1908
static void ffmpegfs_destroy(__attribute__((unused)) void *p)
Clean up filesystem.
Definition fuseops.cc:1399
static int get_source_properties(const std::string &origpath, LPVIRTUALFILE virtualfile)
Calculate the video frame count.
Definition fuseops.cc:1426
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:1888
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:2014
static int ffmpegfs_readlink(const char *path, char *buf, size_t size)
Read the target of a symbolic link.
Definition fuseops.cc:269
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:1766
static int ffmpegfs_statfs(const char *path, struct statvfs *stbuf)
Get file system statistics.
Definition fuseops.cc:1277
static int ffmpegfs_getattr(const char *path, struct stat *stbuf, fuse_file_info *fi)
Get file attributes.
Definition fuseops.cc:548
LPVIRTUALFILE find_file_from_orig(const std::string &origfile)
Look for the file in the cache.
Definition fuseops.cc:1757
static std::string get_number(const char *path, uint32_t *value)
Extract the number for a file name.
Definition fuseops.cc:2228
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:1652
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:1503
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:2170
static int ffmpegfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi, enum fuse_readdir_flags flags)
Read directory contents.
Definition fuseops.cc:307
static void insert(const VIRTUALFILE &virtualfile)
Insert virtualfile into list.
Definition fuseops.cc:1646
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:2379
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:1486
static void flags_to_dir(int *flags)
Convert flags to directory.
Definition fuseops.cc:1717
Provide various log facilities to stderr, disk or syslog.
std::string m_scriptsource
Source script.
Definition ffmpegfs.h:230
time_t m_cache_maintenance
Prune timer interval.
Definition ffmpegfs.h:247
bool smart_transcode() const
Check for smart transcode mode.
Definition ffmpegfs.cc:237
int m_oldnamescheme
Use old output name scheme, can create duplicate filenames.
Definition ffmpegfs.h:254
const FFmpegfs_Format * current_format(LPCVIRTUALFILE virtualfile) const
Get FFmpegfs_Format for a virtual file.
Definition ffmpegfs.cc:242
int64_t m_segment_duration
Duration of one HLS segment file, in AV_TIME_BASE fractional seconds.
Definition ffmpegfs.h:213
RECODESAME m_recodesame
Recode to same format options.
Definition ffmpegfs.h:199
unsigned int m_max_threads
Max. number of recoder threads.
Definition ffmpegfs.h:250
std::string m_mountpath
Mount path: Files from m_mountpath will be mapped to this directory.
Definition ffmpegfs.h:193
int m_enablescript
Enable virtual script.
Definition ffmpegfs.h:228
std::string m_scriptfile
Script name.
Definition ffmpegfs.h:229
std::string m_basepath
Base path: Files from this directory (including all sub directories) will be mapped to m_mountpath.
Definition ffmpegfs.h:192
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:590
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:677
void transcoder_exit()
Exit transcoding.
size_t transcoder_get_size(Cache_Entry *cache_entry)
Return size of output file, as computed by encoder.
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:892
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:402
void transcoder_delete(Cache_Entry *cache_entry)
Free the cache entry structure.
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.