File: recman.h

package info (click to toggle)
vdr-plugin-live 3.5.3-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 6,128 kB
  • sloc: cpp: 12,995; javascript: 3,234; makefile: 241; sh: 40
file content (618 lines) | stat: -rw-r--r-- 27,554 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
#ifndef VDR_LIVE_RECORDINGS_H
#define VDR_LIVE_RECORDINGS_H

// STL headers need to be before VDR tools.h (included by <vdr/recording.h>)
#include <map>
#include <set>
#include <string>
#include <string_view>
#include <vector>
#include <list>

#if TNTVERSION >= 30000
  #include <cxxtools/log.h>  // must be loaded before any VDR include because of duplicate macros (LOG_ERROR, LOG_DEBUG, LOG_INFO)
#endif

#ifndef DISABLE_TEMPLATES_COLLIDING_WITH_STL
// To get rid of the swap definition in vdr/tools.h
#define DISABLE_TEMPLATES_COLLIDING_WITH_STL
#endif

#include <iostream>
#include "stdext.h"
#include "setup.h"

#include <vdr/recording.h>
#include <vdr/channels.h>
#include "stringhelpers.h"
#include "tools.h"
#include "services.h"

namespace vdrlive {

  /**
   *  Some forward declarations
   */

  class RecordingsTree;
  class DuplicatesRecordingsTree;
  class RecordingsItemDir;
  class RecordingsItemDirFileSystem;
  class RecordingsItemRec;
  template<class C> class const_rec_iterator;

  typedef std::shared_ptr<RecordingsTree>           RecordingsTreePtr;
  typedef std::shared_ptr<DuplicatesRecordingsTree> DuplicatesRecordingsTreePtr;
  typedef std::shared_ptr<RecordingsItemDir>        RecordingsItemDirPtr;

  bool operator< (const RecordingsItemDirPtr &a, const RecordingsItemDirPtr &b);
  bool operator< (cSv a, const RecordingsItemDirPtr &b);
  bool operator< (const RecordingsItemDirPtr &a, cSv b);
  bool operator< (int a, const RecordingsItemDirPtr &b);
  bool operator< (const RecordingsItemDirPtr &a, int b);

  int GetNumberOfTsFiles(int recId);

  /**
   *  Class for managing recordings inside the live plugin. It
   *  provides some convenience methods and provides automatic
   *  locking during requests on the recordings or during the
   *  traversion of the recordings tree or lists, which can only be
   *  obtained through methods of the RecordingsManager.
   */
  class RecordingsManager
  {
    public:
      enum class eSortOrder { id=0, name=1, date=2, errors=3, durationDeviation=4, duplicatesLanguage=5, list_end=6 };

// static members
    private:
      static inline cStateKey m_recordingsStateKey;
      static inline time_t m_last_recordings_update = 0;
    public:
      static inline std::vector<RecordingsItemDirPtr> dirs_dummy;
      static inline std::vector<RecordingsItemRec*> all_recordings[(int)(eSortOrder::list_end)];
      static inline eSortOrder m_sortOrder = eSortOrder::name;
      static inline bool m_backwards = false;
      static inline std::regex m_filter_regex;
      static inline std::regex *m_filter_regex_ptr = nullptr; // if a filter is provided, this is set to &m_filter_regex

      static inline RecordingsTreePtr m_recTree;
      static inline DuplicatesRecordingsTreePtr m_duplicatesRecTree;

      static void setSortOrder(eSortOrder sortOrder, bool backwards, cSv filter);

      static time_t GetLastRecordingsUpdate() { return m_last_recordings_update; }
      /**
       *  Returns a shared pointer to a fully populated
       *  recordings tree.
       */
      static RecordingsTreePtr GetRecordingsTree();
      static DuplicatesRecordingsTreePtr GetDuplicatesRecordingsTree();

      /**
       *  fetches a cRecording from VDR's Recordings collection. Returns
       *  NULL if recording was not found
       */
      static const cRecording *GetByHash(cSv hash, const cRecordings* Recordings);

