Pushed latest changes, add Telecine/Interlacing detection to ff_d2v.py
This commit is contained in:
parent
e06f1dfad5
commit
5e4d9d6965
11 changed files with 2403 additions and 2039 deletions
10
.gitignore
vendored
10
.gitignore
vendored
|
@ -138,3 +138,13 @@ dmypy.json
|
|||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
*.code-workspace
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
|
20
README.md
20
README.md
|
@ -2,9 +2,27 @@
|
|||
|
||||
Choggbuster is a set of python scripts aimed at automated preprocessing of video DVDs for archival and filtering
|
||||
|
||||
# Requirements
|
||||
|
||||
- python (obviously)
|
||||
- libdvdcss (for decrypting copy protected DVDs)
|
||||
- libdvdnav (for streaming the VOBs to disk)
|
||||
- libdvdread (for reading decrypted data off of DVDs)
|
||||
- ffmpeg (for demuxing)
|
||||
- ccextractor (for extracting DVD Subtitles)
|
||||
|
||||
# Setup (Windows)
|
||||
|
||||
1. Clone the repo
|
||||
2. `pip install cffi tqdm`
|
||||
3. Grab [libdvdread, libdvdnav](https://www.videolan.org/developers/libdvdnav.html) and [libdvdcss](https://www.videolan.org/developers/libdvdcss.html) from VLC and drop them next to `dvd_ripper.py`
|
||||
4. `python dvd_ripper.py F:\` or `python dvd_ripper.py D:\path\to\DVD.ISO`
|
||||
4. `python dvd_ripper.py F:\` or `python dvd_ripper.py D:\path\to\DVD.ISO`
|
||||
5. this will create a folder `out` with a subfolder for the disc containing:
|
||||
- JSON file with metadata for the DVD title (`XXXX.json` where `X` is the title number)
|
||||
- demuxed streams (`tXXX_aYYY_Z_0xAAA.{ext}` where `X` is the title number, `Y` is the angle number `Z` is stream index and `AAA` is the stream id)
|
||||
- `.m2v` for video
|
||||
- `.ac3` or `.dtx` for audio
|
||||
- `.sub` and `.idx` for subtitles
|
||||
- `.srt` for captions
|
||||
- `.d2v` file for use with AviSynth and Vapoursynth D2V reader
|
||||
- `.info.json` file containing video stream metadata (cropping information, interlaced/progressive frame count and aspect ration information)
|
||||
|
|
688
dvd_reader.h
688
dvd_reader.h
|
@ -1,344 +1,344 @@
|
|||
/*
|
||||
* Copyright (C) 2001, 2002 Billy Biggs <vektor@dumbterm.net>,
|
||||
* Håkan Hjort <d95hjort@dtek.chalmers.se>,
|
||||
* Björn Englund <d4bjorn@dtek.chalmers.se>
|
||||
*
|
||||
* This file is part of libdvdread.
|
||||
*
|
||||
* libdvdread is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* libdvdread is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with libdvdread; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The DVD access interface.
|
||||
*
|
||||
* This file contains the functions that form the interface to to
|
||||
* reading files located on a DVD.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The current version.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The length of one Logical Block of a DVD.
|
||||
*/
|
||||
#define DVD_VIDEO_LB_LEN 2048
|
||||
|
||||
/**
|
||||
* Maximum length of filenames allowed in UDF.
|
||||
*/
|
||||
#define MAX_UDF_FILE_NAME_LEN 2048
|
||||
|
||||
typedef long int off_t;
|
||||
|
||||
/**
|
||||
* Opaque type that is used as a handle for one instance of an opened DVD.
|
||||
*/
|
||||
typedef struct dvd_reader_s dvd_reader_t;
|
||||
typedef struct dvd_reader_device_s dvd_reader_device_t;
|
||||
|
||||
/**
|
||||
* Opaque type for a file read handle, much like a normal fd or FILE *.
|
||||
*/
|
||||
typedef struct dvd_file_s dvd_file_t;
|
||||
|
||||
struct dvd_reader_stream_cb
|
||||
{
|
||||
int ( *pf_seek ) ( void *p_stream, uint64_t i_pos);
|
||||
int ( *pf_read ) ( void *p_stream, void* buffer, int i_read);
|
||||
int ( *pf_readv ) ( void *p_stream, void *p_iovec, int i_blocks);
|
||||
};
|
||||
typedef struct dvd_reader_stream_cb dvd_reader_stream_cb;
|
||||
|
||||
/**
|
||||
* Custom logger callback for DVDOpen[Stream]2
|
||||
* @param private Handle as provided in Open functions
|
||||
* @param level Log level
|
||||
* @param fmt Format string
|
||||
* @param args Arguments list
|
||||
* pf_log(priv, level, fmt, args);
|
||||
*/
|
||||
typedef enum
|
||||
{
|
||||
DVD_LOGGER_LEVEL_INFO,
|
||||
DVD_LOGGER_LEVEL_ERROR,
|
||||
DVD_LOGGER_LEVEL_WARN,
|
||||
DVD_LOGGER_LEVEL_DEBUG,
|
||||
} dvd_logger_level_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
// void ( *pf_log ) ( void *, dvd_logger_level_t, const char *, va_list );
|
||||
void *pf_log;
|
||||
} dvd_logger_cb;
|
||||
|
||||
/**
|
||||
* Public type that is used to provide statistics on a handle.
|
||||
*/
|
||||
typedef struct {
|
||||
off_t size; /**< Total size of file in bytes */
|
||||
int nr_parts; /**< Number of file parts */
|
||||
off_t parts_size[9]; /**< Size of each part in bytes */
|
||||
} dvd_stat_t;
|
||||
|
||||
/**
|
||||
* Opens a block device of a DVD-ROM file, or an image file, or a directory
|
||||
* name for a mounted DVD or HD copy of a DVD.
|
||||
* The second form of Open function (DVDOpenStream) can be used to
|
||||
* provide custom stream_cb functions to access the DVD (see libdvdcss).
|
||||
*
|
||||
* If the given file is a block device, or is the mountpoint for a block
|
||||
* device, then that device is used for CSS authentication using libdvdcss.
|
||||
* If no device is available, then no CSS authentication is performed,
|
||||
* and we hope that the image is decrypted.
|
||||
*
|
||||
* If the path given is a directory, then the files in that directory may be
|
||||
* in any one of these formats:
|
||||
*
|
||||
* path/VIDEO_TS/VTS_01_1.VOB
|
||||
* path/video_ts/vts_01_1.vob
|
||||
* path/VTS_01_1.VOB
|
||||
* path/vts_01_1.vob
|
||||
*
|
||||
* @param path Specifies the the device, file or directory to be used.
|
||||
* @param stream is a private handle used by stream_cb
|
||||
* @param stream_cb is a struct containing seek and read functions
|
||||
* @return If successful a a read handle is returned. Otherwise 0 is returned.
|
||||
*
|
||||
* dvd = DVDOpen(path);
|
||||
* dvd = DVDOpenStream(stream, &stream_cb);
|
||||
*/
|
||||
dvd_reader_t *DVDOpen( const char * );
|
||||
dvd_reader_t *DVDOpenStream( void *, dvd_reader_stream_cb * );
|
||||
|
||||
/**
|
||||
* Same as DVDOpen, but with private handle to be passed back on callbacks
|
||||
*
|
||||
* @param path Specifies the the device, file or directory to be used.
|
||||
* @param priv is a private handle
|
||||
* @param logcb is a custom logger callback struct, or NULL if none needed
|
||||
* @param stream_cb is a struct containing seek and read functions
|
||||
* @return If successful a a read handle is returned. Otherwise 0 is returned.
|
||||
*
|
||||
* dvd = DVDOpen2(priv, logcb, path);
|
||||
* dvd = DVDOpenStream2(priv, logcb, &stream_cb);
|
||||
*/
|
||||
dvd_reader_t *DVDOpen2( void *, const dvd_logger_cb *, const char * );
|
||||
dvd_reader_t *DVDOpenStream2( void *, const dvd_logger_cb *, dvd_reader_stream_cb * );
|
||||
|
||||
/**
|
||||
* Closes and cleans up the DVD reader object.
|
||||
*
|
||||
* You must close all open files before calling this function.
|
||||
*
|
||||
* @param dvd A read handle that should be closed.
|
||||
*
|
||||
* DVDClose(dvd);
|
||||
*/
|
||||
void DVDClose( dvd_reader_t * );
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
typedef enum {
|
||||
DVD_READ_INFO_FILE, /**< VIDEO_TS.IFO or VTS_XX_0.IFO (title) */
|
||||
DVD_READ_INFO_BACKUP_FILE, /**< VIDEO_TS.BUP or VTS_XX_0.BUP (title) */
|
||||
DVD_READ_MENU_VOBS, /**< VIDEO_TS.VOB or VTS_XX_0.VOB (title) */
|
||||
DVD_READ_TITLE_VOBS /**< VTS_XX_[1-9].VOB (title). All files in
|
||||
the title set are opened and read as a
|
||||
single file. */
|
||||
} dvd_read_domain_t;
|
||||
|
||||
/**
|
||||
* Stats a file on the DVD given the title number and domain.
|
||||
* The information about the file is stored in a dvd_stat_t
|
||||
* which contains information about the size of the file and
|
||||
* the number of parts in case of a multipart file and the respective
|
||||
* sizes of the parts.
|
||||
* A multipart file is for instance VTS_02_1.VOB, VTS_02_2.VOB, VTS_02_3.VOB
|
||||
* The size of VTS_02_1.VOB will be stored in stat->parts_size[0],
|
||||
* VTS_02_2.VOB in stat->parts_size[1], ...
|
||||
* The total size (sum of all parts) is stored in stat->size and
|
||||
* stat->nr_parts will hold the number of parts.
|
||||
* Only DVD_READ_TITLE_VOBS (VTS_??_[1-9].VOB) can be multipart files.
|
||||
*
|
||||
* This function is only of use if you want to get the size of each file
|
||||
* in the filesystem. These sizes are not needed to use any other
|
||||
* functions in libdvdread.
|
||||
*
|
||||
* @param dvd A dvd read handle.
|
||||
* @param titlenum Which Video Title Set should be used, VIDEO_TS is 0.
|
||||
* @param domain Which domain.
|
||||
* @param stat Pointer to where the result is stored.
|
||||
* @return If successful 0, otherwise -1.
|
||||
*
|
||||
* int DVDFileStat(dvd, titlenum, domain, stat);
|
||||
*/
|
||||
int DVDFileStat(dvd_reader_t *, int, dvd_read_domain_t, dvd_stat_t *);
|
||||
|
||||
/**
|
||||
* Opens a file on the DVD given the title number and domain.
|
||||
*
|
||||
* If the title number is 0, the video manager information is opened
|
||||
* (VIDEO_TS.[IFO,BUP,VOB]). Returns a file structure which may be
|
||||
* used for reads, or 0 if the file was not found.
|
||||
*
|
||||
* @param dvd A dvd read handle.
|
||||
* @param titlenum Which Video Title Set should be used, VIDEO_TS is 0.
|
||||
* @param domain Which domain.
|
||||
* @return If successful a a file read handle is returned, otherwise 0.
|
||||
*
|
||||
* dvd_file = DVDOpenFile(dvd, titlenum, domain); */
|
||||
dvd_file_t *DVDOpenFile( dvd_reader_t *, int, dvd_read_domain_t );
|
||||
|
||||
/**
|
||||
* Closes a file and frees the associated structure.
|
||||
*
|
||||
* @param dvd_file The file read handle to be closed.
|
||||
*
|
||||
* DVDCloseFile(dvd_file);
|
||||
*/
|
||||
void DVDCloseFile( dvd_file_t * );
|
||||
|
||||
/**
|
||||
* Reads block_count number of blocks from the file at the given block offset.
|
||||
* Returns number of blocks read on success, -1 on error. This call is only
|
||||
* for reading VOB data, and should not be used when reading the IFO files.
|
||||
* When reading from an encrypted drive, blocks are decrypted using libdvdcss
|
||||
* where required.
|
||||
*
|
||||
* @param dvd_file A file read handle.
|
||||
* @param offset Block offset from the start of the file to start reading at.
|
||||
* @param block_count Number of block to read.
|
||||
* @param data Pointer to a buffer to write the data into.
|
||||
* @return Returns number of blocks read on success, -1 on error.
|
||||
*
|
||||
* blocks_read = DVDReadBlocks(dvd_file, offset, block_count, data);
|
||||
*/
|
||||
ssize_t DVDReadBlocks( dvd_file_t *, int, size_t, unsigned char * );
|
||||
|
||||
/**
|
||||
* Seek to the given position in the file. Returns the resulting position in
|
||||
* bytes from the beginning of the file. The seek position is only used for
|
||||
* byte reads from the file, the block read call always reads from the given
|
||||
* offset.
|
||||
*
|
||||
* @param dvd_file A file read handle.
|
||||
* @param seek_offset Byte offset from the start of the file to seek to.
|
||||
* @return The resulting position in bytes from the beginning of the file.
|
||||
*
|
||||
* offset_set = DVDFileSeek(dvd_file, seek_offset);
|
||||
*/
|
||||
int32_t DVDFileSeek( dvd_file_t *, int32_t );
|
||||
|
||||
/**
|
||||
* Reads the given number of bytes from the file. This call can only be used
|
||||
* on the information files, and may not be used for reading from a VOB. This
|
||||
* reads from and increments the currrent seek position for the file.
|
||||
*
|
||||
* @param dvd_file A file read handle.
|
||||
* @param data Pointer to a buffer to write the data into.
|
||||
* @param bytes Number of bytes to read.
|
||||
* @return Returns number of bytes read on success, -1 on error.
|
||||
*
|
||||
* bytes_read = DVDReadBytes(dvd_file, data, bytes);
|
||||
*/
|
||||
ssize_t DVDReadBytes( dvd_file_t *, void *, size_t );
|
||||
|
||||
/**
|
||||
* Returns the file size in blocks.
|
||||
*
|
||||
* @param dvd_file A file read handle.
|
||||
* @return The size of the file in blocks, -1 on error.
|
||||
*
|
||||
* blocks = DVDFileSize(dvd_file);
|
||||
*/
|
||||
ssize_t DVDFileSize( dvd_file_t * );
|
||||
|
||||
/**
|
||||
* Get a unique 128 bit disc ID.
|
||||
* This is the MD5 sum of VIDEO_TS.IFO and the VTS_0?_0.IFO files
|
||||
* in title order (those that exist).
|
||||
* If you need a 'text' representation of the id, print it as a
|
||||
* hexadecimal number, using lowercase letters, discid[0] first.
|
||||
* I.e. the same format as the command-line 'md5sum' program uses.
|
||||
*
|
||||
* @param dvd A read handle to get the disc ID from
|
||||
* @param discid The buffer to put the disc ID into. The buffer must
|
||||
* have room for 128 bits (16 chars).
|
||||
* @return 0 on success, -1 on error.
|
||||
*/
|
||||
int DVDDiscID( dvd_reader_t *, unsigned char * );
|
||||
|
||||
/**
|
||||
* Get the UDF VolumeIdentifier and VolumeSetIdentifier
|
||||
* from the PrimaryVolumeDescriptor.
|
||||
*
|
||||
* @param dvd A read handle to get the disc ID from
|
||||
* @param volid The buffer to put the VolumeIdentifier into.
|
||||
* The VolumeIdentifier is latin-1 encoded (8bit unicode)
|
||||
* null terminated and max 32 bytes (including '\0')
|
||||
* @param volid_size No more than volid_size bytes will be copied to volid.
|
||||
* If the VolumeIdentifier is truncated because of this
|
||||
* it will still be null terminated.
|
||||
* @param volsetid The buffer to put the VolumeSetIdentifier into.
|
||||
* The VolumeIdentifier is 128 bytes as
|
||||
* stored in the UDF PrimaryVolumeDescriptor.
|
||||
* Note that this is not a null terminated string.
|
||||
* @param volsetid_size At most volsetid_size bytes will be copied to volsetid.
|
||||
* @return 0 on success, -1 on error.
|
||||
*/
|
||||
int DVDUDFVolumeInfo( dvd_reader_t *, char *, unsigned int,
|
||||
unsigned char *, unsigned int );
|
||||
|
||||
int DVDFileSeekForce( dvd_file_t *, int offset, int force_size);
|
||||
|
||||
/**
|
||||
* Get the ISO9660 VolumeIdentifier and VolumeSetIdentifier
|
||||
*
|
||||
* * Only use this function as fallback if DVDUDFVolumeInfo returns 0 *
|
||||
* * this will happen on a disc mastered only with a iso9660 filesystem *
|
||||
* * All video DVD discs have UDF filesystem *
|
||||
*
|
||||
* @param dvd A read handle to get the disc ID from
|
||||
* @param volid The buffer to put the VolumeIdentifier into.
|
||||
* The VolumeIdentifier is coded with '0-9','A-Z','_'
|
||||
* null terminated and max 33 bytes (including '\0')
|
||||
* @param volid_size No more than volid_size bytes will be copied to volid.
|
||||
* If the VolumeIdentifier is truncated because of this
|
||||
* it will still be null terminated.
|
||||
* @param volsetid The buffer to put the VolumeSetIdentifier into.
|
||||
* The VolumeIdentifier is 128 bytes as
|
||||
* stored in the ISO9660 PrimaryVolumeDescriptor.
|
||||
* Note that this is not a null terminated string.
|
||||
* @param volsetid_size At most volsetid_size bytes will be copied to volsetid.
|
||||
* @return 0 on success, -1 on error.
|
||||
*/
|
||||
int DVDISOVolumeInfo( dvd_reader_t *, char *, unsigned int,
|
||||
unsigned char *, unsigned int );
|
||||
|
||||
/**
|
||||
* Sets the level of caching that is done when reading from a device
|
||||
*
|
||||
* @param dvd A read handle to get the disc ID from
|
||||
* @param level The level of caching wanted.
|
||||
* -1 - returns the current setting.
|
||||
* 0 - UDF Cache turned off.
|
||||
* 1 - (default level) Pointers to IFO files and some data from
|
||||
* PrimaryVolumeDescriptor are cached.
|
||||
*
|
||||
* @return The level of caching.
|
||||
*/
|
||||
int DVDUDFCacheLevel( dvd_reader_t *, int );
|
||||
/*
|
||||
* Copyright (C) 2001, 2002 Billy Biggs <vektor@dumbterm.net>,
|
||||
* Håkan Hjort <d95hjort@dtek.chalmers.se>,
|
||||
* Björn Englund <d4bjorn@dtek.chalmers.se>
|
||||
*
|
||||
* This file is part of libdvdread.
|
||||
*
|
||||
* libdvdread is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* libdvdread is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with libdvdread; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The DVD access interface.
|
||||
*
|
||||
* This file contains the functions that form the interface to to
|
||||
* reading files located on a DVD.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The current version.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The length of one Logical Block of a DVD.
|
||||
*/
|
||||
#define DVD_VIDEO_LB_LEN 2048
|
||||
|
||||
/**
|
||||
* Maximum length of filenames allowed in UDF.
|
||||
*/
|
||||
#define MAX_UDF_FILE_NAME_LEN 2048
|
||||
|
||||
typedef long int off_t;
|
||||
|
||||
/**
|
||||
* Opaque type that is used as a handle for one instance of an opened DVD.
|
||||
*/
|
||||
typedef struct dvd_reader_s dvd_reader_t;
|
||||
typedef struct dvd_reader_device_s dvd_reader_device_t;
|
||||
|
||||
/**
|
||||
* Opaque type for a file read handle, much like a normal fd or FILE *.
|
||||
*/
|
||||
typedef struct dvd_file_s dvd_file_t;
|
||||
|
||||
struct dvd_reader_stream_cb
|
||||
{
|
||||
int ( *pf_seek ) ( void *p_stream, uint64_t i_pos);
|
||||
int ( *pf_read ) ( void *p_stream, void* buffer, int i_read);
|
||||
int ( *pf_readv ) ( void *p_stream, void *p_iovec, int i_blocks);
|
||||
};
|
||||
typedef struct dvd_reader_stream_cb dvd_reader_stream_cb;
|
||||
|
||||
/**
|
||||
* Custom logger callback for DVDOpen[Stream]2
|
||||
* @param private Handle as provided in Open functions
|
||||
* @param level Log level
|
||||
* @param fmt Format string
|
||||
* @param args Arguments list
|
||||
* pf_log(priv, level, fmt, args);
|
||||
*/
|
||||
typedef enum
|
||||
{
|
||||
DVD_LOGGER_LEVEL_INFO,
|
||||
DVD_LOGGER_LEVEL_ERROR,
|
||||
DVD_LOGGER_LEVEL_WARN,
|
||||
DVD_LOGGER_LEVEL_DEBUG,
|
||||
} dvd_logger_level_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
// void ( *pf_log ) ( void *, dvd_logger_level_t, const char *, va_list );
|
||||
void *pf_log;
|
||||
} dvd_logger_cb;
|
||||
|
||||
/**
|
||||
* Public type that is used to provide statistics on a handle.
|
||||
*/
|
||||
typedef struct {
|
||||
off_t size; /**< Total size of file in bytes */
|
||||
int nr_parts; /**< Number of file parts */
|
||||
off_t parts_size[9]; /**< Size of each part in bytes */
|
||||
} dvd_stat_t;
|
||||
|
||||
/**
|
||||
* Opens a block device of a DVD-ROM file, or an image file, or a directory
|
||||
* name for a mounted DVD or HD copy of a DVD.
|
||||
* The second form of Open function (DVDOpenStream) can be used to
|
||||
* provide custom stream_cb functions to access the DVD (see libdvdcss).
|
||||
*
|
||||
* If the given file is a block device, or is the mountpoint for a block
|
||||
* device, then that device is used for CSS authentication using libdvdcss.
|
||||
* If no device is available, then no CSS authentication is performed,
|
||||
* and we hope that the image is decrypted.
|
||||
*
|
||||
* If the path given is a directory, then the files in that directory may be
|
||||
* in any one of these formats:
|
||||
*
|
||||
* path/VIDEO_TS/VTS_01_1.VOB
|
||||
* path/video_ts/vts_01_1.vob
|
||||
* path/VTS_01_1.VOB
|
||||
* path/vts_01_1.vob
|
||||
*
|
||||
* @param path Specifies the the device, file or directory to be used.
|
||||
* @param stream is a private handle used by stream_cb
|
||||
* @param stream_cb is a struct containing seek and read functions
|
||||
* @return If successful a a read handle is returned. Otherwise 0 is returned.
|
||||
*
|
||||
* dvd = DVDOpen(path);
|
||||
* dvd = DVDOpenStream(stream, &stream_cb);
|
||||
*/
|
||||
dvd_reader_t *DVDOpen( const char * );
|
||||
dvd_reader_t *DVDOpenStream( void *, dvd_reader_stream_cb * );
|
||||
|
||||
/**
|
||||
* Same as DVDOpen, but with private handle to be passed back on callbacks
|
||||
*
|
||||
* @param path Specifies the the device, file or directory to be used.
|
||||
* @param priv is a private handle
|
||||
* @param logcb is a custom logger callback struct, or NULL if none needed
|
||||
* @param stream_cb is a struct containing seek and read functions
|
||||
* @return If successful a a read handle is returned. Otherwise 0 is returned.
|
||||
*
|
||||
* dvd = DVDOpen2(priv, logcb, path);
|
||||
* dvd = DVDOpenStream2(priv, logcb, &stream_cb);
|
||||
*/
|
||||
dvd_reader_t *DVDOpen2( void *, const dvd_logger_cb *, const char * );
|
||||
dvd_reader_t *DVDOpenStream2( void *, const dvd_logger_cb *, dvd_reader_stream_cb * );
|
||||
|
||||
/**
|
||||
* Closes and cleans up the DVD reader object.
|
||||
*
|
||||
* You must close all open files before calling this function.
|
||||
*
|
||||
* @param dvd A read handle that should be closed.
|
||||
*
|
||||
* DVDClose(dvd);
|
||||
*/
|
||||
void DVDClose( dvd_reader_t * );
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
typedef enum {
|
||||
DVD_READ_INFO_FILE, /**< VIDEO_TS.IFO or VTS_XX_0.IFO (title) */
|
||||
DVD_READ_INFO_BACKUP_FILE, /**< VIDEO_TS.BUP or VTS_XX_0.BUP (title) */
|
||||
DVD_READ_MENU_VOBS, /**< VIDEO_TS.VOB or VTS_XX_0.VOB (title) */
|
||||
DVD_READ_TITLE_VOBS /**< VTS_XX_[1-9].VOB (title). All files in
|
||||
the title set are opened and read as a
|
||||
single file. */
|
||||
} dvd_read_domain_t;
|
||||
|
||||
/**
|
||||
* Stats a file on the DVD given the title number and domain.
|
||||
* The information about the file is stored in a dvd_stat_t
|
||||
* which contains information about the size of the file and
|
||||
* the number of parts in case of a multipart file and the respective
|
||||
* sizes of the parts.
|
||||
* A multipart file is for instance VTS_02_1.VOB, VTS_02_2.VOB, VTS_02_3.VOB
|
||||
* The size of VTS_02_1.VOB will be stored in stat->parts_size[0],
|
||||
* VTS_02_2.VOB in stat->parts_size[1], ...
|
||||
* The total size (sum of all parts) is stored in stat->size and
|
||||
* stat->nr_parts will hold the number of parts.
|
||||
* Only DVD_READ_TITLE_VOBS (VTS_??_[1-9].VOB) can be multipart files.
|
||||
*
|
||||
* This function is only of use if you want to get the size of each file
|
||||
* in the filesystem. These sizes are not needed to use any other
|
||||
* functions in libdvdread.
|
||||
*
|
||||
* @param dvd A dvd read handle.
|
||||
* @param titlenum Which Video Title Set should be used, VIDEO_TS is 0.
|
||||
* @param domain Which domain.
|
||||
* @param stat Pointer to where the result is stored.
|
||||
* @return If successful 0, otherwise -1.
|
||||
*
|
||||
* int DVDFileStat(dvd, titlenum, domain, stat);
|
||||
*/
|
||||
int DVDFileStat(dvd_reader_t *, int, dvd_read_domain_t, dvd_stat_t *);
|
||||
|
||||
/**
|
||||
* Opens a file on the DVD given the title number and domain.
|
||||
*
|
||||
* If the title number is 0, the video manager information is opened
|
||||
* (VIDEO_TS.[IFO,BUP,VOB]). Returns a file structure which may be
|
||||
* used for reads, or 0 if the file was not found.
|
||||
*
|
||||
* @param dvd A dvd read handle.
|
||||
* @param titlenum Which Video Title Set should be used, VIDEO_TS is 0.
|
||||
* @param domain Which domain.
|
||||
* @return If successful a a file read handle is returned, otherwise 0.
|
||||
*
|
||||
* dvd_file = DVDOpenFile(dvd, titlenum, domain); */
|
||||
dvd_file_t *DVDOpenFile( dvd_reader_t *, int, dvd_read_domain_t );
|
||||
|
||||
/**
|
||||
* Closes a file and frees the associated structure.
|
||||
*
|
||||
* @param dvd_file The file read handle to be closed.
|
||||
*
|
||||
* DVDCloseFile(dvd_file);
|
||||
*/
|
||||
void DVDCloseFile( dvd_file_t * );
|
||||
|
||||
/**
|
||||
* Reads block_count number of blocks from the file at the given block offset.
|
||||
* Returns number of blocks read on success, -1 on error. This call is only
|
||||
* for reading VOB data, and should not be used when reading the IFO files.
|
||||
* When reading from an encrypted drive, blocks are decrypted using libdvdcss
|
||||
* where required.
|
||||
*
|
||||
* @param dvd_file A file read handle.
|
||||
* @param offset Block offset from the start of the file to start reading at.
|
||||
* @param block_count Number of block to read.
|
||||
* @param data Pointer to a buffer to write the data into.
|
||||
* @return Returns number of blocks read on success, -1 on error.
|
||||
*
|
||||
* blocks_read = DVDReadBlocks(dvd_file, offset, block_count, data);
|
||||
*/
|
||||
ssize_t DVDReadBlocks( dvd_file_t *, int, size_t, unsigned char * );
|
||||
|
||||
/**
|
||||
* Seek to the given position in the file. Returns the resulting position in
|
||||
* bytes from the beginning of the file. The seek position is only used for
|
||||
* byte reads from the file, the block read call always reads from the given
|
||||
* offset.
|
||||
*
|
||||
* @param dvd_file A file read handle.
|
||||
* @param seek_offset Byte offset from the start of the file to seek to.
|
||||
* @return The resulting position in bytes from the beginning of the file.
|
||||
*
|
||||
* offset_set = DVDFileSeek(dvd_file, seek_offset);
|
||||
*/
|
||||
int32_t DVDFileSeek( dvd_file_t *, int32_t );
|
||||
|
||||
/**
|
||||
* Reads the given number of bytes from the file. This call can only be used
|
||||
* on the information files, and may not be used for reading from a VOB. This
|
||||
* reads from and increments the currrent seek position for the file.
|
||||
*
|
||||
* @param dvd_file A file read handle.
|
||||
* @param data Pointer to a buffer to write the data into.
|
||||
* @param bytes Number of bytes to read.
|
||||
* @return Returns number of bytes read on success, -1 on error.
|
||||
*
|
||||
* bytes_read = DVDReadBytes(dvd_file, data, bytes);
|
||||
*/
|
||||
ssize_t DVDReadBytes( dvd_file_t *, void *, size_t );
|
||||
|
||||
/**
|
||||
* Returns the file size in blocks.
|
||||
*
|
||||
* @param dvd_file A file read handle.
|
||||
* @return The size of the file in blocks, -1 on error.
|
||||
*
|
||||
* blocks = DVDFileSize(dvd_file);
|
||||
*/
|
||||
ssize_t DVDFileSize( dvd_file_t * );
|
||||
|
||||
/**
|
||||
* Get a unique 128 bit disc ID.
|
||||
* This is the MD5 sum of VIDEO_TS.IFO and the VTS_0?_0.IFO files
|
||||
* in title order (those that exist).
|
||||
* If you need a 'text' representation of the id, print it as a
|
||||
* hexadecimal number, using lowercase letters, discid[0] first.
|
||||
* I.e. the same format as the command-line 'md5sum' program uses.
|
||||
*
|
||||
* @param dvd A read handle to get the disc ID from
|
||||
* @param discid The buffer to put the disc ID into. The buffer must
|
||||
* have room for 128 bits (16 chars).
|
||||
* @return 0 on success, -1 on error.
|
||||
*/
|
||||
int DVDDiscID( dvd_reader_t *, unsigned char * );
|
||||
|
||||
/**
|
||||
* Get the UDF VolumeIdentifier and VolumeSetIdentifier
|
||||
* from the PrimaryVolumeDescriptor.
|
||||
*
|
||||
* @param dvd A read handle to get the disc ID from
|
||||
* @param volid The buffer to put the VolumeIdentifier into.
|
||||
* The VolumeIdentifier is latin-1 encoded (8bit unicode)
|
||||
* null terminated and max 32 bytes (including '\0')
|
||||
* @param volid_size No more than volid_size bytes will be copied to volid.
|
||||
* If the VolumeIdentifier is truncated because of this
|
||||
* it will still be null terminated.
|
||||
* @param volsetid The buffer to put the VolumeSetIdentifier into.
|
||||
* The VolumeIdentifier is 128 bytes as
|
||||
* stored in the UDF PrimaryVolumeDescriptor.
|
||||
* Note that this is not a null terminated string.
|
||||
* @param volsetid_size At most volsetid_size bytes will be copied to volsetid.
|
||||
* @return 0 on success, -1 on error.
|
||||
*/
|
||||
int DVDUDFVolumeInfo( dvd_reader_t *, char *, unsigned int,
|
||||
unsigned char *, unsigned int );
|
||||
|
||||
int DVDFileSeekForce( dvd_file_t *, int offset, int force_size);
|
||||
|
||||
/**
|
||||
* Get the ISO9660 VolumeIdentifier and VolumeSetIdentifier
|
||||
*
|
||||
* * Only use this function as fallback if DVDUDFVolumeInfo returns 0 *
|
||||
* * this will happen on a disc mastered only with a iso9660 filesystem *
|
||||
* * All video DVD discs have UDF filesystem *
|
||||
*
|
||||
* @param dvd A read handle to get the disc ID from
|
||||
* @param volid The buffer to put the VolumeIdentifier into.
|
||||
* The VolumeIdentifier is coded with '0-9','A-Z','_'
|
||||
* null terminated and max 33 bytes (including '\0')
|
||||
* @param volid_size No more than volid_size bytes will be copied to volid.
|
||||
* If the VolumeIdentifier is truncated because of this
|
||||
* it will still be null terminated.
|
||||
* @param volsetid The buffer to put the VolumeSetIdentifier into.
|
||||
* The VolumeIdentifier is 128 bytes as
|
||||
* stored in the ISO9660 PrimaryVolumeDescriptor.
|
||||
* Note that this is not a null terminated string.
|
||||
* @param volsetid_size At most volsetid_size bytes will be copied to volsetid.
|
||||
* @return 0 on success, -1 on error.
|
||||
*/
|
||||
int DVDISOVolumeInfo( dvd_reader_t *, char *, unsigned int,
|
||||
unsigned char *, unsigned int );
|
||||
|
||||
/**
|
||||
* Sets the level of caching that is done when reading from a device
|
||||
*
|
||||
* @param dvd A read handle to get the disc ID from
|
||||
* @param level The level of caching wanted.
|
||||
* -1 - returns the current setting.
|
||||
* 0 - UDF Cache turned off.
|
||||
* 1 - (default level) Pointers to IFO files and some data from
|
||||
* PrimaryVolumeDescriptor are cached.
|
||||
*
|
||||
* @return The level of caching.
|
||||
*/
|
||||
int DVDUDFCacheLevel( dvd_reader_t *, int );
|
||||
|
|
164
dvd_ripper.py
164
dvd_ripper.py
|
@ -1,65 +1,99 @@
|
|||
import cffi
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
from dvdnav import DVDNav,DVDError
|
||||
from dvdread import DVDRead
|
||||
import subprocess as SP
|
||||
import json
|
||||
from glob import glob
|
||||
import itertools as ITT
|
||||
from vob_demux import demux
|
||||
from ff_d2v import make_d2v
|
||||
|
||||
def loadlib(dll_path, *includes, **kwargs):
|
||||
ffi = cffi.FFI()
|
||||
for include in includes:
|
||||
ffi.cdef(open(include).read(), kwargs)
|
||||
return ffi, ffi.dlopen(dll_path)
|
||||
|
||||
for dvd_path in ITT.chain.from_iterable(map(glob,sys.argv[1:])):
|
||||
r = DVDRead(dvd_path)
|
||||
# r.grab_ifos()
|
||||
# r.grab_vobs()
|
||||
# exit()
|
||||
|
||||
out_folder = os.path.join(
|
||||
"out", "_".join([r.disc_id, r.udf_disc_name or r.iso_disc_name]).replace(" ", "_")
|
||||
)
|
||||
os.makedirs(out_folder, exist_ok=True)
|
||||
d = DVDNav(dvd_path)
|
||||
to_demux = []
|
||||
for k, v in d.titles.items():
|
||||
v["duration"] = v["duration"].total_seconds()
|
||||
v["chapters"] = [c.total_seconds() for c in v["chapters"]]
|
||||
d.titles[k] = v
|
||||
with open(os.path.join(out_folder, f"{k:03}.json"), "w") as fh:
|
||||
json.dump(v, fh)
|
||||
for a in range(0,99):
|
||||
block=0
|
||||
outfile = os.path.join(out_folder, f"t{k:03}_a{a:03}_b{block:03}.vob")
|
||||
to_demux.append(outfile)
|
||||
fh = open(outfile, "wb")
|
||||
try:
|
||||
for block in d.get_blocks(k, a):
|
||||
if isinstance(block, int):
|
||||
outfile = os.path.join(out_folder, f"t{k:03}_a{a:03}_b{block:03}.vob")
|
||||
to_demux.append(outfile)
|
||||
if fh:
|
||||
fh.close()
|
||||
fh = open(outfile, "wb")
|
||||
else:
|
||||
fh.write(block)
|
||||
except DVDError as e:
|
||||
if str(e)!="Invalid angle specified!":
|
||||
raise
|
||||
if fh.tell()==0:
|
||||
fh.close()
|
||||
os.unlink(fh.name)
|
||||
while fh.name in to_demux:
|
||||
to_demux.remove(fh.name)
|
||||
for file in to_demux:
|
||||
demux(file)
|
||||
os.unlink(file)
|
||||
for file in glob(os.path.join(out_folder,"*.m2v")):
|
||||
make_d2v(file)
|
||||
import itertools as ITT
|
||||
import json
|
||||
import os
|
||||
import subprocess as SP
|
||||
import sys
|
||||
import time
|
||||
from glob import glob
|
||||
|
||||
import cffi
|
||||
from datetime import timedelta
|
||||
from dvdnav import DVDError, DVDNav
|
||||
from dvdread import DVDRead
|
||||
from ff_d2v import make_d2v, make_meta
|
||||
from vob_demux import demux
|
||||
|
||||
def close_file_del_if_empty(fh):
|
||||
if not fh:
|
||||
return False
|
||||
if fh.tell() == 0:
|
||||
fh.close()
|
||||
os.unlink(fh.name)
|
||||
return False
|
||||
else:
|
||||
fh.close()
|
||||
return True
|
||||
|
||||
|
||||
dur_thr = 60.0
|
||||
|
||||
def process_m2v_files(path):
|
||||
for file in glob(os.path.join(path,"**", "*.m2v")):
|
||||
make_meta(file)
|
||||
make_d2v(file)
|
||||
|
||||
|
||||
for dvd_path in ITT.chain.from_iterable(map(glob, sys.argv[1:])):
|
||||
r = DVDRead(dvd_path)
|
||||
# r.grab_ifos()
|
||||
# r.grab_vobs()
|
||||
# exit()
|
||||
if os.path.isfile(dvd_path):
|
||||
basename = os.path.splitext(os.path.basename(dvd_path))[0]
|
||||
else:
|
||||
basename = r.iso_disc_name or r.udf_disc_name
|
||||
base_dir = os.path.join("out", "_".join([basename, r.disc_id]).replace(" ", "_"))
|
||||
if os.path.isdir(base_dir):
|
||||
print(f"Output foldrer {base_dir} exists, remove to re-rip DVD")
|
||||
process_m2v_files(base_dir)
|
||||
continue
|
||||
os.makedirs(base_dir, exist_ok=True)
|
||||
d = DVDNav(dvd_path)
|
||||
to_demux = []
|
||||
for k, v in d.titles.items():
|
||||
out_folder=os.path.join(base_dir,f"t{k:03}")
|
||||
v["duration"] = v["duration"].total_seconds()
|
||||
if v["chapters"]:
|
||||
v["chapters"] = [0.0]+[c.total_seconds() for c in v["chapters"]]
|
||||
avg_chapter_len = v["duration"] / len(v["chapters"])
|
||||
# if avg_chapter_len<10:
|
||||
# continue
|
||||
d.titles[k] = v
|
||||
# if not v.get('audio'):
|
||||
# print(f"[{k}|0] Skipping title {k} because it has no audio tracks")
|
||||
# continue
|
||||
# if not v.get('vts'):
|
||||
# print(f"[{k}|0] Skipping title {k} because it has no title sets")
|
||||
# continue
|
||||
if v["duration"] < dur_thr:
|
||||
print(
|
||||
f"[{k}|0] Skipping title {k} because it is shorter than {dur_thr} seconds ({v['duration']} seconds)"
|
||||
)
|
||||
continue
|
||||
os.makedirs(out_folder, exist_ok=True)
|
||||
with open(os.path.join(out_folder, f"title.json"), "w") as fh:
|
||||
json.dump(d.titles[k], fh, indent=4)
|
||||
with open(os.path.join(out_folder, f"chapters.txt"), "w") as fh:
|
||||
if set(v["chapters"])==set([0.0]):
|
||||
continue
|
||||
for n,t in enumerate(v["chapters"],1):
|
||||
if abs(t-v["duration"])<1.0:
|
||||
continue
|
||||
print(f"CHAPTER{n:02}={timedelta(seconds=t)}",file=fh)
|
||||
print(f"CHAPTER{n:02}NAME=Chapter {n}",file=fh)
|
||||
for a in range(0, 99):
|
||||
outfile = os.path.join(out_folder, f"{a:03}.vob")
|
||||
to_demux.append(outfile)
|
||||
fh = open(outfile, "wb")
|
||||
try:
|
||||
for block in d.get_blocks(k, a):
|
||||
fh.write(block)
|
||||
except DVDError as e:
|
||||
if str(e) != "Invalid angle specified!":
|
||||
raise
|
||||
close_file_del_if_empty(fh)
|
||||
to_demux = list(filter(os.path.isfile, to_demux))
|
||||
for file in to_demux:
|
||||
demux(file)
|
||||
os.unlink(file)
|
||||
process_m2v_files(base_dir)
|
||||
|
|
18
dvdcss.h
18
dvdcss.h
|
@ -1,10 +1,10 @@
|
|||
typedef struct dvdcss_s * dvdcss_t;
|
||||
typedef struct dvdcss_stream_cb dvdcss_stream_cb;
|
||||
dvdcss_t dvdcss_open (const char *psz_target);
|
||||
dvdcss_t dvdcss_open_stream (void *p_stream, dvdcss_stream_cb *p_stream_cb);
|
||||
int dvdcss_close (dvdcss_t);
|
||||
int dvdcss_seek (dvdcss_t, int i_blocks, int i_flags);
|
||||
int dvdcss_read (dvdcss_t, void *p_buffer, int i_blocks, int i_flags);
|
||||
int dvdcss_readv(dvdcss_t, void *p_iovec, int i_blocks, int i_flags);
|
||||
const char* dvdcss_error (const dvdcss_t);
|
||||
typedef struct dvdcss_s * dvdcss_t;
|
||||
typedef struct dvdcss_stream_cb dvdcss_stream_cb;
|
||||
dvdcss_t dvdcss_open (const char *psz_target);
|
||||
dvdcss_t dvdcss_open_stream (void *p_stream, dvdcss_stream_cb *p_stream_cb);
|
||||
int dvdcss_close (dvdcss_t);
|
||||
int dvdcss_seek (dvdcss_t, int i_blocks, int i_flags);
|
||||
int dvdcss_read (dvdcss_t, void *p_buffer, int i_blocks, int i_flags);
|
||||
int dvdcss_readv(dvdcss_t, void *p_iovec, int i_blocks, int i_flags);
|
||||
const char* dvdcss_error (const dvdcss_t);
|
||||
int dvdcss_is_scrambled (dvdcss_t);
|
585
dvdnav.py
585
dvdnav.py
|
@ -1,265 +1,320 @@
|
|||
import cffi
|
||||
import os
|
||||
import functools
|
||||
from datetime import timedelta
|
||||
from tqdm import tqdm
|
||||
from dvdread import DVDRead
|
||||
|
||||
|
||||
def loadlib(dll_path, *includes, **kwargs):
|
||||
ffi = cffi.FFI()
|
||||
for include in includes:
|
||||
ffi.cdef(open(include).read(), kwargs)
|
||||
return ffi, ffi.dlopen(dll_path)
|
||||
|
||||
|
||||
class DVDError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class DVDNav(object):
|
||||
def __init__(self, path, verbose=None, method="disc"):
|
||||
if verbose is None:
|
||||
os.environ.pop("DVDCSS_VERBOSE", None)
|
||||
else:
|
||||
os.environ["DVDCSS_VERBOSE"] = str(verbose)
|
||||
os.environ["DVDCSS_METHOD"] = method
|
||||
self.dvd = None
|
||||
self.ffi, self.lib = loadlib(
|
||||
"libdvdnav-4.dll",
|
||||
"dvd_types.h",
|
||||
"dvd_reader.h",
|
||||
"ifo_types.h",
|
||||
"nav_types.h",
|
||||
"dvdnav_events.h",
|
||||
"dvdnav.h",
|
||||
pack=True,
|
||||
)
|
||||
self.path = path
|
||||
self.titles = {}
|
||||
self.open(path)
|
||||
|
||||
def __del__(self):
|
||||
self.__check_error(self.lib.dvdnav_close(self.dvd))
|
||||
self.dvd = None
|
||||
|
||||
def __repr__(self):
|
||||
return "<DVD Path={0.path} Title={0.title} Serial={0.serial}".format(self)
|
||||
|
||||
def get_blocks(self, title, angle=1, slang=None):
|
||||
self.__check_error(self.lib.dvdnav_set_PGC_positioning_flag(self.dvd, 1))
|
||||
self.__check_error(self.lib.dvdnav_title_play(self.dvd, title))
|
||||
curr_angle = self.ffi.new("int32_t*", 0)
|
||||
num_angles = self.ffi.new("int32_t*", 0)
|
||||
self.__check_error(
|
||||
self.lib.dvdnav_get_angle_info(self.dvd, curr_angle, num_angles)
|
||||
)
|
||||
if angle != 0:
|
||||
if angle < 1 or angle > num_angles[0]:
|
||||
raise DVDError("Invalid angle specified!")
|
||||
if angle != curr_angle[0]:
|
||||
self.__check_error(self.lib.dvdnav_angle_change(self.dvd, angle))
|
||||
if slang is not None:
|
||||
self.__check_error(self.lib.dvdnav_spu_language_select(self.dvd, slang))
|
||||
event = self.lib.DVDNAV_NOP
|
||||
buf = self.ffi.new("char[]", 4096)
|
||||
ev = self.ffi.new("int32_t*", self.lib.DVDNAV_NOP)
|
||||
size = self.ffi.new("int32_t*", 0)
|
||||
pos = self.ffi.new("uint32_t*", 0)
|
||||
total_size = self.ffi.new("uint32_t*", 0)
|
||||
domains = {
|
||||
1: "FirstPlay",
|
||||
2: "VTSTitle",
|
||||
4: "VMGM",
|
||||
8: "VTSMenu",
|
||||
}
|
||||
events = {
|
||||
0: "DVDNAV_BLOCK_OK",
|
||||
1: "DVDNAV_NOP",
|
||||
2: "DVDNAV_STILL_FRAME",
|
||||
3: "DVDNAV_SPU_STREAM_CHANGE",
|
||||
4: "DVDNAV_AUDIO_STREAM_CHANGE",
|
||||
5: "DVDNAV_VTS_CHANGE",
|
||||
6: "DVDNAV_CELL_CHANGE",
|
||||
7: "DVDNAV_NAV_PACKET",
|
||||
8: "DVDNAV_STOP",
|
||||
9: "DVDNAV_HIGHLIGHT",
|
||||
10: "DVDNAV_SPU_CLUT_CHANGE",
|
||||
12: "DVDNAV_HOP_CHANNEL",
|
||||
13: "DVDNAV_WAIT",
|
||||
}
|
||||
progbar = tqdm(
|
||||
unit_divisor=1024,
|
||||
unit_scale=True,
|
||||
unit="iB",
|
||||
desc="Ripping DVD",
|
||||
disable=False,
|
||||
)
|
||||
ripped = set()
|
||||
current_vts = None
|
||||
current_cell = None
|
||||
current_pg = None
|
||||
while True:
|
||||
self.__check_error(self.lib.dvdnav_get_next_block(self.dvd, buf, ev, size))
|
||||
if (
|
||||
self.lib.dvdnav_get_position(self.dvd, pos, total_size)
|
||||
== self.lib.DVDNAV_STATUS_OK
|
||||
):
|
||||
progbar.total = total_size[0] * 2048
|
||||
progbar.n = max(progbar.n, min(progbar.total, pos[0] * 2048))
|
||||
progbar.update(0)
|
||||
progbar.set_postfix(
|
||||
vts=current_vts,
|
||||
cell=current_cell,
|
||||
pg=current_pg,
|
||||
angle=angle,
|
||||
title=title,
|
||||
)
|
||||
# print("Got event:",events.get(ev[0],ev[0]),size[0])
|
||||
if ev[0] in [
|
||||
self.lib.DVDNAV_SPU_CLUT_CHANGE,
|
||||
self.lib.DVDNAV_HOP_CHANNEL,
|
||||
self.lib.DVDNAV_NOP,
|
||||
self.lib.DVDNAV_HIGHLIGHT,
|
||||
]:
|
||||
continue
|
||||
elif ev[0] == self.lib.DVDNAV_BLOCK_OK:
|
||||
yield self.ffi.buffer(buf, size[0])[:]
|
||||
elif ev[0] == self.lib.DVDNAV_STOP:
|
||||
progbar.write(f"[{title}|{angle}] Stop")
|
||||
break
|
||||
elif ev[0] == self.lib.DVDNAV_NAV_PACKET:
|
||||
pass
|
||||
elif ev[0] == self.lib.DVDNAV_STILL_FRAME:
|
||||
self.__check_error(self.lib.dvdnav_still_skip(self.dvd))
|
||||
elif ev[0] == self.lib.DVDNAV_WAIT:
|
||||
self.__check_error(self.lib.dvdnav_wait_skip(self.dvd))
|
||||
elif ev[0] == self.lib.DVDNAV_SPU_STREAM_CHANGE:
|
||||
pass
|
||||
elif ev[0] == self.lib.DVDNAV_AUDIO_STREAM_CHANGE:
|
||||
audio = self.ffi.cast("dvdnav_audio_stream_change_event_t*", buf)
|
||||
elif ev[0] == self.lib.DVDNAV_CELL_CHANGE:
|
||||
cell = self.ffi.cast("dvdnav_cell_change_event_t*", buf)
|
||||
current_cell = cell.cellN
|
||||
current_pg = cell.pgN
|
||||
progbar.write(
|
||||
f"[{title}|{angle}] Cell: {cell.cellN} ({cell.cell_start}-{cell.cell_start+cell.cell_length}), PG: {cell.pgN} ({cell.pg_start}-{cell.pg_start+cell.pg_length})"
|
||||
)
|
||||
elif ev[0] == self.lib.DVDNAV_VTS_CHANGE:
|
||||
vts = self.ffi.cast("dvdnav_vts_change_event_t*", buf)
|
||||
new_vts = (vts.new_vtsN, vts.new_domain)
|
||||
ripped.add((vts.old_vtsN, vts.old_domain))
|
||||
# progbar.write(f"[{title}|{angle}] VTS: {vts.old_vtsN} ({vts.old_domain} {old_domain}) -> {vts.new_vtsN} ({vts.new_domain} {new_domain})")
|
||||
if new_vts in ripped: # looped
|
||||
progbar.write(f"[{title}|{angle}] Looped!")
|
||||
break
|
||||
current_vts = (vts.new_vtsN, vts.new_domain)
|
||||
if vts.new_domain == 8: # back to menu
|
||||
progbar.write(f"[{title}|{angle}] Back to menu!")
|
||||
break
|
||||
yield vts.new_vtsN
|
||||
else:
|
||||
progbar.write(
|
||||
f"[{title}|{angle}] Unhandled: {events.get(ev[0],ev[0])} {size[0]}"
|
||||
)
|
||||
self.__check_error(self.lib.dvdnav_stop(self.dvd))
|
||||
|
||||
def __check_error(self, ret):
|
||||
if ret == self.lib.DVDNAV_STATUS_ERR:
|
||||
if self.dvd:
|
||||
err = self.ffi.string(self.lib.dvdnav_err_to_string(self.dvd))
|
||||
raise DVDError(err)
|
||||
raise DVDError("Unknown error")
|
||||
|
||||
def __get_titles(self):
|
||||
titles = self.ffi.new("int32_t*", 0)
|
||||
p_times = self.ffi.new("uint64_t[]", 512)
|
||||
times = self.ffi.new("uint64_t**", p_times)
|
||||
duration = self.ffi.new("uint64_t*", 0)
|
||||
titles = self.ffi.new("int32_t*", 0)
|
||||
self.lib.dvdnav_get_number_of_titles(self.dvd, titles)
|
||||
num_titles = titles[0]
|
||||
for title in range(0, num_titles + 1):
|
||||
if self.lib.dvdnav_get_number_of_parts(self.dvd, title, titles) == 0:
|
||||
continue
|
||||
num_parts = titles[0]
|
||||
self.lib.dvdnav_get_number_of_angles(self.dvd, title, titles)
|
||||
num_angles = titles[0]
|
||||
num_chapters = self.lib.dvdnav_describe_title_chapters(
|
||||
self.dvd, title, times, duration
|
||||
)
|
||||
if duration[0] == 0:
|
||||
continue
|
||||
chapters = []
|
||||
for t in range(num_chapters):
|
||||
chapters.append(timedelta(seconds=times[0][t] / 90000))
|
||||
self.titles[title] = {
|
||||
"parts": num_parts,
|
||||
"angles": num_angles,
|
||||
"duration": timedelta(seconds=duration[0] / 90000),
|
||||
"chapters": chapters,
|
||||
}
|
||||
|
||||
def __get_info(self):
|
||||
s = self.ffi.new("char**", self.ffi.NULL)
|
||||
self.lib.dvdnav_get_title_string(self.dvd, s)
|
||||
self.title = str(self.ffi.string(s[0]), "utf8").strip() or None
|
||||
self.lib.dvdnav_get_serial_string(self.dvd, s)
|
||||
self.serial = str(self.ffi.string(s[0]), "utf8").strip() or None
|
||||
self.__get_titles()
|
||||
|
||||
def open(self, path):
|
||||
audio_attrs = self.ffi.new("audio_attr_t*")
|
||||
spu_attr = self.ffi.new("subp_attr_t*")
|
||||
dvdnav = self.ffi.new("dvdnav_t**", self.ffi.cast("dvdnav_t*", 0))
|
||||
self.__check_error(self.lib.dvdnav_open(dvdnav, bytes(path, "utf8")))
|
||||
self.dvd = dvdnav[0]
|
||||
self.__check_error(self.lib.dvdnav_set_readahead_flag(self.dvd, 1))
|
||||
self.__get_info()
|
||||
for title in self.titles:
|
||||
self.__check_error(self.lib.dvdnav_title_play(self.dvd, title))
|
||||
self.titles[title]["audio"] = {}
|
||||
self.titles[title]["subtitles"] = {}
|
||||
for n in range(255):
|
||||
stream_id = self.lib.dvdnav_get_audio_logical_stream(self.dvd, n)
|
||||
if stream_id == -1:
|
||||
continue
|
||||
self.__check_error(
|
||||
self.lib.dvdnav_get_audio_attr(self.dvd, stream_id, audio_attrs)
|
||||
)
|
||||
alang = None
|
||||
if audio_attrs.lang_type:
|
||||
alang = str(audio_attrs.lang_code.to_bytes(2, "big"), "utf8")
|
||||
channels = audio_attrs.channels + 1
|
||||
codec = {0: "ac3", 2: "mpeg1", 3: "mpeg-2ext", 4: "lpcm", 6: "dts"}[
|
||||
audio_attrs.audio_format
|
||||
]
|
||||
audio_type = {
|
||||
0: None,
|
||||
1: "normal",
|
||||
2: "descriptive",
|
||||
3: "director's commentary",
|
||||
4: "alternate director's commentary",
|
||||
}[audio_attrs.code_extension]
|
||||
self.titles[title]["audio"][n] = {
|
||||
"stream_id": stream_id,
|
||||
"lang": alang,
|
||||
"channels": channels,
|
||||
"codec": codec,
|
||||
"type": audio_type,
|
||||
}
|
||||
for n in range(255):
|
||||
stream_id = self.lib.dvdnav_get_spu_logical_stream(self.dvd, n)
|
||||
if stream_id == -1:
|
||||
continue
|
||||
self.__check_error(
|
||||
self.lib.dvdnav_get_spu_attr(self.dvd, stream_id, spu_attr)
|
||||
)
|
||||
slang = None
|
||||
if spu_attr.type == 1:
|
||||
slang = str(spu_attr.lang_code.to_bytes(2, "big"), "utf8")
|
||||
self.titles[title]["subtitles"][n] = {
|
||||
"stream_id": stream_id,
|
||||
"lang": slang,
|
||||
}
|
||||
self.__check_error(self.lib.dvdnav_stop(self.dvd))
|
||||
import functools
|
||||
import os
|
||||
from datetime import timedelta
|
||||
|
||||
import cffi
|
||||
from tqdm import tqdm
|
||||
|
||||
from dvdread import DVDRead
|
||||
|
||||
|
||||
def loadlib(dll_path, *includes, **kwargs):
|
||||
ffi = cffi.FFI()
|
||||
for include in includes:
|
||||
ffi.cdef(open(include).read(), kwargs)
|
||||
return ffi, ffi.dlopen(dll_path)
|
||||
|
||||
|
||||
domains = {
|
||||
0: "None",
|
||||
1: "FirstPlay",
|
||||
2: "VTSTitle",
|
||||
4: "VMGM",
|
||||
8: "VTSMenu",
|
||||
}
|
||||
events = {
|
||||
0: "DVDNAV_BLOCK_OK",
|
||||
1: "DVDNAV_NOP",
|
||||
2: "DVDNAV_STILL_FRAME",
|
||||
3: "DVDNAV_SPU_STREAM_CHANGE",
|
||||
4: "DVDNAV_AUDIO_STREAM_CHANGE",
|
||||
5: "DVDNAV_VTS_CHANGE",
|
||||
6: "DVDNAV_CELL_CHANGE",
|
||||
7: "DVDNAV_NAV_PACKET",
|
||||
8: "DVDNAV_STOP",
|
||||
9: "DVDNAV_HIGHLIGHT",
|
||||
10: "DVDNAV_SPU_CLUT_CHANGE",
|
||||
12: "DVDNAV_HOP_CHANNEL",
|
||||
13: "DVDNAV_WAIT",
|
||||
}
|
||||
|
||||
class DVDError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class DVDNav(object):
|
||||
def __init__(self, path, verbose=None, method="disc"):
|
||||
if verbose is None:
|
||||
os.environ.pop("DVDCSS_VERBOSE", None)
|
||||
else:
|
||||
os.environ["DVDCSS_VERBOSE"] = str(verbose)
|
||||
os.environ["DVDCSS_METHOD"] = method
|
||||
self.dvd = None
|
||||
self.ffi, self.lib = loadlib(
|
||||
"libdvdnav-4.dll",
|
||||
"dvd_types.h",
|
||||
"dvd_reader.h",
|
||||
"ifo_types.h",
|
||||
"nav_types.h",
|
||||
"dvdnav_events.h",
|
||||
"dvdnav.h",
|
||||
pack=True,
|
||||
)
|
||||
self.path = path
|
||||
self.titles = {}
|
||||
self.open(path)
|
||||
|
||||
def __del__(self):
|
||||
self.__check_error(self.lib.dvdnav_close(self.dvd))
|
||||
self.dvd = None
|
||||
|
||||
def __repr__(self):
|
||||
return "<DVD Path={0.path} Title={0.title} Serial={0.serial}".format(self)
|
||||
|
||||
def get_blocks(self, title, angle=1, slang=None):
|
||||
self.__check_error(self.lib.dvdnav_set_PGC_positioning_flag(self.dvd, 1))
|
||||
self.__check_error(self.lib.dvdnav_title_play(self.dvd, title))
|
||||
curr_angle = self.ffi.new("int32_t*", 0)
|
||||
num_angles = self.ffi.new("int32_t*", 0)
|
||||
self.__check_error(
|
||||
self.lib.dvdnav_get_angle_info(self.dvd, curr_angle, num_angles)
|
||||
)
|
||||
if angle != 0:
|
||||
if angle < 1 or angle > num_angles[0]:
|
||||
raise DVDError("Invalid angle specified!")
|
||||
if angle != curr_angle[0]:
|
||||
self.__check_error(self.lib.dvdnav_angle_change(self.dvd, angle))
|
||||
if slang is not None:
|
||||
self.__check_error(self.lib.dvdnav_spu_language_select(self.dvd, slang))
|
||||
event = self.lib.DVDNAV_NOP
|
||||
buf = self.ffi.new("char[]", 4096)
|
||||
ev = self.ffi.new("int32_t*", self.lib.DVDNAV_NOP)
|
||||
size = self.ffi.new("int32_t*", 0)
|
||||
pos = self.ffi.new("uint32_t*", 0)
|
||||
total_size = self.ffi.new("uint32_t*", 0)
|
||||
progbar = tqdm(
|
||||
unit_divisor=1024,
|
||||
unit_scale=True,
|
||||
unit="iB",
|
||||
desc="Ripping DVD",
|
||||
disable=False,
|
||||
)
|
||||
ripped = set()
|
||||
cells = set()
|
||||
current_vts = (None,None)
|
||||
current_cell = None
|
||||
current_pg = None
|
||||
while True:
|
||||
self.__check_error(self.lib.dvdnav_get_next_block(self.dvd, buf, ev, size))
|
||||
if (
|
||||
self.lib.dvdnav_get_position(self.dvd, pos, total_size)
|
||||
== self.lib.DVDNAV_STATUS_OK
|
||||
):
|
||||
progbar.total = total_size[0] * 2048
|
||||
progbar.n = max(progbar.n, min(progbar.total, pos[0] * 2048))
|
||||
progbar.update(0)
|
||||
progbar.set_postfix(
|
||||
vts=current_vts,
|
||||
cell=current_cell,
|
||||
pg=current_pg,
|
||||
angle=angle,
|
||||
title=title,
|
||||
)
|
||||
# print("Got event:",events.get(ev[0],ev[0]),size[0])
|
||||
if ev[0] in [
|
||||
self.lib.DVDNAV_SPU_CLUT_CHANGE,
|
||||
self.lib.DVDNAV_HOP_CHANNEL,
|
||||
self.lib.DVDNAV_NOP,
|
||||
self.lib.DVDNAV_HIGHLIGHT,
|
||||
]:
|
||||
continue
|
||||
elif ev[0] == self.lib.DVDNAV_BLOCK_OK:
|
||||
yield self.ffi.buffer(buf, size[0])[:]
|
||||
elif ev[0] == self.lib.DVDNAV_STOP:
|
||||
progbar.write(f"[{title}|{angle}] Stop")
|
||||
break
|
||||
elif ev[0] == self.lib.DVDNAV_NAV_PACKET:
|
||||
pass
|
||||
elif ev[0] == self.lib.DVDNAV_STILL_FRAME:
|
||||
self.__check_error(self.lib.dvdnav_still_skip(self.dvd))
|
||||
elif ev[0] == self.lib.DVDNAV_WAIT:
|
||||
self.__check_error(self.lib.dvdnav_wait_skip(self.dvd))
|
||||
elif ev[0] == self.lib.DVDNAV_SPU_STREAM_CHANGE:
|
||||
pass
|
||||
elif ev[0] == self.lib.DVDNAV_AUDIO_STREAM_CHANGE:
|
||||
audio = self.ffi.cast("dvdnav_audio_stream_change_event_t*", buf)
|
||||
elif ev[0] == self.lib.DVDNAV_CELL_CHANGE:
|
||||
cell = self.ffi.cast("dvdnav_cell_change_event_t*", buf)
|
||||
current_cell = cell.cellN
|
||||
current_pg = cell.pgN
|
||||
progbar.write(
|
||||
f"[{title}|{angle}] Cell: {cell.cellN} ({hex(cell.cell_start)}-{hex(cell.cell_start+cell.cell_length)}), PG: {cell.pgN} ({hex(cell.pg_start)}-{hex(cell.pg_start+cell.pg_length)})"
|
||||
)
|
||||
fp=(current_vts[0],current_vts[1],cell.cellN,cell.pgN,cell.cell_length,cell.pg_length,cell.pgc_length,cell.cell_start,cell.pg_start)
|
||||
if fp in cells:
|
||||
progbar.write(f"[{title}|{angle}] Cells Looped!")
|
||||
break
|
||||
cells.add(fp)
|
||||
elif ev[0] == self.lib.DVDNAV_VTS_CHANGE:
|
||||
vts = self.ffi.cast("dvdnav_vts_change_event_t*", buf)
|
||||
old_domain = domains[vts.old_domain]
|
||||
new_domain = domains[vts.new_domain]
|
||||
new_vts = (vts.new_vtsN, vts.new_domain)
|
||||
old_vts = (vts.old_vtsN, vts.old_domain)
|
||||
ripped.add((vts.old_vtsN, vts.old_domain))
|
||||
cells.clear()
|
||||
progbar.write(f"[{title}|{angle}] VTS: {vts.old_vtsN} ({vts.old_domain} {old_domain}) -> {vts.new_vtsN} ({vts.new_domain} {new_domain})")
|
||||
if (new_vts in ripped) or new_vts==old_vts: # looped
|
||||
progbar.write(f"[{title}|{angle}] VTS Looped!")
|
||||
break
|
||||
current_vts = (vts.new_vtsN, vts.new_domain)
|
||||
if vts.new_domain == 8: # back to menu
|
||||
progbar.write(f"[{title}|{angle}] VTS Back to menu!")
|
||||
break
|
||||
# yield vts.new_vtsN
|
||||
else:
|
||||
progbar.write(
|
||||
f"[{title}|{angle}] Unhandled: {events.get(ev[0],ev[0])} {size[0]}"
|
||||
)
|
||||
self.__check_error(self.lib.dvdnav_stop(self.dvd))
|
||||
progbar.close()
|
||||
|
||||
def __check_error(self, ret):
|
||||
if ret == self.lib.DVDNAV_STATUS_ERR:
|
||||
if self.dvd:
|
||||
err = self.ffi.string(self.lib.dvdnav_err_to_string(self.dvd))
|
||||
raise DVDError(err)
|
||||
raise DVDError("Unknown error |