FFmpegfs Fuse Multi Media Filesystem 2.16
vcdentries.cc
Go to the documentation of this file.
1/*
2 * Copyright (C) 2017-2024 Norbert Schlia (nschlia@oblivion-software.de)
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
17 */
18
30#include "vcdentries.h"
31#include "vcdutils.h"
32
33#include <arpa/inet.h>
34#include <cstring>
35#include <sys/stat.h>
36
37#define VCD_SECTOR_SIZE 2352
38#define VCD_SECTOR_OFFS 24
39#define VCD_SECTOR_DATA 2324
44const std::array<char, 12> SYNC = { '\x00', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\x00' };
48//static const char PICTURE_START_CODE[4] = { '\x00', '\x00', '\x01', '\x00' };
52//static const char VIDEO_STREAM_1[4] = { '\x00', '\x00', '\x01', '\xE0' };
53
55{
56 clear();
57}
58
60{
61 m_file_date = -1;
62 m_id.clear();
65 m_chapters.clear();
66 m_disk_path.clear();
67 m_duration = 0;
68}
69
70int VcdEntries::load_file(const std::string & path)
71{
72 FILE * fpi = nullptr;
73 std::string fullname;
74 bool is_vcd = false;
75
76 clear();
77
78 if (!VCDUTILS::locate_file(path, "ENTRIES", fullname, is_vcd))
79 {
80 return ENOENT;
81 }
82
84
85 try
86 {
87 VCDENTRY vcdentry;
88 struct stat stbuf;
89 uint32_t num_entries = 0;
90
91 fpi = fopen(fullname.c_str(), "rb");
92 if (fpi == nullptr)
93 {
94 throw static_cast<int>(errno);
95 }
96
97 if (fstat(fileno(fpi), &stbuf) != 0)
98 {
99 throw static_cast<int>(ferror(fpi));
100 }
101
102 m_file_date = stbuf.st_mtime;
103
104 std::memset(&vcdentry, 0, sizeof(vcdentry));
105
106 if (fread(reinterpret_cast<char *>(&vcdentry), 1, sizeof(vcdentry), fpi) != sizeof(vcdentry))
107 {
108 throw static_cast<int>(ferror(fpi));
109 }
110
111 m_id = VCDUTILS::convert_txt2string(reinterpret_cast<const char *>(vcdentry.m_ID.data()), vcdentry.m_ID.size());
112 m_type = static_cast<VCDTYPE>(vcdentry.m_type);
113 m_profile_tag = static_cast<VCDPROFILETAG>(vcdentry.m_profile_tag);
114 num_entries = htons(vcdentry.m_num_entries);
115 m_duration = 0;
116
117 int sec = BCD2DEC(vcdentry.m_chapter[0].m_msf.m_min) * 60 + BCD2DEC(vcdentry.m_chapter[0].m_msf.m_sec);
118 for (uint32_t chapter_no = 0, total = num_entries; chapter_no < total; chapter_no++)
119 {
120 if (chapter_no && BCD2DEC(vcdentry.m_chapter[chapter_no].m_msf.m_min) * 60 + BCD2DEC(vcdentry.m_chapter[chapter_no].m_msf.m_sec) - sec < 1)
121 {
122 // Skip chapters shorter than 1 second
123 sec = BCD2DEC(vcdentry.m_chapter[chapter_no].m_msf.m_min) * 60 + BCD2DEC(vcdentry.m_chapter[chapter_no].m_msf.m_sec);
124 --num_entries;
125 continue;
126 }
127
128 VcdChapter chapter(vcdentry.m_chapter[chapter_no], is_vcd);
129
130 m_chapters.push_back(chapter);
131 }
132
133 // Calculate durations of all chapters until last. This will be done later as we do not yet know the duration of the stream
134 for (size_t chapter_no = 0; chapter_no < m_chapters.size() - 1; chapter_no++)
135 {
136 VcdChapter & chapter1 = m_chapters[chapter_no];
137 const VcdChapter & chapter2 = m_chapters[chapter_no + 1];
138 int64_t chapter_duration = chapter2.get_start_time() - chapter1.get_start_time();
139
140 // Chapter duration
141 chapter1.m_duration = chapter_duration;
142 // Total duration
143 m_duration += chapter_duration;
144 }
145 }
146 catch (int orgerrno)
147 {
148 if (fpi != nullptr)
149 {
150 fclose(fpi);
151 }
152 return orgerrno;
153 }
154
155 fclose(fpi);
156
157 return scan_chapters();
158}
159
161{
162 FILE * fpi = nullptr;
163 struct stat stbuf;
164
165 std::memset(&stbuf, 0, sizeof(stbuf));
166
167 if (!m_chapters.size())
168 {
169 return EIO; // Fail safe only: Should not happen, at least 1 chapter is required.
170 }
171
172 try
173 {
174 int last_track_no = -1;
175 int64_t first_sync = -1;
176
177 // Build list of chapters
178 for (size_t chapter_no = 0; chapter_no < m_chapters.size(); chapter_no++)
179 {
180 if (last_track_no != m_chapters[chapter_no].get_track_no())
181 {
182 std::string fullname;
183
184 last_track_no = m_chapters[chapter_no].get_track_no();
185
186 int orgerrno = VCDUTILS::locate_video(m_disk_path, last_track_no, fullname);
187 if (orgerrno != 0)
188 {
189 throw static_cast<int>(orgerrno);
190 }
191
192 if (chapter_no)
193 {
194 m_chapters[chapter_no - 1].m_end_pos = static_cast<uint64_t>(stbuf.st_size);
195 }
196
197 if (fpi != nullptr)
198 {
199 fclose(fpi);
200 }
201
202 fpi = fopen(fullname.c_str(), "rb");
203
204 if (fpi == nullptr)
205 {
206 throw static_cast<int>(errno);
207 }
208
209 if (fstat(fileno(fpi), &stbuf) != 0)
210 {
211 throw static_cast<int>(ferror(fpi));
212 }
213
214 // Locate the first sync bytes
215 SEEKRES res = seek_sync(fpi, SYNC);
216
217 if (res != SEEKRES::FOUND)
218 {
219 throw static_cast<int>(EIO);
220 }
221
222 first_sync = ftell(fpi) - static_cast<int64_t>(SYNC.size());
223 }
224
225 int64_t total_chunks = (stbuf.st_size - first_sync) / VCD_SECTOR_SIZE;
226 int64_t first = 0;
227 int64_t last = total_chunks - 1;
228 int64_t middle = (first + last) / 2;
229
230 // Locate sector with correct start time
231 while (first <= last)
232 {
233 VcdChapter buffer(m_chapters[chapter_no].get_is_svcd());
234 long int file_pos = static_cast<long int>(first_sync + middle * VCD_SECTOR_SIZE);
235
236 if (fseek(fpi, file_pos, SEEK_SET))
237 {
238 throw static_cast<int>(ferror(fpi));
239 }
240
241 int orgerrno = buffer.readio(fpi, last_track_no);
242 if (orgerrno)
243 {
244 throw static_cast<int>(orgerrno);
245 }
246
247 if (buffer < m_chapters[chapter_no])
248 {
249 first = middle + 1;
250 }
251 else if (buffer == m_chapters[chapter_no])
252 {
253 m_chapters[chapter_no].m_start_pos = static_cast<uint64_t>(file_pos);
254
255 if (chapter_no)
256 {
257 m_chapters[chapter_no - 1].m_end_pos = static_cast<uint64_t>(file_pos);
258 }
259 break;
260 }
261 else
262 {
263 last = middle - 1;
264 }
265
266 middle = (first + last) / 2;
267 }
268 }
269
270 {
271 VcdChapter buffer(m_chapters[m_chapters.size() - 1].get_is_svcd());
272 int64_t total_chunks = (stbuf.st_size - first_sync) / VCD_SECTOR_SIZE;
273
274 // Read time stamp of last sector
275 if (fseek(fpi, static_cast<long int>(first_sync + (total_chunks - 1) * VCD_SECTOR_SIZE), SEEK_SET))
276 {
277 throw static_cast<int>(ferror(fpi));
278 }
279
280 int orgerrno = buffer.readio(fpi, last_track_no);
281 if (orgerrno)
282 {
283 throw static_cast<int>(orgerrno);
284 }
285
286 VcdChapter & chapter1 = m_chapters[m_chapters.size() - 1];
287 int64_t chapter_duration = buffer.get_start_time() - chapter1.get_start_time();
288
289 // Chapter duration
290 chapter1.m_duration = chapter_duration;
291 // Total duration
292 m_duration += chapter_duration;
293 }
294 }
295 catch (int orgerrno)
296 {
297 if (fpi != nullptr)
298 {
299 fclose(fpi);
300 }
301 return orgerrno;
302 }
303
304 // End of last chapter
305 m_chapters[m_chapters.size() - 1].m_end_pos = static_cast<uint64_t>(stbuf.st_size);
306
307 if (fpi != nullptr)
308 {
309 fclose(fpi);
310 }
311
312 return 0;
313}
314
315VcdEntries::SEEKRES VcdEntries::seek_sync(FILE *fpi, const std::array<char, 12> & sync) const
316{
317 char ch;
318
319 // Read first char
320 if (fread(&ch, 1, 1, fpi) != 1)
321 {
322 return SEEKRES::NOTFOUND;
323 }
324
325 for (size_t n = 1; n <= sync.size(); n++)
326 {
327 if (ch != sync[n - 1])
328 {
329 if (n > 1)
330 {
331 // Restart check
332 n = 0;
333 continue;
334 }
335
336 n = 0;
337 }
338
339 if (n == sync.size())
340 {
341 // Found!
342 break;
343 }
344
345 if (fread(&ch, 1, 1, fpi) != 1)
346 {
347 return SEEKRES::NOTFOUND;
348 }
349 }
350
351 return SEEKRES::FOUND;
352}
353
355{
356 return m_file_date;
357}
358
359const std::string & VcdEntries::get_id() const
360{
361 return m_id;
362}
363
365{
366 return m_type;
367}
368
369std::string VcdEntries::get_type_str() const
370{
372}
373
375{
376 return m_profile_tag;
377}
378
380{
382}
383
385{
386 return static_cast<int>(m_chapters.size());
387}
388
389const VcdChapter *VcdEntries::get_chapter(int chapter_idx) const
390{
391 if (chapter_idx < 0 || chapter_idx >= get_number_of_chapters())
392 {
393 return nullptr;
394 }
395 return &m_chapters[static_cast<size_t>(chapter_idx)];
396}
397
399{
400 return m_duration;
401}
402
403uint64_t VcdEntries::get_size() const
404{
405 size_t chapters = static_cast<size_t>(get_number_of_chapters());
406
407 if (!chapters)
408 {
409 return 0;
410 }
411
412 return (m_chapters[chapters - 1].get_end_pos() - m_chapters[0].get_start_pos());
413}
414
415const std::string & VcdEntries::get_disk_path() const
416{
417 return m_disk_path;
418}
Video CD chapter.
Definition: vcdchapter.h:62
int64_t m_duration
Chapter duration, in AV_TIME_BASE fractional seconds.
Definition: vcdchapter.h:216
int64_t get_start_time() const
Get start position of chapter in AV_TIME_BASE units.
Definition: vcdchapter.cc:169
int readio(FILE *fpi, int track_no)
Read file from disk.
Definition: vcdchapter.cc:76
VcdEntries()
Construct VcdEntries object.
Definition: vcdentries.cc:54
time_t m_file_date
File date.
Definition: vcdentries.h:149
std::string m_disk_path
Path to this disk.
Definition: vcdentries.h:158
VCDTYPE get_type() const
Get disk type.
Definition: vcdentries.cc:364
SEEKRES seek_sync(FILE *fpi, const std::array< char, 12 > &sync) const
Seek for sync bytes.
Definition: vcdentries.cc:315
uint64_t get_size() const
Get disk size (DAT/MPEG only).
Definition: vcdentries.cc:403
std::string m_id
ID of CD.
Definition: vcdentries.h:150
int get_number_of_chapters() const
Get number of chapters on this disk.
Definition: vcdentries.cc:384
void clear()
Reset this object.
Definition: vcdentries.cc:59
time_t get_file_date() const
Get date of disk (taken from INFO.VCD or SVD).
Definition: vcdentries.cc:354
const std::string & get_disk_path() const
Get disk directory.
Definition: vcdentries.cc:415
VCDPROFILETAG m_profile_tag
System profile tag.
Definition: vcdentries.h:152
VCDTYPE m_type
Type of CD.
Definition: vcdentries.h:151
int scan_chapters()
Scan the disk for chapters.
Definition: vcdentries.cc:160
std::vector< VcdChapter > m_chapters
VCD chapters.
Definition: vcdentries.h:154
int load_file(const std::string &path)
Load VCD from path.
Definition: vcdentries.cc:70
const VcdChapter * get_chapter(int chapter_idx) const
Get chapter object.
Definition: vcdentries.cc:389
int64_t m_duration
Total disk duration, in AV_TIME_BASE fractional seconds.
Definition: vcdentries.h:155
@ FOUND
Sync found.
@ NOTFOUND
Sync not found.
std::string get_type_str() const
Get disk type as string.
Definition: vcdentries.cc:369
std::string get_profile_tag_str() const
Get disk profile tag as string.
Definition: vcdentries.cc:379
int64_t get_duration() const
Get the total disk duration in AV_TIME_BASE fractional seconds.
Definition: vcdentries.cc:398
const std::string & get_id() const
Get disk ID.
Definition: vcdentries.cc:359
VCDPROFILETAG get_profile_tag() const
Get disk profile tag.
Definition: vcdentries.cc:374
bool locate_file(const std::string &path, const std::string &filename, std::string &fullname, bool &is_vcd)
Check if path is a S/VCD.
Definition: vcdutils.cc:64
int locate_video(const std::string &path, int track_no, std::string &fullname)
Locate AVSEQ*DAT/MPEG video file for track_no.
Definition: vcdutils.cc:88
std::string convert_txt2string(const char *txt, int size, bool trimmed)
Non-zero terminated text is converted to std::string.
Definition: vcdutils.cc:41
void get_directory(const std::string &fullname, std::string *directory)
Check if fullname is a directory. Remove the filename if necessary.
Definition: vcdutils.cc:151
std::string get_type_str(VCDTYPE type)
Return disk type as a human readable string.
Definition: vcdutils.cc:111
std::string get_profile_tag_str(VCDPROFILETAG tag)
Profile as a human readable string.
Definition: vcdutils.cc:132
Video CD entry.
Definition: vcdutils.h:76
uint16_t m_num_entries
2 Bytes: 1 <= tracks <= 500
Definition: vcdutils.h:96
std::array< VCDCHAPTER, VCD_MAX_CHAPTERS > m_chapter
Chapters.
Definition: vcdutils.h:104
uint8_t m_type
1 Byte: CD type
Definition: vcdutils.h:84
std::array< char, 8 > m_ID
8 Bytes: ID "ENTRYVCD" or "ENTRYSVD"
Definition: vcdutils.h:77
uint8_t m_profile_tag
1 Byte: System Profile Tag.
Definition: vcdutils.h:91
VCDTYPE
Definition: vcdchapter.h:40
@ UNKNOWN
unknown type
VCDPROFILETAG
Definition: vcdchapter.h:50
@ UNKNOWN
unknown file tag
const std::array< char, 12 > SYNC
Chapter synchronisation in S/VCD mpeg/dat files (12 byte: 0x00FFFFFFFFFFFFFFFFFFFF00)
Definition: vcdentries.cc:44
#define VCD_SECTOR_SIZE
Video CD sector size.
Definition: vcdentries.cc:37
S/VCD VcdEntries class.
S/VCD utility functions.
#define BCD2DEC(hex)
Definition: vcdutils.h:40