      /**
       *  get RecordingsItemRec from the all_recordings. Returns
       *  NULL if recording was not found
       */
      static RecordingsItemRec* const GetByIdHash(cSv hash);

      /**
       *  Move a recording with the given hash according to
       *  VDRs recording mechanisms.
       *  @param directory new name of the sub folder this recording is stored in.
       *  @param name new recording folder name.
       *  @param copy create a copy of the original recording rather than moving it.
       *  @param title new title of the recording.
       *  @param shorttext new short text of the recording.
       *  @param description new description of the recording.
       */
      static bool UpdateRecording(cSv hash, cSv directory, cSv name, bool copy, cSv title, cSv shorttext, cSv description);

      /**
       *  Delete recording resume with the given hash according to
       *  VDRs recording mechanisms.
       */
      static void DeleteResume(cRecording const * recording);

      /**
       *  Delete recording marks with the given hash according to
       *  VDRs recording mechanisms.
       */
      static void DeleteMarks(cRecording const * recording);

      /**
       *  Delete a recording with the given hash according to
       *  VDRs recording deletion mechanisms.
       *  If name is provided, it is set to recording->Name()  (name for use in Menues)
       *  return:
       *    0 success
       *    1 no recording with recording_hash exists (name will not be provided ...)
       *    2 recording is in use
       *    3 other error (recording->Delete() returned false)
       */
      static int DeleteRecording(cSv recording_hash, std::string *name = nullptr);

      /**
       *  Determine whether the recording has been archived on
       *  removable media (e.g. DVD-ROM)
       */
      static int GetArchiveType(cRecording const * recording);

      /**
       *  Provide an identification of the removable media
       *  (e.g. DVD-ROM Number or Name) where the recording has
       *  been archived.
       */
      static std::string const GetArchiveId(cRecording const * recording, int archiveType);

      static std::string const GetArchiveDescr(cRecording const * recording);

      /**
       *  Is this recording still recording?
       *  Will access file system (slow), but result is correct even if another VDR is recording
       *
       *  Returns false in case of errors (recording does not exist, ...)
       */
      static bool StillRecording(cSv RecordingFileName);

      /**
       *  Is this recording still recording?
       *  Will not access file system (fast), only check if this VDR is recording
       *
       *  Also true if recording is destination of a cut or copy
       */
      static bool StillRecording(const cRecording *recording);

      /**
       *  Is this recording currently played?
       *  Return codes:
       *    0: this recording is currently played
       *    1: this recording does not exist
       *    2: no recording is played
       *    3: another recording is played
       *  Also return *fileName=recording->FileName() if requested
       */
      static int CheckReplay(cSv recording_hash, std::string *fileName = nullptr);

      static void EnsureValidData();
    private:
      static bool StateChanged();
  };

  /**
   * Class containing possible recordings compare functions
   */
  typedef bool (*tCompRec)(const RecordingsItemRec * first, const RecordingsItemRec * second);
  typedef bool (*tCompDir)(const RecordingsItemDirPtr &a, const RecordingsItemDirPtr &b);
  class RecordingsItemPtrCompare
  {
    public:
// recs
      static bool ByAscendingDate(const RecordingsItemRec * first, const RecordingsItemRec * second);
      static bool ByDuplicatesName(const RecordingsItemRec * first, const RecordingsItemRec * second);
      static bool ByDuplicates(const RecordingsItemRec * first, const RecordingsItemRec * second);
      static bool ByDuplicatesLanguage(const RecordingsItemRec * first, const RecordingsItemRec * second);
      static bool ByAscendingNameDescSort(const RecordingsItemRec * first, const RecordingsItemRec * second);
      static bool ByDescendingRecordingErrors(const RecordingsItemRec * first, const RecordingsItemRec * second);
      static bool ByDescendingDurationDeviation(const RecordingsItemRec * first, const RecordingsItemRec * second);
      static bool ByEpisode(const RecordingsItemRec * first, const RecordingsItemRec * second);
      static bool ByReleaseDate(const RecordingsItemRec * first, const RecordingsItemRec * second);
// dirs
      static bool ByAscendingNameSort(const RecordingsItemDirPtr & first, const RecordingsItemDirPtr & second);
      static bool BySeason(const RecordingsItemDirPtr & first, const RecordingsItemDirPtr & second);
// helpers
      static int FindBestMatch(RecordingsItemRec *&BestMatch, const std::vector<RecordingsItemRec*>::const_iterator & First, const std::vector<RecordingsItemRec*>::const_iterator & Last, const RecordingsItemRec* EPG_Entry);

