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 symbols
|
||||||
cython_debug/
|
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
|
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)
|
# Setup (Windows)
|
||||||
|
|
||||||
1. Clone the repo
|
1. Clone the repo
|
||||||
2. `pip install cffi tqdm`
|
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`
|
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>,
|
* Copyright (C) 2001, 2002 Billy Biggs <vektor@dumbterm.net>,
|
||||||
* Håkan Hjort <d95hjort@dtek.chalmers.se>,
|
* Håkan Hjort <d95hjort@dtek.chalmers.se>,
|
||||||
* Björn Englund <d4bjorn@dtek.chalmers.se>
|
* Björn Englund <d4bjorn@dtek.chalmers.se>
|
||||||
*
|
*
|
||||||
* This file is part of libdvdread.
|
* This file is part of libdvdread.
|
||||||
*
|
*
|
||||||
* libdvdread is free software; you can redistribute it and/or modify
|
* libdvdread is free software; you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation; either version 2 of the License, or
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* libdvdread is distributed in the hope that it will be useful,
|
* libdvdread is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License along
|
* You should have received a copy of the GNU General Public License along
|
||||||
* with libdvdread; if not, write to the Free Software Foundation, Inc.,
|
* with libdvdread; if not, write to the Free Software Foundation, Inc.,
|
||||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The DVD access interface.
|
* The DVD access interface.
|
||||||
*
|
*
|
||||||
* This file contains the functions that form the interface to to
|
* This file contains the functions that form the interface to to
|
||||||
* reading files located on a DVD.
|
* reading files located on a DVD.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current version.
|
* The current version.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The length of one Logical Block of a DVD.
|
* The length of one Logical Block of a DVD.
|
||||||
*/
|
*/
|
||||||
#define DVD_VIDEO_LB_LEN 2048
|
#define DVD_VIDEO_LB_LEN 2048
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maximum length of filenames allowed in UDF.
|
* Maximum length of filenames allowed in UDF.
|
||||||
*/
|
*/
|
||||||
#define MAX_UDF_FILE_NAME_LEN 2048
|
#define MAX_UDF_FILE_NAME_LEN 2048
|
||||||
|
|
||||||
typedef long int off_t;
|
typedef long int off_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opaque type that is used as a handle for one instance of an opened DVD.
|
* 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_s dvd_reader_t;
|
||||||
typedef struct dvd_reader_device_s dvd_reader_device_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 *.
|
* Opaque type for a file read handle, much like a normal fd or FILE *.
|
||||||
*/
|
*/
|
||||||
typedef struct dvd_file_s dvd_file_t;
|
typedef struct dvd_file_s dvd_file_t;
|
||||||
|
|
||||||
struct dvd_reader_stream_cb
|
struct dvd_reader_stream_cb
|
||||||
{
|
{
|
||||||
int ( *pf_seek ) ( void *p_stream, uint64_t i_pos);
|
int ( *pf_seek ) ( void *p_stream, uint64_t i_pos);
|
||||||
int ( *pf_read ) ( void *p_stream, void* buffer, int i_read);
|
int ( *pf_read ) ( void *p_stream, void* buffer, int i_read);
|
||||||
int ( *pf_readv ) ( void *p_stream, void *p_iovec, int i_blocks);
|
int ( *pf_readv ) ( void *p_stream, void *p_iovec, int i_blocks);
|
||||||
};
|
};
|
||||||
typedef struct dvd_reader_stream_cb dvd_reader_stream_cb;
|
typedef struct dvd_reader_stream_cb dvd_reader_stream_cb;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom logger callback for DVDOpen[Stream]2
|
* Custom logger callback for DVDOpen[Stream]2
|
||||||
* @param private Handle as provided in Open functions
|
* @param private Handle as provided in Open functions
|
||||||
* @param level Log level
|
* @param level Log level
|
||||||
* @param fmt Format string
|
* @param fmt Format string
|
||||||
* @param args Arguments list
|
* @param args Arguments list
|
||||||
* pf_log(priv, level, fmt, args);
|
* pf_log(priv, level, fmt, args);
|
||||||
*/
|
*/
|
||||||
typedef enum
|
typedef enum
|
||||||
{
|
{
|
||||||
DVD_LOGGER_LEVEL_INFO,
|
DVD_LOGGER_LEVEL_INFO,
|
||||||
DVD_LOGGER_LEVEL_ERROR,
|
DVD_LOGGER_LEVEL_ERROR,
|
||||||
DVD_LOGGER_LEVEL_WARN,
|
DVD_LOGGER_LEVEL_WARN,
|
||||||
DVD_LOGGER_LEVEL_DEBUG,
|
DVD_LOGGER_LEVEL_DEBUG,
|
||||||
} dvd_logger_level_t;
|
} dvd_logger_level_t;
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
// void ( *pf_log ) ( void *, dvd_logger_level_t, const char *, va_list );
|
// void ( *pf_log ) ( void *, dvd_logger_level_t, const char *, va_list );
|
||||||
void *pf_log;
|
void *pf_log;
|
||||||
} dvd_logger_cb;
|
} dvd_logger_cb;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Public type that is used to provide statistics on a handle.
|
* Public type that is used to provide statistics on a handle.
|
||||||
*/
|
*/
|
||||||
typedef struct {
|
typedef struct {
|
||||||
off_t size; /**< Total size of file in bytes */
|
off_t size; /**< Total size of file in bytes */
|
||||||
int nr_parts; /**< Number of file parts */
|
int nr_parts; /**< Number of file parts */
|
||||||
off_t parts_size[9]; /**< Size of each part in bytes */
|
off_t parts_size[9]; /**< Size of each part in bytes */
|
||||||
} dvd_stat_t;
|
} dvd_stat_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens a block device of a DVD-ROM file, or an image file, or a directory
|
* 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.
|
* name for a mounted DVD or HD copy of a DVD.
|
||||||
* The second form of Open function (DVDOpenStream) can be used to
|
* The second form of Open function (DVDOpenStream) can be used to
|
||||||
* provide custom stream_cb functions to access the DVD (see libdvdcss).
|
* 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
|
* 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.
|
* device, then that device is used for CSS authentication using libdvdcss.
|
||||||
* If no device is available, then no CSS authentication is performed,
|
* If no device is available, then no CSS authentication is performed,
|
||||||
* and we hope that the image is decrypted.
|
* and we hope that the image is decrypted.
|
||||||
*
|
*
|
||||||
* If the path given is a directory, then the files in that directory may be
|
* If the path given is a directory, then the files in that directory may be
|
||||||
* in any one of these formats:
|
* in any one of these formats:
|
||||||
*
|
*
|
||||||
* path/VIDEO_TS/VTS_01_1.VOB
|
* path/VIDEO_TS/VTS_01_1.VOB
|
||||||
* path/video_ts/vts_01_1.vob
|
* path/video_ts/vts_01_1.vob
|
||||||
* path/VTS_01_1.VOB
|
* path/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 path Specifies the the device, file or directory to be used.
|
||||||
* @param stream is a private handle used by stream_cb
|
* @param stream is a private handle used by stream_cb
|
||||||
* @param stream_cb is a struct containing seek and read functions
|
* @param stream_cb is a struct containing seek and read functions
|
||||||
* @return If successful a a read handle is returned. Otherwise 0 is returned.
|
* @return If successful a a read handle is returned. Otherwise 0 is returned.
|
||||||
*
|
*
|
||||||
* dvd = DVDOpen(path);
|
* dvd = DVDOpen(path);
|
||||||
* dvd = DVDOpenStream(stream, &stream_cb);
|
* dvd = DVDOpenStream(stream, &stream_cb);
|
||||||
*/
|
*/
|
||||||
dvd_reader_t *DVDOpen( const char * );
|
dvd_reader_t *DVDOpen( const char * );
|
||||||
dvd_reader_t *DVDOpenStream( void *, dvd_reader_stream_cb * );
|
dvd_reader_t *DVDOpenStream( void *, dvd_reader_stream_cb * );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Same as DVDOpen, but with private handle to be passed back on callbacks
|
* 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 path Specifies the the device, file or directory to be used.
|
||||||
* @param priv is a private handle
|
* @param priv is a private handle
|
||||||
* @param logcb is a custom logger callback struct, or NULL if none needed
|
* @param logcb is a custom logger callback struct, or NULL if none needed
|
||||||
* @param stream_cb is a struct containing seek and read functions
|
* @param stream_cb is a struct containing seek and read functions
|
||||||
* @return If successful a a read handle is returned. Otherwise 0 is returned.
|
* @return If successful a a read handle is returned. Otherwise 0 is returned.
|
||||||
*
|
*
|
||||||
* dvd = DVDOpen2(priv, logcb, path);
|
* dvd = DVDOpen2(priv, logcb, path);
|
||||||
* dvd = DVDOpenStream2(priv, logcb, &stream_cb);
|
* dvd = DVDOpenStream2(priv, logcb, &stream_cb);
|
||||||
*/
|
*/
|
||||||
dvd_reader_t *DVDOpen2( void *, const dvd_logger_cb *, const char * );
|
dvd_reader_t *DVDOpen2( void *, const dvd_logger_cb *, const char * );
|
||||||
dvd_reader_t *DVDOpenStream2( void *, const dvd_logger_cb *, dvd_reader_stream_cb * );
|
dvd_reader_t *DVDOpenStream2( void *, const dvd_logger_cb *, dvd_reader_stream_cb * );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Closes and cleans up the DVD reader object.
|
* Closes and cleans up the DVD reader object.
|
||||||
*
|
*
|
||||||
* You must close all open files before calling this function.
|
* You must close all open files before calling this function.
|
||||||
*
|
*
|
||||||
* @param dvd A read handle that should be closed.
|
* @param dvd A read handle that should be closed.
|
||||||
*
|
*
|
||||||
* DVDClose(dvd);
|
* DVDClose(dvd);
|
||||||
*/
|
*/
|
||||||
void DVDClose( dvd_reader_t * );
|
void DVDClose( dvd_reader_t * );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
typedef enum {
|
typedef enum {
|
||||||
DVD_READ_INFO_FILE, /**< VIDEO_TS.IFO or VTS_XX_0.IFO (title) */
|
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_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_MENU_VOBS, /**< VIDEO_TS.VOB or VTS_XX_0.VOB (title) */
|
||||||
DVD_READ_TITLE_VOBS /**< VTS_XX_[1-9].VOB (title). All files in
|
DVD_READ_TITLE_VOBS /**< VTS_XX_[1-9].VOB (title). All files in
|
||||||
the title set are opened and read as a
|
the title set are opened and read as a
|
||||||
single file. */
|
single file. */
|
||||||
} dvd_read_domain_t;
|
} dvd_read_domain_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stats a file on the DVD given the title number and domain.
|
* Stats a file on the DVD given the title number and domain.
|
||||||
* The information about the file is stored in a dvd_stat_t
|
* The information about the file is stored in a dvd_stat_t
|
||||||
* which contains information about the size of the file and
|
* which contains information about the size of the file and
|
||||||
* the number of parts in case of a multipart file and the respective
|
* the number of parts in case of a multipart file and the respective
|
||||||
* sizes of the parts.
|
* sizes of the parts.
|
||||||
* A multipart file is for instance VTS_02_1.VOB, VTS_02_2.VOB, VTS_02_3.VOB
|
* 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],
|
* The size of VTS_02_1.VOB will be stored in stat->parts_size[0],
|
||||||
* VTS_02_2.VOB in stat->parts_size[1], ...
|
* VTS_02_2.VOB in stat->parts_size[1], ...
|
||||||
* The total size (sum of all parts) is stored in stat->size and
|
* The total size (sum of all parts) is stored in stat->size and
|
||||||
* stat->nr_parts will hold the number of parts.
|
* stat->nr_parts will hold the number of parts.
|
||||||
* Only DVD_READ_TITLE_VOBS (VTS_??_[1-9].VOB) can be multipart files.
|
* 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
|
* 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
|
* in the filesystem. These sizes are not needed to use any other
|
||||||
* functions in libdvdread.
|
* functions in libdvdread.
|
||||||
*
|
*
|
||||||
* @param dvd A dvd read handle.
|
* @param dvd A dvd read handle.
|
||||||
* @param titlenum Which Video Title Set should be used, VIDEO_TS is 0.
|
* @param titlenum Which Video Title Set should be used, VIDEO_TS is 0.
|
||||||
* @param domain Which domain.
|
* @param domain Which domain.
|
||||||
* @param stat Pointer to where the result is stored.
|
* @param stat Pointer to where the result is stored.
|
||||||
* @return If successful 0, otherwise -1.
|
* @return If successful 0, otherwise -1.
|
||||||
*
|
*
|
||||||
* int DVDFileStat(dvd, titlenum, domain, stat);
|
* int DVDFileStat(dvd, titlenum, domain, stat);
|
||||||
*/
|
*/
|
||||||
int DVDFileStat(dvd_reader_t *, int, dvd_read_domain_t, dvd_stat_t *);
|
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.
|
* Opens a file on the DVD given the title number and domain.
|
||||||
*
|
*
|
||||||
* If the title number is 0, the video manager information is opened
|
* If the title number is 0, the video manager information is opened
|
||||||
* (VIDEO_TS.[IFO,BUP,VOB]). Returns a file structure which may be
|
* (VIDEO_TS.[IFO,BUP,VOB]). Returns a file structure which may be
|
||||||
* used for reads, or 0 if the file was not found.
|
* used for reads, or 0 if the file was not found.
|
||||||
*
|
*
|
||||||
* @param dvd A dvd read handle.
|
* @param dvd A dvd read handle.
|
||||||
* @param titlenum Which Video Title Set should be used, VIDEO_TS is 0.
|
* @param titlenum Which Video Title Set should be used, VIDEO_TS is 0.
|
||||||
* @param domain Which domain.
|
* @param domain Which domain.
|
||||||
* @return If successful a a file read handle is returned, otherwise 0.
|
* @return If successful a a file read handle is returned, otherwise 0.
|
||||||
*
|
*
|
||||||
* dvd_file = DVDOpenFile(dvd, titlenum, domain); */
|
* dvd_file = DVDOpenFile(dvd, titlenum, domain); */
|
||||||
dvd_file_t *DVDOpenFile( dvd_reader_t *, int, dvd_read_domain_t );
|
dvd_file_t *DVDOpenFile( dvd_reader_t *, int, dvd_read_domain_t );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Closes a file and frees the associated structure.
|
* Closes a file and frees the associated structure.
|
||||||
*
|
*
|
||||||
* @param dvd_file The file read handle to be closed.
|
* @param dvd_file The file read handle to be closed.
|
||||||
*
|
*
|
||||||
* DVDCloseFile(dvd_file);
|
* DVDCloseFile(dvd_file);
|
||||||
*/
|
*/
|
||||||
void DVDCloseFile( dvd_file_t * );
|
void DVDCloseFile( dvd_file_t * );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads block_count number of blocks from the file at the given block offset.
|
* 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
|
* 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.
|
* 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
|
* When reading from an encrypted drive, blocks are decrypted using libdvdcss
|
||||||
* where required.
|
* where required.
|
||||||
*
|
*
|
||||||
* @param dvd_file A file read handle.
|
* @param dvd_file A file read handle.
|
||||||
* @param offset Block offset from the start of the file to start reading at.
|
* @param offset Block offset from the start of the file to start reading at.
|
||||||
* @param block_count Number of block to read.
|
* @param block_count Number of block to read.
|
||||||
* @param data Pointer to a buffer to write the data into.
|
* @param data Pointer to a buffer to write the data into.
|
||||||
* @return Returns number of blocks read on success, -1 on error.
|
* @return Returns number of blocks read on success, -1 on error.
|
||||||
*
|
*
|
||||||
* blocks_read = DVDReadBlocks(dvd_file, offset, block_count, data);
|
* blocks_read = DVDReadBlocks(dvd_file, offset, block_count, data);
|
||||||
*/
|
*/
|
||||||
ssize_t DVDReadBlocks( dvd_file_t *, int, size_t, unsigned char * );
|
ssize_t DVDReadBlocks( dvd_file_t *, int, size_t, unsigned char * );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Seek to the given position in the file. Returns the resulting position in
|
* 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
|
* 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
|
* byte reads from the file, the block read call always reads from the given
|
||||||
* offset.
|
* offset.
|
||||||
*
|
*
|
||||||
* @param dvd_file A file read handle.
|
* @param dvd_file A file read handle.
|
||||||
* @param seek_offset Byte offset from the start of the file to seek to.
|
* @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.
|
* @return The resulting position in bytes from the beginning of the file.
|
||||||
*
|
*
|
||||||
* offset_set = DVDFileSeek(dvd_file, seek_offset);
|
* offset_set = DVDFileSeek(dvd_file, seek_offset);
|
||||||
*/
|
*/
|
||||||
int32_t DVDFileSeek( dvd_file_t *, int32_t );
|
int32_t DVDFileSeek( dvd_file_t *, int32_t );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads the given number of bytes from the file. This call can only be used
|
* 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
|
* 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.
|
* reads from and increments the currrent seek position for the file.
|
||||||
*
|
*
|
||||||
* @param dvd_file A file read handle.
|
* @param dvd_file A file read handle.
|
||||||
* @param data Pointer to a buffer to write the data into.
|
* @param data Pointer to a buffer to write the data into.
|
||||||
* @param bytes Number of bytes to read.
|
* @param bytes Number of bytes to read.
|
||||||
* @return Returns number of bytes read on success, -1 on error.
|
* @return Returns number of bytes read on success, -1 on error.
|
||||||
*
|
*
|
||||||
* bytes_read = DVDReadBytes(dvd_file, data, bytes);
|
* bytes_read = DVDReadBytes(dvd_file, data, bytes);
|
||||||
*/
|
*/
|
||||||
ssize_t DVDReadBytes( dvd_file_t *, void *, size_t );
|
ssize_t DVDReadBytes( dvd_file_t *, void *, size_t );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the file size in blocks.
|
* Returns the file size in blocks.
|
||||||
*
|
*
|
||||||
* @param dvd_file A file read handle.
|
* @param dvd_file A file read handle.
|
||||||
* @return The size of the file in blocks, -1 on error.
|
* @return The size of the file in blocks, -1 on error.
|
||||||
*
|
*
|
||||||
* blocks = DVDFileSize(dvd_file);
|
* blocks = DVDFileSize(dvd_file);
|
||||||
*/
|
*/
|
||||||
ssize_t DVDFileSize( dvd_file_t * );
|
ssize_t DVDFileSize( dvd_file_t * );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a unique 128 bit disc ID.
|
* Get a unique 128 bit disc ID.
|
||||||
* This is the MD5 sum of VIDEO_TS.IFO and the VTS_0?_0.IFO files
|
* This is the MD5 sum of VIDEO_TS.IFO and the VTS_0?_0.IFO files
|
||||||
* in title order (those that exist).
|
* in title order (those that exist).
|
||||||
* If you need a 'text' representation of the id, print it as a
|
* If you need a 'text' representation of the id, print it as a
|
||||||
* hexadecimal number, using lowercase letters, discid[0] first.
|
* hexadecimal number, using lowercase letters, discid[0] first.
|
||||||
* I.e. the same format as the command-line 'md5sum' program uses.
|
* I.e. the same format as the command-line 'md5sum' program uses.
|
||||||
*
|
*
|
||||||
* @param dvd A read handle to get the disc ID from
|
* @param dvd A read handle to get the disc ID from
|
||||||
* @param discid The buffer to put the disc ID into. The buffer must
|
* @param discid The buffer to put the disc ID into. The buffer must
|
||||||
* have room for 128 bits (16 chars).
|
* have room for 128 bits (16 chars).
|
||||||
* @return 0 on success, -1 on error.
|
* @return 0 on success, -1 on error.
|
||||||
*/
|
*/
|
||||||
int DVDDiscID( dvd_reader_t *, unsigned char * );
|
int DVDDiscID( dvd_reader_t *, unsigned char * );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the UDF VolumeIdentifier and VolumeSetIdentifier
|
* Get the UDF VolumeIdentifier and VolumeSetIdentifier
|
||||||
* from the PrimaryVolumeDescriptor.
|
* from the PrimaryVolumeDescriptor.
|
||||||
*
|
*
|
||||||
* @param dvd A read handle to get the disc ID from
|
* @param dvd A read handle to get the disc ID from
|
||||||
* @param volid The buffer to put the VolumeIdentifier into.
|
* @param volid The buffer to put the VolumeIdentifier into.
|
||||||
* The VolumeIdentifier is latin-1 encoded (8bit unicode)
|
* The VolumeIdentifier is latin-1 encoded (8bit unicode)
|
||||||
* null terminated and max 32 bytes (including '\0')
|
* null terminated and max 32 bytes (including '\0')
|
||||||
* @param volid_size No more than volid_size bytes will be copied to volid.
|
* @param volid_size No more than volid_size bytes will be copied to volid.
|
||||||
* If the VolumeIdentifier is truncated because of this
|
* If the VolumeIdentifier is truncated because of this
|
||||||
* it will still be null terminated.
|
* it will still be null terminated.
|
||||||
* @param volsetid The buffer to put the VolumeSetIdentifier into.
|
* @param volsetid The buffer to put the VolumeSetIdentifier into.
|
||||||
* The VolumeIdentifier is 128 bytes as
|
* The VolumeIdentifier is 128 bytes as
|
||||||
* stored in the UDF PrimaryVolumeDescriptor.
|
* stored in the UDF PrimaryVolumeDescriptor.
|
||||||
* Note that this is not a null terminated string.
|
* Note that this is not a null terminated string.
|
||||||
* @param volsetid_size At most volsetid_size bytes will be copied to volsetid.
|
* @param volsetid_size At most volsetid_size bytes will be copied to volsetid.
|
||||||
* @return 0 on success, -1 on error.
|
* @return 0 on success, -1 on error.
|
||||||
*/
|
*/
|
||||||
int DVDUDFVolumeInfo( dvd_reader_t *, char *, unsigned int,
|
int DVDUDFVolumeInfo( dvd_reader_t *, char *, unsigned int,
|
||||||
unsigned char *, unsigned int );
|
unsigned char *, unsigned int );
|
||||||
|
|
||||||
int DVDFileSeekForce( dvd_file_t *, int offset, int force_size);
|
int DVDFileSeekForce( dvd_file_t *, int offset, int force_size);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the ISO9660 VolumeIdentifier and VolumeSetIdentifier
|
* Get the ISO9660 VolumeIdentifier and VolumeSetIdentifier
|
||||||
*
|
*
|
||||||
* * Only use this function as fallback if DVDUDFVolumeInfo returns 0 *
|
* * Only use this function as fallback if DVDUDFVolumeInfo returns 0 *
|
||||||
* * this will happen on a disc mastered only with a iso9660 filesystem *
|
* * this will happen on a disc mastered only with a iso9660 filesystem *
|
||||||
* * All video DVD discs have UDF filesystem *
|
* * All video DVD discs have UDF filesystem *
|
||||||
*
|
*
|
||||||
* @param dvd A read handle to get the disc ID from
|
* @param dvd A read handle to get the disc ID from
|
||||||
* @param volid The buffer to put the VolumeIdentifier into.
|
* @param volid The buffer to put the VolumeIdentifier into.
|
||||||
* The VolumeIdentifier is coded with '0-9','A-Z','_'
|
* The VolumeIdentifier is coded with '0-9','A-Z','_'
|
||||||
* null terminated and max 33 bytes (including '\0')
|
* null terminated and max 33 bytes (including '\0')
|
||||||
* @param volid_size No more than volid_size bytes will be copied to volid.
|
* @param volid_size No more than volid_size bytes will be copied to volid.
|
||||||
* If the VolumeIdentifier is truncated because of this
|
* If the VolumeIdentifier is truncated because of this
|
||||||
* it will still be null terminated.
|
* it will still be null terminated.
|
||||||
* @param volsetid The buffer to put the VolumeSetIdentifier into.
|
* @param volsetid The buffer to put the VolumeSetIdentifier into.
|
||||||
* The VolumeIdentifier is 128 bytes as
|
* The VolumeIdentifier is 128 bytes as
|
||||||
* stored in the ISO9660 PrimaryVolumeDescriptor.
|
* stored in the ISO9660 PrimaryVolumeDescriptor.
|
||||||
* Note that this is not a null terminated string.
|
* Note that this is not a null terminated string.
|
||||||
* @param volsetid_size At most volsetid_size bytes will be copied to volsetid.
|
* @param volsetid_size At most volsetid_size bytes will be copied to volsetid.
|
||||||
* @return 0 on success, -1 on error.
|
* @return 0 on success, -1 on error.
|
||||||
*/
|
*/
|
||||||
int DVDISOVolumeInfo( dvd_reader_t *, char *, unsigned int,
|
int DVDISOVolumeInfo( dvd_reader_t *, char *, unsigned int,
|
||||||
unsigned char *, unsigned int );
|
unsigned char *, unsigned int );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the level of caching that is done when reading from a device
|
* 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 dvd A read handle to get the disc ID from
|
||||||
* @param level The level of caching wanted.
|
* @param level The level of caching wanted.
|
||||||
* -1 - returns the current setting.
|
* -1 - returns the current setting.
|
||||||
* 0 - UDF Cache turned off.
|
* 0 - UDF Cache turned off.
|
||||||
* 1 - (default level) Pointers to IFO files and some data from
|
* 1 - (default level) Pointers to IFO files and some data from
|
||||||
* PrimaryVolumeDescriptor are cached.
|
* PrimaryVolumeDescriptor are cached.
|
||||||
*
|
*
|
||||||
* @return The level of caching.
|
* @return The level of caching.
|
||||||
*/
|
*/
|
||||||
int DVDUDFCacheLevel( dvd_reader_t *, int );
|
int DVDUDFCacheLevel( dvd_reader_t *, int );
|
||||||
|
|
164
dvd_ripper.py
164
dvd_ripper.py
|
@ -1,65 +1,99 @@
|
||||||
import cffi
|
import itertools as ITT
|
||||||
import os
|
import json
|
||||||
import sys
|
import os
|
||||||
import time
|
import subprocess as SP
|
||||||
from dvdnav import DVDNav,DVDError
|
import sys
|
||||||
from dvdread import DVDRead
|
import time
|
||||||
import subprocess as SP
|
from glob import glob
|
||||||
import json
|
|
||||||
from glob import glob
|
import cffi
|
||||||
import itertools as ITT
|
from datetime import timedelta
|
||||||
from vob_demux import demux
|
from dvdnav import DVDError, DVDNav
|
||||||
from ff_d2v import make_d2v
|
from dvdread import DVDRead
|
||||||
|
from ff_d2v import make_d2v, make_meta
|
||||||
def loadlib(dll_path, *includes, **kwargs):
|
from vob_demux import demux
|
||||||
ffi = cffi.FFI()
|
|
||||||
for include in includes:
|
def close_file_del_if_empty(fh):
|
||||||
ffi.cdef(open(include).read(), kwargs)
|
if not fh:
|
||||||
return ffi, ffi.dlopen(dll_path)
|
return False
|
||||||
|
if fh.tell() == 0:
|
||||||
for dvd_path in ITT.chain.from_iterable(map(glob,sys.argv[1:])):
|
fh.close()
|
||||||
r = DVDRead(dvd_path)
|
os.unlink(fh.name)
|
||||||
# r.grab_ifos()
|
return False
|
||||||
# r.grab_vobs()
|
else:
|
||||||
# exit()
|
fh.close()
|
||||||
|
return True
|
||||||
out_folder = os.path.join(
|
|
||||||
"out", "_".join([r.disc_id, r.udf_disc_name or r.iso_disc_name]).replace(" ", "_")
|
|
||||||
)
|
dur_thr = 60.0
|
||||||
os.makedirs(out_folder, exist_ok=True)
|
|
||||||
d = DVDNav(dvd_path)
|
def process_m2v_files(path):
|
||||||
to_demux = []
|
for file in glob(os.path.join(path,"**", "*.m2v")):
|
||||||
for k, v in d.titles.items():
|
make_meta(file)
|
||||||
v["duration"] = v["duration"].total_seconds()
|
make_d2v(file)
|
||||||
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:
|
for dvd_path in ITT.chain.from_iterable(map(glob, sys.argv[1:])):
|
||||||
json.dump(v, fh)
|
r = DVDRead(dvd_path)
|
||||||
for a in range(0,99):
|
# r.grab_ifos()
|
||||||
block=0
|
# r.grab_vobs()
|
||||||
outfile = os.path.join(out_folder, f"t{k:03}_a{a:03}_b{block:03}.vob")
|
# exit()
|
||||||
to_demux.append(outfile)
|
if os.path.isfile(dvd_path):
|
||||||
fh = open(outfile, "wb")
|
basename = os.path.splitext(os.path.basename(dvd_path))[0]
|
||||||
try:
|
else:
|
||||||
for block in d.get_blocks(k, a):
|
basename = r.iso_disc_name or r.udf_disc_name
|
||||||
if isinstance(block, int):
|
base_dir = os.path.join("out", "_".join([basename, r.disc_id]).replace(" ", "_"))
|
||||||
outfile = os.path.join(out_folder, f"t{k:03}_a{a:03}_b{block:03}.vob")
|
if os.path.isdir(base_dir):
|
||||||
to_demux.append(outfile)
|
print(f"Output foldrer {base_dir} exists, remove to re-rip DVD")
|
||||||
if fh:
|
process_m2v_files(base_dir)
|
||||||
fh.close()
|
continue
|
||||||
fh = open(outfile, "wb")
|
os.makedirs(base_dir, exist_ok=True)
|
||||||
else:
|
d = DVDNav(dvd_path)
|
||||||
fh.write(block)
|
to_demux = []
|
||||||
except DVDError as e:
|
for k, v in d.titles.items():
|
||||||
if str(e)!="Invalid angle specified!":
|
out_folder=os.path.join(base_dir,f"t{k:03}")
|
||||||
raise
|
v["duration"] = v["duration"].total_seconds()
|
||||||
if fh.tell()==0:
|
if v["chapters"]:
|
||||||
fh.close()
|
v["chapters"] = [0.0]+[c.total_seconds() for c in v["chapters"]]
|
||||||
os.unlink(fh.name)
|
avg_chapter_len = v["duration"] / len(v["chapters"])
|
||||||
while fh.name in to_demux:
|
# if avg_chapter_len<10:
|
||||||
to_demux.remove(fh.name)
|
# continue
|
||||||
for file in to_demux:
|
d.titles[k] = v
|
||||||
demux(file)
|
# if not v.get('audio'):
|
||||||
os.unlink(file)
|
# print(f"[{k}|0] Skipping title {k} because it has no audio tracks")
|
||||||
for file in glob(os.path.join(out_folder,"*.m2v")):
|
# continue
|
||||||
make_d2v(file)
|
# 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_s * dvdcss_t;
|
||||||
typedef struct dvdcss_stream_cb dvdcss_stream_cb;
|
typedef struct dvdcss_stream_cb dvdcss_stream_cb;
|
||||||
dvdcss_t dvdcss_open (const char *psz_target);
|
dvdcss_t dvdcss_open (const char *psz_target);
|
||||||
dvdcss_t dvdcss_open_stream (void *p_stream, dvdcss_stream_cb *p_stream_cb);
|
dvdcss_t dvdcss_open_stream (void *p_stream, dvdcss_stream_cb *p_stream_cb);
|
||||||
int dvdcss_close (dvdcss_t);
|
int dvdcss_close (dvdcss_t);
|
||||||
int dvdcss_seek (dvdcss_t, int i_blocks, int i_flags);
|
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_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);
|
int dvdcss_readv(dvdcss_t, void *p_iovec, int i_blocks, int i_flags);
|
||||||
const char* dvdcss_error (const dvdcss_t);
|
const char* dvdcss_error (const dvdcss_t);
|
||||||
int dvdcss_is_scrambled (dvdcss_t);
|
int dvdcss_is_scrambled (dvdcss_t);
|
585
dvdnav.py
585
dvdnav.py
|
@ -1,265 +1,320 @@
|
||||||
import cffi
|
import functools
|
||||||
import os
|
import os
|
||||||
import functools
|
from datetime import timedelta
|
||||||
from datetime import timedelta
|
|
||||||
from tqdm import tqdm
|
import cffi
|
||||||
from dvdread import DVDRead
|
from tqdm import tqdm
|
||||||
|
|
||||||
|
from dvdread import DVDRead
|
||||||
def loadlib(dll_path, *includes, **kwargs):
|
|
||||||
ffi = cffi.FFI()
|
|
||||||
for include in includes:
|
def loadlib(dll_path, *includes, **kwargs):
|
||||||
ffi.cdef(open(include).read(), kwargs)
|
ffi = cffi.FFI()
|
||||||
return ffi, ffi.dlopen(dll_path)
|
for include in includes:
|
||||||
|
ffi.cdef(open(include).read(), kwargs)
|
||||||
|
return ffi, ffi.dlopen(dll_path)
|
||||||
class DVDError(Exception):
|
|
||||||
pass
|
|
||||||
|
domains = {
|
||||||
|
0: "None",
|
||||||
class DVDNav(object):
|
1: "FirstPlay",
|
||||||
def __init__(self, path, verbose=None, method="disc"):
|
2: "VTSTitle",
|
||||||
if verbose is None:
|
4: "VMGM",
|
||||||
os.environ.pop("DVDCSS_VERBOSE", None)
|
8: "VTSMenu",
|
||||||
else:
|
}
|
||||||
os.environ["DVDCSS_VERBOSE"] = str(verbose)
|
events = {
|
||||||
os.environ["DVDCSS_METHOD"] = method
|
0: "DVDNAV_BLOCK_OK",
|
||||||
self.dvd = None
|
1: "DVDNAV_NOP",
|
||||||
self.ffi, self.lib = loadlib(
|
2: "DVDNAV_STILL_FRAME",
|
||||||
"libdvdnav-4.dll",
|
3: "DVDNAV_SPU_STREAM_CHANGE",
|
||||||
"dvd_types.h",
|
4: "DVDNAV_AUDIO_STREAM_CHANGE",
|
||||||
"dvd_reader.h",
|
5: "DVDNAV_VTS_CHANGE",
|
||||||
"ifo_types.h",
|
6: "DVDNAV_CELL_CHANGE",
|
||||||
"nav_types.h",
|
7: "DVDNAV_NAV_PACKET",
|
||||||
"dvdnav_events.h",
|
8: "DVDNAV_STOP",
|
||||||
"dvdnav.h",
|
9: "DVDNAV_HIGHLIGHT",
|
||||||
pack=True,
|
10: "DVDNAV_SPU_CLUT_CHANGE",
|
||||||
)
|
12: "DVDNAV_HOP_CHANNEL",
|
||||||
self.path = path
|
13: "DVDNAV_WAIT",
|
||||||
self.titles = {}
|
}
|
||||||
self.open(path)
|
|
||||||
|
class DVDError(Exception):
|
||||||
def __del__(self):
|
pass
|
||||||
self.__check_error(self.lib.dvdnav_close(self.dvd))
|
|
||||||
self.dvd = None
|
|
||||||
|
class DVDNav(object):
|
||||||
def __repr__(self):
|
def __init__(self, path, verbose=None, method="disc"):
|
||||||
return "<DVD Path={0.path} Title={0.title} Serial={0.serial}".format(self)
|
if verbose is None:
|
||||||
|
os.environ.pop("DVDCSS_VERBOSE", None)
|
||||||
def get_blocks(self, title, angle=1, slang=None):
|
else:
|
||||||
self.__check_error(self.lib.dvdnav_set_PGC_positioning_flag(self.dvd, 1))
|
os.environ["DVDCSS_VERBOSE"] = str(verbose)
|
||||||
self.__check_error(self.lib.dvdnav_title_play(self.dvd, title))
|
os.environ["DVDCSS_METHOD"] = method
|
||||||
curr_angle = self.ffi.new("int32_t*", 0)
|
self.dvd = None
|
||||||
num_angles = self.ffi.new("int32_t*", 0)
|
self.ffi, self.lib = loadlib(
|
||||||
self.__check_error(
|
"libdvdnav-4.dll",
|
||||||
self.lib.dvdnav_get_angle_info(self.dvd, curr_angle, num_angles)
|
"dvd_types.h",
|
||||||
)
|
"dvd_reader.h",
|
||||||
if angle != 0:
|
"ifo_types.h",
|
||||||
if angle < 1 or angle > num_angles[0]:
|
"nav_types.h",
|
||||||
raise DVDError("Invalid angle specified!")
|
"dvdnav_events.h",
|
||||||
if angle != curr_angle[0]:
|
"dvdnav.h",
|
||||||
self.__check_error(self.lib.dvdnav_angle_change(self.dvd, angle))
|
pack=True,
|
||||||
if slang is not None:
|
)
|
||||||
self.__check_error(self.lib.dvdnav_spu_language_select(self.dvd, slang))
|
self.path = path
|
||||||
event = self.lib.DVDNAV_NOP
|
self.titles = {}
|
||||||
buf = self.ffi.new("char[]", 4096)
|
self.open(path)
|
||||||
ev = self.ffi.new("int32_t*", self.lib.DVDNAV_NOP)
|
|
||||||
size = self.ffi.new("int32_t*", 0)
|
def __del__(self):
|
||||||
pos = self.ffi.new("uint32_t*", 0)
|
self.__check_error(self.lib.dvdnav_close(self.dvd))
|
||||||
total_size = self.ffi.new("uint32_t*", 0)
|
self.dvd = None
|
||||||
domains = {
|
|
||||||
1: "FirstPlay",
|
def __repr__(self):
|
||||||
2: "VTSTitle",
|
return "<DVD Path={0.path} Title={0.title} Serial={0.serial}".format(self)
|
||||||
4: "VMGM",
|
|
||||||
8: "VTSMenu",
|
def get_blocks(self, title, angle=1, slang=None):
|
||||||
}
|
self.__check_error(self.lib.dvdnav_set_PGC_positioning_flag(self.dvd, 1))
|
||||||
events = {
|
self.__check_error(self.lib.dvdnav_title_play(self.dvd, title))
|
||||||
0: "DVDNAV_BLOCK_OK",
|
curr_angle = self.ffi.new("int32_t*", 0)
|
||||||
1: "DVDNAV_NOP",
|
num_angles = self.ffi.new("int32_t*", 0)
|
||||||
2: "DVDNAV_STILL_FRAME",
|
self.__check_error(
|
||||||
3: "DVDNAV_SPU_STREAM_CHANGE",
|
self.lib.dvdnav_get_angle_info(self.dvd, curr_angle, num_angles)
|
||||||
4: "DVDNAV_AUDIO_STREAM_CHANGE",
|
)
|
||||||
5: "DVDNAV_VTS_CHANGE",
|
if angle != 0:
|
||||||
6: "DVDNAV_CELL_CHANGE",
|
if angle < 1 or angle > num_angles[0]:
|
||||||
7: "DVDNAV_NAV_PACKET",
|
raise DVDError("Invalid angle specified!")
|
||||||
8: "DVDNAV_STOP",
|
if angle != curr_angle[0]:
|
||||||
9: "DVDNAV_HIGHLIGHT",
|
self.__check_error(self.lib.dvdnav_angle_change(self.dvd, angle))
|
||||||
10: "DVDNAV_SPU_CLUT_CHANGE",
|
if slang is not None:
|
||||||
12: "DVDNAV_HOP_CHANNEL",
|
self.__check_error(self.lib.dvdnav_spu_language_select(self.dvd, slang))
|
||||||
13: "DVDNAV_WAIT",
|
event = self.lib.DVDNAV_NOP
|
||||||
}
|
buf = self.ffi.new("char[]", 4096)
|
||||||
progbar = tqdm(
|
ev = self.ffi.new("int32_t*", self.lib.DVDNAV_NOP)
|
||||||
unit_divisor=1024,
|
size = self.ffi.new("int32_t*", 0)
|
||||||
unit_scale=True,
|
pos = self.ffi.new("uint32_t*", 0)
|
||||||
unit="iB",
|
total_size = self.ffi.new("uint32_t*", 0)
|
||||||
desc="Ripping DVD",
|
progbar = tqdm(
|
||||||
disable=False,
|
unit_divisor=1024,
|
||||||
)
|
unit_scale=True,
|
||||||
ripped = set()
|
unit="iB",
|
||||||
current_vts = None
|
desc="Ripping DVD",
|
||||||
current_cell = None
|
disable=False,
|
||||||
current_pg = None
|
)
|
||||||
while True:
|
ripped = set()
|
||||||
self.__check_error(self.lib.dvdnav_get_next_block(self.dvd, buf, ev, size))
|
cells = set()
|
||||||
if (
|
current_vts = (None,None)
|
||||||
self.lib.dvdnav_get_position(self.dvd, pos, total_size)
|
current_cell = None
|
||||||
== self.lib.DVDNAV_STATUS_OK
|
current_pg = None
|
||||||
):
|
while True:
|
||||||
progbar.total = total_size[0] * 2048
|
self.__check_error(self.lib.dvdnav_get_next_block(self.dvd, buf, ev, size))
|
||||||
progbar.n = max(progbar.n, min(progbar.total, pos[0] * 2048))
|
if (
|
||||||
progbar.update(0)
|
self.lib.dvdnav_get_position(self.dvd, pos, total_size)
|
||||||
progbar.set_postfix(
|
== self.lib.DVDNAV_STATUS_OK
|
||||||
vts=current_vts,
|
):
|
||||||
cell=current_cell,
|
progbar.total = total_size[0] * 2048
|
||||||
pg=current_pg,
|
progbar.n = max(progbar.n, min(progbar.total, pos[0] * 2048))
|
||||||
angle=angle,
|
progbar.update(0)
|
||||||
title=title,
|
progbar.set_postfix(
|
||||||
)
|
vts=current_vts,
|
||||||
# print("Got event:",events.get(ev[0],ev[0]),size[0])
|
cell=current_cell,
|
||||||
if ev[0] in [
|
pg=current_pg,
|
||||||
self.lib.DVDNAV_SPU_CLUT_CHANGE,
|
angle=angle,
|
||||||
self.lib.DVDNAV_HOP_CHANNEL,
|
title=title,
|
||||||
self.lib.DVDNAV_NOP,
|
)
|
||||||
self.lib.DVDNAV_HIGHLIGHT,
|
# print("Got event:",events.get(ev[0],ev[0]),size[0])
|
||||||
]:
|
if ev[0] in [
|
||||||
continue
|
self.lib.DVDNAV_SPU_CLUT_CHANGE,
|
||||||
elif ev[0] == self.lib.DVDNAV_BLOCK_OK:
|
self.lib.DVDNAV_HOP_CHANNEL,
|
||||||
yield self.ffi.buffer(buf, size[0])[:]
|
self.lib.DVDNAV_NOP,
|
||||||
elif ev[0] == self.lib.DVDNAV_STOP:
|
self.lib.DVDNAV_HIGHLIGHT,
|
||||||
progbar.write(f"[{title}|{angle}] Stop")
|
]:
|
||||||
break
|
continue
|
||||||
elif ev[0] == self.lib.DVDNAV_NAV_PACKET:
|
elif ev[0] == self.lib.DVDNAV_BLOCK_OK:
|
||||||
pass
|
yield self.ffi.buffer(buf, size[0])[:]
|
||||||
elif ev[0] == self.lib.DVDNAV_STILL_FRAME:
|
elif ev[0] == self.lib.DVDNAV_STOP:
|
||||||
self.__check_error(self.lib.dvdnav_still_skip(self.dvd))
|
progbar.write(f"[{title}|{angle}] Stop")
|
||||||
elif ev[0] == self.lib.DVDNAV_WAIT:
|
break
|
||||||
self.__check_error(self.lib.dvdnav_wait_skip(self.dvd))
|
elif ev[0] == self.lib.DVDNAV_NAV_PACKET:
|
||||||
elif ev[0] == self.lib.DVDNAV_SPU_STREAM_CHANGE:
|
pass
|
||||||
pass
|
elif ev[0] == self.lib.DVDNAV_STILL_FRAME:
|
||||||
elif ev[0] == self.lib.DVDNAV_AUDIO_STREAM_CHANGE:
|
self.__check_error(self.lib.dvdnav_still_skip(self.dvd))
|
||||||
audio = self.ffi.cast("dvdnav_audio_stream_change_event_t*", buf)
|
elif ev[0] == self.lib.DVDNAV_WAIT:
|
||||||
elif ev[0] == self.lib.DVDNAV_CELL_CHANGE:
|
self.__check_error(self.lib.dvdnav_wait_skip(self.dvd))
|
||||||
cell = self.ffi.cast("dvdnav_cell_change_event_t*", buf)
|
elif ev[0] == self.lib.DVDNAV_SPU_STREAM_CHANGE:
|
||||||
current_cell = cell.cellN
|
pass
|
||||||
current_pg = cell.pgN
|
elif ev[0] == self.lib.DVDNAV_AUDIO_STREAM_CHANGE:
|
||||||
progbar.write(
|
audio = self.ffi.cast("dvdnav_audio_stream_change_event_t*", buf)
|
||||||
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_CELL_CHANGE:
|
||||||
)
|
cell = self.ffi.cast("dvdnav_cell_change_event_t*", buf)
|
||||||
elif ev[0] == self.lib.DVDNAV_VTS_CHANGE:
|
current_cell = cell.cellN
|
||||||
vts = self.ffi.cast("dvdnav_vts_change_event_t*", buf)
|
current_pg = cell.pgN
|
||||||
new_vts = (vts.new_vtsN, vts.new_domain)
|
progbar.write(
|
||||||
ripped.add((vts.old_vtsN, vts.old_domain))
|
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)})"
|
||||||
# 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
|
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)
|
||||||
progbar.write(f"[{title}|{angle}] Looped!")
|
if fp in cells:
|
||||||
break
|
progbar.write(f"[{title}|{angle}] Cells Looped!")
|
||||||
current_vts = (vts.new_vtsN, vts.new_domain)
|
break
|
||||||
if vts.new_domain == 8: # back to menu
|
cells.add(fp)
|
||||||
progbar.write(f"[{title}|{angle}] Back to menu!")
|
elif ev[0] == self.lib.DVDNAV_VTS_CHANGE:
|
||||||
break
|
vts = self.ffi.cast("dvdnav_vts_change_event_t*", buf)
|
||||||
yield vts.new_vtsN
|
old_domain = domains[vts.old_domain]
|
||||||
else:
|
new_domain = domains[vts.new_domain]
|
||||||
progbar.write(
|
new_vts = (vts.new_vtsN, vts.new_domain)
|
||||||
f"[{title}|{angle}] Unhandled: {events.get(ev[0],ev[0])} {size[0]}"
|
old_vts = (vts.old_vtsN, vts.old_domain)
|
||||||
)
|
ripped.add((vts.old_vtsN, vts.old_domain))
|
||||||
self.__check_error(self.lib.dvdnav_stop(self.dvd))
|
cells.clear()
|
||||||
|
progbar.write(f"[{title}|{angle}] VTS: {vts.old_vtsN} ({vts.old_domain} {old_domain}) -> {vts.new_vtsN} ({vts.new_domain} {new_domain})")
|
||||||
def __check_error(self, ret):
|
if (new_vts in ripped) or new_vts==old_vts: # looped
|
||||||
if ret == self.lib.DVDNAV_STATUS_ERR:
|
progbar.write(f"[{title}|{angle}] VTS Looped!")
|
||||||
if self.dvd:
|
break
|
||||||
err = self.ffi.string(self.lib.dvdnav_err_to_string(self.dvd))
|
current_vts = (vts.new_vtsN, vts.new_domain)
|
||||||
raise DVDError(err)
|
if vts.new_domain == 8: # back to menu
|
||||||
raise DVDError("Unknown error")
|
progbar.write(f"[{title}|{angle}] VTS Back to menu!")
|
||||||
|
break
|
||||||
def __get_titles(self):
|
# yield vts.new_vtsN
|
||||||
titles = self.ffi.new("int32_t*", 0)
|
else:
|
||||||
p_times = self.ffi.new("uint64_t[]", 512)
|
progbar.write(
|
||||||
times = self.ffi.new("uint64_t**", p_times)
|
f"[{title}|{angle}] Unhandled: {events.get(ev[0],ev[0])} {size[0]}"
|
||||||
duration = self.ffi.new("uint64_t*", 0)
|
)
|
||||||
titles = self.ffi.new("int32_t*", 0)
|
self.__check_error(self.lib.dvdnav_stop(self.dvd))
|
||||||
self.lib.dvdnav_get_number_of_titles(self.dvd, titles)
|
progbar.close()
|
||||||
num_titles = titles[0]
|
|
||||||
for title in range(0, num_titles + 1):
|
def __check_error(self, ret):
|
||||||
if self.lib.dvdnav_get_number_of_parts(self.dvd, title, titles) == 0:
|
if ret == self.lib.DVDNAV_STATUS_ERR:
|
||||||
continue
|
if self.dvd:
|
||||||
num_parts = titles[0]
|
err = self.ffi.string(self.lib.dvdnav_err_to_string(self.dvd))
|
||||||
self.lib.dvdnav_get_number_of_angles(self.dvd, title, titles)
|
raise DVDError(err)
|
||||||
num_angles = titles[0]
|
raise DVDError("Unknown error")
|
||||||
num_chapters = self.lib.dvdnav_describe_title_chapters(
|
|
||||||
self.dvd, title, times, duration
|
def __get_vts(self,title):
|
||||||
)
|
buf = self.ffi.new("char[]", 4096)
|
||||||
if duration[0] == 0:
|
ev = self.ffi.new("int32_t*", self.lib.DVDNAV_NOP)
|
||||||
continue
|
size = self.ffi.new("int32_t*", 0)
|
||||||
chapters = []
|
pos = self.ffi.new("uint32_t*", 0)
|
||||||
for t in range(num_chapters):
|
total_size = self.ffi.new("uint32_t*", 0)
|
||||||
chapters.append(timedelta(seconds=times[0][t] / 90000))
|
self.__check_error(self.lib.dvdnav_set_PGC_positioning_flag(self.dvd, 1))
|
||||||
self.titles[title] = {
|
self.__check_error(self.lib.dvdnav_title_play(self.dvd, title))
|
||||||
"parts": num_parts,
|
seq=[]
|
||||||
"angles": num_angles,
|
while True:
|
||||||
"duration": timedelta(seconds=duration[0] / 90000),
|
self.__check_error(self.lib.dvdnav_get_next_block(self.dvd, buf, ev, size))
|
||||||
"chapters": chapters,
|
if ev[0] == self.lib.DVDNAV_BLOCK_OK:
|
||||||
}
|
self.__check_error(self.lib.dvdnav_get_position(self.dvd, pos, total_size))
|
||||||
|
# print(title,pos[0],total_size[0])
|
||||||
def __get_info(self):
|
if self.lib.dvdnav_next_pg_search(self.dvd)==0:
|
||||||
s = self.ffi.new("char**", self.ffi.NULL)
|
break
|
||||||
self.lib.dvdnav_get_title_string(self.dvd, s)
|
elif ev[0] == self.lib.DVDNAV_STOP:
|
||||||
self.title = str(self.ffi.string(s[0]), "utf8").strip() or None
|
break
|
||||||
self.lib.dvdnav_get_serial_string(self.dvd, s)
|
elif ev[0] == self.lib.DVDNAV_STILL_FRAME:
|
||||||
self.serial = str(self.ffi.string(s[0]), "utf8").strip() or None
|
self.__check_error(self.lib.dvdnav_still_skip(self.dvd))
|
||||||
self.__get_titles()
|
elif ev[0] == self.lib.DVDNAV_WAIT:
|
||||||
|
self.__check_error(self.lib.dvdnav_wait_skip(self.dvd))
|
||||||
def open(self, path):
|
elif ev[0] == self.lib.DVDNAV_VTS_CHANGE:
|
||||||
audio_attrs = self.ffi.new("audio_attr_t*")
|
vts = self.ffi.cast("dvdnav_vts_change_event_t*", buf)
|
||||||
spu_attr = self.ffi.new("subp_attr_t*")
|
old_domain = domains[vts.old_domain]
|
||||||
dvdnav = self.ffi.new("dvdnav_t**", self.ffi.cast("dvdnav_t*", 0))
|
new_domain = domains[vts.new_domain]
|
||||||
self.__check_error(self.lib.dvdnav_open(dvdnav, bytes(path, "utf8")))
|
seq.append(
|
||||||
self.dvd = dvdnav[0]
|
(vts.new_vtsN, new_domain)
|
||||||
self.__check_error(self.lib.dvdnav_set_readahead_flag(self.dvd, 1))
|
)
|
||||||
self.__get_info()
|
if vts.new_domain==8:
|
||||||
for title in self.titles:
|
break
|
||||||
self.__check_error(self.lib.dvdnav_title_play(self.dvd, title))
|
continue
|
||||||
self.titles[title]["audio"] = {}
|
# print(title,ev[0],size[0])
|
||||||
self.titles[title]["subtitles"] = {}
|
self.__check_error(self.lib.dvdnav_stop(self.dvd))
|
||||||
for n in range(255):
|
# print(title,seq)
|
||||||
stream_id = self.lib.dvdnav_get_audio_logical_stream(self.dvd, n)
|
return seq
|
||||||
if stream_id == -1:
|
# self.__check_error(self.lib.dvdnav_next_pg_search(self.dvd))
|
||||||
continue
|
|
||||||
self.__check_error(
|
def __get_titles(self):
|
||||||
self.lib.dvdnav_get_audio_attr(self.dvd, stream_id, audio_attrs)
|
titles = self.ffi.new("int32_t*", 0)
|
||||||
)
|
p_times = self.ffi.new("uint64_t[]", 512)
|
||||||
alang = None
|
times = self.ffi.new("uint64_t**", p_times)
|
||||||
if audio_attrs.lang_type:
|
duration = self.ffi.new("uint64_t*", 0)
|
||||||
alang = str(audio_attrs.lang_code.to_bytes(2, "big"), "utf8")
|
titles = self.ffi.new("int32_t*", 0)
|
||||||
channels = audio_attrs.channels + 1
|
self.lib.dvdnav_get_number_of_titles(self.dvd, titles)
|
||||||
codec = {0: "ac3", 2: "mpeg1", 3: "mpeg-2ext", 4: "lpcm", 6: "dts"}[
|
num_titles = titles[0]
|
||||||
audio_attrs.audio_format
|
for title in range(0, num_titles + 1):
|
||||||
]
|
if self.lib.dvdnav_get_number_of_parts(self.dvd, title, titles) == 0:
|
||||||
audio_type = {
|
continue
|
||||||
0: None,
|
num_parts = titles[0]
|
||||||
1: "normal",
|
self.lib.dvdnav_get_number_of_angles(self.dvd, title, titles)
|
||||||
2: "descriptive",
|
num_angles = titles[0]
|
||||||
3: "director's commentary",
|
num_chapters = self.lib.dvdnav_describe_title_chapters(
|
||||||
4: "alternate director's commentary",
|
self.dvd, title, times, duration
|
||||||
}[audio_attrs.code_extension]
|
)
|
||||||
self.titles[title]["audio"][n] = {
|
if duration[0] == 0:
|
||||||
"stream_id": stream_id,
|
continue
|
||||||
"lang": alang,
|
chapters = []
|
||||||
"channels": channels,
|
if num_chapters==0 and times[0]==self.ffi.NULL:
|
||||||
"codec": codec,
|
chapters=None
|
||||||
"type": audio_type,
|
for t in range(num_chapters):
|
||||||
}
|
chapters.append(timedelta(seconds=times[0][t] / 90000))
|
||||||
for n in range(255):
|
self.titles[title] = {
|
||||||
stream_id = self.lib.dvdnav_get_spu_logical_stream(self.dvd, n)
|
"parts": num_parts,
|
||||||
if stream_id == -1:
|
"angles": num_angles,
|
||||||
continue
|
"duration": timedelta(seconds=duration[0] / 90000),
|
||||||
self.__check_error(
|
"chapters": chapters,
|
||||||
self.lib.dvdnav_get_spu_attr(self.dvd, stream_id, spu_attr)
|
}
|
||||||
)
|
|
||||||
slang = None
|
def __get_info(self):
|
||||||
if spu_attr.type == 1:
|
s = self.ffi.new("char**", self.ffi.NULL)
|
||||||
slang = str(spu_attr.lang_code.to_bytes(2, "big"), "utf8")
|
self.lib.dvdnav_get_title_string(self.dvd, s)
|
||||||
self.titles[title]["subtitles"][n] = {
|
self.title = str(self.ffi.string(s[0]), "utf8").strip() or None
|
||||||
"stream_id": stream_id,
|
self.lib.dvdnav_get_serial_string(self.dvd, s)
|
||||||
"lang": slang,
|
self.serial = str(self.ffi.string(s[0]), "utf8").strip() or None
|
||||||
}
|
self.__get_titles()
|
||||||
self.__check_error(self.lib.dvdnav_stop(self.dvd))
|
|
||||||
|
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"] = {}
|
||||||
|
# self.titles[title]["vts"] = self.__get_vts(title)
|
||||||
|
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"][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"][stream_id] = {
|
||||||
|
"lang": slang,
|
||||||
|
}
|
||||||
|
self.__check_error(self.lib.dvdnav_stop(self.dvd))
|
||||||
|
# exit("DEBUG!")
|
||||||
|
|
332
dvdread.py
332
dvdread.py
|
@ -1,165 +1,167 @@
|
||||||
import cffi
|
import binascii
|
||||||
import os
|
import functools
|
||||||
import functools
|
import os
|
||||||
import binascii
|
from datetime import timedelta
|
||||||
from datetime import timedelta
|
|
||||||
|
import cffi
|
||||||
|
|
||||||
def loadlib(dll_path, *includes, **kwargs):
|
|
||||||
ffi = cffi.FFI()
|
def loadlib(dll_path, *includes, **kwargs):
|
||||||
for include in includes:
|
ffi = cffi.FFI()
|
||||||
ffi.cdef(open(include).read(), **kwargs)
|
for include in includes:
|
||||||
return ffi, ffi.dlopen(dll_path)
|
ffi.cdef(open(include).read(), **kwargs)
|
||||||
|
return ffi, ffi.dlopen(dll_path)
|
||||||
|
|
||||||
class DVDRead(object):
|
|
||||||
def __init__(self, path, verbose="0", method="disc"):
|
class DVDRead(object):
|
||||||
if verbose is None:
|
def __init__(self, path, verbose="0", method="disc"):
|
||||||
os.environ.pop("DVDCSS_VERBOSE", None)
|
if verbose is None:
|
||||||
else:
|
os.environ.pop("DVDCSS_VERBOSE", None)
|
||||||
os.environ["DVDCSS_VERBOSE"] = str(verbose)
|
else:
|
||||||
os.environ["DVDCSS_METHOD"] = method
|
os.environ["DVDCSS_VERBOSE"] = str(verbose)
|
||||||
self.dvd = None
|
os.environ["DVDCSS_METHOD"] = method
|
||||||
self.ffi, self.lib = loadlib(
|
self.dvd = None
|
||||||
"libdvdread-8.dll",
|
self.ffi, self.lib = loadlib(
|
||||||
"dvd_types.h",
|
"libdvdread-8.dll",
|
||||||
"dvd_reader.h",
|
"dvd_types.h",
|
||||||
"ifo_types.h",
|
"dvd_reader.h",
|
||||||
"ifo_read.h",
|
"ifo_types.h",
|
||||||
"ifo_print.h",
|
"ifo_read.h",
|
||||||
"nav_types.h",
|
"ifo_print.h",
|
||||||
"nav_read.h",
|
"nav_types.h",
|
||||||
"nav_print.h",
|
"nav_read.h",
|
||||||
packed=True,
|
"nav_print.h",
|
||||||
)
|
packed=True,
|
||||||
self.path = path
|
)
|
||||||
self.titles = {}
|
self.path = path
|
||||||
self.open(path)
|
self.titles = {}
|
||||||
self.lb_len=self.lib.DVD_VIDEO_LB_LEN
|
self.open(path)
|
||||||
|
self.lb_len=self.lib.DVD_VIDEO_LB_LEN
|
||||||
def grab_ifo(self,title,bup=False):
|
|
||||||
from tqdm import tqdm
|
def grab_ifo(self,title,bup=False):
|
||||||
buf = self.ffi.new("unsigned char[]", 512)
|
from tqdm import tqdm
|
||||||
if bup:
|
buf = self.ffi.new("unsigned char[]", 512)
|
||||||
fh = self.lib.DVDOpenFile(self.dvd, title, self.lib.DVD_READ_INFO_BACKUP_FILE)
|
if bup:
|
||||||
else:
|
fh = self.lib.DVDOpenFile(self.dvd, title, self.lib.DVD_READ_INFO_BACKUP_FILE)
|
||||||
fh = self.lib.DVDOpenFile(self.dvd, title, self.lib.DVD_READ_INFO_FILE)
|
else:
|
||||||
total_size = self.lib.DVDFileSize(fh)*self.lb_len
|
fh = self.lib.DVDOpenFile(self.dvd, title, self.lib.DVD_READ_INFO_FILE)
|
||||||
remaining = total_size
|
total_size = self.lib.DVDFileSize(fh)*self.lb_len
|
||||||
num_read = True
|
remaining = total_size
|
||||||
pbar = tqdm(total=total_size, unit="iB", unit_scale=True, unit_divisor=1024,leave=False)
|
num_read = True
|
||||||
while num_read:
|
pbar = tqdm(total=total_size, unit="iB", unit_scale=True, unit_divisor=1024,leave=False)
|
||||||
num_read=self.lib.DVDReadBytes( fh, buf, 512)
|
while num_read:
|
||||||
num_read=min(num_read,remaining)
|
num_read=self.lib.DVDReadBytes( fh, buf, 512)
|
||||||
remaining-=num_read
|
num_read=min(num_read,remaining)
|
||||||
pbar.update(num_read)
|
remaining-=num_read
|
||||||
yield self.ffi.buffer(buf,num_read)[:]
|
pbar.update(num_read)
|
||||||
self.lib.DVDCloseFile(fh)
|
yield self.ffi.buffer(buf,num_read)[:]
|
||||||
|
self.lib.DVDCloseFile(fh)
|
||||||
def grab_ifos(self):
|
pbar.close()
|
||||||
vmg_ifo = self.lib.ifoOpen(self.dvd, 0)
|
|
||||||
if vmg_ifo == self.ffi.NULL:
|
def grab_ifos(self):
|
||||||
return
|
vmg_ifo = self.lib.ifoOpen(self.dvd, 0)
|
||||||
title_sets = vmg_ifo.vts_atrt.nr_of_vtss
|
if vmg_ifo == self.ffi.NULL:
|
||||||
for t in range(1,title_sets + 1):
|
return
|
||||||
vts = self.lib.ifoOpen(self.dvd, t)
|
title_sets = vmg_ifo.vts_atrt.nr_of_vtss
|
||||||
if vts == self.ffi.NULL:
|
for t in range(1,title_sets + 1):
|
||||||
continue
|
vts = self.lib.ifoOpen(self.dvd, t)
|
||||||
self.lib.ifoClose(vts)
|
if vts == self.ffi.NULL:
|
||||||
outfile=os.path.join("RIP",f"VTS_{t:02}_0.ifo")
|
continue
|
||||||
with open(outfile, "wb") as out_ifo:
|
self.lib.ifoClose(vts)
|
||||||
for block in self.grab_ifo(t,bup=False):
|
outfile=os.path.join("RIP",f"VTS_{t:02}_0.ifo")
|
||||||
out_ifo.write(block)
|
with open(outfile, "wb") as out_ifo:
|
||||||
outfile=os.path.join("RIP",f"VTS_{t:02}_0.bup")
|
for block in self.grab_ifo(t,bup=False):
|
||||||
with open(outfile, "wb") as out_ifo:
|
out_ifo.write(block)
|
||||||
for block in self.grab_ifo(t,bup=True):
|
outfile=os.path.join("RIP",f"VTS_{t:02}_0.bup")
|
||||||
out_ifo.write(block)
|
with open(outfile, "wb") as out_ifo:
|
||||||
self.lib.ifoClose(vmg_ifo)
|
for block in self.grab_ifo(t,bup=True):
|
||||||
|
out_ifo.write(block)
|
||||||
def grab_vob(self,title):
|
self.lib.ifoClose(vmg_ifo)
|
||||||
from tqdm import tqdm
|
|
||||||
buf = self.ffi.new("unsigned char[]", 512 * self.lb_len)
|
def grab_vob(self,title):
|
||||||
fh = self.lib.DVDOpenFile(self.dvd, title, self.lib.DVD_READ_TITLE_VOBS)
|
from tqdm import tqdm
|
||||||
total_size = self.lib.DVDFileSize(fh)*self.lb_len
|
buf = self.ffi.new("unsigned char[]", 512 * self.lb_len)
|
||||||
remaining = total_size
|
fh = self.lib.DVDOpenFile(self.dvd, title, self.lib.DVD_READ_TITLE_VOBS)
|
||||||
num_read = True
|
total_size = self.lib.DVDFileSize(fh)*self.lb_len
|
||||||
pos=0
|
remaining = total_size
|
||||||
pbar = tqdm(total=total_size, unit="iB", unit_scale=True, unit_divisor=1024,leave=False)
|
num_read = True
|
||||||
while remaining:
|
pos=0
|
||||||
num_read=self.lib.DVDReadBlocks( fh,pos, 512, buf)
|
pbar = tqdm(total=total_size, unit="iB", unit_scale=True, unit_divisor=1024,leave=False)
|
||||||
if num_read<0:
|
while remaining:
|
||||||
raise RuntimeError("Error reading!")
|
num_read=self.lib.DVDReadBlocks( fh,pos, 512, buf)
|
||||||
num_read_bytes=num_read*self.lb_len
|
if num_read<0:
|
||||||
num_read_bytes=min(num_read_bytes,remaining)
|
raise RuntimeError("Error reading!")
|
||||||
remaining-=num_read_bytes
|
num_read_bytes=num_read*self.lb_len
|
||||||
pbar.update(num_read_bytes)
|
num_read_bytes=min(num_read_bytes,remaining)
|
||||||
yield self.ffi.buffer(buf,num_read_bytes)[:]
|
remaining-=num_read_bytes
|
||||||
pos+=num_read
|
pbar.update(num_read_bytes)
|
||||||
pbar.close()
|
yield self.ffi.buffer(buf,num_read_bytes)[:]
|
||||||
self.lib.DVDCloseFile(fh)
|
pos+=num_read
|
||||||
|
pbar.close()
|
||||||
def grab_vobs(self):
|
self.lib.DVDCloseFile(fh)
|
||||||
vmg_ifo = self.lib.ifoOpen(self.dvd, 0)
|
|
||||||
if vmg_ifo == self.ffi.NULL:
|
def grab_vobs(self):
|
||||||
return
|
vmg_ifo = self.lib.ifoOpen(self.dvd, 0)
|
||||||
title_sets = vmg_ifo.vts_atrt.nr_of_vtss
|
if vmg_ifo == self.ffi.NULL:
|
||||||
for t in range(1,title_sets + 1):
|
return
|
||||||
vts = self.lib.ifoOpen(self.dvd, t)
|
title_sets = vmg_ifo.vts_atrt.nr_of_vtss
|
||||||
if vts == self.ffi.NULL:
|
for t in range(1,title_sets + 1):
|
||||||
continue
|
vts = self.lib.ifoOpen(self.dvd, t)
|
||||||
self.lib.ifoClose(vts)
|
if vts == self.ffi.NULL:
|
||||||
outfile=os.path.join("RIP",f"VTS_{t:02}_0.vob")
|
continue
|
||||||
with open(outfile, "wb") as out_ifo:
|
self.lib.ifoClose(vts)
|
||||||
for block in self.grab_vob(t):
|
outfile=os.path.join("RIP",f"VTS_{t:02}_0.vob")
|
||||||
out_ifo.write(block)
|
with open(outfile, "wb") as out_ifo:
|
||||||
self.lib.ifoClose(vmg_ifo)
|
for block in self.grab_vob(t):
|
||||||
|
out_ifo.write(block)
|
||||||
|
self.lib.ifoClose(vmg_ifo)
|
||||||
def test(self):
|
|
||||||
from tqdm import tqdm
|
|
||||||
fn = 0
|
def test(self):
|
||||||
chunk_size = 2048
|
from tqdm import tqdm
|
||||||
buf = self.ffi.new("unsigned char[]", chunk_size * 2048)
|
fn = 0
|
||||||
for fn in range(title_sets + 1):
|
chunk_size = 2048
|
||||||
pos = 0
|
buf = self.ffi.new("unsigned char[]", chunk_size * 2048)
|
||||||
fh = self.lib.DVDOpenFile(self.dvd, fn, self.lib.DVD_READ_TITLE_VOBS)
|
for fn in range(title_sets + 1):
|
||||||
if fh:
|
pos = 0
|
||||||
total_size = self.lib.DVDFileSize(fh)
|
fh = self.lib.DVDOpenFile(self.dvd, fn, self.lib.DVD_READ_TITLE_VOBS)
|
||||||
if total_size == -1:
|
if fh:
|
||||||
self.lib.DVDCloseFile(fh)
|
total_size = self.lib.DVDFileSize(fh)
|
||||||
break
|
if total_size == -1:
|
||||||
pbar = tqdm(total=total_size * 2048, unit="iB", unit_scale=True, unit_divisor=1024,leave=False)
|
self.lib.DVDCloseFile(fh)
|
||||||
last=False
|
break
|
||||||
with open(f"out_{fn}.vob", "wb") as out_vob:
|
pbar = tqdm(total=total_size * 2048, unit="iB", unit_scale=True, unit_divisor=1024,leave=False)
|
||||||
while True:
|
last=False
|
||||||
if (pos+chunk_size)>total_size:
|
with open(f"out_{fn}.vob", "wb") as out_vob:
|
||||||
chunk_size=total_size-pos
|
while True:
|
||||||
count = self.lib.DVDReadBlocks(fh, pos, chunk_size, buf)
|
if (pos+chunk_size)>total_size:
|
||||||
if count == -1:
|
chunk_size=total_size-pos
|
||||||
break
|
count = self.lib.DVDReadBlocks(fh, pos, chunk_size, buf)
|
||||||
pbar.update(
|
if count == -1:
|
||||||
out_vob.write(self.ffi.buffer(buf, count * 2048)[:])
|
break
|
||||||
)
|
pbar.update(
|
||||||
pos += count
|
out_vob.write(self.ffi.buffer(buf, count * 2048)[:])
|
||||||
if pos>=total_size:
|
)
|
||||||
break
|
pos += count
|
||||||
self.lib.DVDCloseFile(fh)
|
if pos>=total_size:
|
||||||
fn += 1
|
break
|
||||||
if fn>200:
|
self.lib.DVDCloseFile(fh)
|
||||||
break
|
fn += 1
|
||||||
|
if fn>200:
|
||||||
def __del__(self):
|
break
|
||||||
if self.dvd:
|
|
||||||
self.lib.DVDClose(self.dvd)
|
def __del__(self):
|
||||||
|
if self.dvd:
|
||||||
def open(self, path):
|
self.lib.DVDClose(self.dvd)
|
||||||
# self.dvd_css=self.css_lib.dvdcss_open()
|
|
||||||
self.dvd = self.lib.DVDOpen(bytes(path, "utf8"))
|
def open(self, path):
|
||||||
vol_id = self.ffi.new("unsigned char[]", 32)
|
# self.dvd_css=self.css_lib.dvdcss_open()
|
||||||
self.lib.DVDDiscID(self.dvd, vol_id)
|
self.dvd = self.lib.DVDOpen(bytes(path, "utf8"))
|
||||||
self.disc_id = str(binascii.hexlify(self.ffi.buffer(vol_id, 16)[:]), "utf8")
|
vol_id = self.ffi.new("unsigned char[]", 32)
|
||||||
self.lib.DVDUDFVolumeInfo(self.dvd, vol_id, 32, self.ffi.NULL, 0)
|
self.lib.DVDDiscID(self.dvd, vol_id)
|
||||||
self.udf_disc_name = str(self.ffi.string(vol_id), "utf8")
|
self.disc_id = str(binascii.hexlify(self.ffi.buffer(vol_id, 16)[:]), "utf8")
|
||||||
self.lib.DVDISOVolumeInfo(self.dvd, vol_id, 32, self.ffi.NULL, 0)
|
self.lib.DVDUDFVolumeInfo(self.dvd, vol_id, 32, self.ffi.NULL, 0)
|
||||||
self.iso_disc_name = str(self.ffi.string(vol_id), "utf8")
|
self.udf_disc_name = str(self.ffi.string(vol_id), "utf8")
|
||||||
self.ffi.release(vol_id)
|
self.lib.DVDISOVolumeInfo(self.dvd, vol_id, 32, self.ffi.NULL, 0)
|
||||||
|
self.iso_disc_name = str(self.ffi.string(vol_id), "utf8")
|
||||||
|
self.ffi.release(vol_id)
|
||||||
|
|
635
ff_d2v.py
635
ff_d2v.py
|
@ -1,205 +1,430 @@
|
||||||
import sys
|
import itertools as ITT
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import subprocess as SP
|
import subprocess as SP
|
||||||
import itertools as ITT
|
import sys
|
||||||
from tqdm import tqdm
|
from pprint import pprint
|
||||||
|
from fractions import Fraction
|
||||||
|
from glob import glob
|
||||||
colorspace = {
|
from collections import Counter
|
||||||
"gbr": 0,
|
from tqdm import tqdm
|
||||||
"bt709": 1,
|
from time import perf_counter
|
||||||
"unknown": 2,
|
|
||||||
"fcc": 4,
|
|
||||||
"bt470bg": 5,
|
def pulldown(fields_per_second, frames_per_second):
|
||||||
"smpte170m": 6,
|
f = Fraction(fields_per_second, frames_per_second)
|
||||||
"smpte240m": 7,
|
|
||||||
"ycgco": 8,
|
|
||||||
"bt2020nc": 9,
|
colorspace = {
|
||||||
"bt2020c": 10,
|
"gbr": 0,
|
||||||
"smpte2085": 11,
|
"bt709": 1,
|
||||||
"chroma-derived-nc": 12,
|
"unknown": 2,
|
||||||
"chroma-derived-c": 13,
|
"fcc": 4,
|
||||||
"ictcp": 14,
|
"bt470bg": 5,
|
||||||
}
|
"smpte170m": 6,
|
||||||
|
"smpte240m": 7,
|
||||||
pict_types = {"I": 0b01, "P": 0b10, "B": 0b11}
|
"ycgco": 8,
|
||||||
|
"bt2020nc": 9,
|
||||||
|
"bt2020c": 10,
|
||||||
def make_info(frames):
|
"smpte2085": 11,
|
||||||
has_interlaced = any(frame["interlaced_frame"] for frame in frames)
|
"chroma-derived-nc": 12,
|
||||||
new_gop = "timecode" in frames[0].get("tags", {})
|
"chroma-derived-c": 13,
|
||||||
info = 0x000
|
"ictcp": 14,
|
||||||
info |= 1 << 11 # always 1
|
}
|
||||||
info |= 0 << 10 # 0=Closed GOP, 1=Open GOP
|
|
||||||
info |= (not has_interlaced) << 9 # Progressive
|
pict_types = {"I": 0b01, "P": 0b10, "B": 0b11}
|
||||||
info |= new_gop << 8
|
|
||||||
return info
|
|
||||||
|
def make_info(frames):
|
||||||
|
has_interlaced = any(frame["interlaced_frame"] for frame in frames)
|
||||||
def make_flags(frames):
|
new_gop = "timecode" in frames[0].get("tags", {})
|
||||||
flags = []
|
info = 0x000
|
||||||
for frame in frames:
|
info |= 1 << 11 # always 1
|
||||||
needs_prev = False
|
info |= 0 << 10 # 0=Closed GOP, 1=Open GOP
|
||||||
progressive = not int(frame["interlaced_frame"])
|
info |= (not has_interlaced) << 9 # Progressive
|
||||||
pt = pict_types[frame["pict_type"]]
|
info |= new_gop << 8
|
||||||
reserved = 0b00
|
return info
|
||||||
tff = int(frame["top_field_first"])
|
|
||||||
rff = int(frame["repeat_pict"])
|
|
||||||
flag = 0b0
|
def make_flags(frames):
|
||||||
flag |= (not needs_prev) << 7
|
flags = []
|
||||||
flag |= progressive << 6
|
for frame in frames:
|
||||||
flag |= pt << 4
|
needs_prev = False
|
||||||
flag |= reserved << 2
|
progressive = not int(frame["interlaced_frame"])
|
||||||
flag |= tff << 1
|
pt = pict_types[frame["pict_type"]]
|
||||||
flag |= rff
|
reserved = 0b00
|
||||||
flags.append(f"{flag:02x}")
|
tff = int(frame["top_field_first"])
|
||||||
return flags
|
rff = int(frame["repeat_pict"])
|
||||||
|
flag = 0b0
|
||||||
|
flag |= (not needs_prev) << 7
|
||||||
def make_line(frames, stream):
|
flag |= progressive << 6
|
||||||
info = f"{make_info(frames):03x}"
|
flag |= pt << 4
|
||||||
matrix = colorspace[stream["color_space"]]
|
flag |= reserved << 2
|
||||||
file = 0
|
flag |= tff << 1
|
||||||
position = frames[0]["pkt_pos"]
|
flag |= rff
|
||||||
skip = 0
|
flags.append(f"{flag:02x}")
|
||||||
vob = 0
|
return flags
|
||||||
cell = 0
|
|
||||||
flags = make_flags(frames)
|
|
||||||
return " ".join(map(str, [info, matrix, file, position, skip, vob, cell, *flags]))
|
def make_line(frames, stream):
|
||||||
|
info = f"{make_info(frames):03x}"
|
||||||
|
matrix = colorspace[stream.get("color_space", "unknown")]
|
||||||
def get_frames(path):
|
file = 0
|
||||||
proc = SP.Popen(
|
position = frames[0]["pkt_pos"]
|
||||||
[
|
skip = 0
|
||||||
"ffprobe",
|
vob = 0
|
||||||
"-probesize",
|
cell = 0
|
||||||
str(0x7FFFFFFF),
|
flags = make_flags(frames)
|
||||||
"-analyzeduration",
|
return " ".join(map(str, [info, matrix, file, position, skip, vob, cell, *flags]))
|
||||||
str(0x7FFFFFFF),
|
|
||||||
"-v",
|
|
||||||
"fatal",
|
def __make_dict(line):
|
||||||
"-i",
|
ret = {}
|
||||||
path,
|
line = line.strip().split("|")
|
||||||
"-select_streams",
|
line_type = line[0]
|
||||||
"v:0",
|
for value in line[1:]:
|
||||||
"-show_frames",
|
entry = ret
|
||||||
"-print_format",
|
if "=" not in value:
|
||||||
"compact",
|
continue
|
||||||
],
|
key_path, value = value.split("=")
|
||||||
stdout=SP.PIPE,
|
key_path = key_path.split(".")
|
||||||
stdin=SP.DEVNULL,
|
for key in key_path[:-1]:
|
||||||
bufsize=0,
|
if ":" in key:
|
||||||
)
|
key = key.split(":")[1]
|
||||||
data = None
|
entry = entry.setdefault(key, {})
|
||||||
for line in proc.stdout:
|
entry[key_path[-1]] = value
|
||||||
line = str(line, "utf8").strip().split("|")
|
return {line_type: ret}
|
||||||
line = {line[0]: dict(v.split("=") for v in line[1:])}
|
|
||||||
yield line
|
|
||||||
ret = proc.wait()
|
def judge(info, num_frames):
|
||||||
if ret != 0:
|
threshold = 1 # BFF/TFF threshold value
|
||||||
exit(ret)
|
min_num_frames = 250 # minimal number of frames
|
||||||
return data
|
idet = info["frame"]["lavfi"]["idet"]
|
||||||
|
idet_v = {}
|
||||||
|
for t in "repeated", "single", "multiple":
|
||||||
def get_streams(path):
|
idet_v[t] = {}
|
||||||
proc = SP.Popen(
|
for k, v in idet[t].items():
|
||||||
[
|
try:
|
||||||
"ffprobe",
|
idet_v[t][k] = int(v)
|
||||||
"-probesize",
|
except ValueError:
|
||||||
str(0x7FFFFFFF),
|
try:
|
||||||
"-analyzeduration",
|
idet_v[t][k] = float(v)
|
||||||
str(0x7FFFFFFF),
|
except ValueError:
|
||||||
"-v",
|
pass
|
||||||
"fatal",
|
idet = {
|
||||||
"-i",
|
"repeat": {k: v for k, v in idet_v["repeated"].items()},
|
||||||
path,
|
"single": {k: v for k, v in idet_v["single"].items()},
|
||||||
"-select_streams",
|
"multiple": {k: v for k, v in idet_v["multiple"].items()},
|
||||||
"v:0",
|
}
|
||||||
"-show_streams",
|
repeat_err = abs(
|
||||||
"-show_format",
|
(idet["repeat"]["neither"] / num_frames) - 0.8
|
||||||
"-print_format",
|
) # 2:3 pulldown,4 frames @ ~24 FPS to ~30 FPS = 20% repeated fields
|
||||||
"json",
|
print(f"Derivation from 2:3 Pulldown: {repeat_err:.2%}")
|
||||||
],
|
tff = idet["multiple"]["tff"]
|
||||||
stdout=SP.PIPE,
|
bff = idet["multiple"]["bff"]
|
||||||
stdin=SP.DEVNULL,
|
progressive = idet["multiple"]["progressive"]
|
||||||
bufsize=0,
|
interlaced = tff + bff
|
||||||
)
|
determined = interlaced + progressive
|
||||||
data = json.load(proc.stdout)
|
print(f"Determined: {determined}")
|
||||||
ret = proc.wait()
|
if interlaced:
|
||||||
if ret != 0:
|
print(f"Interlaced: {interlaced} (TFF: {tff/interlaced:.2%}, BFF: {bff/interlaced:.2%}) = {interlaced/determined:.2%}")
|
||||||
exit(ret)
|
else:
|
||||||
return data["streams"], data["format"]
|
print(f"Interlaced: {interlaced} = {interlaced/determined:.2%}")
|
||||||
|
print(f"Progressive: {progressive} = {progressive/determined:.2%}")
|
||||||
|
if determined == 0:
|
||||||
def make_header(file):
|
return idet
|
||||||
return ["DGIndexProjectFile16", "1", os.path.abspath(file)]
|
idet["num_frames"] = num_frames
|
||||||
|
idet["interlaced"] = interlaced
|
||||||
|
idet["progressive"] = progressive
|
||||||
def make_settings(stream):
|
if determined < 50 or determined < min_num_frames:
|
||||||
pict_size = "x".join(map(str, [stream["width"], stream["height"]]))
|
print("/!\\ Not enough information to determine interlacing type reliably, results may be inacurate /!\\")
|
||||||
frame_rate = list(map(int, stream["r_frame_rate"].split("/")))
|
if interlaced > progressive:
|
||||||
frame_rate = (frame_rate[0] * 1000) // frame_rate[1]
|
if tff > bff:
|
||||||
frame_rate = f"{frame_rate} ({stream['r_frame_rate']})"
|
if repeat_err < 1.0:
|
||||||
header = [
|
idet["vid_type"] = "Telecined TFF"
|
||||||
("Stream_Type", 0), # Elementary Stream
|
else:
|
||||||
("MPEG_Type", 2), # MPEG-2
|
idet["vid_type"] = "Interlaced TFF"
|
||||||
("iDCT_Algorithm", 5), # 64-bit IEEE-1180 Reference
|
elif bff > tff:
|
||||||
("YUVRGB_Scale", int(stream["color_range"] != "tv")),
|
if repeat_err < 1.0:
|
||||||
("Luminance_Filter", "0,0"),
|
idet["vid_type"] = "Telecined BFF"
|
||||||
("Clipping", "0,0,0,0"),
|
else:
|
||||||
("Aspect_Ratio", stream["display_aspect_ratio"]),
|
idet["vid_type"] = "Interlaced BFF"
|
||||||
("Picture_Size", pict_size),
|
else:
|
||||||
("Field_Operation", 0), # Honor Pulldown Flags
|
idet["vid_type"] = "Interlaced?"
|
||||||
("Frame_Rate", frame_rate),
|
else:
|
||||||
("Location", "0,0,0,0"),
|
idet["vid_type"] = "Progressive"
|
||||||
]
|
print(f"Result: {idet['vid_type']}")
|
||||||
for k, v in header:
|
return idet
|
||||||
yield f"{k}={v}"
|
|
||||||
|
|
||||||
|
def get_meta_interlacing(path):
|
||||||
def gen_d2v(path):
|
path = path.replace("\\", "/")
|
||||||
yield from make_header(path)
|
filtergraph = [
|
||||||
yield ""
|
f"movie=\\'{path}\\'",
|
||||||
streams, fmt = get_streams(path)
|
"cropdetect=limit=0.5:round=2",
|
||||||
stream = [s for s in streams if s["codec_type"] == "video"][0]
|
"idet",
|
||||||
stream["index"] = str(stream["index"])
|
]
|
||||||
yield from make_settings(stream)
|
proc = SP.Popen(
|
||||||
yield ""
|
[
|
||||||
line_buffer = []
|
"ffprobe",
|
||||||
frames = get_frames(path)
|
"-loglevel",
|
||||||
prog_bar = tqdm(
|
"fatal",
|
||||||
frames,
|
"-probesize",
|
||||||
total=int(fmt["size"]),
|
str(0x7FFFFFFF),
|
||||||
unit_divisor=1024,
|
"-analyzeduration",
|
||||||
unit_scale=True,
|
str(0x7FFFFFFF),
|
||||||
unit="iB",
|
"-f",
|
||||||
desc="Writing d2v",
|
"lavfi",
|
||||||
)
|
"-i",
|
||||||
for line in prog_bar:
|
",".join(filtergraph),
|
||||||
if "frame" not in line:
|
"-select_streams",
|
||||||
continue
|
"v",
|
||||||
frame = line["frame"]
|
"-show_frames",
|
||||||
prog_bar.n = min(max(prog_bar.n, int(frame["pkt_pos"])), int(fmt["size"]))
|
"-show_streams",
|
||||||
prog_bar.update(0)
|
"-print_format",
|
||||||
if frame["stream_index"] != stream["index"]:
|
"compact",
|
||||||
continue
|
],
|
||||||
if frame["pict_type"] == "I" and line_buffer:
|
stdout=SP.PIPE,
|
||||||
yield make_line(line_buffer, stream)
|
stdin=SP.DEVNULL,
|
||||||
line_buffer.clear()
|
bufsize=0,
|
||||||
line_buffer.append(frame)
|
encoding="utf8",
|
||||||
prog_bar.close()
|
)
|
||||||
yield None
|
total_size = int(get_streams(path)[1]["size"])
|
||||||
|
data = {}
|
||||||
|
pbar = tqdm(
|
||||||
def make_d2v(path):
|
total=total_size,
|
||||||
outfile = os.path.splitext(os.path.basename(path))[0]
|
desc="Analyzing video",
|
||||||
outfile = os.path.extsep.join([outfile, "d2v"])
|
unit_divisor=1024,
|
||||||
a, b = ITT.tee(gen_d2v(path))
|
unit_scale=True,
|
||||||
next(b)
|
unit="iB",
|
||||||
with open(outfile, "w") as fh:
|
leave=False,
|
||||||
for line, next_line in zip(a, b):
|
)
|
||||||
fh.write(line)
|
frame_num = 0
|
||||||
if next_line is None: # last line, append end marker
|
from pprint import pformat, pprint
|
||||||
fh.write(" ff")
|
|
||||||
fh.write("\n")
|
pattern = []
|
||||||
|
for line in proc.stdout:
|
||||||
|
line = __make_dict(line)
|
||||||
|
data.update(line)
|
||||||
|
if "frame" in line:
|
||||||
|
frame_num += 1
|
||||||
|
pbar.n = max(pbar.n, min(total_size, int(line["frame"]["pkt_pos"])))
|
||||||
|
dt = pbar._time() - pbar.start_t
|
||||||
|
if dt:
|
||||||
|
pbar.set_postfix(frame=frame_num, fps=f"{frame_num / dt:.2f}")
|
||||||
|
idet = line["frame"].get("lavfi", {}).get("idet")
|
||||||
|
# rep = idet["repeated"]["current_frame"]
|
||||||
|
# single = idet["single"]["current_frame"]
|
||||||
|
# multi = idet["multiple"]["current_frame"]
|
||||||
|
# pbar.write(repr((rep, single, multi)))
|
||||||
|
pbar.update(0)
|
||||||
|
pbar.close()
|
||||||
|
ret = proc.wait()
|
||||||
|
if ret != 0:
|
||||||
|
exit(ret)
|
||||||
|
stream = data["stream"]
|
||||||
|
# 30000/1001
|
||||||
|
frame_rate = list(map(int, stream["r_frame_rate"].split("/")))
|
||||||
|
frame_rate = Fraction(frame_rate[0], frame_rate[1])
|
||||||
|
|
||||||
|
frame_num = int(stream["nb_read_frames"])
|
||||||
|
cropdetect = data["frame"]["lavfi"]["cropdetect"]
|
||||||
|
idet = judge(data, frame_num)
|
||||||
|
crop = (
|
||||||
|
int(cropdetect["x"]),
|
||||||
|
(int(stream["width"]) - int(cropdetect["w"]) - int(cropdetect["x"])),
|
||||||
|
int(cropdetect["y"]),
|
||||||
|
(int(stream["height"]) - int(cropdetect["h"]) - int(cropdetect["y"])),
|
||||||
|
)
|
||||||
|
print(f"Cropping: {crop}")
|
||||||
|
return {"interlacing":idet, "crop":crop}
|
||||||
|
|
||||||
|
|
||||||
|
def get_frames(path):
|
||||||
|
path = path.replace("\\", "/")
|
||||||
|
proc = SP.Popen(
|
||||||
|
[
|
||||||
|
"ffprobe",
|
||||||
|
"-probesize",
|
||||||
|
str(0x7FFFFFFF),
|
||||||
|
"-analyzeduration",
|
||||||
|
str(0x7FFFFFFF),
|
||||||
|
"-v",
|
||||||
|
"fatal",
|
||||||
|
"-i",
|
||||||
|
path,
|
||||||
|
"-select_streams",
|
||||||
|
"v:0",
|
||||||
|
"-show_frames",
|
||||||
|
"-print_format",
|
||||||
|
"compact",
|
||||||
|
],
|
||||||
|
stdout=SP.PIPE,
|
||||||
|
stdin=SP.DEVNULL,
|
||||||
|
bufsize=0,
|
||||||
|
encoding="utf8",
|
||||||
|
)
|
||||||
|
for line in proc.stdout:
|
||||||
|
yield __make_dict(line)
|
||||||
|
ret = proc.wait()
|
||||||
|
if ret != 0:
|
||||||
|
exit(ret)
|
||||||
|
|
||||||
|
|
||||||
|
def get_streams(path):
|
||||||
|
proc = SP.Popen(
|
||||||
|
[
|
||||||
|
"ffprobe",
|
||||||
|
"-probesize",
|
||||||
|
str(0x7FFFFFFF),
|
||||||
|
"-analyzeduration",
|
||||||
|
str(0x7FFFFFFF),
|
||||||
|
"-v",
|
||||||
|
"fatal",
|
||||||
|
"-i",
|
||||||
|
path,
|
||||||
|
"-select_streams",
|
||||||
|
"v:0",
|
||||||
|
"-show_streams",
|
||||||
|
"-show_format",
|
||||||
|
"-print_format",
|
||||||
|
"json",
|
||||||
|
],
|
||||||
|
stdout=SP.PIPE,
|
||||||
|
stdin=SP.DEVNULL,
|
||||||
|
bufsize=0,
|
||||||
|
)
|
||||||
|
data = json.load(proc.stdout)
|
||||||
|
ret = proc.wait()
|
||||||
|
if ret != 0:
|
||||||
|
exit(ret)
|
||||||
|
return data["streams"], data["format"]
|
||||||
|
|
||||||
|
|
||||||
|
def make_header(file):
|
||||||
|
return ["DGIndexProjectFile16", "1", os.path.abspath(file)]
|
||||||
|
|
||||||
|
|
||||||
|
def make_settings(stream):
|
||||||
|
pict_size = "x".join(map(str, [stream["width"], stream["height"]]))
|
||||||
|
frame_rate = list(map(int, stream["r_frame_rate"].split("/")))
|
||||||
|
frame_rate = (frame_rate[0] * 1000) // frame_rate[1]
|
||||||
|
frame_rate = f"{frame_rate} ({stream['r_frame_rate']})"
|
||||||
|
header = [
|
||||||
|
("Stream_Type", 0), # Elementary Stream
|
||||||
|
("MPEG_Type", 2), # MPEG-2
|
||||||
|
("iDCT_Algorithm", 5), # 64-bit IEEE-1180 Reference
|
||||||
|
("YUVRGB_Scale", int(stream["color_range"] != "tv")),
|
||||||
|
("Luminance_Filter", "0,0"),
|
||||||
|
("Clipping", "0,0,0,0"),
|
||||||
|
("Aspect_Ratio", stream["display_aspect_ratio"]),
|
||||||
|
("Picture_Size", pict_size),
|
||||||
|
("Field_Operation", 0), # Honor Pulldown Flags
|
||||||
|
("Frame_Rate", frame_rate),
|
||||||
|
("Location", "0,0,0,0"),
|
||||||
|
]
|
||||||
|
for k, v in header:
|
||||||
|
yield f"{k}={v}"
|
||||||
|
|
||||||
|
|
||||||
|
def gen_d2v(path):
|
||||||
|
yield from make_header(path)
|
||||||
|
yield ""
|
||||||
|
streams, fmt = get_streams(path)
|
||||||
|
stream = [s for s in streams if s["codec_type"] == "video"][0]
|
||||||
|
stream["index"] = str(stream["index"])
|
||||||
|
yield from make_settings(stream)
|
||||||
|
yield ""
|
||||||
|
line_buffer = []
|
||||||
|
frames = get_frames(path)
|
||||||
|
prog_bar = tqdm(
|
||||||
|
frames,
|
||||||
|
total=int(fmt["size"]),
|
||||||
|
unit_divisor=1024,
|
||||||
|
unit_scale=True,
|
||||||
|
unit="iB",
|
||||||
|
desc="Writing d2v",
|
||||||
|
leave=False,
|
||||||
|
)
|
||||||
|
cropdetect = None
|
||||||
|
idet = None
|
||||||
|
frame_num = 0
|
||||||
|
t_start=perf_counter()
|
||||||
|
for line in prog_bar:
|
||||||
|
if "frame" not in line:
|
||||||
|
continue
|
||||||
|
frame = line["frame"]
|
||||||
|
if frame["stream_index"] != stream["index"]:
|
||||||
|
continue
|
||||||
|
prog_bar.n = min(max(prog_bar.n, int(frame["pkt_pos"])), int(fmt["size"]))
|
||||||
|
fps=frame_num/(perf_counter()-t_start)
|
||||||
|
prog_bar.set_postfix(frame=frame_num,fps=f"{fps:.02f}")
|
||||||
|
prog_bar.update(0)
|
||||||
|
frame_num += 1
|
||||||
|
if frame["pict_type"] == "I" and line_buffer:
|
||||||
|
yield make_line(line_buffer, stream)
|
||||||
|
line_buffer.clear()
|
||||||
|
line_buffer.append(frame)
|
||||||
|
if line_buffer:
|
||||||
|
yield make_line(line_buffer, stream)
|
||||||
|
prog_bar.n = int(fmt["size"])
|
||||||
|
prog_bar.update(0)
|
||||||
|
prog_bar.close()
|
||||||
|
yield None
|
||||||
|
|
||||||
|
def make_meta(path):
|
||||||
|
outdir = os.path.dirname(path)
|
||||||
|
outfile = os.path.splitext(os.path.basename(path))[0]
|
||||||
|
outfile = os.path.join(outdir, os.path.extsep.join([outfile, "info", "json"]))
|
||||||
|
if os.path.isfile(outfile):
|
||||||
|
print(path,"already analyzed, skipping")
|
||||||
|
return
|
||||||
|
print("Analyzing", path)
|
||||||
|
meta = get_meta_interlacing(path)
|
||||||
|
streams, fmt = get_streams(path)
|
||||||
|
stream = streams[0]
|
||||||
|
var = Fraction(int(stream["width"]), int(stream["height"]))
|
||||||
|
dar = Fraction(*map(int, stream["display_aspect_ratio"].split(":")))
|
||||||
|
sar = Fraction(*map(int, stream["sample_aspect_ratio"].split(":")))
|
||||||
|
par = sar * dar
|
||||||
|
meta.update(
|
||||||
|
{
|
||||||
|
"par": [par.numerator, par.denominator],
|
||||||
|
"dar": [dar.numerator, dar.denominator],
|
||||||
|
"sar": [sar.numerator, sar.denominator],
|
||||||
|
"var": [var.numerator, var.denominator],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
print(f"Aspect ratios:")
|
||||||
|
print(f" Pixel {par}")
|
||||||
|
print(f" Display {dar}")
|
||||||
|
print(f" Screen {sar}")
|
||||||
|
print(f" Video {var}")
|
||||||
|
with open(outfile, "w") as fh:
|
||||||
|
json.dump(meta, fh, indent=4)
|
||||||
|
|
||||||
|
|
||||||
|
def make_d2v(path):
|
||||||
|
outdir = os.path.dirname(path)
|
||||||
|
outfile = os.path.splitext(os.path.basename(path))[0]
|
||||||
|
outfile = os.path.join(outdir, os.path.extsep.join([outfile, "d2v"]))
|
||||||
|
outfile_tmp = os.path.extsep.join([outfile, "tmp"])
|
||||||
|
if os.path.isfile(outfile):
|
||||||
|
print(path,"already indexed, skipping")
|
||||||
|
return
|
||||||
|
print("Indexing", path)
|
||||||
|
a, b = ITT.tee(gen_d2v(path))
|
||||||
|
next(b)
|
||||||
|
with open(outfile_tmp, "w") as fh:
|
||||||
|
for line, next_line in zip(a, b):
|
||||||
|
fh.write(line)
|
||||||
|
if next_line is None: # last line, append end marker
|
||||||
|
fh.write(" ff")
|
||||||
|
fh.write("\n")
|
||||||
|
os.rename(outfile_tmp,outfile)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
for file in ITT.chain.from_iterable(map(glob, sys.argv[1:])):
|
||||||
|
make_meta(file)
|
||||||
|
make_d2v(file)
|
||||||
|
|
430
ifo_read.h
430
ifo_read.h
|
@ -1,215 +1,215 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2000, 2001, 2002 Björn Englund <d4bjorn@dtek.chalmers.se>,
|
* Copyright (C) 2000, 2001, 2002 Björn Englund <d4bjorn@dtek.chalmers.se>,
|
||||||
* Håkan Hjort <d95hjort@dtek.chalmers.se>
|
* Håkan Hjort <d95hjort@dtek.chalmers.se>
|
||||||
*
|
*
|
||||||
* This file is part of libdvdread.
|
* This file is part of libdvdread.
|
||||||
*
|
*
|
||||||
* libdvdread is free software; you can redistribute it and/or modify
|
* libdvdread is free software; you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation; either version 2 of the License, or
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* libdvdread is distributed in the hope that it will be useful,
|
* libdvdread is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License along
|
* You should have received a copy of the GNU General Public License along
|
||||||
* with libdvdread; if not, write to the Free Software Foundation, Inc.,
|
* with libdvdread; if not, write to the Free Software Foundation, Inc.,
|
||||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* handle = ifoOpen(dvd, title);
|
* handle = ifoOpen(dvd, title);
|
||||||
*
|
*
|
||||||
* Opens an IFO and reads in all the data for the IFO file corresponding to the
|
* Opens an IFO and reads in all the data for the IFO file corresponding to the
|
||||||
* given title. If title 0 is given, the video manager IFO file is read.
|
* given title. If title 0 is given, the video manager IFO file is read.
|
||||||
* Returns a handle to a completely parsed structure.
|
* Returns a handle to a completely parsed structure.
|
||||||
*/
|
*/
|
||||||
ifo_handle_t *ifoOpen(dvd_reader_t *, int );
|
ifo_handle_t *ifoOpen(dvd_reader_t *, int );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* handle = ifoOpenVMGI(dvd);
|
* handle = ifoOpenVMGI(dvd);
|
||||||
*
|
*
|
||||||
* Opens an IFO and reads in _only_ the vmgi_mat data. This call can be used
|
* Opens an IFO and reads in _only_ the vmgi_mat data. This call can be used
|
||||||
* together with the calls below to read in each segment of the IFO file on
|
* together with the calls below to read in each segment of the IFO file on
|
||||||
* demand.
|
* demand.
|
||||||
*/
|
*/
|
||||||
ifo_handle_t *ifoOpenVMGI(dvd_reader_t *);
|
ifo_handle_t *ifoOpenVMGI(dvd_reader_t *);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* handle = ifoOpenVTSI(dvd, title);
|
* handle = ifoOpenVTSI(dvd, title);
|
||||||
*
|
*
|
||||||
* Opens an IFO and reads in _only_ the vtsi_mat data. This call can be used
|
* Opens an IFO and reads in _only_ the vtsi_mat data. This call can be used
|
||||||
* together with the calls below to read in each segment of the IFO file on
|
* together with the calls below to read in each segment of the IFO file on
|
||||||
* demand.
|
* demand.
|
||||||
*/
|
*/
|
||||||
ifo_handle_t *ifoOpenVTSI(dvd_reader_t *, int);
|
ifo_handle_t *ifoOpenVTSI(dvd_reader_t *, int);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ifoClose(ifofile);
|
* ifoClose(ifofile);
|
||||||
* Cleans up the IFO information. This will free all data allocated for the
|
* Cleans up the IFO information. This will free all data allocated for the
|
||||||
* substructures.
|
* substructures.
|
||||||
*/
|
*/
|
||||||
void ifoClose(ifo_handle_t *);
|
void ifoClose(ifo_handle_t *);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The following functions are for reading only part of the VMGI/VTSI files.
|
* The following functions are for reading only part of the VMGI/VTSI files.
|
||||||
* Returns 1 if the data was successfully read and 0 on error.
|
* Returns 1 if the data was successfully read and 0 on error.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* okay = ifoRead_PLT_MAIT(ifofile);
|
* okay = ifoRead_PLT_MAIT(ifofile);
|
||||||
*
|
*
|
||||||
* Read in the Parental Management Information table, filling the
|
* Read in the Parental Management Information table, filling the
|
||||||
* ifofile->ptl_mait structure and its substructures. This data is only
|
* ifofile->ptl_mait structure and its substructures. This data is only
|
||||||
* located in the video manager information file. This fills the
|
* located in the video manager information file. This fills the
|
||||||
* ifofile->ptl_mait structure and all its substructures.
|
* ifofile->ptl_mait structure and all its substructures.
|
||||||
*/
|
*/
|
||||||
int ifoRead_PTL_MAIT(ifo_handle_t *);
|
int ifoRead_PTL_MAIT(ifo_handle_t *);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* okay = ifoRead_VTS_ATRT(ifofile);
|
* okay = ifoRead_VTS_ATRT(ifofile);
|
||||||
*
|
*
|
||||||
* Read in the attribute table for the main menu vob, filling the
|
* Read in the attribute table for the main menu vob, filling the
|
||||||
* ifofile->vts_atrt structure and its substructures. Only located in the
|
* ifofile->vts_atrt structure and its substructures. Only located in the
|
||||||
* video manager information file. This fills in the ifofile->vts_atrt
|
* video manager information file. This fills in the ifofile->vts_atrt
|
||||||
* structure and all its substructures.
|
* structure and all its substructures.
|
||||||
*/
|
*/
|
||||||
int ifoRead_VTS_ATRT(ifo_handle_t *);
|
int ifoRead_VTS_ATRT(ifo_handle_t *);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* okay = ifoRead_TT_SRPT(ifofile);
|
* okay = ifoRead_TT_SRPT(ifofile);
|
||||||
*
|
*
|
||||||
* Reads the title info for the main menu, filling the ifofile->tt_srpt
|
* Reads the title info for the main menu, filling the ifofile->tt_srpt
|
||||||
* structure and its substructures. This data is only located in the video
|
* structure and its substructures. This data is only located in the video
|
||||||
* manager information file. This structure is mandatory in the IFO file.
|
* manager information file. This structure is mandatory in the IFO file.
|
||||||
*/
|
*/
|
||||||
int ifoRead_TT_SRPT(ifo_handle_t *);
|
int ifoRead_TT_SRPT(ifo_handle_t *);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* okay = ifoRead_VTS_PTT_SRPT(ifofile);
|
* okay = ifoRead_VTS_PTT_SRPT(ifofile);
|
||||||
*
|
*
|
||||||
* Reads in the part of title search pointer table, filling the
|
* Reads in the part of title search pointer table, filling the
|
||||||
* ifofile->vts_ptt_srpt structure and its substructures. This data is only
|
* ifofile->vts_ptt_srpt structure and its substructures. This data is only
|
||||||
* located in the video title set information file. This structure is
|
* located in the video title set information file. This structure is
|
||||||
* mandatory, and must be included in the VTSI file.
|
* mandatory, and must be included in the VTSI file.
|
||||||
*/
|
*/
|
||||||
int ifoRead_VTS_PTT_SRPT(ifo_handle_t *);
|
int ifoRead_VTS_PTT_SRPT(ifo_handle_t *);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* okay = ifoRead_FP_PGC(ifofile);
|
* okay = ifoRead_FP_PGC(ifofile);
|
||||||
*
|
*
|
||||||
* Reads in the first play program chain data, filling the
|
* Reads in the first play program chain data, filling the
|
||||||
* ifofile->first_play_pgc structure. This data is only located in the video
|
* ifofile->first_play_pgc structure. This data is only located in the video
|
||||||
* manager information file (VMGI). This structure is optional.
|
* manager information file (VMGI). This structure is optional.
|
||||||
*/
|
*/
|
||||||
int ifoRead_FP_PGC(ifo_handle_t *);
|
int ifoRead_FP_PGC(ifo_handle_t *);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* okay = ifoRead_PGCIT(ifofile);
|
* okay = ifoRead_PGCIT(ifofile);
|
||||||
*
|
*
|
||||||
* Reads in the program chain information table for the video title set. Fills
|
* Reads in the program chain information table for the video title set. Fills
|
||||||
* in the ifofile->vts_pgcit structure and its substructures, which includes
|
* in the ifofile->vts_pgcit structure and its substructures, which includes
|
||||||
* the data for each program chain in the set. This data is only located in
|
* the data for each program chain in the set. This data is only located in
|
||||||
* the video title set information file. This structure is mandatory, and must
|
* the video title set information file. This structure is mandatory, and must
|
||||||
* be included in the VTSI file.
|
* be included in the VTSI file.
|
||||||
*/
|
*/
|
||||||
int ifoRead_PGCIT(ifo_handle_t *);
|
int ifoRead_PGCIT(ifo_handle_t *);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* okay = ifoRead_PGCI_UT(ifofile);
|
* okay = ifoRead_PGCI_UT(ifofile);
|
||||||
*
|
*
|
||||||
* Reads in the menu PGCI unit table for the menu VOB. For the video manager,
|
* Reads in the menu PGCI unit table for the menu VOB. For the video manager,
|
||||||
* this corresponds to the VIDEO_TS.VOB file, and for each title set, this
|
* this corresponds to the VIDEO_TS.VOB file, and for each title set, this
|
||||||
* corresponds to the VTS_XX_0.VOB file. This data is located in both the
|
* corresponds to the VTS_XX_0.VOB file. This data is located in both the
|
||||||
* video manager and video title set information files. For VMGI files, this
|
* video manager and video title set information files. For VMGI files, this
|
||||||
* fills the ifofile->vmgi_pgci_ut structure and all its substructures. For
|
* fills the ifofile->vmgi_pgci_ut structure and all its substructures. For
|
||||||
* VTSI files, this fills the ifofile->vtsm_pgci_ut structure.
|
* VTSI files, this fills the ifofile->vtsm_pgci_ut structure.
|
||||||
*/
|
*/
|
||||||
int ifoRead_PGCI_UT(ifo_handle_t *);
|
int ifoRead_PGCI_UT(ifo_handle_t *);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* okay = ifoRead_VTS_TMAPT(ifofile);
|
* okay = ifoRead_VTS_TMAPT(ifofile);
|
||||||
*
|
*
|
||||||
* Reads in the VTS Time Map Table, this data is only located in the video
|
* Reads in the VTS Time Map Table, this data is only located in the video
|
||||||
* title set information file. This fills the ifofile->vts_tmapt structure
|
* title set information file. This fills the ifofile->vts_tmapt structure
|
||||||
* and all its substructures. When pressent enables VOBU level time-based
|
* and all its substructures. When pressent enables VOBU level time-based
|
||||||
* seeking for One_Sequential_PGC_Titles.
|
* seeking for One_Sequential_PGC_Titles.
|
||||||
*/
|
*/
|
||||||
int ifoRead_VTS_TMAPT(ifo_handle_t *);
|
int ifoRead_VTS_TMAPT(ifo_handle_t *);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* okay = ifoRead_C_ADT(ifofile);
|
* okay = ifoRead_C_ADT(ifofile);
|
||||||
*
|
*
|
||||||
* Reads in the cell address table for the menu VOB. For the video manager,
|
* Reads in the cell address table for the menu VOB. For the video manager,
|
||||||
* this corresponds to the VIDEO_TS.VOB file, and for each title set, this
|
* this corresponds to the VIDEO_TS.VOB file, and for each title set, this
|
||||||
* corresponds to the VTS_XX_0.VOB file. This data is located in both the
|
* corresponds to the VTS_XX_0.VOB file. This data is located in both the
|
||||||
* video manager and video title set information files. For VMGI files, this
|
* video manager and video title set information files. For VMGI files, this
|
||||||
* fills the ifofile->vmgm_c_adt structure and all its substructures. For VTSI
|
* fills the ifofile->vmgm_c_adt structure and all its substructures. For VTSI
|
||||||
* files, this fills the ifofile->vtsm_c_adt structure.
|
* files, this fills the ifofile->vtsm_c_adt structure.
|
||||||
*/
|
*/
|
||||||
int ifoRead_C_ADT(ifo_handle_t *);
|
int ifoRead_C_ADT(ifo_handle_t *);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* okay = ifoRead_TITLE_C_ADT(ifofile);
|
* okay = ifoRead_TITLE_C_ADT(ifofile);
|
||||||
*
|
*
|
||||||
* Reads in the cell address table for the video title set corresponding to
|
* Reads in the cell address table for the video title set corresponding to
|
||||||
* this IFO file. This data is only located in the video title set information
|
* this IFO file. This data is only located in the video title set information
|
||||||
* file. This structure is mandatory, and must be included in the VTSI file.
|
* file. This structure is mandatory, and must be included in the VTSI file.
|
||||||
* This call fills the ifofile->vts_c_adt structure and its substructures.
|
* This call fills the ifofile->vts_c_adt structure and its substructures.
|
||||||
*/
|
*/
|
||||||
int ifoRead_TITLE_C_ADT(ifo_handle_t *);
|
int ifoRead_TITLE_C_ADT(ifo_handle_t *);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* okay = ifoRead_VOBU_ADMAP(ifofile);
|
* okay = ifoRead_VOBU_ADMAP(ifofile);
|
||||||
*
|
*
|
||||||
* Reads in the VOBU address map for the menu VOB. For the video manager, this
|
* Reads in the VOBU address map for the menu VOB. For the video manager, this
|
||||||
* corresponds to the VIDEO_TS.VOB file, and for each title set, this
|
* corresponds to the VIDEO_TS.VOB file, and for each title set, this
|
||||||
* corresponds to the VTS_XX_0.VOB file. This data is located in both the
|
* corresponds to the VTS_XX_0.VOB file. This data is located in both the
|
||||||
* video manager and video title set information files. For VMGI files, this
|
* video manager and video title set information files. For VMGI files, this
|
||||||
* fills the ifofile->vmgm_vobu_admap structure and all its substructures. For
|
* fills the ifofile->vmgm_vobu_admap structure and all its substructures. For
|
||||||
* VTSI files, this fills the ifofile->vtsm_vobu_admap structure.
|
* VTSI files, this fills the ifofile->vtsm_vobu_admap structure.
|
||||||
*/
|
*/
|
||||||
int ifoRead_VOBU_ADMAP(ifo_handle_t *);
|
int ifoRead_VOBU_ADMAP(ifo_handle_t *);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* okay = ifoRead_TITLE_VOBU_ADMAP(ifofile);
|
* okay = ifoRead_TITLE_VOBU_ADMAP(ifofile);
|
||||||
*
|
*
|
||||||
* Reads in the VOBU address map for the associated video title set. This data
|
* Reads in the VOBU address map for the associated video title set. This data
|
||||||
* is only located in the video title set information file. This structure is
|
* is only located in the video title set information file. This structure is
|
||||||
* mandatory, and must be included in the VTSI file. Fills the
|
* mandatory, and must be included in the VTSI file. Fills the
|
||||||
* ifofile->vts_vobu_admap structure and its substructures.
|
* ifofile->vts_vobu_admap structure and its substructures.
|
||||||
*/
|
*/
|
||||||
int ifoRead_TITLE_VOBU_ADMAP(ifo_handle_t *);
|
int ifoRead_TITLE_VOBU_ADMAP(ifo_handle_t *);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* okay = ifoRead_TXTDT_MGI(ifofile);
|
* okay = ifoRead_TXTDT_MGI(ifofile);
|
||||||
*
|
*
|
||||||
* Reads in the text data strings for the DVD. Fills the ifofile->txtdt_mgi
|
* Reads in the text data strings for the DVD. Fills the ifofile->txtdt_mgi
|
||||||
* structure and all its substructures. This data is only located in the video
|
* structure and all its substructures. This data is only located in the video
|
||||||
* manager information file. This structure is mandatory, and must be included
|
* manager information file. This structure is mandatory, and must be included
|
||||||
* in the VMGI file.
|
* in the VMGI file.
|
||||||
*/
|
*/
|
||||||
int ifoRead_TXTDT_MGI(ifo_handle_t *);
|
int ifoRead_TXTDT_MGI(ifo_handle_t *);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The following functions are used for freeing parsed sections of the
|
* The following functions are used for freeing parsed sections of the
|
||||||
* ifo_handle_t structure and the allocated substructures. The free calls
|
* ifo_handle_t structure and the allocated substructures. The free calls
|
||||||
* below are safe: they will not mind if you attempt to free part of an IFO
|
* below are safe: they will not mind if you attempt to free part of an IFO
|
||||||
* file which was not read in or which does not exist.
|
* file which was not read in or which does not exist.
|
||||||
*/
|
*/
|
||||||
void ifoFree_PTL_MAIT(ifo_handle_t *);
|
void ifoFree_PTL_MAIT(ifo_handle_t *);
|
||||||
void ifoFree_VTS_ATRT(ifo_handle_t *);
|
void ifoFree_VTS_ATRT(ifo_handle_t *);
|
||||||
void ifoFree_TT_SRPT(ifo_handle_t *);
|
void ifoFree_TT_SRPT(ifo_handle_t *);
|
||||||
void ifoFree_VTS_PTT_SRPT(ifo_handle_t *);
|
void ifoFree_VTS_PTT_SRPT(ifo_handle_t *);
|
||||||
void ifoFree_FP_PGC(ifo_handle_t *);
|
void ifoFree_FP_PGC(ifo_handle_t *);
|
||||||
void ifoFree_PGCIT(ifo_handle_t *);
|
void ifoFree_PGCIT(ifo_handle_t *);
|
||||||
void ifoFree_PGCI_UT(ifo_handle_t *);
|
void ifoFree_PGCI_UT(ifo_handle_t *);
|
||||||
void ifoFree_VTS_TMAPT(ifo_handle_t *);
|
void ifoFree_VTS_TMAPT(ifo_handle_t *);
|
||||||
void ifoFree_C_ADT(ifo_handle_t *);
|
void ifoFree_C_ADT(ifo_handle_t *);
|
||||||
void ifoFree_TITLE_C_ADT(ifo_handle_t *);
|
void ifoFree_TITLE_C_ADT(ifo_handle_t *);
|
||||||
void ifoFree_VOBU_ADMAP(ifo_handle_t *);
|
void ifoFree_VOBU_ADMAP(ifo_handle_t *);
|
||||||
void ifoFree_TITLE_VOBU_ADMAP(ifo_handle_t *);
|
void ifoFree_TITLE_VOBU_ADMAP(ifo_handle_t *);
|
||||||
void ifoFree_TXTDT_MGI(ifo_handle_t *);
|
void ifoFree_TXTDT_MGI(ifo_handle_t *);
|
||||||
|
|
||||||
|
|
1366
ifo_types.h
1366
ifo_types.h
File diff suppressed because it is too large
Load diff
194
vob_demux.py
194
vob_demux.py
|
@ -1,87 +1,107 @@
|
||||||
import sys
|
import json
|
||||||
import os
|
import os
|
||||||
import json
|
import subprocess as SP
|
||||||
import subprocess as SP
|
import sys
|
||||||
|
import shutil
|
||||||
|
|
||||||
def get_streams(path):
|
|
||||||
proc = SP.Popen(
|
def get_streams(path):
|
||||||
[
|
proc = SP.Popen(
|
||||||
"ffprobe",
|
[
|
||||||
"-probesize",
|
"ffprobe",
|
||||||
str(0x7FFFFFFF),
|
"-probesize",
|
||||||
"-analyzeduration",
|
str(0x7FFFFFFF),
|
||||||
str(0x7FFFFFFF),
|
"-analyzeduration",
|
||||||
"-v",
|
str(0x7FFFFFFF),
|
||||||
"fatal",
|
"-v",
|
||||||
"-i",
|
"fatal",
|
||||||
path,
|
"-i",
|
||||||
"-show_streams",
|
path,
|
||||||
"-show_format",
|
"-show_streams",
|
||||||
"-print_format",
|
"-show_format",
|
||||||
"json",
|
"-print_format",
|
||||||
],
|
"json",
|
||||||
stdout=SP.PIPE,
|
],
|
||||||
stdin=SP.DEVNULL,
|
stdout=SP.PIPE,
|
||||||
bufsize=0,
|
stdin=SP.DEVNULL,
|
||||||
)
|
bufsize=0,
|
||||||
data = json.load(proc.stdout)
|
)
|
||||||
ret = proc.wait()
|
data = json.load(proc.stdout)
|
||||||
if ret != 0:
|
ret = proc.wait()
|
||||||
return [], {}
|
if ret != 0:
|
||||||
return data["streams"], data["format"]
|
return [], {}
|
||||||
|
return data["streams"], data["format"]
|
||||||
|
|
||||||
types = {
|
def ccextract(files):
|
||||||
"mpeg2video": "m2v",
|
ccextractor = shutil.which("ccextractor") or shutil.which("ccextractorwinfull")
|
||||||
"ac3": "ac3",
|
if ccextractor is None and os.name=="nt":
|
||||||
"dvd_subtitle": "sup",
|
ccextractor=os.path.expandvars(os.path.join("${PROGRAMFILES(X86)}","CCExtractor","ccextractorwinfull.exe"))
|
||||||
}
|
if not os.path.isfile(ccextractor):
|
||||||
|
print("WARNING: CCExtractor not found")
|
||||||
|
return []
|
||||||
def demux(path):
|
new_files=[]
|
||||||
folder = os.path.dirname(path)
|
for file in files:
|
||||||
basename = os.path.splitext(os.path.basename(path))[0]
|
outfile=os.path.splitext(file)[0]
|
||||||
streams, fmt = get_streams(path)
|
outfile=os.path.extsep.join([outfile, "cc.srt"])
|
||||||
cmd = [
|
ret=SP.call([ccextractor, "-sc", "-sbs", "-autodash", "-trim","-nobom","-o", outfile, file])
|
||||||
"ffmpeg",
|
if ret==10:
|
||||||
"-y",
|
if os.path.isfile(outfile):
|
||||||
# "-fflags","+genpts+igndts",
|
os.unlink(outfile)
|
||||||
"-probesize",
|
continue
|
||||||
str(0x7FFFFFFF),
|
new_files.append(outfile)
|
||||||
"-analyzeduration",
|
return new_files
|
||||||
str(0x7FFFFFFF),
|
|
||||||
"-i",
|
|
||||||
path,
|
types = {"mpeg2video": "m2v", "ac3": "ac3", "dvd_subtitle": "sub.mkv", "eia_608": "srt"}
|
||||||
"-strict",
|
|
||||||
"-2",
|
def demux(path):
|
||||||
"-vcodec",
|
folder = os.path.dirname(path)
|
||||||
"copy",
|
basename = os.path.splitext(os.path.basename(path))[0]
|
||||||
"-acodec",
|
streams, _ = get_streams(path)
|
||||||
"copy",
|
cmd = [
|
||||||
"-scodec",
|
"ffmpeg",
|
||||||
"copy",
|
"-y",
|
||||||
]
|
"-probesize",
|
||||||
need_ffmpeg = False
|
str(0x7FFFFFFF),
|
||||||
for stream in streams:
|
"-analyzeduration",
|
||||||
codec = stream["codec_name"]
|
str(0x7FFFFFFF),
|
||||||
ext = types.get(codec, codec)
|
"-i",
|
||||||
idx = stream["index"]
|
path,
|
||||||
hex_id = stream["id"]
|
"-strict",
|
||||||
codec_name = stream["codec_long_name"]
|
"-2",
|
||||||
outfile = os.path.join(folder, f"{basename}_{idx}_{hex_id}")
|
]
|
||||||
if codec=="dvd_nav_packet":
|
caption_files = []
|
||||||
continue
|
for stream in streams:
|
||||||
print(idx, hex_id, codec_name, codec)
|
codec = stream["codec_name"]
|
||||||
if codec == "dvd_subtitle":
|
ext = types.get(codec, codec)
|
||||||
SP.check_call([
|
hex_id = stream["id"]
|
||||||
"mencoder",path,"-vobsuboutindex",str(idx),"-vobsubout", outfile,"-nosound","-ovc", "copy", "-o",os.devnull
|
codec_name = stream["codec_long_name"]
|
||||||
])
|
outfile = os.path.join(folder, f"{basename}_{hex_id}")
|
||||||
continue
|
if codec == "dvd_nav_packet":
|
||||||
cmd += ["-map", f"0:#{hex_id}", "-strict", "-2", outfile + f".{ext}"]
|
continue
|
||||||
need_ffmpeg = True
|
outfile = os.path.extsep.join([outfile, ext])
|
||||||
if need_ffmpeg:
|
print(hex_id, codec_name, codec)
|
||||||
SP.check_call(cmd)
|
if codec == "mpeg2video":
|
||||||
|
caption_files.append(outfile)
|
||||||
if __name__=="__main__":
|
cmd += [
|
||||||
demux(sys.argv[1])
|
"-map",
|
||||||
|
f"0:#{hex_id}",
|
||||||
|
"-vcodec",
|
||||||
|
"copy",
|
||||||
|
"-acodec",
|
||||||
|
"copy",
|
||||||
|
"-scodec",
|
||||||
|
"copy",
|
||||||
|
"-strict",
|
||||||
|
"-2",
|
||||||
|
outfile,
|
||||||
|
]
|
||||||
|
SP.check_call(cmd)
|
||||||
|
ccextract(caption_files)
|
||||||
|
for file in os.listdir(folder):
|
||||||
|
if os.path.isfile(file):
|
||||||
|
if os.stat(file).st_size==0:
|
||||||
|
os.unlink(file)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
demux(sys.argv[1])
|
||||||
|
|
Loading…
Reference in a new issue