      static tCompRec getComp(RecordingsManager::eSortOrder sortOrder);
  };
// search a recording matching an EPG entry. The EPG entry is given with Name, ShortText, Description, Duration, scraperOverview
  bool searchNameDesc(RecordingsItemRec *&RecItem, const std::vector<RecordingsItemRec*> *RecItems, const cEvent *event, cScraperVideo *scraperVideo);

  /**
   *  A recordings item that represents a real recording. This is
   *  the leaf item in the recordings tree or one of the items in
   *  the recordings list.
   */
  class RecordingsItemRec
  {
    friend class RecordingsTree;
    friend class RecordingsItemDir;
    friend class RecordingsItemDirSeason;
    friend class RecordingsItemDirCollection;
    friend class RecordingsItemPtrCompare;
    friend void update_all_recordings_scraper();
    public:
      RecordingsItemRec(const cRecording* recording, cMeasureTime *timeIdentify=nullptr, cMeasureTime *timeOverview=nullptr, cMeasureTime *timeItemRec=nullptr);
      RecordingsItemRec(const cEvent *event, cScraperVideo *scraperVideo); // create dummy

      void finalize(); // call this after constructor, for "work" where cRecording is not required
      bool has_changed(const cRecording* recording); // true if this differs from recording
      void set_seen(bool seen) { m_seen = seen; }
      bool seen() { return m_seen; }

// identify recording
      int Id() const { return m_id;}   // id used by VDR, will not change until VDR restart
      XXH128_hash_t IdHash() const { return m_hash; }  // filename hash, will not change until change of filename
// data from recording / recording->info()
      cSv Name() const { return m_name; }
      cStr NameStr() const { return m_name_str; }  // for strcoll
      cSv ShortText() const { return m_shortText; }
      cSv Description() const { return m_description; }
      cSv ChannelName() const { return m_channelName; }
      cSv Folder() const { return m_folder; }
      int HierarchyLevels() const { return std::count(m_name_vdr.begin(), m_name_vdr.end(), FOLDERDELIMCHAR); }
      int ParentalRating() const { return m_parentalRating; }
      double FramesPerSecond(void) const { return m_framesPerSecond; }
      uint16_t FrameWidth(void) const { return m_frameWidth; }
      uint16_t FrameHeight(void) const { return m_frameHeight; }
#if VDRVERSNUM >= 20605
      eScanType ScanType(void) const { return m_scanType; }
      char ScanTypeChar(void) const { return ScanTypeChars[m_scanType]; }
      eAspectRatio AspectRatio(void) const { return m_aspectRatio; }
#endif
      time_t StartTime() const { return m_startTime; }
      bool StillRecording(const cRecording* recording = nullptr) const;  // true if this is still recording. The result is bufferd
      int Duration() const;  // duration in seconds, is changing for still recording recordings
      int FileSizeMB() const;       // is changing for still recording recordings
      int RecordingErrors() const;  // is changing for still recording recordings

// To display the recording on the UI
      int IsArchived() const { return false; }
      bool IsNew() const { return m_IsNew; }
      const char *NewR() const { return LiveSetup().GetMarkNewRec() && IsNew() ? "_new" : "" ; }
      int NumberTsFiles() const;    // ändert sich bei Recs, die aufnehmen!
// scraper data
      bool scraperDataAvailable() const { return m_s_videoType == tMovie || m_s_videoType == tSeries; }
      tvType scraperVideoType() const { return m_s_videoType; }
      int scraperCollectionId() const { return m_s_collection_id; }
      int scraperEpisodeNumber() const { return m_s_episode_number; }
      int scraperSeasonNumber() const { return m_s_season_number; }
      cSv scraperName() const { return m_s_title; }
      cSv scraperReleaseDate() const { return m_s_release_date; }
      int language() const { return m_language; }
      int SD_HD() const { return m_video_SD_HD; }
      int DurationDeviation() const { return m_scraperVideo?m_scraperVideo->getDurationDeviation():0; } // duration deviation in seconds, ändert sich bei Recs, die aufnehmen!

      bool matches_filter() const; // true if name or short text or descr. match regex of global filter
      bool matches_regex(const std::regex *reg = nullptr) const; // true if name or short text or descr. match regex
      void updateScraperDataIfStillRecording(const cRecording *recording);

    private:
      void getScraperData();
      void getScraperData(const cRecording *recording);
      void updateScraperData(const cRecording *recording);
      int get_SD_HD(const cRecordingInfo *info);
      const cTvMedia &scraperImage() const;
      int CompareTexts(const RecordingsItemRec    *second, int *numEqualChars=NULL) const;
      bool orderDuplicates(const RecordingsItemRec *second, bool alwaysShortText, bool lang = false) const;
    public:
      int CompareStD(const RecordingsItemRec *second, int *numEqualChars=NULL) const;
      void AppendAsJSArray(cToSvConcat<0> &target) const;
      static void AppendAsJSArray(cToSvConcat<0> &target, const_rec_iterator<RecordingsItemRec*> &rec_iterator, bool &first);

      mutable cMeasureTime *m_timeIdentify = nullptr;
      mutable cMeasureTime *m_timeOverview = nullptr;

    private:
      const int m_id;
      const XXH128_hash_t m_hash;
      std::string m_name_vdr;  // name as VDR returns with Name(): folder~name
      const std::string m_file_name; // name as VDR returns with FileName(): name in filesystem, do no sanitize this!
      cSv m_name;
      cStr m_name_str;
      cSv m_folder;
      std::string m_shortText;
      std::string m_description;
      std::string m_channelName;
      uchar m_parentalRating = 0;
      mutable int m_fileSizeMB = -1;
      time_t m_startTime = 0;
      mutable time_t m_stopRecording = std::numeric_limits<time_t>::max();  // this is an estimate only
                // set to 0 for recordings not recording at start of live
                // set to std::numeric_limits<time_t>::max() for currently recording recordings
                // if we test and find a rec not recording, where this is max, we set it to the current time
      mutable int m_duration = -1;
      mutable bool m_IsNew = false;
      mutable int m_recordingErrors = -1;
      double m_framesPerSecond = 0.;
      uint16_t m_frameWidth = 0;
      uint16_t m_frameHeight = 0;
#if VDRVERSNUM >= 20605
      eScanType m_scanType = stUnknown;
      eAspectRatio m_aspectRatio = arUnknown;
#endif
      mutable time_t m_last_scraper_update = 0;
      mutable int m_number_ts_files = -1;
      std::unique_ptr<cScraperVideo_v01> m_scraperVideo;
      tvType m_s_videoType = tNone;
      int m_s_dbid = 0;
      std::string m_s_title;
      std::string m_s_episode_name;
      std::string m_s_IMDB_ID;
      std::string m_s_release_date;
      mutable cTvMedia m_s_image;
      mutable bool m_s_image_requested = false;
      int m_s_runtime = 0;
      int m_s_collection_id = 0;
      int m_s_episode_number = 0;
      int m_s_season_number = 0;
      int m_language = 0;
      int m_video_SD_HD = -2;  // < -2: not checked. -1: Radio. 0 is SD, 1 is HD, >1 is UHD or better

      bool m_seen;
  };

// iterate over all recordings, sorted by id (VDR-id) =====================================
// examples:
//   a) for (RecordingsItemRec *r: all_recordings_iterator() ) { ... }
//   b) std::vector<RecordingsItemRec*> s_name;
//      s_name.assign(all_recordings_iterator(), all_recordings_iterator(iterator_end() ));
//      std::sort(s_name.begin(), s_name.end(), comp)

  class all_recordings_iterator {
    public:
      using iterator_category = std::forward_iterator_tag;
      using value_type = RecordingsItemRec*;
      using difference_type = std::ptrdiff_t;
      using pointer = RecordingsItemRec**;
      using reference = RecordingsItemRec*&;

      all_recordings_iterator(iterator_begin d):
        m_current(RecordingsManager::all_recordings[(int)(RecordingsManager::eSortOrder::id)].begin()) {
        if (m_current == RecordingsManager::all_recordings[(int)(RecordingsManager::eSortOrder::id)].end() ) return;   // empty container
        if (!*m_current) ++(*this);
      }
      all_recordings_iterator():    // creates the begin() iterator !!!!!
        all_recordings_iterator(iterator_begin() ) { }
      all_recordings_iterator(iterator_end d):
        m_current(RecordingsManager::all_recordings[(int)(RecordingsManager::eSortOrder::id)].end()) {}

      all_recordings_iterator &operator++() {
        for (++m_current; m_current != RecordingsManager::all_recordings[(int)(RecordingsManager::eSortOrder::id)].end(); ++m_current)
          if (*m_current) return *this;
        return *this;
      }
      all_recordings_iterator  operator++(int) { auto tmp = *this; ++*this; return tmp; }
      bool operator==(all_recordings_iterator other) const { return m_current == other.m_current; }
      bool operator!=(all_recordings_iterator other) const { return m_current != other.m_current; }
      reference operator*() { return *m_current; }
      RecordingsItemRec *operator->() { return *m_current; }
    private:
      std::vector<RecordingsItemRec*>::iterator m_current;
  };
inline all_recordings_iterator begin(all_recordings_iterator &it) { return it; }
inline all_recordings_iterator end  (all_recordings_iterator &it) { return all_recordings_iterator(iterator_end() ); }

// C == RecordingsItemRec*   -> iterate over recordings
// C == RecordingsItemDirPtr -> iterate over directories
template<typename C>
  class const_rec_iterator {
    public:
      const_rec_iterator(const std::vector<C> &items, bool backwards, std::regex *regex_filter = nullptr);
      const_rec_iterator(const RecordingsItemDirFileSystem* recordingsItemDirFileSystem, unsigned max_items = std::numeric_limits<unsigned>::max() );
      const_rec_iterator<C> &set_begin();

      const_rec_iterator<C> &set_container(const std::vector<C> &items);
      const_rec_iterator<C> &set_max_items(unsigned max_items);
      const_rec_iterator<C> &set_backwards(bool backwards);
      bool empty() const { return *this == iterator_end(); }
      size_t size();
      const_rec_iterator &operator++();
      bool operator==(iterator_end other) const;
      bool operator!=(iterator_end other) const { return !(*this == other); }
      const C operator*() { return *m_current; }
    private:
      bool current_matches() const; // true if m_current is in dir && matches filter

      bool m_backwards;
      std::regex *m_regex_filter;
      const RecordingsItemDirFileSystem *m_recordingsItemDirFileSystem = nullptr;
      typename std::vector<C>::const_iterator m_begin;
      typename std::vector<C>::const_iterator m_end;
      typename std::vector<C>::const_iterator m_current;
      bool m_backwards_end = false;
      unsigned m_index = 0;  // count actually matching items
      unsigned m_max_items = std::numeric_limits<unsigned>::max();
  };
template<typename C>
inline const_rec_iterator<C> begin(const_rec_iterator<C> &it) { return it; }
template<typename C>
inline iterator_end end(const_rec_iterator<C> &it) { return iterator_end(); }

/**
 *  A recordings item that resembles a directory with other
 *  subdirectories and/or real recordings.
 */
  class RecordingsItemDir
  {
    friend class RecordingsTree;
    friend class DuplicatesRecordingsTree;

    public:
      virtual ~RecordingsItemDir();
      RecordingsItemDir(cSv name, int level);

      cSv Name() const { return m_name; }
      cStr NameStr() const { return m_name; }
      int Level() const { return m_level; }

      void finishRecordingsTree();
// this is called recursively
// recs: sort m_entries, Order: m_cmp_rec (if defined. Otherwise: no sort)
// dirs: sort m_subdirs, Order: m_cmp_dir (if defined. Otherwise: m_name_for_sort)

      virtual bool operator< (const RecordingsItemDirPtr &sec) const { return m_name < sec->m_name; }
      virtual bool operator< (cSv sec) const { return m_name < sec; }
      virtual bool operator> (cSv sec) const { return m_name > sec; }
      virtual bool operator< (int sec) const { return false; }
      virtual bool operator> (int sec) const { return false; }
      virtual const_rec_iterator<RecordingsItemRec*> get_rec_iterator() const;
      const_rec_iterator<RecordingsItemDirPtr> get_dir_iterator() const;

      virtual int numberOfRecordings() const; // these are the overall recordings, without the regex filter!
      virtual RecordingsItemDirPtr addDirIfNotExists(cSv dirName);
      RecordingsItemDirPtr addDirCollectionIfNotExists(int collectionId, const RecordingsItemRec *rPtr);
      RecordingsItemDirPtr addDirSeasonIfNotExists(int seasonNumber, const RecordingsItemRec *rPtr);
      bool checkNew() const;
      bool matches_regex(std::regex *regex_filter = nullptr) const;
      void addDirList(std::vector<std::string> &dirs, cSv basePath) const;

      void setTvShow(const RecordingsItemRec* rPtr);

      int scraperCollectionId() const { return m_s_collection_id; }
      int scraperSeasonNumber() const { return m_s_season_number; }
      const cTvMedia &scraperImage() const;
      bool dirEntriesSorted() const { return m_cmp_dir != NULL; }

    protected:
      std::string m_name;  // display name
      int m_level;
// other dirs in this dir:
      std::vector<RecordingsItemDirPtr> m_subdirs;
      bool (*m_cmp_dir)(const RecordingsItemDirPtr &itemPtr1, const RecordingsItemDirPtr &itemPtr2) = NULL;
// recordings in this dir:
      mutable std::vector<RecordingsItemRec*> m_entries;
      mutable RecordingsManager::eSortOrder m_sortOrder = (RecordingsManager::eSortOrder)-1;
      bool (*m_cmp_rec)(const RecordingsItemRec* itemPtr1, const RecordingsItemRec* itemPtr2) = NULL;
// scraper data
      const RecordingsItemRec *m_rec_item = nullptr; // in this rec item (if available), there are the relevant scraper data
               // for dirs (collection), it points to a rec item with relevant data for the collection
               // similar for dirs (TV show, season).
               // for others, it is just nullptr -> no data available
      mutable cTvMedia m_s_image;
      mutable bool m_s_image_requested = false;
      cImageLevels m_imageLevels;
      int m_s_collection_id = 0;
      int m_s_season_number = 0;
  };

  class RecordingsItemDirFileSystem : public RecordingsItemDir
  {
    public:
      RecordingsItemDirFileSystem(cSv name, cSv name_contains, int level);
      bool Contains(const RecordingsItemRec *rec) const;
      bool Contains(const RecordingsItemDirPtr &r) const { return true; };
      RecordingsItemDirPtr addDirIfNotExists(cSv dirName);
      const_rec_iterator<RecordingsItemRec*> get_rec_iterator() const;
      int numberOfRecordings() const;
    private:
      std::string m_name_contains;  // all recordings with this folder name are in this folder
  };
  class RecordingsItemDirFlat : public RecordingsItemDir
  {
    public:
      RecordingsItemDirFlat(): RecordingsItemDir(cSv(), 0) {}
      const_rec_iterator<RecordingsItemRec*> get_rec_iterator() const;
  };
  class RecordingsItemDirSeason : public RecordingsItemDir
  {
    public:
      RecordingsItemDirSeason(int level, const RecordingsItemRec *rPtr);

      virtual bool operator< (const RecordingsItemDirPtr &sec) const { return m_s_season_number < sec->scraperSeasonNumber(); }
      virtual bool operator< (cSv sec) const { return false; }
      virtual bool operator> (cSv sec) const { return false; }
      virtual bool operator< (int sec) const { return m_s_season_number < sec; }
      virtual bool operator> (int sec) const { return m_s_season_number > sec; }
      const_rec_iterator<RecordingsItemRec*> get_rec_iterator() const;
  };

  class RecordingsItemDirCollection : public RecordingsItemDir
  {
    public:
      RecordingsItemDirCollection(int level, const RecordingsItemRec *rPtr);

      virtual bool operator< (const RecordingsItemDirPtr &sec) const { return m_s_collection_id < sec->scraperCollectionId(); }
      virtual bool operator< (cSv sec) const { return false; }
      virtual bool operator> (cSv sec) const { return false; }
      virtual bool operator< (int sec) const { return m_s_collection_id < sec; }
      virtual bool operator> (int sec) const { return m_s_collection_id > sec; }
      const_rec_iterator<RecordingsItemRec*> get_rec_iterator() const;
  };


  /**
   *  The recordings tree contains all recordings in a file system
   *  tree like fashion.
   */
  class RecordingsTree
  {
    friend class RecordingsManager;

    private:
      RecordingsTree();

    public:
      ~RecordingsTree();

      RecordingsItemDirPtr getRoot() const { return m_root; }
      std::vector<std::string> getAllDirs() { std::vector<std::string> result; m_rootFileSystem->addDirList(result, ""); return result; }
      const std::vector<RecordingsItemRec*> *allRecordings() { return &RecordingsManager::all_recordings[(int)(RecordingsManager::m_sortOrder)];}
      const std::vector<RecordingsItemRec*> *allRecordings(RecordingsManager::eSortOrder sortOrder);

      int MaxLevel() const { return m_maxLevel; }

    private:
      int m_maxLevel;
      RecordingsItemDirPtr m_root;
      RecordingsItemDirPtr m_rootFileSystem;
  };

  /**
   *  The duplicates recordings tree contains all duplicate recordings in a
   *  tree like fashion.
   */
  class DuplicatesRecordingsTree
  {
    friend class RecordingsManager;

    private:
      DuplicatesRecordingsTree(RecordingsTreePtr &recordingsTree);

    public:
      RecordingsItemDirPtr getRoot() const { return m_root; }
      RecordingsItemDirPtr getFlatRoot() const { return m_flat_root; }

    private:
      RecordingsItemDirPtr m_root;
      RecordingsItemDirPtr m_flat_root;
  };

  inline void swap(RecordingsItemDirPtr &a, RecordingsItemDirPtr &b) {
    a.swap(b);
  }

void AppendScraperData(cToSvConcat<0> &target, cSv s_IMDB_ID, const cTvMedia &s_image, tvType s_videoType, cSv s_title, int s_season_number, int s_episode_number, cSv s_episode_name, int s_runtime, cSv s_release_date);

std::string recordingErrorsHtml(int recordingErrors);

void addDuplicateRecordingsNoSd(std::vector<RecordingsItemRec*> &DuplicateRecItems, RecordingsTreePtr &RecordingsTree);
void addDuplicateRecordingsLang(std::vector<RecordingsItemRec*> &DuplicateRecItems, RecordingsTreePtr &RecordingsTree);
void addDuplicateRecordingsSd(std::vector<RecordingsItemRec*> &DuplicateRecItems, RecordingsTreePtr &RecordingsTree);

template<std::size_t N>
cToSvConcat<N> & StringAppendFrameParams(cToSvConcat<N> &s, const RecordingsItemRec *itemRec) {
#if VDRVERSNUM >= 20605
  if (!itemRec) return s;
  if (itemRec->FrameWidth() && itemRec->FrameHeight() ) {
    s << itemRec->FrameWidth() << 'x' << itemRec->FrameHeight();
    if (itemRec->FramesPerSecond() > 0) {
      s.appendFormatted("/%.2g", itemRec->FramesPerSecond() );
      if (itemRec->ScanType() != stUnknown)
        s.append(1, itemRec->ScanTypeChar());
     }
     if (itemRec->AspectRatio() != arUnknown)
       s << ' ' << AspectRatioTexts[itemRec->AspectRatio()];
  }
#endif
  return s;
}
} // namespace vdrlive

#endif // VDR_LIVE_RECORDINGS_H