├── .dockerignore ├── .envrc ├── .github ├── ISSUE_TEMPLATE │ ├── bug-report.yml │ └── feature-request.yml └── workflows │ ├── cx_freeze.yml │ ├── docker-build.yml │ ├── test-basic-funcs.yml │ └── test-web-funcs.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── config.yml ├── data ├── actress_alias.json ├── genre_avsox.csv ├── genre_javbus.csv ├── genre_javdb.csv └── genre_javlib.csv ├── docker └── Dockerfile ├── image ├── JavSP.ico ├── JavSP.svg ├── sub_mark.png └── unc_mark.png ├── javsp ├── __main__.py ├── avid.py ├── chromium.py ├── config.py ├── cropper │ ├── __init__.py │ ├── interface.py │ ├── slimeface_crop.py │ └── utils.py ├── datatype.py ├── file.py ├── func.py ├── image.py ├── lib.py ├── nfo.py ├── print.py ├── prompt.py └── web │ ├── airav.py │ ├── arzon.py │ ├── arzon_iv.py │ ├── avsox.py │ ├── avwiki.py │ ├── base.py │ ├── dl_getchu.py │ ├── exceptions.py │ ├── fanza.py │ ├── fc2.py │ ├── fc2fan.py │ ├── fc2ppvdb.py │ ├── gyutto.py │ ├── jav321.py │ ├── javbus.py │ ├── javdb.py │ ├── javlib.py │ ├── javmenu.py │ ├── mgstage.py │ ├── njav.py │ ├── prestige.py │ ├── proxyfree.py │ └── translate.py ├── poetry.lock ├── pyproject.toml ├── setup.py ├── tools ├── airav_search.py ├── call_crawler.py ├── check_genre.py ├── config_migration.py └── version.py └── unittest ├── conftest.py ├── data ├── 012717_472 (airav).json ├── 080719-976 (airav).json ├── 082713-417 (airav).json ├── 082713-417 (avsox).json ├── 082713-417 (javbus).json ├── 082713-417 (javdb).json ├── 130614-KEIKO (avsox).json ├── 130614-KEIKO (javbus).json ├── 130614-KEIKO (javdb).json ├── 145dmn000007 (fanza).json ├── 145tb017 (fanza).json ├── 1stars931r (fanza).json ├── 259LUXU-593 (avwiki).json ├── 300MIUM-1001 (avwiki).json ├── 62knbm009 (fanza).json ├── ABC-000 (prestige).json ├── ABP-647 (airav).json ├── ABP-647 (mgstage).json ├── ABP-647 (prestige).json ├── AGEMIX-175 (javbus).json ├── AGEMIX-175 (javdb).json ├── AGEMIX-175 (javlib).json ├── AbW-001 (javlib).json ├── CND-037 (airav).json ├── CSCT-011 (arzon).json ├── DCV-137 (jav321).json ├── FC2-10000 (fc2).json ├── FC2-10000 (javdb).json ├── FC2-10000 (javmenu).json ├── FC2-1899973 (javmenu).json ├── FC2-238629 (avsox).json ├── FC2-2735981 (javdb).json ├── FC2-2764073 (javmenu).json ├── FC2-3189680 (javdb).json ├── FC2-718323 (avsox).json ├── FC2-718323 (fc2).json ├── FC2-718323 (javmenu).json ├── FC2-985469 (avsox).json ├── FC2-985469 (javdb).json ├── GANA-2156 (javbus).json ├── GETCHU-4016932 (dl_getchu).json ├── GETCHU-4041026 (dl_getchu).json ├── GETCHU-4049233 (dl_getchu).json ├── GETCHU-4052694 (dl_getchu).json ├── GYUTTO-242158 (gyutto).json ├── GYUTTO-266923 (gyutto).json ├── ION-020 (javdb).json ├── IPX-177 (airav).json ├── IPX-177 (jav321).json ├── IPX-177 (javbus).json ├── IPX-177 (javdb).json ├── IPX-177 (javlib).json ├── IPX-177 (javmenu).json ├── IPZ-037 (javbus).json ├── KIDM-1137B (arzon_iv).json ├── KING-048 (javbus).json ├── MTF-020 (javbus).json ├── NANP-030 (javbus).json ├── OPCYN-174 (jav321).json ├── RED-096 (airav).json ├── SCUTE-1177 (jav321).json ├── SHMO-031 (arzon_iv).json ├── SIRO-4718 (mgstage).json ├── SKY-247 (jav321).json ├── SKYHD-012 (airav).json ├── SONE-273 (arzon).json ├── SQTE-148 (airav).json ├── SQTE-148 (javbus).json ├── SSIS-698 (avwiki).json ├── SSNI-589 (javlib).json ├── STAR-299 (airav).json ├── STAR-676 (javbus).json ├── STARS-213 (javlib).json ├── STARS-256 (javdb).json ├── d_aisoft3356 (fanza).json ├── hjmo00214 (fanza).json ├── ipx-001 (javlib).json ├── midv-001 (javdb).json ├── midv-001 (javlib).json ├── parathd03639 (fanza).json └── sqte00300 (fanza).json ├── test_avid.py ├── test_crawlers.py ├── test_exe.py ├── test_file.py ├── test_func.py ├── test_lib.py ├── test_proxyfree.py └── testdata_avid.txt /.dockerignore: -------------------------------------------------------------------------------- 1 | **/__pycache__/ 2 | .github/ 3 | .idea/ 4 | .vscode/ 5 | .devcontainer/ 6 | **/*.log -------------------------------------------------------------------------------- /.envrc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | layout_poetry() { 4 | if [[ ! -f pyproject.toml ]]; then 5 | log_error 'No pyproject.toml found. Use `poetry new` or `poetry init` to create one first.' 6 | exit 2 7 | fi 8 | 9 | local VENV=$(poetry env info --path) 10 | if [[ -z $VENV || ! -d $VENV/bin ]]; then 11 | log_error 'No poetry virtual environment found. Use `poetry install` to create one first.' 12 | exit 2 13 | fi 14 | 15 | export VIRTUAL_ENV=$VENV 16 | export POETRY_ACTIVE=1 17 | PATH_add "$VENV/bin" 18 | } 19 | 20 | layout_poetry 21 | 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.yml: -------------------------------------------------------------------------------- 1 | name: 报告问题 (Bug) 2 | description: 报告你遇到的问题 (Bug) 3 | title: 用一句话描述这个问题 4 | labels: ["bug", "triage"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | 为了方便复现和排查问题,请按照下面的提示补充问题详情 10 | - type: textarea 11 | id: what-happened 12 | attributes: 13 | label: 问题详情 14 | description: 描述这个bug的出现场景、现象等 15 | placeholder: | 16 | - 如果这个问题只在使用特定的配置时出现,请附上你配置文件中的相关项。 17 | - 如果这个问题只在部分影片上出现,必须说明这些影片的完整文件名或者番号。 18 | (假设某些影片标题中的特殊字符导致整理失败,而你既不提供影片番号也不提供日志的话,我是无法排查和修复的) 19 | validations: 20 | required: true 21 | - type: dropdown 22 | id: execute-method 23 | attributes: 24 | label: 运行方式 25 | description: 你运行的是打包后的exe程序吗? 26 | options: 27 | - 我运行的是打包后的exe程序 28 | - 我是从源代码运行的 29 | validations: 30 | required: true 31 | - type: dropdown 32 | id: proxy 33 | attributes: 34 | label: 代理 35 | description: 是否启用了网络代理? 36 | options: 37 | - 是 38 | - 否 39 | validations: 40 | required: true 41 | - type: textarea 42 | id: log 43 | attributes: 44 | label: 日志 45 | description: 请将软件所在目录下的日志文件`JavSP.log`上传到这里 46 | placeholder: 激活此输入框后,你可以直接将文件拖动到此输入框或者点击下面的“Attach files by dragging & dropping, selecting or pasting them”上传文件 47 | - type: textarea 48 | id: screenshot 49 | attributes: 50 | label: 运行截图(可选) 51 | description: 添加截图有助于定位和排查问题 52 | placeholder: 激活此输入框后,你可以按“Ctrl+V”快速上传剪贴板中的截图,或者参考上传日志的方法上传图片文件 53 | - type: checkboxes 54 | id: promise 55 | attributes: 56 | label: 提交须知 57 | description: 提交问题前,请确认你使用的是[最新版本](https://github.com/Yuukiy/JavSP/releases/latest)并且已经阅读过[Wiki帮助文档](https://github.com/Yuukiy/JavSP/wiki) 58 | options: 59 | - label: 我确认使用的是最新版本并且阅读过Wiki帮助文档 60 | required: true 61 | - label: 我确认已经搜索过Issue区,没有与我遇到的情况相同的Issue。 62 | required: true 63 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.yml: -------------------------------------------------------------------------------- 1 | name: 功能建议和新功能开发请求 2 | description: 功能建议和新功能开发请求 3 | title: 用一句话描述这个问题 4 | labels: ["feature", "triage"] 5 | body: 6 | - type: textarea 7 | id: what-you-want 8 | attributes: 9 | label: 功能建议 10 | placeholder: | 11 | 请在这里描述你对现有功能的建议或者希望添加的新功能。 12 | 如果你对功能的实现方案已经有初步设想,也欢迎一并说明。 13 | validations: 14 | required: true 15 | - type: checkboxes 16 | id: promise 17 | attributes: 18 | label: 提交须知 19 | description: 提交问题前,请确认你使用的是[最新版本](https://github.com/Yuukiy/JavSP/releases/latest)并且已经阅读过[Wiki帮助文档](https://github.com/Yuukiy/JavSP/wiki) 20 | options: 21 | - label: 我确认使用的是最新版本并且已经阅读过Wiki帮助文档 22 | required: true 23 | -------------------------------------------------------------------------------- /.github/workflows/cx_freeze.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: Cx_Freeze 4 | 5 | # Controls when the workflow will run 6 | on: 7 | # Triggers the workflow on push or pull request events 8 | push: 9 | branches: 10 | - master 11 | pull_request: 12 | branches: 13 | - master 14 | 15 | # Allows you to run this workflow manually from the Actions tab 16 | workflow_dispatch: 17 | 18 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 19 | jobs: 20 | # This workflow contains a single job called "build" 21 | build: 22 | name: Build binaries on ${{ matrix.os }} 23 | runs-on: ${{ matrix.os }} 24 | strategy: 25 | matrix: 26 | os: [ubuntu-latest, windows-latest, macos-13] 27 | 28 | # Steps represent a sequence of tasks that will be executed as part of the job 29 | steps: 30 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 31 | - uses: actions/checkout@v4 32 | with: 33 | submodules: 'true' 34 | 35 | - name: Fetch tags 36 | run: git fetch --prune --unshallow --tags 37 | 38 | - name: Install poetry 39 | run: pipx install poetry 40 | 41 | - name: Setup Python 3.10 42 | uses: actions/setup-python@v5 43 | with: 44 | python-version: '3.10' 45 | cache: 'poetry' 46 | 47 | - name: Setup dynamic versioning 48 | run: poetry self add poetry-dynamic-versioning 49 | 50 | - name: Install dependencies 51 | run: | 52 | poetry install 53 | 54 | - name: Build with Cx_Freeze 55 | run: poetry run python setup.py build_exe -b dist 56 | 57 | - name: Set VERSION variable for windows 58 | shell: bash 59 | run: | 60 | echo "VERSION=`poetry run python tools/version.py`" >> $GITHUB_ENV 61 | 62 | - name: Upload build artifact 63 | uses: actions/upload-artifact@v4 64 | with: 65 | name: JavSP-${{ env.VERSION }}-${{ matrix.os }} 66 | path: dist 67 | -------------------------------------------------------------------------------- /.github/workflows/docker-build.yml: -------------------------------------------------------------------------------- 1 | name: Build and Push Docker image 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | tags: 7 | - 'v*' 8 | 9 | env: 10 | # Use docker.io for Docker Hub if empty 11 | REGISTRY: ghcr.io 12 | # github.repository as / 13 | IMAGE_NAME: ${{ github.repository }} 14 | 15 | jobs: 16 | build-and-push: 17 | runs-on: ubuntu-latest 18 | 19 | steps: 20 | - name: Check out code 21 | uses: actions/checkout@v4 22 | with: 23 | # Fetch all history for all tags and branches 24 | fetch-depth: 0 25 | 26 | - name: Log in to GitHub Container Registry 27 | uses: docker/login-action@v2 28 | with: 29 | registry: ghcr.io 30 | username: ${{ github.actor }} 31 | password: ${{ secrets.GITHUB_TOKEN }} 32 | 33 | - name: Docker meta 34 | id: meta 35 | uses: docker/metadata-action@v5 36 | with: 37 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 38 | 39 | - name: Build and push Docker image 40 | uses: docker/build-push-action@v5 41 | with: 42 | context: . 43 | file: ./docker/Dockerfile 44 | push: ${{ github.event_name != 'pull_request' }} 45 | tags: ${{ steps.meta.outputs.tags }} 46 | labels: ${{ steps.meta.outputs.labels }} 47 | -------------------------------------------------------------------------------- /.github/workflows/test-basic-funcs.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a single version of Python 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: "Unit Test: basic functions" 5 | 6 | on: 7 | push: 8 | branches: 9 | - master 10 | pull_request: 11 | branches: 12 | - master 13 | # Allows you to run this workflow manually from the Actions tab 14 | workflow_dispatch: 15 | 16 | jobs: 17 | test-basic-funcs: 18 | 19 | name: Test basic funcs on ${{ matrix.os }} 20 | runs-on: ${{ matrix.os }} 21 | strategy: 22 | matrix: 23 | os: [windows-latest] 24 | 25 | steps: 26 | - uses: actions/checkout@v4 27 | with: 28 | submodules: 'true' 29 | 30 | - name: Install poetry 31 | run: pipx install poetry 32 | 33 | - name: Setup Python 3.10 34 | uses: actions/setup-python@v5 35 | with: 36 | python-version: '3.10' 37 | cache: 'poetry' 38 | - name: Setup dynamic versioning 39 | run: poetry self add poetry-dynamic-versioning 40 | - name: Install dependencies 41 | run: | 42 | poetry install 43 | - name: Test avid.py 44 | run: | 45 | poetry run pytest unittest/test_avid.py 46 | - name: Test file.py 47 | run: | 48 | poetry run pytest unittest/test_file.py 49 | - name: Test func.py 50 | run: | 51 | poetry run pytest unittest/test_func.py 52 | - name: Upload log as artifact 53 | uses: actions/upload-artifact@v4 54 | if: ${{ always() }} 55 | with: 56 | name: JavSP-basic-funcs-${{ matrix.os }}.log 57 | path: javspn/JavSP.log 58 | -------------------------------------------------------------------------------- /.github/workflows/test-web-funcs.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a single version of Python 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: "Unit Test: web-based functions" 5 | 6 | on: 7 | push: 8 | branches: 9 | - master 10 | pull_request: 11 | branches: 12 | - master 13 | schedule: 14 | # Web crawlers might be outdated because of website updating, so run periodically to follow the changes 15 | - cron: '9 9 * * 3,6,0' 16 | # Allows you to run this workflow manually from the Actions tab 17 | workflow_dispatch: 18 | 19 | jobs: 20 | test-web-crawlers: 21 | 22 | runs-on: windows-latest 23 | env: 24 | PYTHONIOENCODING: "utf-8" 25 | PYTEST_ADDOPTS: "-rA --color=yes --tb=long --showlocals" 26 | 27 | steps: 28 | - uses: actions/checkout@v4 29 | with: 30 | submodules: 'true' 31 | 32 | - name: Install poetry 33 | run: pipx install poetry 34 | 35 | - name: Setup Python 3.10 36 | uses: actions/setup-python@v5 37 | with: 38 | python-version: '3.10' 39 | cache: 'poetry' 40 | - name: Setup dynamic versioning 41 | run: poetry self add poetry-dynamic-versioning 42 | - name: Install dependencies 43 | run: | 44 | poetry install 45 | - name: Switch code page 46 | run: | 47 | chcp 65001 48 | - name: Public IP 49 | id: ip 50 | uses: haythem/public-ip@v1.3 51 | - name: Test proxyfree.py 52 | run: | 53 | poetry run pytest unittest/test_proxyfree.py 54 | - name: Test web crawlers 55 | run: | 56 | poetry run pytest unittest/test_crawlers.py 57 | - name: Upload log as artifact 58 | uses: actions/upload-artifact@v4 59 | if: ${{ always() }} 60 | with: 61 | name: JavSP-web-funcs.log 62 | path: javspn/JavSP.log 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | 53 | # Translations 54 | *.mo 55 | *.pot 56 | 57 | # Django stuff: 58 | *.log 59 | local_settings.py 60 | db.sqlite3 61 | db.sqlite3-journal 62 | 63 | # Flask stuff: 64 | instance/ 65 | .webassets-cache 66 | 67 | # Scrapy stuff: 68 | .scrapy 69 | 70 | # Sphinx documentation 71 | docs/_build/ 72 | 73 | # PyBuilder 74 | target/ 75 | 76 | # Jupyter Notebook 77 | .ipynb_checkpoints 78 | 79 | # IPython 80 | profile_default/ 81 | ipython_config.py 82 | 83 | # pyenv 84 | .python-version 85 | 86 | # pipenv 87 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 88 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 89 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 90 | # install all needed dependencies. 91 | #Pipfile.lock 92 | 93 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 94 | __pypackages__/ 95 | 96 | # Celery stuff 97 | celerybeat-schedule 98 | celerybeat.pid 99 | 100 | # SageMath parsed files 101 | *.sage.py 102 | 103 | # Environments 104 | .env 105 | .venv 106 | env/ 107 | venv/ 108 | ENV/ 109 | env.bak/ 110 | venv.bak/ 111 | 112 | # Spyder project settings 113 | .spyderproject 114 | .spyproject 115 | 116 | # Rope project settings 117 | .ropeproject 118 | 119 | # mkdocs documentation 120 | /site 121 | 122 | # mypy 123 | .mypy_cache/ 124 | .dmypy.json 125 | dmypy.json 126 | 127 | # Pyre type checker 128 | .pyre/ 129 | 130 | # hook script generated for pyinstaller 131 | ver_hook.py 132 | # cached data 133 | *.cache.json 134 | 135 | .idea 136 | .vscode 137 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [Unreleased](https://github.com/Yuukiy/Javsp/compare/v1.8...HEAD) 4 | 5 | ### Added 6 | - 添加无码和字幕的水印 [#73](https://github.com/Yuukiy/JavSP/commit/eaedc84049597eaab1ba064229f9b5bcf38aa504) 7 | - 支持刮削剧照 [#176](https://github.com/Yuukiy/JavSP/issues/176) 8 | - direnv 适配 [134b279](https://github.com/Yuukiy/JavSP/commit/134b279151aead587db0b12d1a30781f2e1be5b1) 9 | - 添加硬链接支持 [#374](https://github.com/Yuukiy/JavSP/pull/374) 10 | - 添加Docker镜像,每次tag发布将会同步更新一个Docker镜像到ghcr.io上。[#322](https://github.com/Yuukiy/JavSP/pull/322) 11 | 12 | 参见[Package javsp](https://github.com/Yuukiy/JavSP/pkgs/container/javsp)。 13 | - 添加新的爬虫`arzon`, `arzon_iv` [#377](https://github.com/Yuukiy/JavSP/pull/377) 14 | - Slimeface人脸识别 [#380](https://github.com/Yuukiy/JavSP/pull/380) 15 | - 支持Linux和MacOS(x64)二进制 [a754e1c](https://github.com/Yuukiy/JavSP/commit/a754e1ce0f14b0ca9dcc6d43d8e7d322a3da1c43) 16 | - 添加选项`other.interactive`来表示程序是否应该在interactive模式下运行 17 | 18 | ### Changed 19 | - 使用 Poetry 作为构建系统 [134b279](https://github.com/Yuukiy/JavSP/commit/134b279151aead587db0b12d1a30781f2e1be5b1) 20 | - 使用 Cx_Freeze 作为打包工具 [134b279](https://github.com/Yuukiy/JavSP/commit/134b279151aead587db0b12d1a30781f2e1be5b1) 21 | - 将 Groq 翻译接口重构为 OpenAI 通用翻译接口 [#371](https://github.com/Yuukiy/JavSP/pull/371) 22 | - FIX: 修复图标没有添加到封面上的 bug [#262](https://github.com/Yuukiy/JavSP/issues/176) 23 | - 用更高清的Logo替换旧的Logo [7b8690f](https://github.com/Yuukiy/JavSP/commit/7b8690fb4af831c0e5ad5ed97cac61d51117c7eb) 24 | - 重构配置文件,现在使用YAML保存配置文件 [e096d83](https://github.com/Yuukiy/JavSP/commit/e096d8394a4db29bb4a1123b3d05021de201207d) 25 | 26 | 旧用户迁移可以使用[这个脚本](./tools/config_migration.py) 27 | - 除了`-c,--config`以外的其他命令行参数,都被改为与配置文件的命名一致的传入方法。 [e096d83](https://github.com/Yuukiy/JavSP/commit/e096d8394a4db29bb4a1123b3d05021de201207d) 28 | 29 | 比如需要重写扫描的目录可以这样传入参数: 30 | ``` 31 | poetry run javsp -- --oscanner.input_directory '/some/directory' 32 | ``` 33 | - 删除了所有环境变量,现在环境变量的传入方法如下。 [e096d83](https://github.com/Yuukiy/JavSP/commit/e096d8394a4db29bb4a1123b3d05021de201207d) 34 | ``` 35 | env JAVSP_SCANNER.INPUT_DIRECTORY='/some/directory' poetry run javsp 36 | ``` 37 | - 为了引入对类型注释的支持,最低Python版本现在为3.10 38 | 39 | - 重构封面剪裁逻辑 [#380](https://github.com/Yuukiy/JavSP/pull/380) 40 | 41 | ### Removed 42 | - Pyinstaller 打包描述文件 [134b279](https://github.com/Yuukiy/JavSP/commit/134b279151aead587db0b12d1a30781f2e1be5b1) 43 | - requirements.txt [134b279](https://github.com/Yuukiy/JavSP/commit/134b279151aead587db0b12d1a30781f2e1be5b1) 44 | - MovieID.ignore_whole_word 功能和ignore_regex重复 [e096d83](https://github.com/Yuukiy/JavSP/commit/e096d8394a4db29bb4a1123b3d05021de201207d) 45 | - NamingRule.media_servers:由于不常用删除,之后会出更general的解决方案 [#353](https://github.com/Yuukiy/JavSP/issues/353) 46 | - Baidu AIP人脸识别,请使用Slimeface替代。 47 | 48 | ## v1.8 - 2024-09-28 49 | 50 | ### Added 51 | - 新增站点njav, fc2ppvdb 52 | - 添加选项控制封面选择逻辑,优先使用非javdb的封面以避免水印 53 | - 支持自定义要写入到nfo的genre和tag中的字段 54 | - 支持添加uncensored标签到poster图片 55 | - 支持调用Claude(haiku)和Groq(llama3.1-70b)翻译接口 56 | 57 | ### Changed 58 | - 适配网页和接口的变化: avsox, fc2, fanza, mgstage, prestige, javmenu 59 | - 修复写入nfo时的拼写问题 60 | - 修复Windows下无法读取Cookies的问题 61 | - 修复封面图片url存在?参数时下载图片失败的问题 62 | - 解决图片下载请求被javbus拦截的问题 63 | - 优化google翻译参数和速率,减少被QoS 64 | - 为Cloudflare拦截导致的失败请求给出提示 65 | - 改进T38-000系列影片的番号识别 66 | - msin: 站点关闭,移除相应代码及测试用例 67 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![JavSP](./image/JavSP.svg) 2 | 3 | # Jav Scraper Package 4 | 5 | **汇总多站点数据的AV元数据刮削器** 6 | 7 | 提取影片文件名中的番号信息,自动抓取并汇总多个站点数据的 AV 元数据,按照指定的规则分类整理影片文件,并创建供 Emby、Jellyfin、Kodi 等软件使用的元数据文件 8 | 9 | **WebUI**: UI界面不是[此项目的目标](https://github.com/Yuukiy/JavSP/issues/148)。 10 | 11 | **i18n**: This project currently supports only Chinese. However, if you're willing, you can [vote here](https://github.com/Yuukiy/JavSP/discussions/157) for the language you'd like to see added 12 | 13 | ![License](https://img.shields.io/github/license/Yuukiy/JavSP) 14 | [![LICENSE](https://img.shields.io/badge/license-Anti%20996-blue.svg)](https://github.com/996icu/996.ICU/blob/master/LICENSE) 15 | ![Python 3.10](https://img.shields.io/badge/python-3.10-green.svg) 16 | [![Crawlers test](https://img.shields.io/github/actions/workflow/status/Yuukiy/JavSP/test-web-funcs.yml?label=crawlers%20test)](https://github.com/Yuukiy/JavSP/actions/workflows/test-web-funcs.yml) 17 | [![Latest release](https://img.shields.io/github/v/release/Yuukiy/JavSP)](https://github.com/Yuukiy/JavSP/releases/latest) 18 | [![996.icu](https://img.shields.io/badge/link-996.icu-red.svg)](https://996.icu) 19 | ## 功能特点 20 | 21 | 下面这些是一些已实现或待实现的功能,在逐渐实现和完善,如果想到新的功能点也会加进来。 22 | 23 | - [x] 自动识别影片番号 24 | - [x] 支持处理影片分片 25 | - [x] 汇总多个站点的数据生成NFO数据文件 26 | - [x] 每天自动对站点抓取器进行测试 27 | - [x] 多线程并行抓取 28 | - [x] 下载高清封面 29 | - [x] 基于AI人体分析裁剪素人等非常规封面的海报 30 | - [x] 自动检查和更新新版本 31 | - [x] 翻译标题和剧情简介 32 | - [ ] 匹配本地字幕 33 | - [ ] 使用小缩略图创建文件夹封面 34 | - [ ] 保持不同站点间 genre 分类的统一 35 | - [ ] 不同的运行模式(抓取数据+整理,仅抓取数据) 36 | - [ ] 可选:所有站点均抓取失败时由人工介入 37 | 38 | ## 迁移 39 | 40 | 功能修改日志:[ChangeLog](./CHANGELOG.md) 41 | 42 | 如果你之前使用的是config.ini,请重新配置JavSP,或者通过[这个脚本](./tools/config_migration.py)来将其迁移到最新的config.yml。 43 | 44 | ## [安装并运行JavSP](https://github.com/Yuukiy/JavSP/wiki/%E5%AE%89%E8%A3%85%E5%B9%B6%E8%BF%90%E8%A1%8CJavSP) 45 | 46 | ## 使用 47 | 48 | 软件开箱即用。如果想让软件更符合你的使用需求,也许你需要更改配置文件: 49 | 50 | > 以任意文本编辑器打开 ```config.yml```,根据各个配置项的说明选择你需要的配置即可。 51 | 52 | 此外软件也支持从命令行指定运行参数(命令行参数的优先级高于配置文件)。运行 ```JavSP -h``` 查看支持的参数列表 53 | 54 | 更详细的使用说明请前往 [JavSP Wiki](https://github.com/Yuukiy/JavSP/wiki) 查看 55 | 56 | 如果使用的时候遇到问题也欢迎给我反馈😊 57 | 58 | ## 问题反馈 59 | 60 | 如果使用中遇到了 Bug,请[前往 Issue 区反馈](https://github.com/Yuukiy/JavSP/issues)(提问前请先搜索是否已有类似问题) 61 | 62 | 63 | ## 参与贡献 64 | 65 | 此项目不需要捐赠。如果你想要帮助改进这个项目,欢迎通过以下方式参与进来(并不仅局限于代码): 66 | 67 | - 帮助撰写和改进Wiki 68 | 69 | - 帮助完善单元测试数据(不必非要写代码,例如如果你发现有某系列的番号识别不准确,总结一下提issue也是很好的) 70 | 71 | - 帮助翻译 genre 72 | 73 | - Bugfix / 新功能?欢迎发 Pull Request 74 | 75 | - 要不考虑点个 Star ?(我会很开心的) 76 | 77 | 78 | ## 许可 79 | 80 | 此项目的所有权利与许可受 GPL-3.0 License 与 [Anti 996 License](https://github.com/996icu/996.ICU/blob/master/LICENSE_CN) 共同限制。此外,如果你使用此项目,表明你还额外接受以下条款: 81 | 82 | - 本软件仅供学习 Python 和技术交流使用 83 | 84 | - 请勿在微博、微信等墙内的公共社交平台上宣传此项目 85 | 86 | - 用户在使用本软件时,请遵守当地法律法规 87 | 88 | - 禁止将本软件用于商业用途 89 | 90 | 91 | --- 92 | 93 | ![Star History Chart](https://api.star-history.com/svg?repos=Yuukiy/JavSP&type=Date) 94 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.12-slim AS builder 2 | 3 | WORKDIR /app 4 | 5 | ENV PATH=/root/.local/bin:$PATH 6 | RUN pip install pipx && \ 7 | pipx ensurepath && \ 8 | pipx install poetry 9 | RUN apt update && apt install -y git 10 | 11 | COPY . . 12 | 13 | RUN poetry self add poetry-dynamic-versioning && \ 14 | poetry config virtualenvs.in-project true && \ 15 | poetry install && \ 16 | rm -rf /app/.git 17 | 18 | 19 | FROM python:3.12-slim as runner 20 | 21 | WORKDIR /app 22 | 23 | COPY --from=builder /app/ /app/ 24 | 25 | ENTRYPOINT ["/app/.venv/bin/javsp"] 26 | CMD ["-i", "/video"] 27 | -------------------------------------------------------------------------------- /image/JavSP.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuukiy/JavSP/c4cfe61188234dd24c75b53b42b054327fef3e58/image/JavSP.ico -------------------------------------------------------------------------------- /image/sub_mark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuukiy/JavSP/c4cfe61188234dd24c75b53b42b054327fef3e58/image/sub_mark.png -------------------------------------------------------------------------------- /image/unc_mark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuukiy/JavSP/c4cfe61188234dd24c75b53b42b054327fef3e58/image/unc_mark.png -------------------------------------------------------------------------------- /javsp/cropper/__init__.py: -------------------------------------------------------------------------------- 1 | from javsp.config import SlimefaceEngine 2 | from javsp.cropper.interface import Cropper, DefaultCropper 3 | from javsp.cropper.slimeface_crop import SlimefaceCropper 4 | 5 | def get_cropper(engine: SlimefaceEngine | None) -> Cropper: 6 | if engine is None: 7 | return DefaultCropper() 8 | if engine.name == 'slimeface': 9 | return SlimefaceCropper() 10 | -------------------------------------------------------------------------------- /javsp/cropper/interface.py: -------------------------------------------------------------------------------- 1 | from PIL.Image import Image 2 | from abc import ABC, abstractmethod 3 | class Cropper(ABC): 4 | @abstractmethod 5 | def crop_specific(self, fanart: Image, ratio: float) -> Image: 6 | pass 7 | 8 | def crop(self, fanart: Image, ratio: float | None = None) -> Image: 9 | if ratio is None: 10 | ratio = 1.42 11 | return self.crop_specific(fanart, ratio) 12 | 13 | class DefaultCropper(Cropper): 14 | def crop_specific(self, fanart: Image, ratio: float) -> Image: 15 | """将给定的fanart图片文件裁剪为适合poster尺寸的图片""" 16 | (fanart_w, fanart_h) = fanart.size 17 | (poster_w, poster_h) = \ 18 | (int(fanart_h / ratio), fanart_h) \ 19 | if fanart_h / fanart_w < ratio \ 20 | else (fanart_w, int(fanart_w * ratio)) # 图片太“瘦”时以宽度来定裁剪高度 21 | 22 | dh = int((fanart_h - poster_h) / 2) 23 | box = (fanart_w - poster_w, dh, fanart_w, poster_h + dh) 24 | return fanart.crop(box) 25 | -------------------------------------------------------------------------------- /javsp/cropper/slimeface_crop.py: -------------------------------------------------------------------------------- 1 | from PIL import Image 2 | from javsp.cropper.interface import Cropper, DefaultCropper 3 | from javsp.cropper.utils import get_bound_box_by_face 4 | 5 | class SlimefaceCropper(Cropper): 6 | def crop_specific(self, fanart: Image.Image, ratio: float) -> Image.Image: 7 | try: 8 | # defer the libary import so we don't break if missing dependencies 9 | from slimeface import detectRGB 10 | bbox_confs = detectRGB(fanart.width, fanart.height, fanart.convert('RGB').tobytes()) 11 | bbox_confs.sort(key=lambda conf_bbox: -conf_bbox[4]) # last arg stores confidence 12 | face = bbox_confs[0][:-1] 13 | poster_box = get_bound_box_by_face(face, fanart.size, ratio) 14 | return fanart.crop(poster_box) 15 | except: 16 | return DefaultCropper().crop_specific(fanart, ratio) 17 | 18 | if __name__ == '__main__': 19 | from argparse import ArgumentParser 20 | 21 | arg_parser = ArgumentParser(prog='slimeface crop') 22 | 23 | arg_parser.add_argument('-i', '--image', help='path to image to detect') 24 | 25 | args, _ = arg_parser.parse_known_args() 26 | 27 | if(args.image is None): 28 | print("USAGE: slimeface_crop.py -i/--image [path]") 29 | exit(1) 30 | 31 | input = Image.open(args.image) 32 | im = SlimefaceCropper().crop(input) 33 | im.save('output.png') 34 | 35 | -------------------------------------------------------------------------------- /javsp/cropper/utils.py: -------------------------------------------------------------------------------- 1 | def get_poster_size(image_shape: tuple[int, int], ratio: float) -> tuple[int, int]: 2 | (fanart_w, fanart_h) = image_shape 3 | (poster_w, poster_h) = \ 4 | (int(fanart_h / ratio), fanart_h) \ 5 | if fanart_h / fanart_w < ratio \ 6 | else (fanart_w, int(fanart_w * ratio)) # 图片太“瘦”时以宽度来定裁剪高度 7 | return (poster_w, poster_h) 8 | 9 | def get_bound_box_by_face(face: tuple[int, int, int, int], image_shape: tuple[int, int], ratio: float) -> tuple[int, int, int, int]: 10 | """ 11 | returns (left, upper, right, lower) 12 | """ 13 | 14 | (fanart_w, fanart_h) = image_shape 15 | (poster_w, poster_h) = get_poster_size(image_shape, ratio) 16 | 17 | # face coordinates 18 | fx, fy, fw, fh = face 19 | 20 | # face center 21 | cx, cy = fx + fw / 2, fy + fh / 2 22 | 23 | poster_left = max(cx - poster_w / 2, 0) 24 | poster_left = min(poster_left, fanart_w - poster_w) 25 | poster_left = int(poster_left) 26 | return (poster_left, 0, poster_left + poster_w, poster_h) 27 | 28 | -------------------------------------------------------------------------------- /javsp/image.py: -------------------------------------------------------------------------------- 1 | """处理本地图片的相关功能""" 2 | from enum import Enum 3 | import os 4 | import logging 5 | from PIL import Image, ImageOps 6 | 7 | 8 | __all__ = ['valid_pic', 'get_pic_size', 'add_label_to_poster', 'LabelPostion'] 9 | 10 | logger = logging.getLogger(__name__) 11 | 12 | 13 | def valid_pic(pic_path): 14 | """检查图片文件是否完整""" 15 | try: 16 | img = ImageOps.exif_transpose(Image.open(pic_path)) 17 | img.load() 18 | return True 19 | except Exception as e: 20 | logger.debug(e, exc_info=True) 21 | return False 22 | 23 | 24 | # 位置枚举 25 | class LabelPostion(Enum): 26 | """水印位置枚举""" 27 | TOP_LEFT = 1 28 | TOP_RIGHT = 2 29 | BOTTOM_LEFT = 3 30 | BOTTOM_RIGHT = 4 31 | 32 | def add_label_to_poster(poster: Image.Image, mark_pic_file: Image.Image, pos: LabelPostion) -> Image.Image: 33 | """向poster中添加标签(水印)""" 34 | mark_img = mark_pic_file.convert('RGBA') 35 | r,g,b,a = mark_img.split() 36 | # 计算水印位置 37 | if pos == LabelPostion.TOP_LEFT: 38 | box = (0, 0) 39 | elif pos == LabelPostion.TOP_RIGHT: 40 | box = (poster.size[0] - mark_img.size[0], 0) 41 | elif pos == LabelPostion.BOTTOM_LEFT: 42 | box = (0, poster.size[1] - mark_img.size[1]) 43 | elif pos == LabelPostion.BOTTOM_RIGHT: 44 | box = (poster.size[0] - mark_img.size[0], poster.size[1] - mark_img.size[1]) 45 | poster.paste(mark_img, box=box, mask=a) 46 | return poster 47 | 48 | 49 | def get_pic_size(pic_path): 50 | """获取图片文件的分辨率""" 51 | pic = ImageOps.exif_transpose(Image.open(pic_path)) 52 | return pic.size 53 | -------------------------------------------------------------------------------- /javsp/lib.py: -------------------------------------------------------------------------------- 1 | """用来组织不需要依赖任何自定义类型的功能函数""" 2 | import os 3 | import re 4 | import sys 5 | from pathlib import Path 6 | 7 | 8 | __all__ = ['re_escape', 'resource_path', 'strftime_to_minutes', 'detect_special_attr'] 9 | 10 | 11 | _special_chars_map = {i: '\\' + chr(i) for i in b'()[]{}?*+|^$\\.'} 12 | def re_escape(s: str) -> str: 13 | """用来对字符串进行转义,以将转义后的字符串用于构造正则表达式""" 14 | pattern = s.translate(_special_chars_map) 15 | return pattern 16 | 17 | 18 | def resource_path(path: str) -> str: 19 | """获取一个随代码打包的文件在解压后的路径""" 20 | if getattr(sys, "frozen", False): 21 | return path 22 | else: 23 | path_joined = Path(__file__).parent.parent / path 24 | return str(path_joined) 25 | 26 | 27 | def strftime_to_minutes(s: str) -> int: 28 | """将HH:MM:SS或MM:SS的时长转换为分钟数返回 29 | 30 | Args: 31 | s (str): HH:MM:SS or MM:SS 32 | 33 | Returns: 34 | [int]: 取整后的分钟数 35 | """ 36 | items = list(map(int, s.split(':'))) 37 | if len(items) == 2: 38 | minutes = items[0] + round(items[1]/60) 39 | elif len(items) == 3: 40 | minutes = items[0] * 60 + items[1] + round(items[2]/60) 41 | else: 42 | raise ValueError(f"无法将字符串'{s}'转换为分钟") 43 | return minutes 44 | 45 | 46 | _PATTERN = re.compile(r'(uncen(sor(ed)?)?([- _\s]*leak(ed)?)?|[无無][码碼](流出|破解))', flags=re.I) 47 | def detect_special_attr(filepath: str, avid: str = None) -> str: 48 | """通过文件名检测影片是否有特殊属性(内嵌字幕、无码流出/破解) 49 | 50 | Returns: 51 | [str]: '', 'U', 'C', 'UC' 52 | """ 53 | result = '' 54 | base = os.path.splitext(os.path.basename(filepath))[0].upper() 55 | # 尝试使用正则匹配 56 | match = _PATTERN.search(base) 57 | if match: 58 | result += 'U' 59 | # 尝试匹配-C/-U/-UC后缀的影片 60 | postfix = base.split('-')[-1] 61 | if postfix in ('U', 'C', 'UC'): 62 | result += postfix 63 | elif avid: 64 | pattern_str = re.sub(r'[_-]', '[_-]*', avid) + r'(UC|U|C)\b' 65 | match = re.search(pattern_str, base, flags=re.I) 66 | if match: 67 | result += match.group(1) 68 | # 最终格式化 69 | result = ''.join(sorted(set(result), reverse=True)) 70 | return result 71 | 72 | 73 | if __name__ == "__main__": 74 | print(detect_special_attr('ipx-177cd1.mp4', 'IPX-177')) 75 | -------------------------------------------------------------------------------- /javsp/nfo.py: -------------------------------------------------------------------------------- 1 | """与操作nfo文件相关的功能""" 2 | from lxml.etree import tostring 3 | from lxml.builder import E 4 | 5 | 6 | from javsp.datatype import MovieInfo 7 | from javsp.config import Cfg 8 | 9 | 10 | def write_nfo(info: MovieInfo, nfo_file): 11 | """将存储了影片信息的'info'写入到nfo文件中""" 12 | # NFO spec: https://kodi.wiki/view/NFO_files/Movies 13 | nfo = E.movie() 14 | dic = info.get_info_dic() 15 | 16 | if info.nfo_title: 17 | nfo.append(E.title(info.nfo_title)) 18 | else: 19 | nfo.append(E.title(info.title)) 20 | 21 | # 仅在标题被处理过时'ori_title'字段才会有值 22 | if info.ori_title: 23 | nfo.append(E.originaltitle(info.ori_title)) 24 | 25 | # Kodi的文档中评分支持多个来源,但经测试,添加了多个评分时Kodi也只显示了第一个评分 26 | if info.score: 27 | nfo.append(E.rating(info.score)) 28 | 29 | # 目前没有合适的字段用于outline(一行简短的介绍),力求不在nfo中写入冗余的信息,因此不添加outline标签 30 | # 而且无论是Kodi还是Jellyfin中都没有找到实际显示outline的位置;tagline倒是都有发现 31 | 32 | if info.plot: 33 | nfo.append(E.plot(info.plot)) 34 | 35 | # 目前没有合适的字段用于tagline(一行简短的介绍) 36 | 37 | # 并不是每个数据源都有影片的时长信息(例如airav) 38 | if info.duration: 39 | nfo.append(E.runtime(info.duration)) 40 | 41 | # thumb字段可以用来为不同的aspect强制指定图片文件名 42 | # 例如可以将'NoPoster.jpg'指定给'ABC-123.mp4',而不必按照poster文件名的常规命名规则来 43 | # 但是Emby不支持此特性,Jellyfin的文档和社区都比较弱,没找到相关说明,推测多半也不支持 44 | 45 | # fanart通常也是通过给fanart图片命名来匹配 46 | nfo.append(E.mpaa('NC-17')) # 分级 47 | 48 | # 将DVD ID和CID写入到uniqueid字段 49 | if info.dvdid: 50 | nfo.append(E.uniqueid(info.dvdid, type='num', default='true')) 51 | if info.cid: 52 | nfo.append(E.uniqueid(info.cid, type='cid')) 53 | 54 | # 选择要写入的genre数据源字段:将[]作为后备结果,以确保genre结果为None时后续不会抛出异常 55 | for genre_item in (info.genre_norm, info.genre, []): 56 | if genre_item: 57 | break 58 | 59 | genre = genre_item.copy() 60 | # 添加自定义分类 61 | for genre_new in Cfg().summarizer.nfo.custom_genres_fields: 62 | genre.append(genre_new.format(**dic)) 63 | # 分类去重 64 | genre = list(set(genre)) 65 | # 写入genre分类:优先使用genre_norm。在Jellyfin上,只有genre可以直接跳转,tag不可以 66 | # 也同时写入tag。TODO: 还没有研究tag和genre在Kodi上的区别 67 | for i in genre: 68 | nfo.append(E.genre(i)) 69 | 70 | tags = [] 71 | # 添加自定义tag 72 | for tag_new in Cfg().summarizer.nfo.custom_tags_fields: 73 | tags.append(tag_new.format(**dic)) 74 | # 去重 75 | tags = list(set(tags)) 76 | # 写入tag 77 | for i in tags: 78 | nfo.append(E.tag(i)) 79 | 80 | # Kodi上的country字段没说必须使用国家的代码(比如JP),所以目前暂定直接使用国家名 81 | nfo.append(E.country('日本')) 82 | 83 | if info.serial: 84 | # 部分影片有系列。set字段支持overview作为介绍,但是目前没发现有地方可以获取到系列的介绍 85 | nfo.append(E.set(E.name(info.serial))) 86 | 87 | if info.director: 88 | nfo.append(E.director(info.director)) 89 | 90 | # 发行日期。文档中关于'year'字段的说明: Do not use. Use instead 91 | if info.publish_date: 92 | nfo.append(E.premiered(info.publish_date)) 93 | 94 | # 原文是 Production studio: 因此这里写入的是影片制作商 95 | if info.producer: 96 | nfo.append(E.studio(info.producer)) 97 | 98 | # trailer 预告片 99 | if info.preview_video: 100 | nfo.append(E.trailer(info.preview_video)) 101 | 102 | # TODO: fileinfo 字段,看起来可以给定字幕语言和类型,留待开发 103 | 104 | # 写入演员名。Kodi支持用thumb显示演员头像,如果能获取到演员头像也一并写入 105 | if info.actress: 106 | for i in info.actress: 107 | if (info.actress_pics) and (i in info.actress_pics): 108 | nfo.append(E.actor(E.name(i), E.thumb(info.actress_pics[i]))) 109 | else: 110 | nfo.append(E.actor(E.name(i))) 111 | 112 | with open(nfo_file, 'wt', encoding='utf-8') as f: 113 | f.write(tostring(nfo, encoding='unicode', pretty_print=True, 114 | doctype='')) 115 | 116 | 117 | if __name__ == "__main__": 118 | import pretty_errors 119 | pretty_errors.configure(display_link=True) 120 | info = MovieInfo(from_file=R'unittest\data\IPX-177 (javbus).json') 121 | write_nfo(info) 122 | -------------------------------------------------------------------------------- /javsp/print.py: -------------------------------------------------------------------------------- 1 | """改写内置的print函数,将其输出重定向到tqdm""" 2 | import tqdm 3 | import inspect 4 | 5 | 6 | __all__ = ['TqdmOut'] 7 | 8 | 9 | # 普通输出和tqdm的输出混在一起会导致显示错乱,故在使用tqdm时要使用tqdm.write方法。 10 | # 但是不希望又每个模块都使用tqdm.write方法,这样会显得混乱而且会导致与tqdm强耦合。 11 | # 这个模块被设计来解决上面的问题:导入此模块后,全局覆盖内置的print,将输出都重定向到tqdm。 12 | # 导入只在项目入口执行,这将改写所有后续导入的模块中的print的行为; 13 | # 在单个模块内,不执行导入,这样的话在各个模块内仍然可以直接使用print 14 | 15 | builtin_print = print 16 | def flex_print(*args, **kwargs): 17 | try: 18 | tqdm.tqdm.write(*args, **kwargs) 19 | except: 20 | builtin_print(*args, ** kwargs) 21 | # 替换内置的print 22 | inspect.builtins.print = flex_print 23 | 24 | 25 | class TqdmOut: 26 | """用于将logging的stream输出重定向到tqdm""" 27 | @classmethod 28 | def write(cls, s, file=None, nolock=False): 29 | tqdm.tqdm.write(s, file=file, end='', nolock=nolock) 30 | -------------------------------------------------------------------------------- /javsp/prompt.py: -------------------------------------------------------------------------------- 1 | from javsp.config import Cfg 2 | def prompt(message: str, what: str) -> str: 3 | if Cfg().other.interactive: 4 | return input(message) 5 | else: 6 | print(f"缺少{what}") 7 | exit(1) 8 | -------------------------------------------------------------------------------- /javsp/web/arzon.py: -------------------------------------------------------------------------------- 1 | """从arzon抓取数据""" 2 | import os 3 | import sys 4 | import logging 5 | import re 6 | 7 | from javsp.web.base import request_get 8 | from javsp.web.exceptions import * 9 | from javsp.datatype import MovieInfo 10 | import requests 11 | from lxml import html 12 | 13 | logger = logging.getLogger(__name__) 14 | base_url = "https://www.arzon.jp" 15 | 16 | def get_cookie(): 17 | # https://www.arzon.jp/index.php?action=adult_customer_agecheck&agecheck=1&redirect=https%3A%2F%2Fwww.arzon.jp%2F 18 | skip_verify_url = "http://www.arzon.jp/index.php?action=adult_customer_agecheck&agecheck=1" 19 | session = requests.Session() 20 | session.get(skip_verify_url, timeout=(12, 7)) 21 | return session.cookies.get_dict() 22 | 23 | def parse_data(movie: MovieInfo): 24 | """解析指定番号的影片数据""" 25 | full_id = movie.dvdid 26 | cookies = get_cookie() 27 | url = f'{base_url}/itemlist.html?t=&m=all&s=&q={full_id}' 28 | # url = f'{base_url}/imagelist.html?q={full_id}' 29 | r = request_get(url, cookies, delay_raise=True) 30 | if r.status_code == 404: 31 | raise MovieNotFoundError(__name__, movie.dvdid) 32 | # https://stackoverflow.com/questions/15830421/xml-unicode-strings-with-encoding-declaration-are-not-supported 33 | data = html.fromstring(r.content) 34 | 35 | urls = data.xpath("//h2/a/@href") 36 | if len(urls) == 0: 37 | raise MovieNotFoundError(__name__, movie.dvdid) 38 | 39 | item_url = base_url + urls[0] 40 | e = request_get(item_url, cookies, delay_raise=True) 41 | item = html.fromstring(e.content) 42 | 43 | title = item.xpath("//div[@class='detail_title_new2']//h1/text()")[0] 44 | cover = item.xpath("//td[@align='center']//a/img/@src")[0] 45 | item_text = item.xpath("//div[@class='item_text']/text()") 46 | plot = [item.strip() for item in item_text if item.strip() != ''][0] 47 | preview_pics_arr = item.xpath("//div[@class='detail_img']//img/@src") 48 | # 使用列表推导式添加 "http:" 并去除 "m_" 49 | preview_pics = [("https:" + url).replace("m_", "") for url in preview_pics_arr] 50 | 51 | container = item.xpath("//div[@class='item_register']/table//tr") 52 | for row in container: 53 | key = row.xpath("./td[1]/text()")[0] 54 | contents = row.xpath("./td[2]//text()") 55 | content = [item.strip() for item in contents if item.strip() != ''] 56 | index = 0 57 | value = content[index] if content and index < len(content) else None 58 | if key == "AV女優:": 59 | movie.actress = content 60 | if key == "AVメーカー:": 61 | movie.producer = value 62 | if key == "AVレーベル:": 63 | video_type = value 64 | if key == "シリーズ:": 65 | movie.serial = value 66 | if key == "監督:": 67 | movie.director = value 68 | if key == "発売日:" and value: 69 | movie.publish_date = re.search(r"\d{4}/\d{2}/\d{2}", value).group(0).replace("/", "-") 70 | if key == "収録時間:" and value: 71 | movie.duration = re.search(r'([\d.]+)分', value).group(1) 72 | if key == "品番:": 73 | dvd_id = value 74 | elif key == "タグ:": 75 | genre = value 76 | 77 | genres = '' 78 | if video_type: 79 | genres = [video_type] 80 | if(genre != None): 81 | genres.append(genre) 82 | 83 | movie.genre = genres 84 | movie.url = item_url 85 | movie.title = title 86 | movie.plot = plot 87 | movie.cover = f'https:{cover}' 88 | movie.preview_pics = preview_pics 89 | 90 | if __name__ == "__main__": 91 | import pretty_errors 92 | pretty_errors.configure(display_link=True) 93 | logger.root.handlers[1].level = logging.DEBUG 94 | 95 | movie = MovieInfo('csct-011') 96 | try: 97 | parse_data(movie) 98 | print(movie) 99 | except CrawlerError as e: 100 | logger.error(e, exc_info=1) 101 | -------------------------------------------------------------------------------- /javsp/web/arzon_iv.py: -------------------------------------------------------------------------------- 1 | """从arzon抓取数据""" 2 | import os 3 | import sys 4 | import logging 5 | import re 6 | 7 | from javsp.web.base import request_get 8 | from javsp.web.exceptions import * 9 | from javsp.datatype import MovieInfo 10 | import requests 11 | from lxml import html 12 | 13 | logger = logging.getLogger(__name__) 14 | base_url = "https://www.arzon.jp" 15 | 16 | def get_cookie(): 17 | # https://www.arzon.jp/index.php?action=adult_customer_agecheck&agecheck=1&redirect=https%3A%2F%2Fwww.arzon.jp%2F 18 | skip_verify_url = "http://www.arzon.jp/index.php?action=adult_customer_agecheck&agecheck=1" 19 | session = requests.Session() 20 | session.get(skip_verify_url, timeout=(12, 7)) 21 | return session.cookies.get_dict() 22 | 23 | def parse_data(movie: MovieInfo): 24 | """解析指定番号的影片数据""" 25 | full_id = movie.dvdid 26 | cookies = get_cookie() 27 | url = f'{base_url}/imagelist.html?q={full_id}' 28 | r = request_get(url, cookies, delay_raise=True) 29 | if r.status_code == 404: 30 | raise MovieNotFoundError(__name__, movie.dvdid) 31 | # https://stackoverflow.com/questions/15830421/xml-unicode-strings-with-encoding-declaration-are-not-supported 32 | data = html.fromstring(r.content) 33 | 34 | urls = data.xpath("//h2/a/@href") 35 | if len(urls) == 0: 36 | raise MovieNotFoundError(__name__, movie.dvdid) 37 | 38 | item_url = base_url + urls[0] 39 | e = request_get(item_url, cookies, delay_raise=True) 40 | item = html.fromstring(e.content) 41 | 42 | title = item.xpath("//div[@class='detail_title_new']//h1/text()")[0] 43 | cover = item.xpath("//td[@align='center']//a/img/@src")[0] 44 | item_text = item.xpath("//div[@class='item_text']/text()") 45 | plot = [item.strip() for item in item_text if item.strip() != ''][0] 46 | 47 | container = item.xpath("//div[@class='item_register']/table//tr") 48 | for row in container: 49 | key = row.xpath("./td[1]/text()")[0] 50 | contents = row.xpath("./td[2]//text()") 51 | content = [item.strip() for item in contents if item.strip() != ''] 52 | index = 0 53 | value = content[index] if content and index < len(content) else None 54 | if key == "タレント:": 55 | movie.actress = content 56 | if key == "イメージメーカー:": 57 | movie.producer = value 58 | if key == "イメージレーベル:": 59 | video_type = value 60 | if key == "監督:": 61 | movie.director = value 62 | if key == "発売日:" and value: 63 | movie.publish_date = re.search(r"\d{4}/\d{2}/\d{2}", value).group(0).replace("/", "-") 64 | if key == "収録時間:" and value: 65 | movie.duration = re.search(r'([\d.]+)分', value).group(1) 66 | if key == "品番:": 67 | dvd_id = value 68 | elif key == "タグ:": 69 | genre = value 70 | 71 | genres = '' 72 | if video_type: 73 | genres = [video_type] 74 | if(genre != None): 75 | genres.append(genre) 76 | 77 | movie.genre = genres 78 | movie.url = item_url 79 | movie.title = title 80 | movie.plot = plot 81 | movie.cover = f'https:{cover}' 82 | 83 | if __name__ == "__main__": 84 | import pretty_errors 85 | pretty_errors.configure(display_link=True) 86 | logger.root.handlers[1].level = logging.DEBUG 87 | 88 | movie = MovieInfo('KIDM-1137B') 89 | try: 90 | parse_data(movie) 91 | print(movie) 92 | except CrawlerError as e: 93 | logger.error(e, exc_info=1) 94 | -------------------------------------------------------------------------------- /javsp/web/avsox.py: -------------------------------------------------------------------------------- 1 | """从avsox抓取数据""" 2 | import logging 3 | 4 | from javsp.web.base import get_html 5 | from javsp.web.exceptions import * 6 | from javsp.config import Cfg, CrawlerID 7 | from javsp.datatype import MovieInfo 8 | 9 | 10 | logger = logging.getLogger(__name__) 11 | base_url = str(Cfg().network.proxy_free[CrawlerID.avsox]) 12 | 13 | 14 | def parse_data(movie: MovieInfo): 15 | """解析指定番号的影片数据""" 16 | # avsox无法直接跳转到影片的网页,因此先搜索再从搜索结果中寻找目标网页 17 | full_id = movie.dvdid 18 | if full_id.startswith('FC2-'): 19 | full_id = full_id.replace('FC2-', 'FC2-PPV-') 20 | html = get_html(f'{base_url}tw/search/{full_id}') 21 | ids = html.xpath("//div[@class='photo-info']/span/date[1]/text()") 22 | urls = html.xpath("//a[contains(@class, 'movie-box')]/@href") 23 | ids_lower = list(map(str.lower, ids)) 24 | if full_id.lower() in ids_lower: 25 | url = urls[ids_lower.index(full_id.lower())] 26 | url = url.replace('/tw/', '/cn/', 1) 27 | else: 28 | raise MovieNotFoundError(__name__, movie.dvdid, ids) 29 | 30 | # 提取影片信息 31 | html = get_html(url) 32 | container = html.xpath("/html/body/div[@class='container']")[0] 33 | title = container.xpath("h3/text()")[0] 34 | cover = container.xpath("//a[@class='bigImage']/@href")[0] 35 | info = container.xpath("div/div[@class='col-md-3 info']")[0] 36 | dvdid = info.xpath("p/span[@style]/text()")[0] 37 | publish_date = info.xpath("p/span[text()='发行时间:']")[0].tail.strip() 38 | duration = info.xpath("p/span[text()='长度:']")[0].tail.replace('分钟', '').strip() 39 | producer, serial = None, None 40 | producer_tag = info.xpath("p[text()='制作商: ']")[0].getnext().xpath("a") 41 | if producer_tag: 42 | producer = producer_tag[0].text_content() 43 | serial_tag = info.xpath("p[text()='系列:']") 44 | if serial_tag: 45 | serial = serial_tag[0].getnext().xpath("a/text()")[0] 46 | genre = info.xpath("p/span[@class='genre']/a/text()") 47 | actress = container.xpath("//a[@class='avatar-box']/span/text()") 48 | 49 | movie.dvdid = dvdid.replace('FC2-PPV-', 'FC2-') 50 | movie.url = url 51 | movie.title = title.replace(dvdid, '').strip() 52 | movie.cover = cover 53 | movie.publish_date = publish_date 54 | movie.duration = duration 55 | movie.genre = genre 56 | movie.actress = actress 57 | if full_id.startswith('FC2-'): 58 | # avsox把FC2作品的拍摄者归类到'系列'而制作商固定为'FC2-PPV',这既不合理也与其他的站点不兼容,因此进行调整 59 | movie.producer = serial 60 | else: 61 | movie.producer = producer 62 | movie.serial = serial 63 | 64 | 65 | if __name__ == "__main__": 66 | import pretty_errors 67 | pretty_errors.configure(display_link=True) 68 | logger.root.handlers[1].level = logging.DEBUG 69 | 70 | movie = MovieInfo('082713-417') 71 | try: 72 | parse_data(movie) 73 | print(movie) 74 | except CrawlerError as e: 75 | logger.error(e, exc_info=1) 76 | -------------------------------------------------------------------------------- /javsp/web/avwiki.py: -------------------------------------------------------------------------------- 1 | """从av-wiki抓取数据""" 2 | import logging 3 | 4 | 5 | from javsp.web.base import * 6 | from javsp.web.exceptions import * 7 | from javsp.datatype import MovieInfo 8 | 9 | logger = logging.getLogger(__name__) 10 | base_url = 'https://av-wiki.net' 11 | 12 | 13 | def parse_data(movie: MovieInfo): 14 | """从网页抓取并解析指定番号的数据 15 | Args: 16 | movie (MovieInfo): 要解析的影片信息,解析后的信息直接更新到此变量内 17 | """ 18 | movie.url = url = f'{base_url}/{movie.dvdid}' 19 | resp = request_get(url, delay_raise=True) 20 | if resp.status_code == 404: 21 | raise MovieNotFoundError(__name__, movie.dvdid) 22 | html = resp2html(resp) 23 | 24 | cover_tag = html.xpath("//header/div/a[@class='image-link-border']/img") 25 | if cover_tag: 26 | try: 27 | srcset = cover_tag[0].get('srcset').split(', ') 28 | src_set_urls = {} 29 | for src in srcset: 30 | url, width = src.split() 31 | width = int(width.rstrip('w')) 32 | src_set_urls[width] = url 33 | max_pic = sorted(src_set_urls.items(), key=lambda x:x[0], reverse=True) 34 | movie.cover = max_pic[0][1] 35 | except: 36 | movie.cover = cover_tag[0].get('src') 37 | body = html.xpath("//section[@class='article-body']")[0] 38 | title = body.xpath("div/p/text()")[0] 39 | title = title.replace(f"【{movie.dvdid}】", '') 40 | cite_url = body.xpath("div/cite/a/@href")[0] 41 | cite_url = cite_url.split('?aff=')[0] 42 | info = body.xpath("dl[@class='dltable']")[0] 43 | dt_txt_ls, dd_tags = info.xpath("dt/text()"), info.xpath("dd") 44 | data = {} 45 | for dt_txt, dd in zip(dt_txt_ls, dd_tags): 46 | dt_txt = dt_txt.strip() 47 | a_tag = dd.xpath('a') 48 | if len(a_tag) == 0: 49 | dd_txt = dd.text.strip() 50 | else: 51 | dd_txt = [i.text.strip() for i in a_tag] 52 | if isinstance(dd_txt, list) and dt_txt != 'AV女優名': # 只有女优名以列表的数据格式保留 53 | dd_txt = dd_txt[0] 54 | data[dt_txt] = dd_txt 55 | 56 | ATTR_MAP = {'メーカー': 'producer', 'AV女優名': 'actress', 'メーカー品番': 'dvdid', 'シリーズ': 'serial', '配信開始日': 'publish_date'} 57 | for key, attr in ATTR_MAP.items(): 58 | setattr(movie, attr, data.get(key)) 59 | movie.title = title 60 | movie.uncensored = False # 服务器在日本且面向日本国内公开发售,不会包含无码片 61 | 62 | 63 | if __name__ == "__main__": 64 | import pretty_errors 65 | pretty_errors.configure(display_link=True) 66 | 67 | movie = MovieInfo('259LUXU-593') 68 | try: 69 | parse_data(movie) 70 | print(movie) 71 | except CrawlerError as e: 72 | logger.error(e, exc_info=1) 73 | -------------------------------------------------------------------------------- /javsp/web/dl_getchu.py: -------------------------------------------------------------------------------- 1 | """从dl.getchu官网抓取数据""" 2 | import re 3 | import logging 4 | 5 | from javsp.web.base import resp2html, request_get 6 | from javsp.web.exceptions import * 7 | from javsp.datatype import MovieInfo 8 | 9 | logger = logging.getLogger(__name__) 10 | 11 | # https://dl.getchu.com/i/item4045373 12 | base_url = 'https://dl.getchu.com' 13 | # dl.getchu用utf-8会乱码 14 | base_encode = 'euc-jp' 15 | 16 | 17 | def get_movie_title(html): 18 | container = html.xpath("//form[@action='https://dl.getchu.com/cart/']/div/table[2]") 19 | if len(container) > 0: 20 | container = container[0] 21 | rows = container.xpath('.//tr') 22 | title = '' 23 | for row in rows: 24 | for cell in row.xpath('.//td/div'): 25 | # 获取单元格文本内容 26 | if cell.text: 27 | title = str(cell.text).strip() 28 | return title 29 | 30 | 31 | def get_movie_img(html, getchu_id): 32 | img_src = '' 33 | container = html.xpath(f'//img[contains(@src, "{getchu_id}top.jpg")]') 34 | if len(container) > 0: 35 | container = container[0] 36 | img_src = container.get('src') 37 | return img_src 38 | 39 | 40 | def get_movie_preview(html, getchu_id): 41 | preview_pics = [] 42 | container = html.xpath(f'//img[contains(@src, "{getchu_id}_")]') 43 | if len(container) > 0: 44 | for c in container: 45 | preview_pics.append(c.get('src')) 46 | return preview_pics 47 | 48 | 49 | DURATION_PATTERN = re.compile(r'(?:動画)?(\d+)分') 50 | def parse_data(movie: MovieInfo): 51 | """解析指定番号的影片数据""" 52 | # 去除番号中的'GETCHU'字样 53 | id_uc = movie.dvdid.upper() 54 | if not id_uc.startswith('GETCHU-'): 55 | raise ValueError('Invalid GETCHU number: ' + movie.dvdid) 56 | getchu_id = id_uc.replace('GETCHU-', '') 57 | # 抓取网页 58 | url = f'{base_url}/i/item{getchu_id}' 59 | r = request_get(url, delay_raise=True) 60 | if r.status_code == 404: 61 | raise MovieNotFoundError(__name__, movie.dvdid) 62 | html = resp2html(r, base_encode) 63 | container = html.xpath("//form[@action='https://dl.getchu.com/cart/']/div/table[3]") 64 | if len(container) > 0: 65 | container = container[0] 66 | # 将表格提取为键值对 67 | rows = container.xpath('.//table/tr') 68 | kv_rows = [i for i in rows if len(i) == 2] 69 | data = {} 70 | for row in kv_rows: 71 | # 获取单元格文本内容 72 | key = row.xpath("td[@class='bluetext']/text()")[0] 73 | # 是否包含a标签: 有的属性是用表示的,不是text 74 | a_tags = row.xpath("td[2]/a") 75 | if a_tags: 76 | value = [i.text for i in a_tags] 77 | else: 78 | # 获取第2个td标签的内容(下标从1开始计数) 79 | value = row.xpath("td[2]/text()") 80 | data[key] = value 81 | 82 | for key, value in data.items(): 83 | if key == 'サークル': 84 | movie.producer = value[0] 85 | elif key == '作者': 86 | # 暂时没有在getchu找到多个actress的片子 87 | movie.actress = [i.strip() for i in value] 88 | elif key == '画像数&ページ数': 89 | match = DURATION_PATTERN.search(' '.join(value)) 90 | if match: 91 | movie.duration = match.group(1) 92 | elif key == '配信開始日': 93 | movie.publish_date = value[0].replace('/', '-') 94 | elif key == '趣向': 95 | movie.genre = value 96 | elif key == '作品内容': 97 | idx = -1 98 | for i, line in enumerate(value): 99 | if line.lstrip().startswith('※'): 100 | idx = i 101 | break 102 | movie.plot = ''.join(value[:idx]) 103 | 104 | movie.title = get_movie_title(html) 105 | movie.cover = get_movie_img(html, getchu_id) 106 | movie.preview_pics = get_movie_preview(html, getchu_id) 107 | movie.dvdid = id_uc 108 | movie.url = url 109 | 110 | 111 | if __name__ == "__main__": 112 | import pretty_errors 113 | 114 | pretty_errors.configure(display_link=True) 115 | logger.root.handlers[1].level = logging.DEBUG 116 | 117 | movie = MovieInfo('getchu-4041026') 118 | try: 119 | parse_data(movie) 120 | print(movie) 121 | except CrawlerError as e: 122 | logger.error(e, exc_info=1) 123 | -------------------------------------------------------------------------------- /javsp/web/exceptions.py: -------------------------------------------------------------------------------- 1 | """网页抓取相关的异常""" 2 | __all__ = ['CrawlerError', 'MovieNotFoundError', 'MovieDuplicateError', 'SiteBlocked', 3 | 'SitePermissionError', 'CredentialError', 'WebsiteError', 'OtherError'] 4 | 5 | 6 | class CrawlerError(Exception): 7 | """所有站点抓取器相关异常的基类""" 8 | 9 | 10 | class MovieNotFoundError(CrawlerError): 11 | """表示某个站点没有抓取到某部影片""" 12 | # 保持异常消息的简洁,同时又支持使用'logger.info(e, exc_info=True)'记录完整信息 13 | def __init__(self, mod, avid, *args) -> None: 14 | msg = f"{mod}: 未找到影片: '{avid}'" 15 | super().__init__(msg, *args) 16 | 17 | def __str__(self): 18 | return self.args[0] 19 | 20 | 21 | class MovieDuplicateError(CrawlerError): 22 | """影片重复""" 23 | def __init__(self, mod, avid, dup_count, *args) -> None: 24 | msg = f"{mod}: '{avid}': 存在{dup_count}个完全匹配目标番号的搜索结果" 25 | super().__init__(msg, *args) 26 | 27 | def __str__(self): 28 | return self.args[0] 29 | 30 | 31 | class SiteBlocked(CrawlerError): 32 | """由于IP段或者触发反爬机制等原因导致用户被站点封锁""" 33 | 34 | 35 | class SitePermissionError(CrawlerError): 36 | """由于缺少权限而无法访问影片资源""" 37 | 38 | 39 | class CredentialError(CrawlerError): 40 | """由于缺少Cookies等凭据而无法访问影片资源""" 41 | 42 | 43 | class WebsiteError(CrawlerError): 44 | """非预期的状态码等网页故障""" 45 | 46 | 47 | class OtherError(CrawlerError): 48 | """其他尚未分类的错误""" 49 | -------------------------------------------------------------------------------- /javsp/web/fc2fan.py: -------------------------------------------------------------------------------- 1 | """解析fc2fan本地镜像的数据""" 2 | # FC2官网的影片下架就无法再抓取数据,如果用户有fc2fan的镜像,那可以尝试从镜像中解析影片数据 3 | import os 4 | import re 5 | import logging 6 | import lxml.html 7 | import requests 8 | 9 | 10 | from javsp.web.base import resp2html 11 | from javsp.web.exceptions import * 12 | from javsp.config import Cfg 13 | from javsp.datatype import MovieInfo 14 | 15 | 16 | logger = logging.getLogger(__name__) 17 | base_path = str(Cfg().crawler.fc2fan_local_path) 18 | use_local_mirror = os.path.exists(base_path) 19 | 20 | 21 | def parse_data(movie: MovieInfo): 22 | """解析指定番号的影片数据""" 23 | if use_local_mirror: 24 | html_file = f'{base_path}/{movie.dvdid}.html' 25 | if not os.path.exists(html_file): 26 | raise MovieNotFoundError(__name__, movie.dvdid, html_file) 27 | html = lxml.html.parse(html_file) 28 | else: 29 | url = f"https://fc2club.top/html/{movie.dvdid}.html" 30 | r = requests.get(url) 31 | if r.status_code == 404: 32 | raise MovieNotFoundError(__name__, movie.dvdid) 33 | elif r.text == '': 34 | raise WebsiteError(f'fc2fan: 站点不可用 (HTTP {r.status_code}): {url}') 35 | html = resp2html(r) 36 | try: 37 | container = html.xpath("//div[@class='col-sm-8']")[0] 38 | except IndexError: 39 | raise WebsiteError(f'fc2fan: 站点不可用') 40 | title = container.xpath("h3/text()")[0] 41 | score_str = container.xpath("h5/strong[text()='影片评分']")[0].tail.strip() 42 | match = re.search(r'\d+', score_str) 43 | if match: 44 | score = int(match.group()) / 10 # fc2fan站长是按100分来打分的 45 | movie.score = f'{score:.1f}' 46 | resource_info = container.xpath("h5/strong[text()='资源参数']")[0].tail 47 | if '无码' in resource_info: 48 | movie.uncensored = True 49 | elif '有码' in resource_info: 50 | movie.uncensored = False 51 | # FC2没有制作商和发行商的区分,作为个人市场,卖家更接近于制作商 52 | producer = container.xpath("h5/strong[text()='卖家信息']")[0].getnext().text 53 | if producer: 54 | movie.producer = producer.strip() 55 | genre = container.xpath("h5/strong[text()='影片标签']/../a/text()") 56 | actress = container.xpath("h5/strong[text()='女优名字']/../a/text()") 57 | preview_pics = container.xpath("//ul[@class='slides']/li/img/@src") 58 | if use_local_mirror: 59 | preview_pics = [os.path.normpath(os.path.join(base_path, i)) for i in preview_pics] 60 | # big_preview = container.xpath("//img[@id='thumbpic']/../@href")[0] # 影片真实截图,目前暂时用不到 61 | 62 | movie.title = title 63 | movie.genre = genre 64 | movie.actress = actress 65 | if preview_pics: 66 | movie.preview_pics = preview_pics 67 | movie.cover = preview_pics[0] 68 | 69 | 70 | if __name__ == "__main__": 71 | import pretty_errors 72 | pretty_errors.configure(display_link=True) 73 | logger.root.handlers[1].level = logging.DEBUG 74 | 75 | movie = MovieInfo('FC2-1879420') 76 | try: 77 | parse_data(movie) 78 | print(movie) 79 | except CrawlerError as e: 80 | logger.error(e, exc_info=1) 81 | -------------------------------------------------------------------------------- /javsp/web/fc2ppvdb.py: -------------------------------------------------------------------------------- 1 | """从FC2PPVDB抓取数据""" 2 | import logging 3 | from typing import List 4 | 5 | 6 | from javsp.web.base import get_html 7 | from javsp.web.exceptions import * 8 | from javsp.lib import strftime_to_minutes 9 | from javsp.datatype import MovieInfo 10 | 11 | 12 | logger = logging.getLogger(__name__) 13 | base_url = 'https://fc2ppvdb.com' 14 | 15 | 16 | def parse_data(movie: MovieInfo): 17 | """解析指定番号的影片数据""" 18 | # 去除番号中的'FC2'字样 19 | id_uc = movie.dvdid.upper() 20 | if not id_uc.startswith('FC2-'): 21 | raise ValueError('Invalid FC2 number: ' + movie.dvdid) 22 | fc2_id = id_uc.replace('FC2-', '') 23 | # 抓取网页 24 | url = f'{base_url}/articles/{fc2_id}' 25 | html = get_html(url) 26 | container = html.xpath("//div[@class='container lg:px-5 px-2 py-12 mx-auto']/div[1]") 27 | if len(container) > 0: 28 | container = container[0] 29 | else: 30 | raise MovieNotFoundError(__name__, movie.dvdid) 31 | 32 | title = container.xpath("//h2/a/text()") 33 | thumb_pic = container.xpath(f"//img[@alt='{fc2_id}']/@src") 34 | duration_str = container.xpath("//div[starts-with(text(),'収録時間:')]/span/text()") 35 | actress = container.xpath("//div[starts-with(text(),'女優:')]/span/a/text()") 36 | genre = container.xpath("//div[starts-with(text(),'タグ:')]/span/a/text()") 37 | publish_date = container.xpath("//div[starts-with(text(),'販売日:')]/span/text()") 38 | publisher = container.xpath("//div[starts-with(text(),'販売者:')]/span/a/text()") 39 | uncensored_str = container.xpath("//div[starts-with(text(),'モザイク:')]/span/text()") 40 | uncensored_str_f = get_list_first(uncensored_str); 41 | uncensored = True if uncensored_str_f == '無' else False if uncensored_str_f == '有' else None 42 | preview_pics = None 43 | preview_video = container.xpath("//a[starts-with(text(),'サンプル動画')]/@href") 44 | 45 | movie.dvdid = id_uc 46 | movie.url = url 47 | movie.title = get_list_first(title) 48 | movie.genre = genre 49 | movie.actress = actress 50 | movie.duration = str(strftime_to_minutes(get_list_first(duration_str))) 51 | movie.publish_date = get_list_first(publish_date) 52 | movie.publisher = get_list_first(publisher) 53 | movie.uncensored = uncensored 54 | movie.preview_pics = preview_pics 55 | movie.preview_video = get_list_first(preview_video) 56 | 57 | # FC2的封面是220x220的,和正常封面尺寸、比例都差太多。如果有预览图片,则使用第一张预览图作为封面 58 | if movie.preview_pics: 59 | movie.cover = preview_pics[0] 60 | else: 61 | movie.cover = get_list_first(thumb_pic) 62 | 63 | def get_list_first(list:List): 64 | return list[0] if list and len(list) > 0 else None 65 | 66 | if __name__ == "__main__": 67 | import pretty_errors 68 | pretty_errors.configure(display_link=True) 69 | logger.root.handlers[1].level = logging.DEBUG 70 | 71 | movie = MovieInfo('FC2-4497837') 72 | try: 73 | parse_data(movie) 74 | print(movie) 75 | except CrawlerError as e: 76 | logger.error(e, exc_info=1) 77 | -------------------------------------------------------------------------------- /javsp/web/gyutto.py: -------------------------------------------------------------------------------- 1 | """从https://gyutto.com/官网抓取数据""" 2 | import logging 3 | import time 4 | 5 | from javsp.web.base import resp2html, request_get 6 | from javsp.web.exceptions import * 7 | from javsp.datatype import MovieInfo 8 | 9 | logger = logging.getLogger(__name__) 10 | 11 | # https://dl.gyutto.com/i/item266923 12 | base_url = 'http://gyutto.com' 13 | base_encode = 'euc-jp' 14 | 15 | def get_movie_title(html): 16 | container = html.xpath("//h1") 17 | if len(container) > 0: 18 | container = container[0] 19 | title = container.text 20 | 21 | return title 22 | 23 | def get_movie_img(html, index = 1): 24 | images = [] 25 | container = html.xpath("//a[@class='highslide']/img") 26 | if len(container) > 0: 27 | if index == 0: 28 | return container[0].get('src') 29 | 30 | for row in container: 31 | images.append(row.get('src')) 32 | 33 | return images 34 | 35 | def parse_data(movie: MovieInfo): 36 | """解析指定番号的影片数据""" 37 | # 去除番号中的'gyutto'字样 38 | id_uc = movie.dvdid.upper() 39 | if not id_uc.startswith('GYUTTO-'): 40 | raise ValueError('Invalid gyutto number: ' + movie.dvdid) 41 | gyutto_id = id_uc.replace('GYUTTO-', '') 42 | # 抓取网页 43 | url = f'{base_url}/i/item{gyutto_id}?select_uaflag=1' 44 | r = request_get(url, delay_raise=True) 45 | if r.status_code == 404: 46 | raise MovieNotFoundError(__name__, movie.dvdid) 47 | html = resp2html(r, base_encode) 48 | container = html.xpath("//dl[@class='BasicInfo clearfix']") 49 | 50 | for row in container: 51 | key = row.xpath(".//dt/text()") 52 | if key[0] == "サークル": 53 | producer = ''.join(row.xpath(".//dd/a/text()")) 54 | elif key[0] == "ジャンル": 55 | genre = row.xpath(".//dd/a/text()") 56 | elif key[0] == "配信開始日": 57 | date = row.xpath(".//dd/text()") 58 | date_str = ''.join(date) 59 | date_time = time.strptime(date_str, "%Y年%m月%d日") 60 | publish_date = time.strftime("%Y-%m-%d", date_time) 61 | 62 | plot = html.xpath("//div[@class='unit_DetailLead']/p/text()")[0] 63 | 64 | movie.title = get_movie_title(html) 65 | movie.cover = get_movie_img(html, 0) 66 | movie.preview_pics = get_movie_img(html) 67 | movie.dvdid = id_uc 68 | movie.url = url 69 | movie.producer = producer 70 | # movie.actress = actress 71 | # movie.duration = duration 72 | movie.publish_date = publish_date 73 | movie.genre = genre 74 | movie.plot = plot 75 | 76 | if __name__ == "__main__": 77 | import pretty_errors 78 | 79 | pretty_errors.configure(display_link=True) 80 | logger.root.handlers[1].level = logging.DEBUG 81 | movie = MovieInfo('gyutto-266923') 82 | 83 | try: 84 | parse_data(movie) 85 | print(movie) 86 | except CrawlerError as e: 87 | logger.error(e, exc_info=1) 88 | -------------------------------------------------------------------------------- /javsp/web/jav321.py: -------------------------------------------------------------------------------- 1 | """从jav321抓取数据""" 2 | import re 3 | import logging 4 | 5 | 6 | from javsp.web.base import post_html 7 | from javsp.web.exceptions import * 8 | from javsp.datatype import MovieInfo 9 | 10 | 11 | logger = logging.getLogger(__name__) 12 | base_url = 'https://www.jav321.com' 13 | 14 | 15 | def parse_data(movie: MovieInfo): 16 | """解析指定番号的影片数据""" 17 | html = post_html(f'{base_url}/search', data={'sn': movie.dvdid}) 18 | page_url = html.xpath("//ul[@class='dropdown-menu']/li/a/@href")[0] 19 | #TODO: 注意cid是dmm的概念。如果影片来自MGSTAGE,这里的cid很可能是jav321自己添加的,例如 345SIMM-542 20 | cid = page_url.split('/')[-1] # /video/ipx00177 21 | # 如果从URL匹配到的cid是'search',说明还停留在搜索页面,找不到这部影片 22 | if cid == 'search': 23 | raise MovieNotFoundError(__name__, movie.dvdid) 24 | title = html.xpath("//div[@class='panel-heading']/h3/text()")[0] 25 | info = html.xpath("//div[@class='col-md-9']")[0] 26 | # jav321的不同信息字段间没有明显分隔,只能通过url来匹配目标标签 27 | company_tags = info.xpath("a[contains(@href,'/company/')]/text()") 28 | if company_tags: 29 | movie.producer = company_tags[0] 30 | # actress, actress_pics 31 | # jav321现在连女优信息都没有了,首页通过女优栏跳转过去也全是空白 32 | actress, actress_pics = [], {} 33 | actress_tags = html.xpath("//div[@class='thumbnail']/a[contains(@href,'/star/')]/img") 34 | for tag in actress_tags: 35 | name = tag.tail.strip() 36 | pic_url = tag.get('src') 37 | actress.append(name) 38 | # jav321的女优头像完全是应付了事:即使女优实际没有头像,也会有一个看起来像模像样的url, 39 | # 因而无法通过url判断女优头像图片是否有效。有其他选择时最好不要使用jav321的女优头像数据 40 | actress_pics[name] = pic_url 41 | # genre, genre_id 42 | genre_tags = info.xpath("a[contains(@href,'/genre/')]") 43 | genre, genre_id = [], [] 44 | for tag in genre_tags: 45 | genre.append(tag.text) 46 | genre_id.append(tag.get('href').split('/')[-2]) # genre/4025/1 47 | dvdid = info.xpath("b[text()='品番']")[0].tail.replace(': ', '').upper() 48 | publish_date = info.xpath("b[text()='配信開始日']")[0].tail.replace(': ', '') 49 | duration_div = info.xpath("b[text()='収録時間']") 50 | if duration_div: 51 | match = re.search(r'\d+', duration_div[0].tail) 52 | if match: 53 | movie.duration = match.group(0) 54 | # 仅部分影片有评分且评分只能粗略到星级而没有分数,要通过星级的图片来判断,如'/img/35.gif'表示3.5星 55 | score_tag = info.xpath("//b[text()='平均評価']/following-sibling::img/@data-original") 56 | if score_tag: 57 | score = int(score_tag[0][5:7])/5 # /10*2 58 | movie.score = str(score) 59 | serial_tag = info.xpath("a[contains(@href,'/series/')]/text()") 60 | if serial_tag: 61 | movie.serial = serial_tag[0] 62 | preview_video_tag = info.xpath("//video/source/@src") 63 | if preview_video_tag: 64 | movie.preview_video = preview_video_tag[0] 65 | plot_tag = info.xpath("//div[@class='panel-body']/div[@class='row']/div[@class='col-md-12']/text()") 66 | if plot_tag: 67 | movie.plot = plot_tag[0] 68 | preview_pics = html.xpath("//div[@class='col-xs-12 col-md-12']/p/a/img[@class='img-responsive']/@src") 69 | if len(preview_pics) == 0: 70 | # 尝试搜索另一种布局下的封面,需要使用onerror过滤掉明明没有封面时网站往里面塞的默认URL 71 | preview_pics = html.xpath("//div/div/div[@class='col-md-3']/img[@onerror and @class='img-responsive']/@src") 72 | # 有的图片链接里有多个//,网站质量堪忧…… 73 | preview_pics = [i[:8] + i[8:].replace('//', '/') for i in preview_pics] 74 | # 磁力和ed2k链接是依赖js脚本加载的,无法通过静态网页来解析 75 | 76 | movie.url = page_url 77 | movie.cid = cid 78 | movie.dvdid = dvdid 79 | movie.title = title 80 | movie.actress = actress 81 | movie.actress_pics = actress_pics 82 | movie.genre = genre 83 | movie.genre_id = genre_id 84 | movie.publish_date = publish_date 85 | # preview_pics的第一张图始终是封面,剩下的才是预览图 86 | if len(preview_pics) > 0: 87 | movie.cover = preview_pics[0] 88 | movie.preview_pics = preview_pics[1:] 89 | 90 | 91 | if __name__ == "__main__": 92 | import pretty_errors 93 | pretty_errors.configure(display_link=True) 94 | logger.root.handlers[1].level = logging.DEBUG 95 | 96 | movie = MovieInfo('SCUTE-1177') 97 | try: 98 | parse_data(movie) 99 | print(movie) 100 | except CrawlerError as e: 101 | logger.error(e, exc_info=1) 102 | -------------------------------------------------------------------------------- /javsp/web/javmenu.py: -------------------------------------------------------------------------------- 1 | """从JavMenu抓取数据""" 2 | import logging 3 | 4 | from javsp.web.base import Request, resp2html 5 | from javsp.web.exceptions import * 6 | from javsp.datatype import MovieInfo 7 | 8 | 9 | request = Request() 10 | 11 | logger = logging.getLogger(__name__) 12 | base_url = 'https://mrzyx.xyz' 13 | 14 | 15 | def parse_data(movie: MovieInfo): 16 | """从网页抓取并解析指定番号的数据 17 | Args: 18 | movie (MovieInfo): 要解析的影片信息,解析后的信息直接更新到此变量内 19 | """ 20 | # JavMenu网页做得很不走心,将就了 21 | url = f'{base_url}/{movie.dvdid}' 22 | r = request.get(url) 23 | if r.history: 24 | # 被重定向到主页说明找不到影片资源 25 | raise MovieNotFoundError(__name__, movie.dvdid) 26 | 27 | html = resp2html(r) 28 | container = html.xpath("//div[@class='col-md-9 px-0']")[0] 29 | title = container.xpath("div[@class='col-12 mb-3']/h1/strong/text()")[0] 30 | # 竟然还在标题里插广告,真的疯了。要不是我已经写了抓取器,才懒得维护这个破站 31 | title = title.replace(' | JAV目錄大全 | 每日更新', '') 32 | title = title.replace(' 免費在線看', '').replace(' 免費AV在線看', '') 33 | cover_tag = container.xpath("//div[@class='single-video']") 34 | if len(cover_tag) > 0: 35 | video_tag = cover_tag[0].find('video') 36 | # URL首尾竟然也有空格…… 37 | movie.cover = video_tag.get('data-poster').strip() 38 | # 预览影片改为blob了,无法获取 39 | # movie.preview_video = video_tag.find('source').get('src').strip() 40 | else: 41 | cover_img_tag = container.xpath("//img[@class='lazy rounded']/@data-src") 42 | if cover_img_tag: 43 | movie.cover = cover_img_tag[0].strip() 44 | info = container.xpath("//div[@class='card-body']")[0] 45 | publish_date = info.xpath("div/span[contains(text(), '日期:')]")[0].getnext().text 46 | duration = info.xpath("div/span[contains(text(), '時長:')]")[0].getnext().text.replace('分鐘', '') 47 | producer = info.xpath("div/span[contains(text(), '製作:')]/following-sibling::a/span/text()") 48 | if producer: 49 | movie.producer = producer[0] 50 | genre_tags = info.xpath("//a[@class='genre']") 51 | genre, genre_id = [], [] 52 | for tag in genre_tags: 53 | items = tag.get('href').split('/') 54 | pre_id = items[-3] + '/' + items[-1] 55 | genre.append(tag.text.strip()) 56 | genre_id.append(pre_id) 57 | # genre的链接中含有censored字段,但是无法用来判断影片是否有码,因为完全不可靠…… 58 | actress = info.xpath("div/span[contains(text(), '女優:')]/following-sibling::*/a/text()") or None 59 | magnet_table = container.xpath("//table[contains(@class, 'magnet-table')]/tbody") 60 | if magnet_table: 61 | magnet_links = magnet_table[0].xpath("tr/td/a/@href") 62 | # 它的FC2数据是从JavDB抓的,JavDB更换图片服务器后它也跟上了,似乎数据更新频率还可以 63 | movie.magnet = [i.replace('[javdb.com]','') for i in magnet_links] 64 | preview_pics = container.xpath("//a[@data-fancybox='gallery']/@href") 65 | 66 | if (not movie.cover) and preview_pics: 67 | movie.cover = preview_pics[0] 68 | movie.url = url 69 | movie.title = title.replace(movie.dvdid, '').strip() 70 | movie.preview_pics = preview_pics 71 | movie.publish_date = publish_date 72 | movie.duration = duration 73 | movie.genre = genre 74 | movie.genre_id = genre_id 75 | movie.actress = actress 76 | 77 | 78 | if __name__ == "__main__": 79 | import pretty_errors 80 | pretty_errors.configure(display_link=True) 81 | logger.root.handlers[1].level = logging.DEBUG 82 | 83 | movie = MovieInfo('FC2-718323') 84 | try: 85 | parse_data(movie) 86 | print(movie) 87 | except CrawlerError as e: 88 | logger.error(e, exc_info=1) 89 | -------------------------------------------------------------------------------- /javsp/web/prestige.py: -------------------------------------------------------------------------------- 1 | """从蚊香社-prestige抓取数据""" 2 | import re 3 | import logging 4 | 5 | 6 | from javsp.web.base import * 7 | from javsp.web.exceptions import * 8 | from javsp.datatype import MovieInfo 9 | 10 | 11 | logger = logging.getLogger(__name__) 12 | base_url = 'https://www.prestige-av.com' 13 | # prestige要求访问者携带已通过R18认证的cookies才能够获得完整数据,否则会被重定向到认证页面 14 | # (其他多数网站的R18认证只是在网页上遮了一层,完整数据已经传回,不影响爬虫爬取) 15 | cookies = {'__age_auth__': 'true'} 16 | 17 | 18 | def parse_data(movie: MovieInfo): 19 | """从网页抓取并解析指定番号的数据 20 | Args: 21 | movie (MovieInfo): 要解析的影片信息,解析后的信息直接更新到此变量内 22 | """ 23 | url = f'{base_url}/goods/goods_detail.php?sku={movie.dvdid}' 24 | resp = request_get(url, cookies=cookies, delay_raise=True) 25 | if resp.status_code == 500: 26 | # 500错误表明prestige没有这部影片的数据,不是网络问题,因此不再重试 27 | raise MovieNotFoundError(__name__, movie.dvdid) 28 | elif resp.status_code == 403: 29 | raise SiteBlocked('prestige不允许从当前IP所在地区访问,请尝试更换为日本地区代理') 30 | resp.raise_for_status() 31 | html = resp2html(resp) 32 | container_tags = html.xpath("//section[@class='px-4 mb-4 md:px-8 md:mb-16']") 33 | if not container_tags: 34 | raise MovieNotFoundError(__name__, movie.dvdid) 35 | 36 | container = container_tags[0] 37 | title = container.xpath("h1/span")[0].tail.strip() 38 | cover = container.xpath("//div[@class='c-ratio-image mr-8']/picture/source/img/@src")[0] 39 | cover = cover.split('?')[0] 40 | actress = container.xpath("//p[text()='出演者:']/following-sibling::div/p/a/text()") 41 | # 移除女优名中的空格,使女优名与其他网站保持一致 42 | actress = [i.strip().replace(' ', '') for i in actress] 43 | duration_str = container.xpath("//p[text()='収録時間:']")[0].getnext().text_content() 44 | match = re.search(r'\d+', duration_str) 45 | if match: 46 | movie.duration = match.group(0) 47 | date_url = container.xpath("//p[text()='発売日:']/following-sibling::div/a/@href")[0] 48 | publish_date = date_url.split('?date=')[-1] 49 | producer = container.xpath("//p[text()='メーカー:']/following-sibling::div/a/text()")[0].strip() 50 | dvdid = container.xpath("//p[text()='品番:']/following-sibling::div/p/text()")[0] 51 | genre_tags = container.xpath("//p[text()='ジャンル:']/following-sibling::div/a") 52 | genre = [tag.text.strip() for tag in genre_tags] 53 | serial = container.xpath("//p[text()='レーベル:']/following-sibling::div/a/text()")[0].strip() 54 | plot = container.xpath("//h2[text()='商品紹介']/following-sibling::div/p")[0].text.strip() 55 | preview_pics = container.xpath("//h2[text()='サンプル画像']/following-sibling::div/div/picture/source/img/@src") 56 | preview_pics = [i.split('?')[0] for i in preview_pics] 57 | 58 | # prestige改版后已经无法获取高清封面,此前已经获取的高清封面地址也已失效 59 | movie.url = url 60 | movie.dvdid = dvdid 61 | movie.title = title 62 | movie.cover = cover 63 | movie.actress = actress 64 | movie.publish_date = publish_date 65 | movie.producer = producer 66 | movie.genre = genre 67 | movie.serial = serial 68 | movie.plot = plot 69 | movie.preview_pics = preview_pics 70 | movie.uncensored = False # prestige服务器在日本且面向日本国内公开发售,不会包含无码片 71 | 72 | 73 | if __name__ == "__main__": 74 | import pretty_errors 75 | pretty_errors.configure(display_link=True) 76 | logger.root.handlers[1].level = logging.DEBUG 77 | 78 | movie = MovieInfo('ABP-647') 79 | try: 80 | parse_data(movie) 81 | print(movie) 82 | except CrawlerError as e: 83 | logger.error(e, exc_info=1) 84 | -------------------------------------------------------------------------------- /javsp/web/proxyfree.py: -------------------------------------------------------------------------------- 1 | """获取各个网站的免代理地址""" 2 | import re 3 | import sys 4 | 5 | from javsp.web.base import is_connectable, get_html, get_resp_text, request_get 6 | 7 | 8 | def get_proxy_free_url(site_name: str, prefer_url=None) -> str: 9 | """获取指定网站的免代理地址 10 | Args: 11 | site_name (str): 站点名称 12 | prefer_url (str, optional): 优先测试此url是否可用 13 | Returns: 14 | str: 指定站点的免代理地址(失败时为空字符串) 15 | """ 16 | if prefer_url and is_connectable(prefer_url, timeout=5): 17 | return prefer_url 18 | # 当prefer_url不可用时,尝试自动获取指定网站的免代理地址 19 | site_name = site_name.lower() 20 | func_name = f'_get_{site_name}_urls' 21 | get_funcs = [i for i in dir(sys.modules[__name__]) if i.startswith('_get_')] 22 | if func_name in get_funcs: 23 | get_urls = getattr(sys.modules[__name__], func_name) 24 | try: 25 | urls = get_urls() 26 | return _choose_one(urls) 27 | except: 28 | return '' 29 | else: 30 | raise Exception("Dont't know how to get proxy-free url for " + site_name) 31 | 32 | 33 | def _choose_one(urls) -> str: 34 | for url in urls: 35 | if is_connectable(url, timeout=5): 36 | return url 37 | return '' 38 | 39 | 40 | def _get_avsox_urls() -> list: 41 | html = get_html('https://tellme.pw/avsox') 42 | urls = html.xpath('//h4/strong/a/@href') 43 | return urls 44 | 45 | 46 | def _get_javbus_urls() -> list: 47 | html = get_html('https://www.javbus.one/') 48 | text = html.text_content() 49 | urls = re.findall(r'防屏蔽地址:(https://(?:[\d\w][-\d\w]{1,61}[\d\w]\.){1,2}[a-z]{2,})', text, re.I | re.A) 50 | return urls 51 | 52 | 53 | def _get_javlib_urls() -> list: 54 | html = get_html('https://github.com/javlibcom') 55 | text = html.xpath("//div[@class='p-note user-profile-bio mb-3 js-user-profile-bio f4']")[0].text_content() 56 | match = re.search(r'[\w\.]+', text, re.A) 57 | if match: 58 | domain = f'https://www.{match.group(0)}.com' 59 | return [domain] 60 | 61 | 62 | def _get_javdb_urls() -> list: 63 | html = get_html('https://jav524.app') 64 | js_links = html.xpath("//script[@src]/@src") 65 | for link in js_links: 66 | if '/js/index' in link: 67 | text = get_resp_text(request_get(link)) 68 | match = re.search(r'\$officialUrl\s*=\s*"(https://(?:[\d\w][-\d\w]{1,61}[\d\w]\.){1,2}[a-z]{2,})"', text, flags=re.I | re.A) 69 | if match: 70 | return [match.group(1)] 71 | 72 | 73 | if __name__ == "__main__": 74 | print('javdb:\t', _get_javdb_urls()) 75 | print('javlib:\t', _get_javlib_urls()) 76 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "javsp" 3 | version = "0.0.0" 4 | description = "汇总多站点数据的AV元数据刮削器" 5 | authors = ["Yuukiy <76897913+Yuukiy@users.noreply.github.com>"] 6 | license = "GPL-3.0-only" 7 | readme = "README.md" 8 | 9 | [tool.poetry-dynamic-versioning] 10 | enable = true 11 | vcs = "git" 12 | format = "v{base}.{distance}" 13 | 14 | [tool.poetry.dependencies] 15 | python = "<3.13,>=3.10" 16 | cloudscraper = "1.2.71" 17 | colorama = "0.4.4" 18 | pillow = "10.2.0" 19 | pretty-errors = "1.2.19" 20 | requests = "2.31.0" 21 | tqdm = "4.59.0" 22 | # https://stackoverflow.com/questions/446209/possible-values-from-sys-platform 23 | pywin32 = {version = "^306", markers = "sys_platform == 'win32'"} 24 | pywin32-ctypes = {version = "^0.2.2", markers = "sys_platform == 'win32'"} 25 | cryptography = "^42.0.5" 26 | pycryptodome = "^3.20.0" 27 | lxml = {extras = ["html-clean"], version = "^5.2.1"} 28 | confz = "^2.0.1" 29 | pydantic-extra-types = "^2.9.0" 30 | pendulum = "^3.0.0" 31 | slimeface = "^2024.9.27" 32 | 33 | [tool.poetry.scripts] 34 | javsp = "javsp.__main__:entry" 35 | server = "javsp.server:entry" 36 | 37 | [[tool.poetry.source]] 38 | name = "mirrors" 39 | url = "https://pypi.tuna.tsinghua.edu.cn/simple/" 40 | priority = "primary" 41 | 42 | 43 | [tool.poetry.group.dev.dependencies] 44 | pytest = "^8.1.1" 45 | flake8 = "^7.0.0" 46 | cx-freeze = "^7.2.2" 47 | types-lxml = "^2024.4.14" 48 | types-pillow = "^10.2.0.20240822" 49 | 50 | [build-system] 51 | requires = ["poetry-core>=1.0.0", "poetry-dynamic-versioning>=1.0.0,<2.0.0"] 52 | build-backend = "poetry_dynamic_versioning.backend" 53 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | from typing import List, Tuple 3 | from cx_Freeze import setup, Executable 4 | 5 | # https://github.com/marcelotduarte/cx_Freeze/issues/1288 6 | base = None 7 | 8 | proj_root = os.path.abspath(os.path.dirname(__file__)) 9 | 10 | 11 | include_files: List[Tuple[str, str]] = [ 12 | (f'{proj_root}/config.yml', 'config.yml'), 13 | (f'{proj_root}/data', 'data'), 14 | (f'{proj_root}/image', 'image') 15 | ] 16 | 17 | includes = [] 18 | 19 | for file in os.listdir('javsp/web'): 20 | name, ext = os.path.splitext(file) 21 | if ext == '.py': 22 | includes.append('javsp.web.' + name) 23 | 24 | packages = [ 25 | 'pendulum' # pydantic_extra_types depends on pendulum 26 | ] 27 | 28 | build_exe = { 29 | 'include_files': include_files, 30 | 'includes': includes, 31 | 'excludes': ['unittest'], 32 | 'packages': packages, 33 | } 34 | 35 | javsp = Executable( 36 | './javsp/__main__.py', 37 | target_name='JavSP', 38 | base=base, 39 | icon='./image/JavSP.ico', 40 | ) 41 | 42 | setup( 43 | name='JavSP', 44 | options = {'build_exe': build_exe}, 45 | executables=[javsp] 46 | ) 47 | 48 | -------------------------------------------------------------------------------- /tools/airav_search.py: -------------------------------------------------------------------------------- 1 | """获取airav指定关键词的所有搜索结果""" 2 | import os 3 | import sys 4 | import json 5 | 6 | 7 | sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) 8 | from javsp.web.base import Request 9 | 10 | request = Request() 11 | request.headers['Accept-Language'] = 'zh-TW,zh;q=0.9' 12 | 13 | base_url = 'https://www.airav.wiki' 14 | 15 | 16 | def search(keyword): 17 | """搜索指定影片的所有结果""" 18 | all_results = [] 19 | page = 1 20 | data = {'offset': 0, 'count': 1, 'result': []} 21 | while (data['offset'] + len(data['result']) < data['count']): 22 | url = f'{base_url}/api/video/list?lang=zh-TW&lng=zh-TW&search={keyword}&page={page}' 23 | data = request.get(url).json() 24 | all_results.extend(data['result']) 25 | print(f"Get page {page}: {len(data['result'])} movie(s)") 26 | page += 1 27 | for i in all_results: 28 | if not i['url']: 29 | i['url'] = f"{base_url}/video/{i['barcode']}" 30 | return all_results 31 | 32 | 33 | if __name__ == "__main__": 34 | keyword = '版' 35 | results = search(keyword) 36 | with open(f'airav_search_{keyword}.json', 'wt', encoding='utf-8') as f: 37 | json.dump(results, f, indent=2, ensure_ascii=False) 38 | -------------------------------------------------------------------------------- /tools/call_crawler.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """调用抓取器抓取数据""" 4 | import os 5 | import sys 6 | 7 | 8 | import pretty_errors 9 | from tqdm import tqdm 10 | 11 | 12 | pretty_errors.configure(display_link=True) 13 | 14 | 15 | file_dir = os.path.dirname(__file__) 16 | data_dir = os.path.abspath(os.path.join(file_dir, '../unittest/data')) 17 | sys.path.insert(0, os.path.abspath(os.path.join(file_dir, '..'))) 18 | from javsp.datatype import MovieInfo 19 | 20 | 21 | # 搜索抓取器并导入它们 22 | all_crawler = {} 23 | exclude_files = ['fc2fan'] 24 | for file in os.listdir('../javsp/web'): 25 | name, ext = os.path.splitext(file) 26 | if ext == '.py' and name not in exclude_files: 27 | modu = 'javsp.web.' + name 28 | __import__(modu) 29 | if hasattr(sys.modules[modu], 'parse_data'): 30 | parser = getattr(sys.modules[modu], 'parse_data') 31 | all_crawler[name] = parser 32 | 33 | 34 | # 生成本地的测试数据作为测试数据,以确保未来对抓取器进行修改时,不会影响到现有功能 35 | def call_crawlers(dvdid_list: list, used_crawlers=None): 36 | """抓取影片数据 37 | 38 | Args: 39 | dvdid_list (list): 影片番号的列表 40 | crawlers (list[str], optional): 要使用的抓取器,未指定时将使用全部抓取器 41 | """ 42 | if used_crawlers: 43 | crawlers = {i:all_crawler[i] for i in used_crawlers} 44 | else: 45 | crawlers = all_crawler 46 | outer_bar = tqdm(dvdid_list, desc='抓取影片数据', leave=False) 47 | for avid in outer_bar: 48 | success, fail = [], [] 49 | outer_bar.set_description(f'抓取影片数据: {avid}') 50 | inner_bar = tqdm(crawlers.items(), desc='抓取器', leave=False) 51 | for name, parser in inner_bar: 52 | inner_bar.set_description(f'正在抓取{name}'.rjust(10+len(avid))) 53 | # 每次都会创建一个全新的实例,所以不同抓取器的结果之间不会有影响 54 | if name != 'fanza': 55 | movie = MovieInfo(avid) 56 | else: 57 | movie = MovieInfo(cid=avid) 58 | try: 59 | parser(movie) 60 | path = f"{data_dir}{os.sep}{avid} ({name}).json" 61 | movie.dump(path) 62 | success.append(name) 63 | except: 64 | fail.append(name) 65 | out = "{} 抓取完成: 成功{}个 {}; 失败{}个 {}".format(avid, len(success), ' '.join(success), len(fail), ' '.join(fail)) 66 | tqdm.write(out) 67 | 68 | 69 | if __name__ == "__main__": 70 | if len(sys.argv) > 1: 71 | # 带参数调用时,将参数全部视作番号并调用所有抓取器抓取数据 72 | call_crawlers(sys.argv[1:]) 73 | else: 74 | user_in = input('请输入要抓取数据的影片番号: ') 75 | dvdid_list = user_in.split() 76 | # 提示选择要使用的抓取器 77 | names = list(all_crawler.keys()) 78 | for i in range(len(names)): 79 | print(f"{i+1}. {names[i]}", end=' ') 80 | user_in2 = input('\n请选择要使用的抓取器(回车表示全部使用): ') 81 | if user_in2: 82 | items = user_in2.split() 83 | indexes = [int(i)-1 for i in items if i.isdigit()] 84 | valid_indexes = [i for i in indexes if i < len(names)] 85 | used = [names[i] for i in valid_indexes] 86 | else: 87 | used = names 88 | # 开始抓取数据 89 | call_crawlers(dvdid_list, used) 90 | -------------------------------------------------------------------------------- /tools/version.py: -------------------------------------------------------------------------------- 1 | import importlib.metadata as meta 2 | 3 | javsp_version = meta.version('javsp') 4 | 5 | print(javsp_version) 6 | -------------------------------------------------------------------------------- /unittest/conftest.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import pytest 4 | from glob import glob 5 | 6 | 7 | data_dir = os.path.join(os.path.dirname(__file__), 'data') 8 | 9 | 10 | def pytest_addoption(parser): 11 | parser.addoption( 12 | "--only", action="store", default="", help="仅测试指定抓取器的数据" 13 | ) 14 | 15 | def pytest_runtest_logreport(report): 16 | """定制 short test summary info 显示格式""" 17 | # report 的部分属性形如 18 | # nodeid: unittest/test_crawlers.py::test_crawler[082713-417: avsox] 19 | # location: ('unittest\\test_crawlers.py', 27, 'test_crawler[082713-417: avsox]') 20 | # keywords: {'082713-417: avsox': 1, 'unittest/test_crawlers.py': 1, 'test_crawler[082713-417: avsox]': 1, 'JavSP': 1} 21 | 22 | # 为test_crawlers.py定制short test summary格式 23 | if 'test_crawlers.py::' in report.nodeid: 24 | report.nodeid = re.sub(r'^.*::test_crawler', '', report.nodeid) 25 | 26 | 27 | @pytest.fixture 28 | def crawler(request): 29 | return request.config.getoption("--only") 30 | 31 | 32 | def pytest_generate_tests(metafunc): 33 | if 'crawler_params' in metafunc.fixturenames: 34 | # 根据测试数据文件夹中的文件生成测试数据 35 | testcases = {} 36 | data_files = glob(data_dir + os.sep + '*.json') 37 | target_crawler = metafunc.config.getoption("--only") 38 | for file in data_files: 39 | basename = os.path.basename(file) 40 | match = re.match(r"([-\w]+) \((\w+)\)", basename, re.I) 41 | if match: 42 | avid, scraper = match.groups() 43 | name = f'{avid}: {scraper}' 44 | # 仅当未指定抓取器或者指定的抓取器与当前抓取器相同时,才实际执行抓取和比较 45 | if (not target_crawler) or scraper == target_crawler: 46 | testcases[name] = (avid, scraper, file) 47 | # 生成测试用例(testcases的键名将用作测试用例ID) 48 | metafunc.parametrize("crawler_params", testcases.values(), ids=testcases.keys()) 49 | -------------------------------------------------------------------------------- /unittest/data/012717_472 (airav).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "1pondo_012717_472", 3 | "cid": null, 4 | "url": "https://www.airav.wiki/video/1pondo_012717_472", 5 | "plot": "早上看到鄰居人妻江波涼出來倒垃圾沒穿胸罩真是欠幹啊…太太想說只是倒個垃圾嘛不穿應該沒差、但讓男人忍不住勃起被幹翻也沒話說啊!", 6 | "cover": "https://wiki-img.airav.wiki/storage/big_pic/99-27-00287.jpg", 7 | "big_cover": null, 8 | "genre": [ 9 | "熟女", 10 | "潮吹", 11 | "中出", 12 | "癡女", 13 | "巨乳", 14 | "口交", 15 | "無碼片", 16 | "AV女優片", 17 | "窈窕", 18 | "不戴套", 19 | "美腿", 20 | "抬腿開鮑", 21 | "HD高畫質" 22 | ], 23 | "genre_id": null, 24 | "genre_norm": null, 25 | "score": null, 26 | "title": "早起鄰居人妻不穿奶罩倒垃圾真欠幹 江波涼", 27 | "ori_title": null, 28 | "magnet": null, 29 | "serial": null, 30 | "actress": [], 31 | "actress_pics": null, 32 | "director": null, 33 | "duration": null, 34 | "producer": "一本道", 35 | "publisher": null, 36 | "uncensored": null, 37 | "publish_date": "2017-01-27", 38 | "preview_pics": [], 39 | "preview_video": "http://freemsall.airav.cc/mv_download/D5F98B5916D18CC6BFC43C0F3908E8CC/623C397E/mv/17C46753BDFC0468E7AC7C1257981610/623C397E/0/36000/257186/high.mp4" 40 | } -------------------------------------------------------------------------------- /unittest/data/080719-976 (airav).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "caribbeancom080719-976", 3 | "cid": null, 4 | "url": "https://www.airav.wiki/video/caribbeancom080719-976", 5 | "plot": "這次讓我們再來公開更多在加勒比以往看不到的AV女優姬川優奈精選片段、讓她穿上絲襪來猛插、身體敏感到痙攣超爽、被大屌男優肏壞子宮、搖著美乳大昇天、愛姬川優奈的你絕對不要錯過啦!", 6 | "cover": "https://wiki-img.airav.wiki/storage/big_pic/99-27-01907.jpg", 7 | "big_cover": null, 8 | "genre": [ 9 | "中出", 10 | "明星臉", 11 | "女僕", 12 | "無碼片", 13 | "巨乳", 14 | "舔鮑", 15 | "窈窕", 16 | "按摩棒", 17 | "裸體圍裙", 18 | "精選集", 19 | "HD高畫質" 20 | ], 21 | "genre_id": null, 22 | "genre_norm": null, 23 | "score": null, 24 | "title": "噴精快手 姬川優奈精選 2", 25 | "ori_title": null, 26 | "magnet": null, 27 | "serial": null, 28 | "actress": [ 29 | "姬川優奈" 30 | ], 31 | "actress_pics": null, 32 | "director": null, 33 | "duration": null, 34 | "producer": "加勒比", 35 | "publisher": null, 36 | "uncensored": null, 37 | "publish_date": "2019-08-07", 38 | "preview_pics": [], 39 | "preview_video": "http://freemsall.airav.cc/mv_download/8DB2A8AC4A82C9D2B704AD60D5DFE766/623C38B3/mv/A7FE7001CF8328EAC2218865FFBB95DA/623C38B3/0/36000/280191/high.mp4" 40 | } -------------------------------------------------------------------------------- /unittest/data/082713-417 (airav).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "caribbeancom_082713-417", 3 | "cid": null, 4 | "url": "https://www.airav.wiki/video/caribbeancom_082713-417", 5 | "plot": "想要成為AKB48,有著白皙透嫩的美白肌膚露的尾野真知子化身為超可愛的女僕前來伺候主人您!!用著豪華的肉體來進行秘密的枕邊營業!不僅自慰,口交,就連插入也不是問題喔!!", 6 | "cover": "https://wiki-img.airav.wiki/storage/big_pic/99-07-9390.jpg", 7 | "big_cover": null, 8 | "genre": [ 9 | "角色扮演", 10 | "企劃片", 11 | "中出", 12 | "巨乳", 13 | "中文字幕", 14 | "女僕", 15 | "口交", 16 | "白虎", 17 | "打手槍", 18 | "自慰", 19 | "舔鮑", 20 | "美少女", 21 | "裸體圍裙", 22 | "噴精", 23 | "偶像明星", 24 | "按摩棒", 25 | "不戴套", 26 | "口內射精", 27 | "美尻" 28 | ], 29 | "genre_id": null, 30 | "genre_norm": null, 31 | "score": null, 32 | "title": "新人偶像伺候女僕 尾野真知子", 33 | "ori_title": null, 34 | "magnet": null, 35 | "serial": null, 36 | "actress": [ 37 | "尾野真知子" 38 | ], 39 | "actress_pics": null, 40 | "director": null, 41 | "duration": null, 42 | "producer": "加勒比", 43 | "publisher": null, 44 | "uncensored": null, 45 | "publish_date": "2013-08-27", 46 | "preview_pics": [], 47 | "preview_video": "http://freemsall.airav.cc/mv_download/E2C2DD60BEF59B84C26995BE9F73C0AC/623C38BD/mv/B4E60C9F8F2C677311DDD25C01DF66CA/623C38BD/0/36000/59344/normal.mp4" 48 | } -------------------------------------------------------------------------------- /unittest/data/082713-417 (avsox).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "082713-417", 3 | "cid": null, 4 | "url": "https://avsox.click/cn/movie/bfdd4a5b8187eac4", 5 | "plot": null, 6 | "cover": "https://file.netcdn.space/storage/caribbeancom/moviepages/082713-417/images/l_l.jpg", 7 | "big_cover": null, 8 | "genre": [ 9 | "振动", 10 | "口交", 11 | "萝莉", 12 | "舔阴", 13 | "手淫", 14 | "自慰", 15 | "内射", 16 | "巨乳", 17 | "美乳", 18 | "口内射精", 19 | "女仆", 20 | "围裙", 21 | "美臀", 22 | "角色扮演", 23 | "偶像" 24 | ], 25 | "genre_id": null, 26 | "genre_norm": null, 27 | "score": null, 28 | "title": "新人アイドルご奉仕メイド", 29 | "ori_title": null, 30 | "magnet": null, 31 | "serial": "オリジナル動画", 32 | "actress": [ 33 | "尾野真知子" 34 | ], 35 | "actress_pics": null, 36 | "director": null, 37 | "duration": "70", 38 | "producer": "カリビアンコム( Caribbeancom )", 39 | "publisher": null, 40 | "uncensored": null, 41 | "publish_date": "2013-08-27", 42 | "preview_pics": null, 43 | "preview_video": null 44 | } -------------------------------------------------------------------------------- /unittest/data/082713-417 (javbus).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "082713-417", 3 | "cid": null, 4 | "url": "https://www.javbus.com/082713-417", 5 | "plot": null, 6 | "cover": "https://www.javbus.com/imgs/cover/bq2_b.jpg", 7 | "big_cover": null, 8 | "genre": [ 9 | "振動", 10 | "無毛", 11 | "舔陰", 12 | "手淫", 13 | "自慰", 14 | "內射", 15 | "巨乳", 16 | "美乳", 17 | "口內射精", 18 | "女僕", 19 | "圍裙", 20 | "美臀", 21 | "角色扮演", 22 | "偶像", 23 | "企劃物", 24 | "口交", 25 | "獨佔動畫" 26 | ], 27 | "genre_id": null, 28 | "genre_norm": [ 29 | "振動", 30 | "無毛", 31 | "舔陰", 32 | "手淫", 33 | "自慰", 34 | "內射", 35 | "巨乳", 36 | "美乳", 37 | "口內射精", 38 | "女僕", 39 | "圍裙", 40 | "美臀", 41 | "角色扮演", 42 | "偶像", 43 | "企劃物", 44 | "口交", 45 | "獨佔動畫" 46 | ], 47 | "score": null, 48 | "title": "新人アイドルご奉仕メイド", 49 | "ori_title": null, 50 | "magnet": null, 51 | "serial": "カリビアンコム", 52 | "actress": [ 53 | "尾野真知子" 54 | ], 55 | "actress_pics": { 56 | "尾野真知子": "https://www.javbus.com/imgs/actress/46l.jpg" 57 | }, 58 | "director": null, 59 | "duration": "60", 60 | "producer": "カリビアンコム", 61 | "publisher": null, 62 | "uncensored": true, 63 | "publish_date": "2013-08-27", 64 | "preview_pics": [], 65 | "preview_video": null 66 | } -------------------------------------------------------------------------------- /unittest/data/130614-KEIKO (avsox).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "130614-KEIKO", 3 | "cid": null, 4 | "url": "https://avsox.click/cn/movie/f77287dd6908a94b", 5 | "plot": null, 6 | "cover": "https://file.netcdn.space/storage/1000giri/130614-KEIKO/19515.jpg", 7 | "big_cover": null, 8 | "genre": [ 9 | "舔阴", 10 | "内射", 11 | "学生", 12 | "角色扮演", 13 | "企划物", 14 | "口交", 15 | "独占动画", 16 | "少女" 17 | ], 18 | "genre_id": null, 19 | "genre_norm": null, 20 | "score": null, 21 | "title": "めっちゃシタイ!!改#054〜スタイル抜群で!めっちゃ!かわいい!JKと!生ハメ!〜", 22 | "ori_title": null, 23 | "magnet": null, 24 | "serial": null, 25 | "actress": [ 26 | "ケイコ" 27 | ], 28 | "actress_pics": null, 29 | "director": null, 30 | "duration": "39", 31 | "producer": "1000人斬り( 1000giri )", 32 | "publisher": null, 33 | "uncensored": null, 34 | "publish_date": "2013-06-14", 35 | "preview_pics": null, 36 | "preview_video": null 37 | } -------------------------------------------------------------------------------- /unittest/data/130614-KEIKO (javbus).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "130614-KEIKO", 3 | "cid": null, 4 | "url": "https://www.javbus.com/130614-KEIKO", 5 | "plot": null, 6 | "cover": "https://www.javbus.com/imgs/cover/f23_b.jpg", 7 | "big_cover": null, 8 | "genre": [ 9 | "舔陰", 10 | "內射", 11 | "學生", 12 | "角色扮演", 13 | "企劃物", 14 | "口交", 15 | "獨佔動畫", 16 | "少女" 17 | ], 18 | "genre_id": null, 19 | "genre_norm": [ 20 | "舔陰", 21 | "內射", 22 | "學生", 23 | "角色扮演", 24 | "企劃物", 25 | "口交", 26 | "獨佔動畫", 27 | "少女" 28 | ], 29 | "score": null, 30 | "title": "めっちゃシタイ!!改#054〜スタイル抜群で!めっちゃ!かわいい!JKと!生ハメ!〜", 31 | "ori_title": null, 32 | "magnet": null, 33 | "serial": null, 34 | "actress": [ 35 | "ケイコ" 36 | ], 37 | "actress_pics": {}, 38 | "director": null, 39 | "duration": "39", 40 | "producer": "1000giri", 41 | "publisher": null, 42 | "uncensored": true, 43 | "publish_date": "2013-06-14", 44 | "preview_pics": [], 45 | "preview_video": null 46 | } -------------------------------------------------------------------------------- /unittest/data/130614-KEIKO (javdb).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "130614-KEIKO", 3 | "cid": null, 4 | "url": "https://javdb.com/v/rz4WD", 5 | "plot": null, 6 | "cover": "https://c0.jdbstatic.com/covers/rz/rz4WD.jpg", 7 | "big_cover": null, 8 | "genre": [ 9 | "口交", 10 | "中出", 11 | "女學生", 12 | "角色扮演", 13 | "美少女" 14 | ], 15 | "genre_id": null, 16 | "genre_norm": [ 17 | "口交", 18 | "中出", 19 | "女學生", 20 | "Cosplay", 21 | "美少女" 22 | ], 23 | "score": "7.00", 24 | "title": "めっちゃシタイ!!改#054〜スタイル抜群で!めっちゃ!かわいい!JKと!生ハメ!〜", 25 | "ori_title": null, 26 | "magnet": [ 27 | "magnet:?xt=urn:btih:0c7821eacf03ac7c2c50bbaa5fd53e1264604528&dn=【特务】【sex8】1000人斬130614-keiko 風格出眾的JK生+無毛白板PAI.3", 28 | "magnet:?xt=urn:btih:3262c02619b30749fadec30e115f5c86b306f541&dn=第一會所新片@SIS001@(1000人斬り)(130614-keiko)めっちゃシタイ!!改#054~スタイル抜群で!めっちゃ!かわいい!JKと!生ハメ!~ケイコ_ミイナ", 29 | "magnet:?xt=urn:btih:05797f3e15a73608cc978d0cfee1ae84462543b4&dn=1000-giri 130614-Keiko Miina", 30 | "magnet:?xt=urn:btih:82d4e276f9843947d32bea78effe4e27057f5981&dn=1000-Giri – 130614 – Keiko.mp4" 31 | ], 32 | "serial": null, 33 | "actress": [ 34 | "けいこ" 35 | ], 36 | "actress_pics": null, 37 | "director": null, 38 | "duration": "39", 39 | "producer": "1000giri", 40 | "publisher": null, 41 | "uncensored": true, 42 | "publish_date": "2013-06-14", 43 | "preview_pics": [], 44 | "preview_video": null 45 | } -------------------------------------------------------------------------------- /unittest/data/145dmn000007 (fanza).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": null, 3 | "cid": "145dmn000007", 4 | "url": "https://www.dmm.co.jp/digital/nikkatsu/-/detail/=/cid=145dmn000007/", 5 | "plot": "どんな厳格な男でも、性欲に我を失ってしまう時がある。ましてやそれが若くセクシーな女性達に触れる機会の多い職業ならば…。テレビ番組のアシスタント・ディレクター、予約が殺到する超人気のカリスマ美容師、そして聖職とも言える女子校教師。責任感に抑圧される彼らのストレスが限界を越えた時、おぞましきセックス犯罪が誘発される…!!", 6 | "cover": "https://pics.dmm.co.jp/digital/video/145dmn000007/145dmn000007pl.jpg", 7 | "big_cover": null, 8 | "genre": [ 9 | "ドラマ", 10 | "Vシネマ", 11 | "縛り・緊縛", 12 | "ハメ撮り" 13 | ], 14 | "genre_id": [ 15 | "4114", 16 | "4110", 17 | "5021", 18 | "6002" 19 | ], 20 | "genre_norm": null, 21 | "score": "4.00", 22 | "title": "実録性犯罪ファイル 職業別ストレス症候群", 23 | "ori_title": null, 24 | "magnet": null, 25 | "serial": "実録 性犯罪ファイル", 26 | "actress": [ 27 | "河愛純", 28 | "相沢リリ", 29 | "野田めぐみ", 30 | "入江浩治", 31 | "麻央はじめ", 32 | "神戸顕一", 33 | "すわしんじ" 34 | ], 35 | "actress_pics": null, 36 | "director": "笠原唯央", 37 | "duration": "59", 38 | "producer": "TMC", 39 | "publisher": null, 40 | "uncensored": false, 41 | "publish_date": "2022-08-12", 42 | "preview_pics": [ 43 | "https://pics.dmm.co.jp/digital/video/145dmn000007/145dmn000007-1.jpg", 44 | "https://pics.dmm.co.jp/digital/video/145dmn000007/145dmn000007-2.jpg", 45 | "https://pics.dmm.co.jp/digital/video/145dmn000007/145dmn000007-3.jpg", 46 | "https://pics.dmm.co.jp/digital/video/145dmn000007/145dmn000007-4.jpg", 47 | "https://pics.dmm.co.jp/digital/video/145dmn000007/145dmn000007-5.jpg", 48 | "https://pics.dmm.co.jp/digital/video/145dmn000007/145dmn000007-6.jpg", 49 | "https://pics.dmm.co.jp/digital/video/145dmn000007/145dmn000007-7.jpg", 50 | "https://pics.dmm.co.jp/digital/video/145dmn000007/145dmn000007-8.jpg", 51 | "https://pics.dmm.co.jp/digital/video/145dmn000007/145dmn000007-9.jpg", 52 | "https://pics.dmm.co.jp/digital/video/145dmn000007/145dmn000007-10.jpg", 53 | "https://pics.dmm.co.jp/digital/video/145dmn000007/145dmn000007-11.jpg", 54 | "https://pics.dmm.co.jp/digital/video/145dmn000007/145dmn000007-12.jpg", 55 | "https://pics.dmm.co.jp/digital/video/145dmn000007/145dmn000007-13.jpg", 56 | "https://pics.dmm.co.jp/digital/video/145dmn000007/145dmn000007-14.jpg", 57 | "https://pics.dmm.co.jp/digital/video/145dmn000007/145dmn000007-15.jpg", 58 | "https://pics.dmm.co.jp/digital/video/145dmn000007/145dmn000007-16.jpg", 59 | "https://pics.dmm.co.jp/digital/video/145dmn000007/145dmn000007-17.jpg", 60 | "https://pics.dmm.co.jp/digital/video/145dmn000007/145dmn000007-18.jpg", 61 | "https://pics.dmm.co.jp/digital/video/145dmn000007/145dmn000007-19.jpg", 62 | "https://pics.dmm.co.jp/digital/video/145dmn000007/145dmn000007-20.jpg" 63 | ], 64 | "preview_video": "" 65 | } -------------------------------------------------------------------------------- /unittest/data/145tb017 (fanza).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": null, 3 | "cid": "145tb00017", 4 | "url": "https://www.dmm.co.jp/digital/nikkatsu/-/detail/=/cid=145tb017/", 5 | "plot": "どんなオンナの股もこじあける!スーパー課長は、下半身もテクニシャン!!その男は、指一本触れずエクスタシーへと導く!愛と性戯の猛烈ビジネスマンが、日本経済も立て直す!?", 6 | "cover": "https://pics.dmm.co.jp/digital/video/145tb00017/145tb00017pl.jpg", 7 | "big_cover": null, 8 | "genre": [ 9 | "Vシネマ", 10 | "スレンダー", 11 | "OL" 12 | ], 13 | "genre_id": [ 14 | "4110", 15 | "2006", 16 | "1001" 17 | ], 18 | "genre_norm": null, 19 | "score": 0, 20 | "title": "課長 痴魔耕作 スーパーエロリーマン", 21 | "ori_title": null, 22 | "magnet": null, 23 | "serial": "痴魔耕作", 24 | "actress": [ 25 | "宝生奈々", 26 | "夕樹舞子", 27 | "佐倉萌", 28 | "相沢知美" 29 | ], 30 | "actress_pics": null, 31 | "director": "河崎実", 32 | "duration": "76", 33 | "producer": "TMC", 34 | "publisher": null, 35 | "uncensored": false, 36 | "publish_date": "2005-09-22", 37 | "preview_pics": [], 38 | "preview_video": "" 39 | } -------------------------------------------------------------------------------- /unittest/data/1stars931r (fanza).json: -------------------------------------------------------------------------------- 1 | { 2 | "作为此类影片的代表": "https://www.dmm.co.jp/rental/ppr/-/detail/=/cid=1stars931r/", 3 | "dvdid": null, 4 | "cid": "1stars931r", 5 | "url": "https://www.dmm.co.jp/rental/ppr/-/detail/=/cid=1stars931r/", 6 | "plot": "", 7 | "cover": "https://pics.dmm.co.jp/mono/movie/1stars931r/1stars931rpl.jpg", 8 | "big_cover": null, 9 | "genre": [ 10 | "巨乳", 11 | "ドキュメンタリー", 12 | "単体作品", 13 | "アイドル・芸能人", 14 | "デビュー作品", 15 | "サンプル動画", 16 | "軟体" 17 | ], 18 | "genre_id": [ 19 | "2001", 20 | "4023", 21 | "4025", 22 | "4118", 23 | "6006", 24 | "6102", 25 | "6935" 26 | ], 27 | "genre_norm": null, 28 | "score": 45, 29 | "title": "芸能界引退後、即AVデビュー 渚恋生", 30 | "ori_title": null, 31 | "magnet": null, 32 | "serial": "AV DEBUT(STAR)", 33 | "actress": [ 34 | "渚恋生" 35 | ], 36 | "actress_pics": null, 37 | "director": "星シュート", 38 | "duration": "210", 39 | "producer": "SODクリエイト", 40 | "publisher": null, 41 | "uncensored": false, 42 | "publish_date": null, 43 | "preview_pics": [ 44 | "https://pics.dmm.co.jp/digital/video/1stars00931/1stars00931-1.jpg", 45 | "https://pics.dmm.co.jp/digital/video/1stars00931/1stars00931-2.jpg", 46 | "https://pics.dmm.co.jp/digital/video/1stars00931/1stars00931-3.jpg", 47 | "https://pics.dmm.co.jp/digital/video/1stars00931/1stars00931-4.jpg", 48 | "https://pics.dmm.co.jp/digital/video/1stars00931/1stars00931-5.jpg", 49 | "https://pics.dmm.co.jp/digital/video/1stars00931/1stars00931-6.jpg", 50 | "https://pics.dmm.co.jp/digital/video/1stars00931/1stars00931-7.jpg", 51 | "https://pics.dmm.co.jp/digital/video/1stars00931/1stars00931-8.jpg" 52 | ], 53 | "preview_video": "https://cc3001.dmm.co.jp/litevideo/freepv/1/1st/1stars931/1stars931mhb.mp4" 54 | } -------------------------------------------------------------------------------- /unittest/data/259LUXU-593 (avwiki).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "259LUXU-593", 3 | "cid": null, 4 | "url": "https://av-wiki.net/259LUXU-593", 5 | "plot": null, 6 | "cover": "https://image.mgstage.com/images/luxutv/259luxu/593/pb_e_259luxu-593.jpg", 7 | "big_cover": null, 8 | "genre": null, 9 | "genre_id": null, 10 | "genre_norm": null, 11 | "score": null, 12 | "title": "ラグジュTV 608", 13 | "ori_title": null, 14 | "magnet": null, 15 | "serial": "ラグジュTV", 16 | "actress": [ 17 | "白咲りの" 18 | ], 19 | "actress_pics": null, 20 | "director": null, 21 | "duration": null, 22 | "producer": "ラグジュTV", 23 | "publisher": null, 24 | "uncensored": false, 25 | "publish_date": "2017-03-26", 26 | "preview_pics": null, 27 | "preview_video": null 28 | } -------------------------------------------------------------------------------- /unittest/data/300MIUM-1001 (avwiki).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "300MIUM-1001", 3 | "cid": null, 4 | "url": "https://av-wiki.net/300MIUM-1001", 5 | "plot": null, 6 | "cover": "https://image.mgstage.com/images/prestigepremium/300mium/1001/pb_e_300mium-1001.jpg", 7 | "big_cover": null, 8 | "genre": null, 9 | "genre_id": null, 10 | "genre_norm": null, 11 | "score": null, 12 | "title": "【癒しのGカップ陸マネ】デカ乳JDを彼女としてレンタル!口説き落として本来禁止のエロ行為までヤリまくった一部始終を完全REC!!純朴ピュア娘のくせに、弾力最高の最終兵器Gカップを隠し持つ超逸材!!挟まれたい乳・オブザイヤー2023堂々受賞!!【レンタル彼女】", 13 | "ori_title": null, 14 | "magnet": null, 15 | "serial": "レンタル彼女", 16 | "actress": [ 17 | "沙優七羽" 18 | ], 19 | "actress_pics": null, 20 | "director": null, 21 | "duration": null, 22 | "producer": "プレステージプレミアム(PRESTIGE PREMIUM)", 23 | "publisher": null, 24 | "uncensored": false, 25 | "publish_date": "2023-11-19", 26 | "preview_pics": null, 27 | "preview_video": null 28 | } -------------------------------------------------------------------------------- /unittest/data/62knbm009 (fanza).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": null, 3 | "cid": "62knbm009", 4 | "url": "https://www.dmm.co.jp/mono/anime/-/detail/=/cid=62knbm009/", 5 | "plot": "原作「同居する粘液」(出版:キルタイムコミュニケーション)OVA化第1弾!\n\n正体不明な粘液生物との奇妙な同居生活\n\n【STORY】\n藤原ユージはツイていない人生を送っていた。\n子供のころは割ってもいない花瓶のことで責められたり、\n同僚のミスで会社を首になったりもしていた。\n\nバイト先のコンビニでは他人が起こしたミスを\n年下の女の子である高宮から責められるのであった。\n気分が落ち込んだまま帰宅すると見知らぬ女の子が出迎えてくれる。\n戸惑うユージを前に女の子は正体を明かす。\nそれは3ヵ月前から一緒に暮らしている粘液生命体であった。\n人の言葉を話し姿を変えられる生物はユージに好意を抱いている。\n粘液生命体の好意に対してユージはそっけない。\n粘液生命体はユージの気を引く為バイト先の同僚である高宮に姿を変え誘惑してくるのであった。\n\n原作「同居する粘液」より「第1話」収録\n(C)2023 DATE/キルタイムコミュニケーション/King Bee/ダグダグ\n\n──────────────────\nタイトル:同居する粘液 第1話\nサブタイトル:日常の中の非日常\nレーベル名:King Bee\n原作:「同居する粘液」(出版:キルタイムコミュニケーション)\n販売元:メディアバンク\n年齢制限:18歳未満購入不可\n倫理規定:日本コンテンツ審査センター\n仕様:DVD片面1層/トール黒/20分。", 6 | "cover": "https://pics.dmm.co.jp/mono/movie/adult/62knbm009/62knbm009pl.jpg", 7 | "big_cover": null, 8 | "genre": [ 9 | "女子校生", 10 | "美少女", 11 | "巨乳", 12 | "サンプル動画", 13 | "ファンタジー" 14 | ], 15 | "genre_id": [ 16 | "1049", 17 | "1051", 18 | "2017", 19 | "6102", 20 | "6123" 21 | ], 22 | "genre_norm": null, 23 | "score": "5.00", 24 | "title": "同居する粘液 第1話日常の中の非日常", 25 | "ori_title": null, 26 | "magnet": null, 27 | "serial": "同居する粘液", 28 | "actress": null, 29 | "actress_pics": null, 30 | "director": null, 31 | "duration": "20", 32 | "producer": "メディアバンク", 33 | "publisher": null, 34 | "uncensored": false, 35 | "publish_date": "2023-11-10", 36 | "preview_pics": [ 37 | "https://pics.dmm.co.jp/digital/video/62knbm009/62knbm009-1.jpg", 38 | "https://pics.dmm.co.jp/digital/video/62knbm009/62knbm009-2.jpg", 39 | "https://pics.dmm.co.jp/digital/video/62knbm009/62knbm009-3.jpg", 40 | "https://pics.dmm.co.jp/digital/video/62knbm009/62knbm009-4.jpg", 41 | "https://pics.dmm.co.jp/digital/video/62knbm009/62knbm009-5.jpg", 42 | "https://pics.dmm.co.jp/digital/video/62knbm009/62knbm009-6.jpg", 43 | "https://pics.dmm.co.jp/digital/video/62knbm009/62knbm009-7.jpg", 44 | "https://pics.dmm.co.jp/digital/video/62knbm009/62knbm009-8.jpg", 45 | "https://pics.dmm.co.jp/digital/video/62knbm009/62knbm009-9.jpg", 46 | "https://pics.dmm.co.jp/digital/video/62knbm009/62knbm009-10.jpg", 47 | "https://pics.dmm.co.jp/digital/video/62knbm009/62knbm009-11.jpg", 48 | "https://pics.dmm.co.jp/digital/video/62knbm009/62knbm009-12.jpg", 49 | "https://pics.dmm.co.jp/digital/video/62knbm009/62knbm009-13.jpg", 50 | "https://pics.dmm.co.jp/digital/video/62knbm009/62knbm009-14.jpg", 51 | "https://pics.dmm.co.jp/digital/video/62knbm009/62knbm009-15.jpg" 52 | ], 53 | "preview_video": null 54 | } -------------------------------------------------------------------------------- /unittest/data/ABC-000 (prestige).json: -------------------------------------------------------------------------------- 1 | { 2 | "注": "此文件用来测试找不到番号时的处理,故各字段故意为空", 3 | "dvdid": "ABC-000", 4 | "cid": null, 5 | "url": null, 6 | "plot": null, 7 | "cover": null, 8 | "big_cover": null, 9 | "genre": null, 10 | "genre_id": null, 11 | "genre_norm": null, 12 | "score": null, 13 | "title": null, 14 | "ori_title": null, 15 | "magnet": null, 16 | "serial": null, 17 | "actress": null, 18 | "actress_pics": null, 19 | "director": null, 20 | "duration": null, 21 | "producer": null, 22 | "publisher": null, 23 | "uncensored": null, 24 | "publish_date": null, 25 | "preview_pics": null, 26 | "preview_video": null 27 | } -------------------------------------------------------------------------------- /unittest/data/ABP-647 (airav).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "ABP-647", 3 | "cid": null, 4 | "url": "https://www.airav.wiki/video/ABP-647", 5 | "plot": "プレステージ専屬女優『瀬名 きらり』があなたの妄想を実現させます!日常生活の中でふとした瞬間に頭をよぎるエッチな妄想の數々。ちょっぴりドジなレストラン店員さんがボクの股間に飲み物をこぼしたなら、責任を感じる彼女にバックヤードで汚れたパンツを脫がしてもらいフェラでご奉仕させるのに!もしも彼女と訪れた喋り聲ひとつしないマンガ喫茶で欲情してしまったら、聲が漏れるのを必至に我慢する姿に興奮してびちょ濡れマ○コを突きまくるのに!男なら誰もが一度は妄想し、股間を膨らませたことのあるエッチな鉄板シチュエーションを完全主観でお屆けする4シチュエーション160分!!", 6 | "cover": "https://wiki-img.airav.wiki/storage/big_pic/99-90-05441.jpg", 7 | "big_cover": null, 8 | "genre": [ 9 | "顏射", 10 | "口交", 11 | "妄想", 12 | "AV女優片", 13 | "強迫口交" 14 | ], 15 | "genre_id": null, 16 | "genre_norm": null, 17 | "score": null, 18 | "title": "絕對經典場景幹砲 6 瀨名光莉", 19 | "ori_title": null, 20 | "magnet": null, 21 | "serial": null, 22 | "actress": [ 23 | "瀨名光莉" 24 | ], 25 | "actress_pics": null, 26 | "director": null, 27 | "duration": null, 28 | "producer": "PRESTIGE", 29 | "publisher": null, 30 | "uncensored": null, 31 | "publish_date": "2017-10-06", 32 | "preview_pics": [], 33 | "preview_video": "http://freemsall.tnfvbblwhr.work/mp4vod_path/NXTweMhwxTm4_px_IE3qMg/1663999444/0/3600/229090/normal.mp4" 34 | } -------------------------------------------------------------------------------- /unittest/data/ABP-647 (mgstage).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "ABP-647", 3 | "cid": null, 4 | "url": "https://www.mgstage.com/product/product_detail/ABP-647/", 5 | "plot": "【MGSだけのおまけ映像付き(+25分)】 プレステージ専属女優『瀬名きらり』があなたの妄想を実現させます!日常生活の中でふとした瞬間に頭をよぎるエッチな妄想の数々。ちょっぴりドジなレストラン店員さんがボクの股間に飲み物をこぼしたなら、責任を感じる彼女にバックヤードで汚れたパンツを脱がしてもらいフェラでご奉仕させるのに!もしも彼女と訪れた喋り声ひとつしないマンガ喫茶で欲情してしまったら、声が漏れるのを必至に我慢する姿に興奮してびちょ濡れマ○コを突きまくるのに!男なら誰もが一度は妄想し、股間を膨らませたことのあるエッチな鉄板シチュエーションを完全主観でお届けする4シチュエーション160分!!", 6 | "cover": "https://image.mgstage.com/images/prestige/abp/647/pb_e_abp-647.jpg", 7 | "big_cover": null, 8 | "genre": [ 9 | "企画", 10 | "単体作品", 11 | "顔射", 12 | "イラマチオ", 13 | "コスプレ", 14 | "美尻", 15 | "MGSだけのおまけ映像付き" 16 | ], 17 | "genre_id": null, 18 | "genre_norm": null, 19 | "score": "6.60", 20 | "title": "【MGSだけのおまけ映像付き+25分】絶対的鉄板シチュエーション 6 瀬名きらり", 21 | "ori_title": null, 22 | "magnet": null, 23 | "serial": "絶対的鉄板シチュエーション", 24 | "actress": [ 25 | "瀬名きらり" 26 | ], 27 | "actress_pics": null, 28 | "director": null, 29 | "duration": "185", 30 | "producer": "プレステージ", 31 | "publisher": null, 32 | "uncensored": false, 33 | "publish_date": "2017-09-28", 34 | "preview_pics": [ 35 | "https://image.mgstage.com/images/prestige/abp/647/cap_e_0_abp-647.jpg", 36 | "https://image.mgstage.com/images/prestige/abp/647/cap_e_1_abp-647.jpg", 37 | "https://image.mgstage.com/images/prestige/abp/647/cap_e_2_abp-647.jpg", 38 | "https://image.mgstage.com/images/prestige/abp/647/cap_e_3_abp-647.jpg", 39 | "https://image.mgstage.com/images/prestige/abp/647/cap_e_4_abp-647.jpg", 40 | "https://image.mgstage.com/images/prestige/abp/647/cap_e_5_abp-647.jpg", 41 | "https://image.mgstage.com/images/prestige/abp/647/cap_e_6_abp-647.jpg", 42 | "https://image.mgstage.com/images/prestige/abp/647/cap_e_7_abp-647.jpg", 43 | "https://image.mgstage.com/images/prestige/abp/647/cap_e_8_abp-647.jpg", 44 | "https://image.mgstage.com/images/prestige/abp/647/cap_e_9_abp-647.jpg" 45 | ], 46 | "preview_video": "https://sample.mgstage.com/sample/prestige/abp/647/ABP-647.mp4" 47 | } -------------------------------------------------------------------------------- /unittest/data/ABP-647 (prestige).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "ABP-647", 3 | "cid": null, 4 | "url": "https://www.prestige-av.com/goods/goods_detail.php?sku=ABP-647", 5 | "plot": "プレステージ専属女優『瀬名 きらり』があなたの妄想を実現させます!日常生活の中でふとした瞬間に頭をよぎるエッチな妄想の数々。ちょっぴりドジなレストラン店員さんがボクの股間に飲み物をこぼしたなら、責任を感じる彼女にバックヤードで汚れたパンツを脱がしてもらいフェラでご奉仕させるのに!もしも彼女と訪れた喋り声ひとつしないマンガ喫茶で欲情してしまったら、声が漏れるのを必至に我慢する姿に興奮してびちょ濡れマ○コを突きまくるのに!男なら誰もが一度は妄想し、股間を膨らませたことのあるエッチな鉄板シチュエーションを完全主観でお届けする4シチュエーション160分!!", 6 | "cover": "https://www.prestige-av.com/api/media/goods/prestige/abp/647/pf_abp-647.jpg", 7 | "big_cover": null, 8 | "genre": [ 9 | "お掃除フェラ", 10 | "イラマチオ", 11 | "女優", 12 | "顔射" 13 | ], 14 | "genre_id": null, 15 | "genre_norm": null, 16 | "score": null, 17 | "title": "絶対的鉄板シチュエーション 6", 18 | "ori_title": null, 19 | "magnet": null, 20 | "serial": "ABSOLUTELY PERFECT", 21 | "actress": [ 22 | "瀬名きらり" 23 | ], 24 | "actress_pics": null, 25 | "director": null, 26 | "duration": "160", 27 | "producer": "プレステージ", 28 | "publisher": null, 29 | "uncensored": false, 30 | "publish_date": "2017-10-05", 31 | "preview_pics": [ 32 | "https://www.prestige-av.com/api/media/goods/prestige/abp/647/cap_e_0_abp-647.jpg", 33 | "https://www.prestige-av.com/api/media/goods/prestige/abp/647/cap_e_1_abp-647.jpg", 34 | "https://www.prestige-av.com/api/media/goods/prestige/abp/647/cap_e_2_abp-647.jpg", 35 | "https://www.prestige-av.com/api/media/goods/prestige/abp/647/cap_e_3_abp-647.jpg", 36 | "https://www.prestige-av.com/api/media/goods/prestige/abp/647/cap_e_4_abp-647.jpg", 37 | "https://www.prestige-av.com/api/media/goods/prestige/abp/647/cap_e_5_abp-647.jpg", 38 | "https://www.prestige-av.com/api/media/goods/prestige/abp/647/cap_e_6_abp-647.jpg", 39 | "https://www.prestige-av.com/api/media/goods/prestige/abp/647/cap_e_7_abp-647.jpg", 40 | "https://www.prestige-av.com/api/media/goods/prestige/abp/647/cap_e_8_abp-647.jpg", 41 | "https://www.prestige-av.com/api/media/goods/prestige/abp/647/cap_e_9_abp-647.jpg" 42 | ], 43 | "preview_video": null 44 | } -------------------------------------------------------------------------------- /unittest/data/AGEMIX-175 (javbus).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "AGEMIX-175", 3 | "cid": null, 4 | "url": "https://www.javbus.com/AGEMIX-175", 5 | "plot": null, 6 | "cover": "https://www.javbus.com/pics/cover/42ic_b.jpg", 7 | "big_cover": null, 8 | "genre": [ 9 | "乳液", 10 | "姐姐", 11 | "其他戀物癖", 12 | "打手槍" 13 | ], 14 | "genre_id": null, 15 | "genre_norm": [ 16 | "乳汁", 17 | "姐姐", 18 | "其他恋物癖", 19 | "打手枪" 20 | ], 21 | "score": null, 22 | "title": "添い寝手コキ 2", 23 | "ori_title": null, 24 | "magnet": null, 25 | "serial": "添い寝手コキ", 26 | "actress": [ 27 | "坂下えみり", 28 | "上原ソニア", 29 | "茜笑美", 30 | "板野有紀", 31 | "綾瀬れん", 32 | "夏目優希" 33 | ], 34 | "actress_pics": { 35 | "坂下えみり": "https://www.javbus.com/pics/actress/bjl_a.jpg", 36 | "茜笑美": "https://www.javbus.com/pics/actress/8o2_a.jpg", 37 | "板野有紀": "https://www.javbus.com/pics/actress/88e_a.jpg", 38 | "綾瀬れん": "https://www.javbus.com/pics/actress/7nf_a.jpg", 39 | "夏目優希": "https://www.javbus.com/pics/actress/7hw_a.jpg" 40 | }, 41 | "director": null, 42 | "duration": "113", 43 | "producer": "SEXAgent", 44 | "publisher": "SEXAgent", 45 | "uncensored": false, 46 | "publish_date": "2013-12-10", 47 | "preview_pics": [ 48 | "https://pics.dmm.co.jp/digital/video/h_213agemix00175/h_213agemix00175jp-1.jpg", 49 | "https://pics.dmm.co.jp/digital/video/h_213agemix00175/h_213agemix00175jp-2.jpg", 50 | "https://pics.dmm.co.jp/digital/video/h_213agemix00175/h_213agemix00175jp-3.jpg", 51 | "https://pics.dmm.co.jp/digital/video/h_213agemix00175/h_213agemix00175jp-4.jpg", 52 | "https://pics.dmm.co.jp/digital/video/h_213agemix00175/h_213agemix00175jp-5.jpg", 53 | "https://pics.dmm.co.jp/digital/video/h_213agemix00175/h_213agemix00175jp-6.jpg", 54 | "https://pics.dmm.co.jp/digital/video/h_213agemix00175/h_213agemix00175jp-7.jpg", 55 | "https://pics.dmm.co.jp/digital/video/h_213agemix00175/h_213agemix00175jp-8.jpg", 56 | "https://pics.dmm.co.jp/digital/video/h_213agemix00175/h_213agemix00175jp-9.jpg", 57 | "https://pics.dmm.co.jp/digital/video/h_213agemix00175/h_213agemix00175jp-10.jpg" 58 | ], 59 | "preview_video": null 60 | } -------------------------------------------------------------------------------- /unittest/data/AGEMIX-175 (javdb).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "AGEMIX-175", 3 | "cid": null, 4 | "url": "https://javdb.com/v/B88K9", 5 | "plot": null, 6 | "cover": "https://c0.jdbstatic.com/covers/b8/B88K9.jpg", 7 | "big_cover": null, 8 | "genre": [ 9 | "手淫", 10 | "其他戀物癖", 11 | "姐姐", 12 | "乳液" 13 | ], 14 | "genre_id": null, 15 | "genre_norm": [ 16 | "手淫", 17 | "其他恋物癖", 18 | "姐姐", 19 | "乳汁" 20 | ], 21 | "score": "10.00", 22 | "title": "添い寝手コキ 2", 23 | "ori_title": null, 24 | "magnet": [ 25 | "magnet:?xt=urn:btih:a214b875642480483553c04944f74df4316257a2&dn=AGEMIX-175", 26 | "magnet:?xt=urn:btih:35d0e7eb4bb945698fea8d080503658af7c1bee7&dn=#_agemix-175", 27 | "magnet:?xt=urn:btih:f5cb1fb07f90e8213774c8a31b475dcf9db43adb&dn=AGEMIX-175 添い寝手コキ 2 綾瀬れん 板野有紀 夏目優希 坂下えみり 奏音" 28 | ], 29 | "serial": "添い寝手コキ", 30 | "actress": [ 31 | "板野有紀", 32 | "坂下えみり", 33 | "奏音", 34 | "福咲れん", 35 | "夏目優希" 36 | ], 37 | "actress_pics": null, 38 | "director": null, 39 | "duration": "113", 40 | "producer": "SEX Agent", 41 | "publisher": "SEX Agent", 42 | "uncensored": false, 43 | "publish_date": "2013-08-14", 44 | "preview_pics": [ 45 | "https://c0.jdbstatic.com/samples/b8/B88K9_l_0.jpg", 46 | "https://c0.jdbstatic.com/samples/b8/B88K9_l_1.jpg", 47 | "https://c0.jdbstatic.com/samples/b8/B88K9_l_2.jpg", 48 | "https://c0.jdbstatic.com/samples/b8/B88K9_l_3.jpg", 49 | "https://c0.jdbstatic.com/samples/b8/B88K9_l_4.jpg", 50 | "https://c0.jdbstatic.com/samples/b8/B88K9_l_5.jpg", 51 | "https://c0.jdbstatic.com/samples/b8/B88K9_l_6.jpg", 52 | "https://c0.jdbstatic.com/samples/b8/B88K9_l_7.jpg", 53 | "https://c0.jdbstatic.com/samples/b8/B88K9_l_8.jpg", 54 | "https://c0.jdbstatic.com/samples/b8/B88K9_l_9.jpg" 55 | ], 56 | "preview_video": "https://cc3001.dmm.co.jp/litevideo/freepv/h/h_2/h_213agemix175/h_213agemix175_dm_w.mp4" 57 | } -------------------------------------------------------------------------------- /unittest/data/AGEMIX-175 (javlib).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "AGEMIX-175", 3 | "cid": null, 4 | "url": "https://www.javlibrary.com/cn/?v=javlijbv74", 5 | "plot": null, 6 | "cover": "https://pics.dmm.co.jp/mono/movie/adult/h_213agemix175/h_213agemix175pl.jpg", 7 | "big_cover": null, 8 | "genre": [ 9 | "打手枪", 10 | "其他恋物癖", 11 | "姐姐", 12 | "乳液" 13 | ], 14 | "genre_id": null, 15 | "genre_norm": null, 16 | "score": null, 17 | "title": "添い寝手コキ 2", 18 | "ori_title": null, 19 | "magnet": null, 20 | "serial": null, 21 | "actress": [ 22 | "夏目优希", 23 | "奏音", 24 | "板野有纪", 25 | "坂下えみり", 26 | "大冢莲" 27 | ], 28 | "actress_pics": null, 29 | "director": null, 30 | "duration": "113", 31 | "producer": "SEX Agent", 32 | "publisher": "SEX Agent", 33 | "uncensored": null, 34 | "publish_date": "2013-08-14", 35 | "preview_pics": null, 36 | "preview_video": null 37 | } -------------------------------------------------------------------------------- /unittest/data/AbW-001 (javlib).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "ABW-001", 3 | "cid": null, 4 | "url": "https://www.javlibrary.com/cn/?v=javmezbn2e", 5 | "plot": null, 6 | "cover": "https://pics.dmm.co.jp/mono/movie/adult/118abw001/118abw001pl.jpg", 7 | "big_cover": null, 8 | "genre": [ 9 | "中出", 10 | "单体作品", 11 | "巨乳", 12 | "乳交", 13 | "白天出轨" 14 | ], 15 | "genre_id": null, 16 | "genre_norm": null, 17 | "score": "8.20", 18 | "title": "声が出せない状況で…こっそり いちゃラブ「密着」SEX vol.01 エロぉ~い吐息と体温を感じる超接近3本番ALL中出し3連発!! 河合あすな", 19 | "ori_title": null, 20 | "magnet": null, 21 | "serial": null, 22 | "actress": [ 23 | "河合あすな" 24 | ], 25 | "actress_pics": null, 26 | "director": "マサルパンサー", 27 | "duration": "140", 28 | "producer": "プレステージ", 29 | "publisher": "ABSOLUTELY WONDERFUL", 30 | "uncensored": null, 31 | "publish_date": "2020-08-28", 32 | "preview_pics": null, 33 | "preview_video": null 34 | } -------------------------------------------------------------------------------- /unittest/data/CND-037 (airav).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "CND-037", 3 | "cid": null, 4 | "url": "https://www.airav.wiki/video/CND-037", 5 | "plot": null, 6 | "cover": "https://wiki-img.airav.wiki/storage/big_pic/99-07-20067.jpg", 7 | "big_cover": null, 8 | "genre": [], 9 | "genre_id": null, 10 | "genre_norm": null, 11 | "score": null, 12 | "title": null, 13 | "ori_title": null, 14 | "magnet": null, 15 | "serial": null, 16 | "actress": [ 17 | "鈴木心春" 18 | ], 19 | "actress_pics": null, 20 | "director": null, 21 | "duration": null, 22 | "producer": null, 23 | "publisher": null, 24 | "uncensored": null, 25 | "publish_date": "2020-10-31", 26 | "preview_pics": [], 27 | "preview_video": "http://freems5.airav.cc/mv_download/313B10CA071FC910454689BB227D0847/611E6BB3/mv/417137F836FBC097CF313381D102D55B/611E6BB3/0/36000/331522/normal.mp4" 28 | } -------------------------------------------------------------------------------- /unittest/data/CSCT-011 (arzon).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "CSCT-011", 3 | "cid": null, 4 | "url": "https://www.arzon.jp/item_1586691.html", 5 | "plot": "そのオ○コで悪夢を断ち斬れ!鬼化した妹を人間に戻すため淫らに奮闘する兄妹の物語。屋敷に1匹の鬼が忍び込み、柱たちに術を掛け眠らせる。夢の中、添い遂げたい想い人と一心不乱に身体を震わせる。", 6 | "cover": "https://img.arzon.jp/image/1/1586/1586691L.jpg", 7 | "big_cover": null, 8 | "genre": [ 9 | "COSCRAFT" 10 | ], 11 | "genre_id": null, 12 | "genre_norm": null, 13 | "score": null, 14 | "title": "鬼詰のオメコ 無限発射編", 15 | "ori_title": null, 16 | "magnet": null, 17 | "serial": null, 18 | "actress": [ 19 | "渚みつき", 20 | "田中ねね", 21 | "阿部乃みく", 22 | "紺野ひかる" 23 | ], 24 | "actress_pics": null, 25 | "director": null, 26 | "duration": "130", 27 | "producer": "トータル・メディア・エージェンシー(TMA)", 28 | "publisher": null, 29 | "uncensored": null, 30 | "publish_date": "2020-10-23", 31 | "preview_pics": [ 32 | "https://img.arzon.jp/image/1/1586/1586691P-01.jpg", 33 | "https://img.arzon.jp/image/1/1586/1586691P-02.jpg", 34 | "https://img.arzon.jp/image/1/1586/1586691P-03.jpg", 35 | "https://img.arzon.jp/image/1/1586/1586691P-04.jpg", 36 | "https://img.arzon.jp/image/1/1586/1586691P-05.jpg", 37 | "https://img.arzon.jp/image/1/1586/1586691P-06.jpg", 38 | "https://img.arzon.jp/image/1/1586/1586691P-07.jpg", 39 | "https://img.arzon.jp/image/1/1586/1586691P-08.jpg", 40 | "https://img.arzon.jp/image/1/1586/1586691P-09.jpg", 41 | "https://img.arzon.jp/image/1/1586/1586691P-10.jpg", 42 | "https://img.arzon.jp/image/1/1586/1586691P-11.jpg", 43 | "https://img.arzon.jp/image/1/1586/1586691P-12.jpg", 44 | "https://img.arzon.jp/image/1/1586/1586691P-13.jpg", 45 | "https://img.arzon.jp/image/1/1586/1586691P-14.jpg", 46 | "https://img.arzon.jp/image/1/1586/1586691P-15.jpg", 47 | "https://img.arzon.jp/image/1/1586/1586691P-16.jpg", 48 | "https://img.arzon.jp/image/1/1586/1586691P-17.jpg", 49 | "https://img.arzon.jp/image/1/1586/1586691P-18.jpg", 50 | "https://img.arzon.jp/image/1/1586/1586691P-19.jpg", 51 | "https://img.arzon.jp/image/1/1586/1586691P-20.jpg" 52 | ], 53 | "preview_video": null 54 | } 55 | -------------------------------------------------------------------------------- /unittest/data/DCV-137 (jav321).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "DCV-137", 3 | "cid": "277dcv-137", 4 | "url": "https://en.jav321.com/video/277dcv-137", 5 | "plot": null, 6 | "cover": "https://www.jav321.com/images/documentv/277dcv/137/pb_e_277dcv-137.jpg", 7 | "big_cover": null, 8 | "genre": [ 9 | "ナンパ", 10 | "パイズリ", 11 | "企画", 12 | "巨乳", 13 | "素人", 14 | "顔射" 15 | ], 16 | "genre_id": [ 17 | "4006", 18 | "5019", 19 | "4007", 20 | "2001", 21 | "4024", 22 | "5023" 23 | ], 24 | "genre_norm": null, 25 | "score": null, 26 | "title": "家まで送ってイイですか? case.137 爆乳元年!シリーズ一番の爆乳!Iカップキャバ嬢!!⇒顔よりもブラジャーの方がデカい!規格外のダイナマイトボディ!⇒爆乳で会話ができる!男を虜にする、エロすぎる接客術とは…⇒男の反応が全て…M男を骨抜きにするチ〇コ&ア〇ルの開発術!⇒SでもありMでもあり…30回イキまくり!⇒”家出時代”男と寝まくったそのワケとは! ", 27 | "ori_title": null, 28 | "magnet": null, 29 | "serial": null, 30 | "actress": [], 31 | "actress_pics": {}, 32 | "director": null, 33 | "duration": "106", 34 | "producer": "ドキュメンTV", 35 | "publisher": null, 36 | "uncensored": null, 37 | "publish_date": "2019-05-17", 38 | "preview_pics": [ 39 | "https://www.jav321.com/images/documentv/277dcv/137/cap_e_0_277dcv-137.jpg", 40 | "https://www.jav321.com/images/documentv/277dcv/137/cap_e_1_277dcv-137.jpg", 41 | "https://www.jav321.com/images/documentv/277dcv/137/cap_e_2_277dcv-137.jpg", 42 | "https://www.jav321.com/images/documentv/277dcv/137/cap_e_3_277dcv-137.jpg", 43 | "https://www.jav321.com/images/documentv/277dcv/137/cap_e_4_277dcv-137.jpg", 44 | "https://www.jav321.com/images/documentv/277dcv/137/cap_e_5_277dcv-137.jpg", 45 | "https://www.jav321.com/images/documentv/277dcv/137/cap_e_6_277dcv-137.jpg", 46 | "https://www.jav321.com/images/documentv/277dcv/137/cap_e_7_277dcv-137.jpg", 47 | "https://www.jav321.com/images/documentv/277dcv/137/cap_e_8_277dcv-137.jpg", 48 | "https://www.jav321.com/images/documentv/277dcv/137/cap_e_9_277dcv-137.jpg", 49 | "https://www.jav321.com/images/documentv/277dcv/137/cap_e_10_277dcv-137.jpg", 50 | "https://www.jav321.com/images/documentv/277dcv/137/cap_e_11_277dcv-137.jpg", 51 | "https://www.jav321.com/images/documentv/277dcv/137/cap_e_12_277dcv-137.jpg", 52 | "https://www.jav321.com/images/documentv/277dcv/137/cap_e_13_277dcv-137.jpg", 53 | "https://www.jav321.com/images/documentv/277dcv/137/cap_e_14_277dcv-137.jpg", 54 | "https://www.jav321.com/images/documentv/277dcv/137/cap_e_15_277dcv-137.jpg", 55 | "https://www.jav321.com/images/documentv/277dcv/137/cap_e_16_277dcv-137.jpg", 56 | "https://www.jav321.com/images/documentv/277dcv/137/cap_e_17_277dcv-137.jpg", 57 | "https://www.jav321.com/images/documentv/277dcv/137/cap_e_18_277dcv-137.jpg", 58 | "https://www.jav321.com/images/documentv/277dcv/137/cap_e_19_277dcv-137.jpg", 59 | "https://www.jav321.com/images/documentv/277dcv/137/cap_e_20_277dcv-137.jpg", 60 | "https://www.jav321.com/images/documentv/277dcv/137/cap_e_21_277dcv-137.jpg", 61 | "https://www.jav321.com/images/documentv/277dcv/137/cap_e_22_277dcv-137.jpg", 62 | "https://www.jav321.com/images/documentv/277dcv/137/cap_e_23_277dcv-137.jpg", 63 | "https://www.jav321.com/images/documentv/277dcv/137/cap_e_24_277dcv-137.jpg", 64 | "https://www.jav321.com/images/documentv/277dcv/137/cap_e_25_277dcv-137.jpg", 65 | "https://www.jav321.com/images/documentv/277dcv/137/cap_e_26_277dcv-137.jpg", 66 | "https://www.jav321.com/images/documentv/277dcv/137/cap_e_27_277dcv-137.jpg", 67 | "https://www.jav321.com/images/documentv/277dcv/137/cap_e_28_277dcv-137.jpg", 68 | "https://www.jav321.com/images/documentv/277dcv/137/cap_e_29_277dcv-137.jpg", 69 | "https://www.jav321.com/images/documentv/277dcv/137/cap_e_30_277dcv-137.jpg", 70 | "https://www.jav321.com/images/documentv/277dcv/137/cap_e_31_277dcv-137.jpg", 71 | "https://www.jav321.com/images/documentv/277dcv/137/cap_e_32_277dcv-137.jpg", 72 | "https://www.jav321.com/images/documentv/277dcv/137/cap_e_33_277dcv-137.jpg", 73 | "https://www.jav321.com/images/documentv/277dcv/137/cap_e_34_277dcv-137.jpg", 74 | "https://www.jav321.com/images/documentv/277dcv/137/cap_e_35_277dcv-137.jpg", 75 | "https://www.jav321.com/images/documentv/277dcv/137/cap_e_36_277dcv-137.jpg" 76 | ], 77 | "preview_video": "https://sample.mgstage.com/sample/documentv/277dcv/137/277DCV-137_sample.mp4" 78 | } -------------------------------------------------------------------------------- /unittest/data/FC2-10000 (fc2).json: -------------------------------------------------------------------------------- 1 | { 2 | "注": "此文件用来测试找不到番号时的处理,故各字段故意为空", 3 | "dvdid": "FC2-10000", 4 | "cid": null, 5 | "url": null, 6 | "plot": null, 7 | "cover": null, 8 | "big_cover": null, 9 | "genre": null, 10 | "genre_id": null, 11 | "genre_norm": null, 12 | "score": null, 13 | "title": null, 14 | "ori_title": null, 15 | "magnet": null, 16 | "serial": null, 17 | "actress": null, 18 | "actress_pics": null, 19 | "director": null, 20 | "duration": null, 21 | "producer": null, 22 | "publisher": null, 23 | "uncensored": null, 24 | "publish_date": null, 25 | "preview_pics": null, 26 | "preview_video": null 27 | } -------------------------------------------------------------------------------- /unittest/data/FC2-10000 (javdb).json: -------------------------------------------------------------------------------- 1 | { 2 | "注": "此文件用来测试找不到番号时的处理,故各字段故意为空", 3 | "dvdid": "FC2-10000", 4 | "cid": null, 5 | "url": null, 6 | "plot": null, 7 | "cover": null, 8 | "big_cover": null, 9 | "genre": null, 10 | "genre_id": null, 11 | "genre_norm": null, 12 | "score": null, 13 | "title": null, 14 | "ori_title": null, 15 | "magnet": null, 16 | "serial": null, 17 | "actress": null, 18 | "actress_pics": null, 19 | "director": null, 20 | "duration": null, 21 | "producer": null, 22 | "publisher": null, 23 | "uncensored": null, 24 | "publish_date": null, 25 | "preview_pics": null, 26 | "preview_video": null 27 | } -------------------------------------------------------------------------------- /unittest/data/FC2-10000 (javmenu).json: -------------------------------------------------------------------------------- 1 | { 2 | "注": "此文件用来测试找不到番号时的处理,故各字段故意为空", 3 | "dvdid": "FC2-10000", 4 | "cid": null, 5 | "url": null, 6 | "plot": null, 7 | "cover": null, 8 | "big_cover": null, 9 | "genre": null, 10 | "genre_id": null, 11 | "genre_norm": null, 12 | "score": null, 13 | "title": null, 14 | "ori_title": null, 15 | "magnet": null, 16 | "serial": null, 17 | "actress": null, 18 | "actress_pics": null, 19 | "director": null, 20 | "duration": null, 21 | "producer": null, 22 | "publisher": null, 23 | "uncensored": null, 24 | "publish_date": null, 25 | "preview_pics": null, 26 | "preview_video": null 27 | } -------------------------------------------------------------------------------- /unittest/data/FC2-1899973 (javmenu).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "FC2-1899973", 3 | "cid": null, 4 | "url": "https://mrzyx.xyz/FC2-1899973", 5 | "plot": null, 6 | "cover": "https://c0.jdbstatic.com/samples/en/ENgb2_l_0.jpg", 7 | "big_cover": null, 8 | "genre": [ 9 | "私人攝影", 10 | "素人", 11 | "原作", 12 | "美女", 13 | "無碼", 14 | "妻子出軌" 15 | ], 16 | "genre_id": [ 17 | "censored/3011", 18 | "censored/3026", 19 | "censored/3007", 20 | "censored/3028", 21 | "censored/3024", 22 | "censored/3037" 23 | ], 24 | "genre_norm": null, 25 | "score": null, 26 | "title": "【個撮無・衝撃のグラビアGcup美女は社長秘書・NTR・ガチでヤバすぎる動画流出】脱サラした会社の美人社長秘書兼愛人をオカシましたwグラドルだった衝撃Gcup神BODYを貪り鬼イカせ&種付け注入", 27 | "ori_title": null, 28 | "magnet": [ 29 | "magnet:?xt=urn:btih:bb9265f0c1a313e819be75fa264ed35b645924b4&dn=FC2-PPV-1899973", 30 | "magnet:?xt=urn:btih:29aa7a603c59669efd9b0e2f9616aa4bb3a87b3c&dn=FC2PPV-1899973.torrent" 31 | ], 32 | "serial": null, 33 | "actress": null, 34 | "actress_pics": null, 35 | "director": null, 36 | "duration": "104", 37 | "producer": "人事部かとう", 38 | "publisher": null, 39 | "uncensored": null, 40 | "publish_date": "2021-07-03", 41 | "preview_pics": [ 42 | "https://c0.jdbstatic.com/samples/en/ENgb2_l_0.jpg", 43 | "https://c0.jdbstatic.com/samples/en/ENgb2_l_1.jpg", 44 | "https://c0.jdbstatic.com/samples/en/ENgb2_l_2.jpg", 45 | "https://c0.jdbstatic.com/samples/en/ENgb2_l_3.jpg", 46 | "https://c0.jdbstatic.com/samples/en/ENgb2_l_4.jpg", 47 | "https://c0.jdbstatic.com/samples/en/ENgb2_l_5.jpg", 48 | "https://c0.jdbstatic.com/samples/en/ENgb2_l_6.jpg", 49 | "https://c0.jdbstatic.com/samples/en/ENgb2_l_7.jpg", 50 | "https://c0.jdbstatic.com/samples/en/ENgb2_l_8.jpg", 51 | "https://c0.jdbstatic.com/samples/en/ENgb2_l_9.jpg" 52 | ], 53 | "preview_video": null 54 | } -------------------------------------------------------------------------------- /unittest/data/FC2-238629 (avsox).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "FC2-238629", 3 | "cid": null, 4 | "url": null, 5 | "plot": null, 6 | "cover": null, 7 | "big_cover": null, 8 | "genre": null, 9 | "genre_id": null, 10 | "genre_norm": null, 11 | "score": null, 12 | "title": null, 13 | "ori_title": null, 14 | "magnet": null, 15 | "serial": null, 16 | "actress": null, 17 | "actress_pics": null, 18 | "director": null, 19 | "duration": null, 20 | "producer": null, 21 | "publisher": null, 22 | "uncensored": null, 23 | "publish_date": null, 24 | "preview_pics": null, 25 | "preview_video": null 26 | } -------------------------------------------------------------------------------- /unittest/data/FC2-2735981 (javdb).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "FC2-2735981", 3 | "cid": null, 4 | "url": "https://javdb365.com/v/d25M0", 5 | "plot": null, 6 | "cover": null, 7 | "big_cover": null, 8 | "genre": null, 9 | "genre_id": null, 10 | "genre_norm": null, 11 | "score": "7.26", 12 | "title": "高額寄せ集め2", 13 | "ori_title": null, 14 | "magnet": null, 15 | "serial": null, 16 | "actress": null, 17 | "actress_pics": null, 18 | "director": null, 19 | "duration": null, 20 | "producer": null, 21 | "publisher": null, 22 | "uncensored": null, 23 | "publish_date": "2022-03-19", 24 | "preview_pics": null, 25 | "preview_video": null 26 | } -------------------------------------------------------------------------------- /unittest/data/FC2-2764073 (javmenu).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "FC2-2764073", 3 | "cid": null, 4 | "url": "https://mrzyx.xyz/FC2-2764073", 5 | "plot": null, 6 | "cover": "https://c0.jdbstatic.com/samples/d0/d0z1g_l_0.jpg", 7 | "big_cover": null, 8 | "genre": [ 9 | "私人攝影", 10 | "素人", 11 | "內射", 12 | "原作", 13 | "流出", 14 | "可愛", 15 | "年輕" 16 | ], 17 | "genre_id": [ 18 | "censored/3011", 19 | "censored/3026", 20 | "censored/3018", 21 | "censored/3007", 22 | "censored/3022", 23 | "censored/3020", 24 | "censored/3031" 25 | ], 26 | "genre_norm": null, 27 | "score": null, 28 | "title": "※年度末限定4980→980pt【違法/売 春] U⓯未経験の娘。刑法に触れているので早めに削除します。", 29 | "ori_title": null, 30 | "magnet": [ 31 | "magnet:?xt=urn:btih:9bc8d2355e76e0ae689596a44aad5059d12e9fdb&dn=FC2PPV 2764073 ※年度末限定4980→980pt【違法 売 春] U⓯未経験の娘。刑法に触れているので早めに削除します。 [有].mp4", 32 | "magnet:?xt=urn:btih:a6f20f3bd652c0f303b736cdcf9487f2f478007d&dn=FC2-PPV-2764073", 33 | "magnet:?xt=urn:btih:fd58c43a7787c03e8f74db6bc66901c90acae9e8&dn=FC2-PPV-2764073" 34 | ], 35 | "serial": null, 36 | "actress": [ 37 | "西宮このみ" 38 | ], 39 | "actress_pics": null, 40 | "director": null, 41 | "duration": "30", 42 | "producer": "えろぷり", 43 | "publisher": null, 44 | "uncensored": null, 45 | "publish_date": "2022-03-30", 46 | "preview_pics": [ 47 | "https://c0.jdbstatic.com/samples/d0/d0z1g_l_0.jpg" 48 | ], 49 | "preview_video": null 50 | } -------------------------------------------------------------------------------- /unittest/data/FC2-3189680 (javdb).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "FC2-3189680", 3 | "cid": null, 4 | "url": "https://javdb365.com/v/rmVapJ", 5 | "plot": null, 6 | "cover": "https://c0.jdbstatic.com/covers/rm/rmVapJ.jpg", 7 | "big_cover": null, 8 | "genre": null, 9 | "genre_id": null, 10 | "genre_norm": null, 11 | "score": "7.98", 12 | "title": "【体育館倉庫】某ハーフ子役モデルを高額援助。計2回のゴムなし大量中出し。※4K特典(1時間越え)", 13 | "ori_title": null, 14 | "magnet": null, 15 | "serial": null, 16 | "actress": null, 17 | "actress_pics": null, 18 | "director": null, 19 | "duration": null, 20 | "producer": null, 21 | "publisher": null, 22 | "uncensored": null, 23 | "publish_date": "2023-02-20", 24 | "preview_pics": null, 25 | "preview_video": null 26 | } -------------------------------------------------------------------------------- /unittest/data/FC2-718323 (avsox).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "FC2-718323", 3 | "cid": null, 4 | "url": "https://avsox.click/cn/movie/8747275c332b5050", 5 | "plot": null, 6 | "cover": "https://file.netcdn.space/storage/fc2ppv/718323/pl.jpg", 7 | "big_cover": null, 8 | "genre": [ 9 | "内射", 10 | "第一视角", 11 | "人妻", 12 | "美人" 13 | ], 14 | "genre_id": null, 15 | "genre_norm": null, 16 | "score": null, 17 | "title": "【個人撮影】破壊力抜群のデカケツ美人妻れいなさんと再戦そして大量中出し【背徳の制服編】", 18 | "ori_title": null, 19 | "magnet": null, 20 | "serial": null, 21 | "actress": [], 22 | "actress_pics": null, 23 | "director": null, 24 | "duration": "77", 25 | "producer": "EX-STANDARD", 26 | "publisher": null, 27 | "uncensored": null, 28 | "publish_date": "2017-11-30", 29 | "preview_pics": null, 30 | "preview_video": null 31 | } -------------------------------------------------------------------------------- /unittest/data/FC2-718323 (fc2).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "FC2-718323", 3 | "cid": null, 4 | "url": "https://adult.contents.fc2.com/article/718323/", 5 | "plot": null, 6 | "cover": "https://contents-thumbnail2.fc2.com/w1280/storage58000.contents.fc2.com/file/315/31464497/1642819488.71.jpg", 7 | "big_cover": null, 8 | "genre": [ 9 | "人妻", 10 | "ハメ撮り", 11 | "中出し", 12 | "個人撮影", 13 | "オリジナル", 14 | "無修正", 15 | "寝取られ", 16 | "美人", 17 | "可愛い", 18 | "生ハメ" 19 | ], 20 | "genre_id": null, 21 | "genre_norm": null, 22 | "score": "9.92", 23 | "title": "【個人撮影】破壊*抜群のデカケツ美人妻れいなさんと再戦そして大量中出し【背徳の編】", 24 | "ori_title": null, 25 | "magnet": null, 26 | "serial": null, 27 | "actress": null, 28 | "actress_pics": null, 29 | "director": null, 30 | "duration": "78", 31 | "producer": "EX-STANDARD", 32 | "publisher": null, 33 | "uncensored": null, 34 | "publish_date": "2017-11-30", 35 | "preview_pics": [ 36 | "https://contents-thumbnail2.fc2.com/w1280/storage58000.contents.fc2.com/file/315/31464497/1642819488.71.jpg", 37 | "https://contents-thumbnail2.fc2.com/w1280/storage58000.contents.fc2.com/file/315/31464497/1642819488.8.jpg", 38 | "https://contents-thumbnail2.fc2.com/w1280/storage58000.contents.fc2.com/file/315/31464497/1642819489.29.jpg", 39 | "https://contents-thumbnail2.fc2.com/w1280/storage58000.contents.fc2.com/file/315/31464497/1642819489.42.jpg", 40 | "https://contents-thumbnail2.fc2.com/w1280/storage58000.contents.fc2.com/file/315/31464497/1642819489.71.jpg", 41 | "https://contents-thumbnail2.fc2.com/w1280/storage58000.contents.fc2.com/file/315/31464497/1642819489.95.jpg", 42 | "https://contents-thumbnail2.fc2.com/w1280/storage58000.contents.fc2.com/file/315/31464497/1642819490.22.jpg", 43 | "https://contents-thumbnail2.fc2.com/w1280/storage58000.contents.fc2.com/file/315/31464497/1642819490.49.jpg", 44 | "https://contents-thumbnail2.fc2.com/w1280/storage58000.contents.fc2.com/file/315/31464497/1642819490.76.jpg", 45 | "https://contents-thumbnail2.fc2.com/w1280/storage58000.contents.fc2.com/file/315/31464497/1642819491.01.jpg" 46 | ], 47 | "preview_video": "https://vip-videoprem49000.fc2.com/up/201711/29/L/4/cut20171129L0ACggL4.mp4?mid=17fa28e28423683ba038449d47f9aa09" 48 | } -------------------------------------------------------------------------------- /unittest/data/FC2-718323 (javmenu).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "FC2-718323", 3 | "cid": null, 4 | "url": "https://mrzyx.xyz/FC2-718323", 5 | "plot": null, 6 | "cover": "https://c0.jdbstatic.com/samples/dw/DWD4a_l_0.jpg", 7 | "big_cover": null, 8 | "genre": [ 9 | "私人攝影", 10 | "內射", 11 | "原作", 12 | "美女", 13 | "無碼", 14 | "無套性交", 15 | "可愛", 16 | "人妻" 17 | ], 18 | "genre_id": [ 19 | "censored/3011", 20 | "censored/3018", 21 | "censored/3007", 22 | "censored/3028", 23 | "censored/3024", 24 | "censored/3049", 25 | "censored/3020", 26 | "censored/3043" 27 | ], 28 | "genre_norm": null, 29 | "score": null, 30 | "title": "【個人撮影】破壊力抜群のデカケツ美人妻れいなさんと再戦そして大量中出し【背徳の制服編】", 31 | "ori_title": null, 32 | "magnet": [ 33 | "magnet:?xt=urn:btih:f9b5540026ed91a447497a2a8faddd586cc77e61&dn=[Thz.la]fc2ppv_718323", 34 | "magnet:?xt=urn:btih:8bd621d4a3ed19ad24dd66998ffe708c58d98576&dn=fc2-718323-hd", 35 | "magnet:?xt=urn:btih:5ebf15f5fc430694bdf049a1500ccc18c9025f66&dn=fc2-718323-hd", 36 | "magnet:?xt=urn:btih:6211845d561af6ad1eab273236a5b0e50878bbb4&dn=fc2-718323" 37 | ], 38 | "serial": null, 39 | "actress": null, 40 | "actress_pics": null, 41 | "director": null, 42 | "duration": "77", 43 | "producer": "EX-STANDARD", 44 | "publisher": null, 45 | "uncensored": null, 46 | "publish_date": "2017-11-30", 47 | "preview_pics": [ 48 | "https://c0.jdbstatic.com/samples/dw/DWD4a_l_0.jpg", 49 | "https://c0.jdbstatic.com/samples/dw/DWD4a_l_1.jpg", 50 | "https://c0.jdbstatic.com/samples/dw/DWD4a_l_2.jpg", 51 | "https://c0.jdbstatic.com/samples/dw/DWD4a_l_3.jpg", 52 | "https://c0.jdbstatic.com/samples/dw/DWD4a_l_4.jpg", 53 | "https://c0.jdbstatic.com/samples/dw/DWD4a_l_5.jpg", 54 | "https://c0.jdbstatic.com/samples/dw/DWD4a_l_6.jpg", 55 | "https://c0.jdbstatic.com/samples/dw/DWD4a_l_7.jpg", 56 | "https://c0.jdbstatic.com/samples/dw/DWD4a_l_8.jpg", 57 | "https://c0.jdbstatic.com/samples/dw/DWD4a_l_9.jpg" 58 | ], 59 | "preview_video": null 60 | } -------------------------------------------------------------------------------- /unittest/data/FC2-985469 (avsox).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "FC2-985469", 3 | "cid": null, 4 | "url": "https://avsox.click/cn/movie/7f993993987fe5b9", 5 | "plot": null, 6 | "cover": "https://file.netcdn.space/storage/fc2ppv/985469/pl.jpg", 7 | "big_cover": null, 8 | "genre": [ 9 | "素人", 10 | "内射", 11 | "第一视角", 12 | "角色扮演", 13 | "恋物癖" 14 | ], 15 | "genre_id": null, 16 | "genre_norm": null, 17 | "score": null, 18 | "title": "【個人撮影】JD2回生ちゃんに中出し!エロマンガ先生のパジャマコスで中出しえっちさせててもらいました♪", 19 | "ori_title": null, 20 | "magnet": null, 21 | "serial": null, 22 | "actress": [], 23 | "actress_pics": null, 24 | "director": null, 25 | "duration": "113", 26 | "producer": "COS☆ぱこ", 27 | "publisher": null, 28 | "uncensored": null, 29 | "publish_date": "2018-11-23", 30 | "preview_pics": null, 31 | "preview_video": null 32 | } -------------------------------------------------------------------------------- /unittest/data/FC2-985469 (javdb).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "FC2-985469", 3 | "cid": null, 4 | "url": "https://javdb365.com/v/nzA44", 5 | "plot": null, 6 | "cover": "https://c0.jdbstatic.com/covers/nz/nzA44.jpg", 7 | "big_cover": null, 8 | "genre": null, 9 | "genre_id": null, 10 | "genre_norm": null, 11 | "score": "8.80", 12 | "title": "【個人撮影・無】JD2回生ちゃんに中出し!エロマンガ先生のパジャマコスで中出しえっちさせててもらいました♪", 13 | "ori_title": null, 14 | "magnet": null, 15 | "serial": null, 16 | "actress": null, 17 | "actress_pics": null, 18 | "director": null, 19 | "duration": null, 20 | "producer": null, 21 | "publisher": null, 22 | "uncensored": null, 23 | "publish_date": "2018-11-23", 24 | "preview_pics": null, 25 | "preview_video": null 26 | } -------------------------------------------------------------------------------- /unittest/data/GANA-2156 (javbus).json: -------------------------------------------------------------------------------- 1 | { 2 | "注": "此文件用来测试找不到番号时的处理,故各字段故意为空", 3 | "dvdid": "GANA-2156", 4 | "cid": null, 5 | "url": null, 6 | "plot": null, 7 | "cover": null, 8 | "big_cover": null, 9 | "genre": null, 10 | "genre_id": null, 11 | "genre_norm": null, 12 | "score": null, 13 | "title": null, 14 | "ori_title": null, 15 | "magnet": null, 16 | "serial": null, 17 | "actress": null, 18 | "actress_pics": null, 19 | "director": null, 20 | "duration": null, 21 | "producer": null, 22 | "publisher": null, 23 | "uncensored": null, 24 | "publish_date": null, 25 | "preview_pics": null, 26 | "preview_video": null 27 | } -------------------------------------------------------------------------------- /unittest/data/GETCHU-4016932 (dl_getchu).json: -------------------------------------------------------------------------------- 1 | { 2 | "注": "此文件用来测试找不到番号时的处理,故各字段故意为空", 3 | "dvdid": "GETCHU-4016932", 4 | "cid": null, 5 | "url": null, 6 | "plot": null, 7 | "cover": null, 8 | "big_cover": null, 9 | "genre": null, 10 | "genre_id": null, 11 | "genre_norm": null, 12 | "score": null, 13 | "title": null, 14 | "ori_title": null, 15 | "magnet": null, 16 | "serial": null, 17 | "actress": null, 18 | "actress_pics": null, 19 | "director": null, 20 | "duration": null, 21 | "producer": null, 22 | "publisher": null, 23 | "uncensored": null, 24 | "publish_date": null, 25 | "preview_pics": null, 26 | "preview_video": null 27 | } -------------------------------------------------------------------------------- /unittest/data/GETCHU-4041026 (dl_getchu).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "GETCHU-4041026", 3 | "cid": null, 4 | "url": "https://dl.getchu.com/i/item4041026", 5 | "plot": "ごきげんいかがですか。\n\n5chやTwitterで女の子やタレントの悪口書きすぎて開示請求に怯えるバツイチ高卒中年おじさんです。\n\n世間では暴露系YouTuberや、性****告白が話題ですね。他人(美男美女)やタレントの不幸が大好きなので、暴露の拡散に日々努めています。ですが、最近では普通の女ですら、やたら「意識」が高くなってて、男サイドとしては「なんだかな〜」と思いますよね?女なんて、男を勃起させる存在、男のズリネタとして生きるべき存在、私なんかはそう思いますがね?黙ってろよ、といった気持ちですよ。\n\n\nさて、お金が無いので、オナニーのオカズは自給自足が基本。マッチングでセックスする場合も必ず「撮影」を上乗せ。正直、その時点での数万円の出費は痛いですけれど、後のオナニーライフの充実を考慮すれば安いいもの。\n\n射精の瞬間とか抜きどころとか、プレーの流れを完全に把握した、いわば\"\"神\"\"の視点でオナニーできるわけです。何度も何度も繰り返し女とやってる感覚を味わえる永久オカズ機関。この手法を編み出した時には、自分が天才だと思いました。\n\n今回もヘビロテコレクションから放出。他人に自分のセックスを売るとか正気の沙汰じゃありません。女の子には「個人的に楽しむ動画だから」「流出なんてしないよ〜笑」と有耶無耶にして撮ったものを、こんな場所でこっそり配信してしまうんですから。バレたら、開示請求どころの騒ぎじゃない。\n\nそんな、いわくつきの代物です。\n\nサンプル画像をチェックしてみてください。\n\nどうですか?\n\n超カワイイ美少女じゃないですか?\n\nエッチを撮りたくなった私の気持ちもわかるでしょう?\n\n元嫁の何倍も可愛くて、こういう女の子と結婚すればよかった、と後悔しました。\n\n女の子の自宅に行った時、そういった変態趣味(コスプレイヤー)だという事実が発覚。「まだ恥ずかしくて人前で着たことがない」と顔を真っ赤にして言うので「見たい見たい〜」「原●だよね、俺もプレイしてるよ〜」と、理解を示します。コスプレイヤーとセックスするの超好き。\n\n耳まで赤くしてコスチュームを着てくれました。\n\nオッパイのサイズは89cmEカップ。乳輪大きめエロ漫画系オッパイ。コスチュームのままで抱きます。キャラに襲いかかってる妄想を重ねると何倍も興奮。\n\n「オナ使用」を考えて、女の子の顔とかオッパイとかオマ●コ、フェラ顔…じっくり撮りまくり。そのへんのオタクとは一味も二味も違うのだ。AVをクソみたいに視て育った世代ならではの\"\"ズリネタ撮れ高\"\"。\n\nこの宅コスレイヤー美少女は、日常だったら会話すらできない別人種。どちらかといえば、普段SNSで悪口を浴びせてる美男美女側の女の子。その子にナマの固いのをブチ込む快感。癖になるよ。\n\n日頃の鬱憤が全てペニスに集中してカリもパンパン。尻もデカくて張りがあるので、後ろからバコバコ突き回し。打ち付けすぎて、尻肉が真っ赤に充血するのが笑えました。女の子のヌレヌレ穴の中でオチンチンを擦らせてもらいました。\n\n生ハメも中出しも本当に嫌がって必死抵抗。ごめんね?でも、上になって?って頼んだ時はけっこうノリノリで腰振ってたよね?セックス好きだって告白してたよね?完全に同意だよね?\n\n「孕んじゃえ」と願いながら、金玉汁をブリブリと中に射精。4日ほど溜め込んだ臭くてドロドロの元気良い中年ザーメンがへばりつきます。\n", 6 | "cover": "https://dl.getchu.com/data/item_img/40410/4041026/4041026top.jpg", 7 | "big_cover": null, 8 | "genre": [ 9 | "フェラ", 10 | "巨乳", 11 | "中出し", 12 | "孕ませ", 13 | "着衣エッチ", 14 | "尻", 15 | "オタク", 16 | "フェチ", 17 | "おっぱい", 18 | "肉欲", 19 | "ポニーテール", 20 | "美少女" 21 | ], 22 | "genre_id": null, 23 | "genre_norm": null, 24 | "score": null, 25 | "title": "コスプレイヤーの匂い【神里◯華編】ガチ絶対的美少女宅コス富裕層お嬢様ちゃん乳輪デカめエロ漫画系オッパイぷるん真正生中出し自宅孕ませハメ撮り「後ろから腰ピストン叩きつけられすぎて美尻が真っ赤にパコ充血」", 26 | "ori_title": null, 27 | "magnet": null, 28 | "serial": null, 29 | "actress": [ 30 | "オニマルムネチカ" 31 | ], 32 | "actress_pics": null, 33 | "director": null, 34 | "duration": "56", 35 | "producer": "セックスパラノイア", 36 | "publisher": null, 37 | "uncensored": null, 38 | "publish_date": "2022-04-05", 39 | "preview_pics": [ 40 | "https://dl.getchu.com/data/item_img/40410/4041026/4041026_2977.jpg", 41 | "https://dl.getchu.com/data/item_img/40410/4041026/4041026_2978.jpg", 42 | "https://dl.getchu.com/data/item_img/40410/4041026/4041026_2979.jpg" 43 | ], 44 | "preview_video": null 45 | } -------------------------------------------------------------------------------- /unittest/data/GETCHU-4049233 (dl_getchu).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "GETCHU-4049233", 3 | "cid": null, 4 | "url": "https://dl.getchu.com/i/item4049233", 5 | "plot": "極上Iカップ98cmバストのグラマラス体型で、撮影会に参加すれば常に満枠のコスプレイヤーさんです。こんなにも男好きする破壊力抜群グラドル級恵体を持ちながらも、性格は柔和でおっとり優しいお姉さん系。その豊満ボディと包容力のある癒し系ボイスは、さながらママ。\n\nママ、といえば、頼光ママ。\n\n現在、当該ゲームを久々にプレイ中にて、無性に頼光ママのエッチ撮影をしたくなり、こちらのレイヤーさんを召喚。\n\n「頼光ママとヤリたいんだけど、コスチューム持ってたよね」と聞けば、「そうそう、持ってるんだけど肌色露出が多すぎてイベントで着れなくて温存中なの〜」との返答。\n\nなるほど、たしかに最近では撮影会イベントにおける露出や過激マン見せアヘアヘポーズへの風当たりが非常に**いですよね。世間的にも女性への配慮だとかセクハラだとか、AVへの規制だとか、エッチ界隈の何もかもが厳しくなっています。当サークルはレイヤーさんが大好きなので、しっかり彼女たちを守った活動をしたいところですが、行き過ぎた忖度が界隈の停滞を招くことだけは避けねばなりません。はっきり書きますよ?\n\nエロいことばっかり考えてる、しょうもないムッツリどスケベ女は実在します。\n\nエッチな体を視られて興奮したい\n\n男を興奮させてシコシコ射精のズリネタになりたい\n\nそんなズリネタ願望を抱える真性スケベ体質女性は数多く存在するのです。これは紛れもない事実。オカズ志願丸出しで過激コスチューム姿をSNSやファンサイトに投稿してシコられまくっています。ほんと変態女ばっかりですよ!町中では脚やケツ、オッパイ谷間を隠すロングスカートやファストファッションでエロ欲望を隠すくせに、裏では見せまくりの脱ぎまくり。どんだけ変態なんだ!って話です。こういうエッチ女まで規制されないよう、彼女たちがズリネタ的存在で、男のオナニー射精に「使って」やるべき「道具」ということを広めなければなりません!このズリネタ女が!!女なんて男の射精用にどんどん消費すればいいのです!!エッチ女、ドスケベ女は男の慰み者になってしかるべきなのです!!!!!もっと勃起するようにエロエロになれ!もっと興奮射精させろ!あああ…いく、いく…イック…!…はぁはぁ…失礼しました、ついついヒートアップしてしまいました。\n\nというような内容を、レイヤーさんに伝えました。たとえ、会場サイドや運営から「そんな過激なポージングはダメだ!規制だ!出禁だ」と注意されようが、やるところまでやろうと。徹底的にエロいコスプレボディを撮り尽くしてオナネタROMを完成させてやるぞと確約。レイヤーさんの目の色が変わりました。すでにカメコ視線妄想で脳イキしているようです。その興奮を、撮影会当日まで維持してきたのはバレバレでした。着替え部屋から出てきたレイヤーさんに、全参加者が興奮のため息。エッッッッッッッッ…!\n\n極悪エロおっぱい頼光ママが完コス降臨したのです!ご自慢ボディを丸出しにする過激コスチュームに本人も興奮しているのか、顔はうっすらピンク色。「いっぱい撮って視線でヤッて」と目で訴えてきます。それならば…と参加カメコも、サークルメンバーもフル勃起常態でレンズを向けていきます…。\n\n…と、なんともエロすぎるR18コスプレROMが完成しました。\n\n見せて、視せて、魅せまくりの極射ヌキヌキシーン満載。Iカップ神乳だけじゃありませんよ?見事な腰くびれ、極上プリ尻、そしてそして、禁断の丸見えドパイパンぷっくりオマ○コ…もうヨダレと我慢汁と射精汁が止まりませんでした。あとは、サンプルを参考に購入お願い致します。\n", 6 | "cover": "https://dl.getchu.com/data/item_img/40492/4049233/4049233top.jpg", 7 | "big_cover": null, 8 | "genre": [ 9 | "パンチラ", 10 | "リアル系", 11 | "チャイナ", 12 | "その他", 13 | "下着", 14 | "ストッキング", 15 | "尻", 16 | "フェチ", 17 | "おっぱい", 18 | "お姉さん", 19 | "ツルペタ" 20 | ], 21 | "genre_id": null, 22 | "genre_norm": null, 23 | "score": null, 24 | "title": "僕だけの頼光ママ", 25 | "ori_title": null, 26 | "magnet": null, 27 | "serial": null, 28 | "actress": [ 29 | "朱雀" 30 | ], 31 | "actress_pics": null, 32 | "director": null, 33 | "duration": null, 34 | "producer": "匠ぷにもえ", 35 | "publisher": null, 36 | "uncensored": null, 37 | "publish_date": "2023-05-16", 38 | "preview_pics": [], 39 | "preview_video": null 40 | } -------------------------------------------------------------------------------- /unittest/data/GETCHU-4052694 (dl_getchu).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "GETCHU-4052694", 3 | "cid": null, 4 | "url": "https://dl.getchu.com/i/item4052694", 5 | "plot": "中国アイドルレイヤーMISAKI 過去作復刻シーリズ\n\n!【顔無し】!\n\n〈本文紹介〉\n彼はきっと「原○」のプレイヤーなのだろうと思い、ゲーム中の牧師「バーバラ」の服に着替えた美咲は、今回は足と足だけでなく、胸も全部使います。ストッキングが破れているようです。中に差し込みましょう。これで彼はまた興奮し始めました。やはりこの人はストッキングに抵抗力がありません。\n写真を撮るよりエッチなことをしたほうが自分には向いているかもしれませ\nんが、 今回演じた役は「原○」の珊瑚の宮心海、 かわいい白い糸、 きれいな\n頭飾り、そして人を引き付ける包まれた胸で、今回はどんな方法で自分を満\n足させるのでしょうか。 思わず撮影を2回射精させたくなった…\n\n\n・画像サイズ 3200×2134pixel\n・収録画像 28枚\n\n・動画サイズ 1920×1080p", 6 | "cover": "https://dl.getchu.com/data/item_img/40526/4052694/4052694top.jpg", 7 | "big_cover": null, 8 | "genre": [ 9 | "お嬢様", 10 | "着衣エッチ", 11 | "足コキ", 12 | "外国人", 13 | "手コキ", 14 | "おっぱい" 15 | ], 16 | "genre_id": null, 17 | "genre_norm": null, 18 | "score": null, 19 | "title": "あなただけのアイドルになりたいシリーズ「神里綾華篇」", 20 | "ori_title": null, 21 | "magnet": null, 22 | "serial": null, 23 | "actress": [ 24 | "MISAKI" 25 | ], 26 | "actress_pics": null, 27 | "director": null, 28 | "duration": "30", 29 | "producer": "薫月企画", 30 | "publisher": null, 31 | "uncensored": null, 32 | "publish_date": "2023-10-20", 33 | "preview_pics": [ 34 | "https://dl.getchu.com/data/item_img/40526/4052694/4052694_2977.jpg", 35 | "https://dl.getchu.com/data/item_img/40526/4052694/4052694_2978.jpg", 36 | "https://dl.getchu.com/data/item_img/40526/4052694/4052694_2979.jpg" 37 | ], 38 | "preview_video": null 39 | } -------------------------------------------------------------------------------- /unittest/data/GYUTTO-242158 (gyutto).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "GYUTTO-242158", 3 | "cid": null, 4 | "url": "http://gyutto.com/i/item242158?select_uaflag=1", 5 | "plot": "春のオニまつり開催!第一弾酒吞童子ちゃん!高再現度レイヤー!ロ●ロ●すけべな美少女ボディ!お酒をたっぷり呑ませて***わせてヤリまくり!ハメ穴丸見え大開脚!イキまくりの真性生中出し !", 6 | "cover": "http://gyutto.com/data/item_img/2421/242158/242158.jpg", 7 | "big_cover": null, 8 | "genre": [ 9 | "ツルペタ", 10 | "中出し", 11 | "微乳・貧乳", 12 | "パロディ", 13 | "和服・着物", 14 | "美少女", 15 | "コスプレ動画" 16 | ], 17 | "genre_id": null, 18 | "genre_norm": null, 19 | "score": null, 20 | "title": "「○学年の平均身長より小さいとか正直勃起しますよね」体長147cmクソ雑魚メスガキ酒くずレイヤーとハッピー中出し孕ませ子作りアルコールシャワーセックス【金玉汁抜いて、酒に溶かして呑 み干したくなるわぁ…】 ", 21 | "ori_title": null, 22 | "magnet": null, 23 | "serial": null, 24 | "actress": null, 25 | "actress_pics": null, 26 | "director": null, 27 | "duration": null, 28 | "producer": "ぷにもえ!", 29 | "publisher": null, 30 | "uncensored": null, 31 | "publish_date": "2021-04-10", 32 | "preview_pics": [ 33 | "http://gyutto.com/data/item_img/2421/242158/242158.jpg", 34 | "http://gyutto.com/data/item_img/2421/242158/242158_430.jpg", 35 | "http://gyutto.com/data/item_img/2421/242158/242158_431.jpg", 36 | "http://gyutto.com/data/item_img/2421/242158/242158_432.jpg" 37 | ], 38 | "preview_video": null 39 | } -------------------------------------------------------------------------------- /unittest/data/GYUTTO-266923 (gyutto).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "GYUTTO-266923", 3 | "cid": null, 4 | "url": "http://gyutto.com/i/item266923?select_uaflag=1", 5 | "plot": "シコリティ高すぎイキ狂い巨乳肉便器ちゃん。本能から妊娠望んでる女はこうなるらC。無責任中出し合法系ビッチなので着床狙いで子宮ドプドプにしておきました。おかずローテ入り不可避極上オナネタ", 6 | "cover": "http://gyutto.com/data/item_img/2669/266923/266923.jpg", 7 | "big_cover": null, 8 | "genre": [ 9 | "巨乳", 10 | "フェラ", 11 | "中出し", 12 | "おっぱい", 13 | "孕ませ", 14 | "ニーソックス・ニーソ", 15 | "美少女", 16 | "コスプレ動画+写真" 17 | ], 18 | "genre_id": null, 19 | "genre_norm": null, 20 | "score": null, 21 | "title": "【一発かんたんDL版】受精中毒レム!子宮堕ちビッチ発狂痙攣イキでおっぱい揺れ揺れ!妊娠したがり美巨乳ちゃんの孕み様を見よ!完全肉便器極エロボディ子種仕込み中出し絶頂孕まSEX!! ", 22 | "ori_title": null, 23 | "magnet": null, 24 | "serial": null, 25 | "actress": null, 26 | "actress_pics": null, 27 | "director": null, 28 | "duration": null, 29 | "producer": "こすっち", 30 | "publisher": null, 31 | "uncensored": null, 32 | "publish_date": "2023-12-08", 33 | "preview_pics": [ 34 | "http://gyutto.com/data/item_img/2669/266923/266923.jpg", 35 | "http://gyutto.com/data/item_img/2669/266923/266923_430.jpg", 36 | "http://gyutto.com/data/item_img/2669/266923/266923_431.jpg", 37 | "http://gyutto.com/data/item_img/2669/266923/266923_432.jpg" 38 | ], 39 | "preview_video": null 40 | } -------------------------------------------------------------------------------- /unittest/data/ION-020 (javdb).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "ION-020", 3 | "cid": null, 4 | "url": "https://javdb.com/v/9qvxw", 5 | "plot": null, 6 | "cover": "https://c0.jdbstatic.com/covers/9q/9qvxw.jpg", 7 | "big_cover": null, 8 | "genre": [ 9 | "精選綜合", 10 | "素人作品", 11 | "中出", 12 | "美少女電影", 13 | "偷窺", 14 | "白天出軌" 15 | ], 16 | "genre_id": null, 17 | "genre_norm": [ 18 | "精选?综合", 19 | "素人作品", 20 | "中出", 21 | "美少女电影", 22 | "偷窥", 23 | "白天出轨" 24 | ], 25 | "score": "8.00", 26 | "title": "萌", 27 | "ori_title": null, 28 | "magnet": [ 29 | "magnet:?xt=urn:btih:faabed261ac6aa88f8d87d1f180164a9d28b41c3&dn=ion-020 .mp4", 30 | "magnet:?xt=urn:btih:723aaf5998de9e55e483c33e5558a7417803a569&dn=ION-020.mp4", 31 | "magnet:?xt=urn:btih:3b41b6245efc53fb4ba2690a7cbd6bd4426b1534&dn=ION-020", 32 | "magnet:?xt=urn:btih:4c50351a17c2af97441243caa4f7d304b01a5aa7&dn=ion-020", 33 | "magnet:?xt=urn:btih:4c53a9f79fa366cbfbe2ca0673ab453fe2e8dd3b&dn=ion-020", 34 | "magnet:?xt=urn:btih:35122181963b19e1e4a544dae590bf37e55dd862&dn=ion-020", 35 | "magnet:?xt=urn:btih:416ddb3abdea58d28ee8e95f5b2483cb1b1a256b&dn=ion-020", 36 | "magnet:?xt=urn:btih:9ae11166c949e0dceefd640b234284334fb85b74&dn=ion-020" 37 | ], 38 | "serial": null, 39 | "actress": [ 40 | "七瀬ひな" 41 | ], 42 | "actress_pics": null, 43 | "director": null, 44 | "duration": "59", 45 | "producer": null, 46 | "publisher": "ION イイ女を寝取りたい", 47 | "uncensored": false, 48 | "publish_date": "2019-10-02", 49 | "preview_pics": [ 50 | "https://c0.jdbstatic.com/samples/9q/9qvxw_l_0.jpg", 51 | "https://c0.jdbstatic.com/samples/9q/9qvxw_l_1.jpg", 52 | "https://c0.jdbstatic.com/samples/9q/9qvxw_l_2.jpg", 53 | "https://c0.jdbstatic.com/samples/9q/9qvxw_l_3.jpg", 54 | "https://c0.jdbstatic.com/samples/9q/9qvxw_l_4.jpg" 55 | ], 56 | "preview_video": "https://cc3001.dmm.co.jp/litevideo/freepv/i/ion/ion020/ion020_dm_w.mp4" 57 | } -------------------------------------------------------------------------------- /unittest/data/IPX-177 (airav).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "IPX-177", 3 | "cid": null, 4 | "url": "https://www.airav.wiki/video/IPX-177", 5 | "plot": "迷你裙與過膝襪間的絕對領域!讓人忍不住想摸的Q彈肌膚、肉感大腿!邊看這絕對領域邊足交、股交、臀交!享受著衣幹砲快感,讓身穿過膝襪的小惡魔罵到高潮!", 6 | "cover": "https://wiki-img.airav.wiki/storage/big_pic/99-91-05781.jpg", 7 | "big_cover": null, 8 | "genre": [ 9 | "癡女", 10 | "中文字幕", 11 | "打手槍", 12 | "美少女", 13 | "AV女優片", 14 | "美腿", 15 | "HD高畫質" 16 | ], 17 | "genre_id": null, 18 | "genre_norm": null, 19 | "score": null, 20 | "title": "讓高傲妹妹穿過膝襪露絕對領域癡女玩弄 相澤南", 21 | "ori_title": null, 22 | "magnet": null, 23 | "serial": null, 24 | "actress": [ 25 | "相澤南" 26 | ], 27 | "actress_pics": null, 28 | "director": null, 29 | "duration": null, 30 | "producer": "IDEA POCKET", 31 | "publisher": null, 32 | "uncensored": null, 33 | "publish_date": "2018-07-19", 34 | "preview_pics": [], 35 | "preview_video": "http://freems.airav.cc/mv_download/E40E9F7C8A4289EBF8E854FF1286CD7B/611E71C4/mv/E392B8E7244E94E20124731BB1658894/611E71C4/0/36000/267912/normal.mp4" 36 | } -------------------------------------------------------------------------------- /unittest/data/IPX-177 (jav321).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "IPX-177", 3 | "cid": "ipx00177", 4 | "url": "https://en.jav321.com/video/ipx00177", 5 | "plot": "ミニスカートとニーハイから覗くきれいな素肌、ニーソの太ももへの食い込み…全てを兼ね備えた「絶対領域」!ピチピチの肌感、ぷにぷにハミもも思わず触りたくなること間違いなし!さらに絶対領域を眺めながら足コキ、股コキ、尻コキ!完全着衣で堪能!!ニーハイを履いた小悪魔に罵られながらイク!!「マヂきもいんだけど!触り方が!!」「完璧すぎる、、絶対領域はぁはぁ」", 6 | "cover": "http://pics.dmm.co.jp/digital/video/ipx00177/ipx00177pl.jpg", 7 | "big_cover": null, 8 | "genre": [ 9 | "デジモ", 10 | "ハイビジョン", 11 | "単体作品", 12 | "手コキ", 13 | "独占配信", 14 | "痴女", 15 | "美少女", 16 | "脚フェチ" 17 | ], 18 | "genre_id": [ 19 | "6004", 20 | "6533", 21 | "4025", 22 | "5004", 23 | "6548", 24 | "1031", 25 | "1027", 26 | "4008" 27 | ], 28 | "genre_norm": null, 29 | "score": "7.0", 30 | "title": "生意気な妹にニーハイを履かせ僕だけの「絶対領域」を誕生させ僕好みに痴女らせた。 相沢みなみ ", 31 | "ori_title": null, 32 | "magnet": null, 33 | "serial": null, 34 | "actress": [ 35 | "相沢みなみ" 36 | ], 37 | "actress_pics": { 38 | "相沢みなみ": "http://pics.dmm.co.jp/mono/actjpgs/aizawa_minami.jpg" 39 | }, 40 | "director": null, 41 | "duration": "170", 42 | "producer": "アイデアポケット", 43 | "publisher": null, 44 | "uncensored": null, 45 | "publish_date": "2018-07-19", 46 | "preview_pics": [ 47 | "http://pics.dmm.co.jp/digital/video/ipx00177/ipx00177jp-1.jpg", 48 | "http://pics.dmm.co.jp/digital/video/ipx00177/ipx00177jp-2.jpg", 49 | "http://pics.dmm.co.jp/digital/video/ipx00177/ipx00177jp-3.jpg", 50 | "http://pics.dmm.co.jp/digital/video/ipx00177/ipx00177jp-4.jpg", 51 | "http://pics.dmm.co.jp/digital/video/ipx00177/ipx00177jp-5.jpg", 52 | "http://pics.dmm.co.jp/digital/video/ipx00177/ipx00177jp-6.jpg", 53 | "http://pics.dmm.co.jp/digital/video/ipx00177/ipx00177jp-7.jpg", 54 | "http://pics.dmm.co.jp/digital/video/ipx00177/ipx00177jp-8.jpg", 55 | "http://pics.dmm.co.jp/digital/video/ipx00177/ipx00177jp-9.jpg", 56 | "http://pics.dmm.co.jp/digital/video/ipx00177/ipx00177jp-10.jpg", 57 | "http://pics.dmm.co.jp/digital/video/ipx00177/ipx00177jp-11.jpg", 58 | "http://pics.dmm.co.jp/digital/video/ipx00177/ipx00177jp-12.jpg" 59 | ], 60 | "preview_video": "http://awspv3001.r18.com/litevideo/freepv/i/ipx/ipx00177/ipx00177_dmb_w.mp4" 61 | } -------------------------------------------------------------------------------- /unittest/data/IPX-177 (javbus).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "IPX-177", 3 | "cid": null, 4 | "url": "https://www.javbus.com/IPX-177", 5 | "plot": null, 6 | "cover": "https://www.javbus.com/pics/cover/6n54_b.jpg", 7 | "big_cover": null, 8 | "genre": [ 9 | "單體作品", 10 | "DMM獨家", 11 | "打手槍", 12 | "蕩婦", 13 | "戀腿癖", 14 | "美少女", 15 | "數位馬賽克", 16 | "高畫質" 17 | ], 18 | "genre_id": null, 19 | "genre_norm": [ 20 | "单体作品", 21 | "打手枪", 22 | "荡妇", 23 | "恋腿癖", 24 | "美少女" 25 | ], 26 | "score": null, 27 | "title": "生意気な妹にニーハイを履かせ僕だけの「絶対領域」を誕生させ僕好みに痴女らせた。 相沢みなみ", 28 | "ori_title": null, 29 | "magnet": null, 30 | "serial": null, 31 | "actress": [ 32 | "相沢みなみ" 33 | ], 34 | "actress_pics": { 35 | "相沢みなみ": "https://www.javbus.com/pics/actress/qfy_a.jpg" 36 | }, 37 | "director": "まえだのかほり", 38 | "duration": "170", 39 | "producer": "アイデアポケット", 40 | "publisher": "ティッシュ", 41 | "uncensored": false, 42 | "publish_date": "2018-07-14", 43 | "preview_pics": [ 44 | "https://pics.dmm.co.jp/digital/video/ipx00177/ipx00177jp-1.jpg", 45 | "https://pics.dmm.co.jp/digital/video/ipx00177/ipx00177jp-2.jpg", 46 | "https://pics.dmm.co.jp/digital/video/ipx00177/ipx00177jp-3.jpg", 47 | "https://pics.dmm.co.jp/digital/video/ipx00177/ipx00177jp-4.jpg", 48 | "https://pics.dmm.co.jp/digital/video/ipx00177/ipx00177jp-5.jpg", 49 | "https://pics.dmm.co.jp/digital/video/ipx00177/ipx00177jp-6.jpg", 50 | "https://pics.dmm.co.jp/digital/video/ipx00177/ipx00177jp-7.jpg", 51 | "https://pics.dmm.co.jp/digital/video/ipx00177/ipx00177jp-8.jpg", 52 | "https://pics.dmm.co.jp/digital/video/ipx00177/ipx00177jp-9.jpg", 53 | "https://pics.dmm.co.jp/digital/video/ipx00177/ipx00177jp-10.jpg", 54 | "https://pics.dmm.co.jp/digital/video/ipx00177/ipx00177jp-11.jpg", 55 | "https://pics.dmm.co.jp/digital/video/ipx00177/ipx00177jp-12.jpg" 56 | ], 57 | "preview_video": null 58 | } -------------------------------------------------------------------------------- /unittest/data/IPX-177 (javdb).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "IPX-177", 3 | "cid": null, 4 | "url": "https://javdb.com/v/0ez7k", 5 | "plot": null, 6 | "cover": "https://c0.jdbstatic.com/covers/0e/0ez7k.jpg", 7 | "big_cover": null, 8 | "genre": [ 9 | "美少女", 10 | "單體作品", 11 | "蕩婦", 12 | "足交", 13 | "數位馬賽克", 14 | "手淫", 15 | "戀腿癖", 16 | "絲襪、過膝襪", 17 | "無碼破解", 18 | "高跟鞋" 19 | ], 20 | "genre_id": null, 21 | "genre_norm": [ 22 | "美少女", 23 | "單體作品", 24 | "荡妇", 25 | "足交", 26 | "手淫", 27 | "恋腿癖", 28 | "过膝袜", 29 | "無碼破解", 30 | "高跟鞋" 31 | ], 32 | "score": "9.38", 33 | "title": "生意気な妹にニーハイを履かせ僕だけの「絶対領域」を誕生させ僕好みに痴女らせた。 相沢みなみ", 34 | "ori_title": null, 35 | "magnet": [ 36 | "magnet:?xt=urn:btih:3492f27212cccafb45c6b7687dc714281e7a085d&dn=IPX-177-U.torrent.无码破解", 37 | "magnet:?xt=urn:btih:91688f292ec619c39797510d41110be8316ce777&dn=[7sht.me]ipx-177-C.torrent", 38 | "magnet:?xt=urn:btih:4fb8a4e49b0f877d8c56d8a885fdcfbf3821db2b&dn=IPX-177 相沢みなみ【中文字幕】.mp4", 39 | "magnet:?xt=urn:btih:c0c799b22aa99b7ebd1e4c6350ebc1ea81d141de&dn=[IPX-177].mp4", 40 | "magnet:?xt=urn:btih:3f6f17675ab821584f7c29913530d4240b366f71&dn=IPX-177.mp4", 41 | "magnet:?xt=urn:btih:2a65a53601aa7e3c6356a64f5756b4e2ffffc7c5&dn=[Thz.la]ipx-177", 42 | "magnet:?xt=urn:btih:1c9f68f66d106af48171f99c1f91a96141a042e3&dn=IPX-177.mp4", 43 | "magnet:?xt=urn:btih:6da97cbe5e9eb27dd6d594d4e58f5c69e9fae085&dn=ipx-177", 44 | "magnet:?xt=urn:btih:e8422d364cb8ae75026c59ebb259d05f393adbad&dn=HD-ipx-177", 45 | "magnet:?xt=urn:btih:2c338baad9ecb729fab8830ea1405304da3582ab&dn=[SHANA][IPX-177].mp4", 46 | "magnet:?xt=urn:btih:41a7be7df1c934cf4b8daf50e803ba214c04c6e8&dn=IPX-177 生意気な妹にニーハイを履かせ僕だけの「絶対領域」を誕生させ僕好みに痴女らせた。 相沢みなみ.mp4", 47 | "magnet:?xt=urn:btih:4cb65bcdf29dbeecea7a3af3ca42a51611dae7cf&dn=你好我的心@第一会所@IPX-177", 48 | "magnet:?xt=urn:btih:0acce05e77e5413d0751ccf99161bff1c1ff05e9&dn=[中文字幕]IPX-177", 49 | "magnet:?xt=urn:btih:8002b5fbd5b5f93c2ae65c2003f176db9586e3c9&dn=[HD中文字幕] IPX-177 生意気な妹にニーハイを履かせ僕だけの「絶対領域」を誕生させ僕好みに痴女らせた。 相沢みなみ", 50 | "magnet:?xt=urn:btih:cd0e0a31e19195997e1b177e08b707f9f63f7edb&dn=112.[1030.ws]ipx-177", 51 | "magnet:?xt=urn:btih:0a4cce961736e73ae2eb15387fbed651a97a7b8f&dn=IPX-177 Aizawa Minami Younger Sister", 52 | "magnet:?xt=urn:btih:fef6fa8fdb529ca87b63ef3d257288670c9859bc&dn=IPX-177-fuckbe.com.mp4", 53 | "magnet:?xt=urn:btih:7f490c6468ee5f611570ba8083912f5d0495d797&dn=hjd-2048.com-0713-ipx-177-h264" 54 | ], 55 | "serial": "僕だけの「絶対領域」を誕生させ僕好みに痴女らせた。", 56 | "actress": [ 57 | "相沢みなみ" 58 | ], 59 | "actress_pics": null, 60 | "director": "まえだのかほり", 61 | "duration": "170", 62 | "producer": "IDEA POCKET", 63 | "publisher": "ティッシュ", 64 | "uncensored": false, 65 | "publish_date": "2018-07-19", 66 | "preview_pics": [ 67 | "https://c0.jdbstatic.com/samples/0e/0ez7k_l_0.jpg", 68 | "https://c0.jdbstatic.com/samples/0e/0ez7k_l_1.jpg", 69 | "https://c0.jdbstatic.com/samples/0e/0ez7k_l_2.jpg", 70 | "https://c0.jdbstatic.com/samples/0e/0ez7k_l_3.jpg", 71 | "https://c0.jdbstatic.com/samples/0e/0ez7k_l_4.jpg", 72 | "https://c0.jdbstatic.com/samples/0e/0ez7k_l_5.jpg", 73 | "https://c0.jdbstatic.com/samples/0e/0ez7k_l_6.jpg", 74 | "https://c0.jdbstatic.com/samples/0e/0ez7k_l_7.jpg", 75 | "https://c0.jdbstatic.com/samples/0e/0ez7k_l_8.jpg", 76 | "https://c0.jdbstatic.com/samples/0e/0ez7k_l_9.jpg", 77 | "https://c0.jdbstatic.com/samples/0e/0ez7k_l_10.jpg", 78 | "https://c0.jdbstatic.com/samples/0e/0ez7k_l_11.jpg" 79 | ], 80 | "preview_video": "https://cc3001.dmm.co.jp/litevideo/freepv/i/ipx/ipx00177/ipx00177_dm_w.mp4" 81 | } -------------------------------------------------------------------------------- /unittest/data/IPX-177 (javlib).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "IPX-177", 3 | "cid": null, 4 | "url": "https://www.javlibrary.com/cn/?v=javli7dz24", 5 | "plot": null, 6 | "cover": "https://pics.dmm.co.jp/mono/movie/adult/ipx177/ipx177pl.jpg", 7 | "big_cover": null, 8 | "genre": [ 9 | "打手枪", 10 | "单体作品", 11 | "美少女", 12 | "荡妇", 13 | "恋腿癖", 14 | "数位马赛克" 15 | ], 16 | "genre_id": null, 17 | "genre_norm": null, 18 | "score": "8.70", 19 | "title": "生意気な妹にニーハイを履かせ僕だけの「絶対領域」を誕生させ僕好みに痴女らせた。 相沢みなみ", 20 | "ori_title": null, 21 | "magnet": null, 22 | "serial": null, 23 | "actress": [ 24 | "相沢みなみ" 25 | ], 26 | "actress_pics": null, 27 | "director": "まえだのかほり", 28 | "duration": "170", 29 | "producer": "IDEA POCKET", 30 | "publisher": "ティッシュ", 31 | "uncensored": null, 32 | "publish_date": "2018-07-19", 33 | "preview_pics": null, 34 | "preview_video": null 35 | } -------------------------------------------------------------------------------- /unittest/data/IPX-177 (javmenu).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "IPX-177", 3 | "cid": null, 4 | "url": "https://mrzyx.xyz/IPX-177", 5 | "plot": null, 6 | "cover": "https://pics.vpdmm.cc/digital/video/ipx00177/ipx00177pl.jpg", 7 | "big_cover": null, 8 | "genre": [ 9 | "美少女", 10 | "蕩婦", 11 | "足交", 12 | "數位馬賽克", 13 | "手淫", 14 | "戀腿癖", 15 | "過膝襪", 16 | "無碼破解", 17 | "高跟鞋", 18 | "單體作品" 19 | ], 20 | "genre_id": [ 21 | "censored/5", 22 | "censored/48", 23 | "censored/61", 24 | "censored/85", 25 | "censored/102", 26 | "censored/107", 27 | "censored/198", 28 | "censored/348", 29 | "censored/352", 30 | "censored/28" 31 | ], 32 | "genre_norm": null, 33 | "score": null, 34 | "title": "生意気な妹にニーハイを履かせ僕だけの「絶対領域」を誕生させ僕好みに痴女らせた。 相沢みなみ", 35 | "ori_title": null, 36 | "magnet": [ 37 | "magnet:?xt=urn:btih:3492f27212cccafb45c6b7687dc714281e7a085d&dn=IPX-177-U.torrent.无码破解", 38 | "magnet:?xt=urn:btih:91688f292ec619c39797510d41110be8316ce777&dn=[7sht.me]ipx-177-C.torrent", 39 | "magnet:?xt=urn:btih:4fb8a4e49b0f877d8c56d8a885fdcfbf3821db2b&dn=IPX-177 相沢みなみ【中文字幕】.mp4", 40 | "magnet:?xt=urn:btih:c0c799b22aa99b7ebd1e4c6350ebc1ea81d141de&dn=[IPX-177].mp4", 41 | "magnet:?xt=urn:btih:3f6f17675ab821584f7c29913530d4240b366f71&dn=IPX-177.mp4", 42 | "magnet:?xt=urn:btih:2a65a53601aa7e3c6356a64f5756b4e2ffffc7c5&dn=[Thz.la]ipx-177", 43 | "magnet:?xt=urn:btih:1c9f68f66d106af48171f99c1f91a96141a042e3&dn=IPX-177.mp4", 44 | "magnet:?xt=urn:btih:6da97cbe5e9eb27dd6d594d4e58f5c69e9fae085&dn=ipx-177", 45 | "magnet:?xt=urn:btih:e8422d364cb8ae75026c59ebb259d05f393adbad&dn=HD-ipx-177", 46 | "magnet:?xt=urn:btih:2c338baad9ecb729fab8830ea1405304da3582ab&dn=[SHANA][IPX-177].mp4", 47 | "magnet:?xt=urn:btih:41a7be7df1c934cf4b8daf50e803ba214c04c6e8&dn=IPX-177 生意気な妹にニーハイを履かせ僕だけの「絶対領域」を誕生させ僕好みに痴女らせた。 相沢みなみ.mp4", 48 | "magnet:?xt=urn:btih:4cb65bcdf29dbeecea7a3af3ca42a51611dae7cf&dn=你好我的心@第一会所@IPX-177", 49 | "magnet:?xt=urn:btih:0acce05e77e5413d0751ccf99161bff1c1ff05e9&dn=[中文字幕]IPX-177", 50 | "magnet:?xt=urn:btih:8002b5fbd5b5f93c2ae65c2003f176db9586e3c9&dn=[HD中文字幕] IPX-177 生意気な妹にニーハイを履かせ僕だけの「絶対領域」を誕生させ僕好みに痴女らせた。 相沢みなみ", 51 | "magnet:?xt=urn:btih:cd0e0a31e19195997e1b177e08b707f9f63f7edb&dn=112.[1030.ws]ipx-177", 52 | "magnet:?xt=urn:btih:0a4cce961736e73ae2eb15387fbed651a97a7b8f&dn=IPX-177 Aizawa Minami Younger Sister" 53 | ], 54 | "serial": null, 55 | "actress": [ 56 | "相澤南", 57 | "久道実", 58 | "渡辺琢磨" 59 | ], 60 | "actress_pics": null, 61 | "director": null, 62 | "duration": "170", 63 | "producer": "IDEA POCKET", 64 | "publisher": null, 65 | "uncensored": null, 66 | "publish_date": "2018-07-19", 67 | "preview_pics": [ 68 | "https://pics.vpdmm.cc/digital/video/ipx00177/ipx00177jp-1.jpg", 69 | "https://pics.vpdmm.cc/digital/video/ipx00177/ipx00177jp-2.jpg", 70 | "https://pics.vpdmm.cc/digital/video/ipx00177/ipx00177jp-3.jpg", 71 | "https://pics.vpdmm.cc/digital/video/ipx00177/ipx00177jp-4.jpg", 72 | "https://pics.vpdmm.cc/digital/video/ipx00177/ipx00177jp-5.jpg", 73 | "https://pics.vpdmm.cc/digital/video/ipx00177/ipx00177jp-6.jpg", 74 | "https://pics.vpdmm.cc/digital/video/ipx00177/ipx00177jp-7.jpg", 75 | "https://pics.vpdmm.cc/digital/video/ipx00177/ipx00177jp-8.jpg", 76 | "https://pics.vpdmm.cc/digital/video/ipx00177/ipx00177jp-9.jpg", 77 | "https://pics.vpdmm.cc/digital/video/ipx00177/ipx00177jp-10.jpg", 78 | "https://pics.vpdmm.cc/digital/video/ipx00177/ipx00177jp-11.jpg", 79 | "https://pics.vpdmm.cc/digital/video/ipx00177/ipx00177jp-12.jpg" 80 | ], 81 | "preview_video": null 82 | } -------------------------------------------------------------------------------- /unittest/data/IPZ-037 (javbus).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "IPZ-037", 3 | "cid": null, 4 | "url": "https://www.javbus.com/IPZ-037", 5 | "plot": null, 6 | "cover": "https://www.javbus.com/pics/cover/1ng2_b.jpg", 7 | "big_cover": null, 8 | "genre": [ 9 | "高畫質", 10 | "數位馬賽克", 11 | "DMM獨家", 12 | "單體作品", 13 | "多P" 14 | ], 15 | "genre_id": null, 16 | "genre_norm": [ 17 | "单体作品", 18 | "多P" 19 | ], 20 | "score": null, 21 | "title": "断り切れずにヤラせちゃう女 私押しに弱いんです Rio", 22 | "ori_title": null, 23 | "magnet": null, 24 | "serial": "断り切れずにヤラせちゃう女私押しに弱いんです", 25 | "actress": [ 26 | "Rio(柚木ティナ)" 27 | ], 28 | "actress_pics": { 29 | "Rio(柚木ティナ)": "https://www.javbus.com/pics/actress/2mx_a.jpg" 30 | }, 31 | "director": "K.C.武田", 32 | "duration": "175", 33 | "producer": "アイデアポケット", 34 | "publisher": "ティッシュ", 35 | "uncensored": false, 36 | "publish_date": "2013-01-26", 37 | "preview_pics": [ 38 | "https://pics.dmm.co.jp/digital/video/ipz00037/ipz00037jp-1.jpg", 39 | "https://pics.dmm.co.jp/digital/video/ipz00037/ipz00037jp-2.jpg", 40 | "https://pics.dmm.co.jp/digital/video/ipz00037/ipz00037jp-3.jpg", 41 | "https://pics.dmm.co.jp/digital/video/ipz00037/ipz00037jp-4.jpg", 42 | "https://pics.dmm.co.jp/digital/video/ipz00037/ipz00037jp-5.jpg", 43 | "https://pics.dmm.co.jp/digital/video/ipz00037/ipz00037jp-6.jpg", 44 | "https://pics.dmm.co.jp/digital/video/ipz00037/ipz00037jp-7.jpg", 45 | "https://pics.dmm.co.jp/digital/video/ipz00037/ipz00037jp-8.jpg", 46 | "https://pics.dmm.co.jp/digital/video/ipz00037/ipz00037jp-9.jpg", 47 | "https://pics.dmm.co.jp/digital/video/ipz00037/ipz00037jp-10.jpg", 48 | "https://pics.dmm.co.jp/digital/video/ipz00037/ipz00037jp-11.jpg", 49 | "https://pics.dmm.co.jp/digital/video/ipz00037/ipz00037jp-12.jpg" 50 | ], 51 | "preview_video": null 52 | } -------------------------------------------------------------------------------- /unittest/data/KIDM-1137B (arzon_iv).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "KIDM-1137B", 3 | "cid": null, 4 | "url": "https://www.arzon.jp/image_1783726.html", 5 | "plot": "数々のグランプリを獲得、井上茉倫の最新イメージ。ブルーレイでは初フルヌードとなる今作は、全てのチャプターで感じています。", 6 | "cover": "https://img.arzon.jp/image/7/1783/1783726L.jpg", 7 | "big_cover": null, 8 | "genre": [ 9 | "Empress" 10 | ], 11 | "genre_id": null, 12 | "genre_norm": null, 13 | "score": null, 14 | "title": "井上茉倫/裸体(ブルーレイ)", 15 | "ori_title": null, 16 | "magnet": null, 17 | "serial": null, 18 | "actress": [], 19 | "actress_pics": null, 20 | "director": null, 21 | "duration": "105", 22 | "producer": "キングダム", 23 | "publisher": null, 24 | "uncensored": null, 25 | "publish_date": "2024-07-26", 26 | "preview_pics": null, 27 | "preview_video": null 28 | } -------------------------------------------------------------------------------- /unittest/data/KING-048 (javbus).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "KING-048", 3 | "cid": null, 4 | "url": "https://www.javbus.com/KING-048", 5 | "plot": null, 6 | "cover": "https://www.busfan.cfd/pics/cover/8kib_b.jpg", 7 | "big_cover": null, 8 | "genre": [ 9 | "女優按摩棒", 10 | "潮吹", 11 | "中出", 12 | "高中女生" 13 | ], 14 | "genre_id": null, 15 | "genre_norm": [ 16 | "女优按摩棒", 17 | "潮吹", 18 | "中出", 19 | "高中女生" 20 | ], 21 | "score": null, 22 | "title": "らら", 23 | "ori_title": null, 24 | "magnet": null, 25 | "serial": null, 26 | "actress": [], 27 | "actress_pics": {}, 28 | "director": null, 29 | "duration": "62", 30 | "producer": null, 31 | "publisher": null, 32 | "uncensored": false, 33 | "publish_date": "2021-09-15", 34 | "preview_pics": [ 35 | "https://www.busfan.cfd/pics/sample/8kib_1.jpg", 36 | "https://www.busfan.cfd/pics/sample/8kib_2.jpg", 37 | "https://www.busfan.cfd/pics/sample/8kib_3.jpg", 38 | "https://www.busfan.cfd/pics/sample/8kib_4.jpg", 39 | "https://www.busfan.cfd/pics/sample/8kib_5.jpg" 40 | ], 41 | "preview_video": null 42 | } -------------------------------------------------------------------------------- /unittest/data/MTF-020 (javbus).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "MTF-020", 3 | "cid": null, 4 | "url": "https://www.javbus.com/MTF-020", 5 | "plot": null, 6 | "cover": "https://www.javbus.com/pics/cover/3nv0_b.jpg", 7 | "big_cover": null, 8 | "genre": [ 9 | "美少女", 10 | "亂倫", 11 | "偶像藝人", 12 | "單體作品", 13 | "高中女生" 14 | ], 15 | "genre_id": null, 16 | "genre_norm": [ 17 | "美少女", 18 | "乱伦", 19 | "偶像艺人", 20 | "单体作品", 21 | "高中女生" 22 | ], 23 | "score": null, 24 | "title": "制服シンデレラの秘密", 25 | "ori_title": null, 26 | "magnet": null, 27 | "serial": null, 28 | "actress": [ 29 | "浅倉舞" 30 | ], 31 | "actress_pics": { 32 | "浅倉舞": "https://www.javbus.com/pics/actress/3af_a.jpg" 33 | }, 34 | "director": null, 35 | "duration": "55", 36 | "producer": "h.m.p", 37 | "publisher": "Tiffany", 38 | "uncensored": false, 39 | "publish_date": null, 40 | "preview_pics": [ 41 | "https://pics.dmm.co.jp/digital/video/41mtf00020/41mtf00020jp-1.jpg", 42 | "https://pics.dmm.co.jp/digital/video/41mtf00020/41mtf00020jp-2.jpg", 43 | "https://pics.dmm.co.jp/digital/video/41mtf00020/41mtf00020jp-3.jpg", 44 | "https://pics.dmm.co.jp/digital/video/41mtf00020/41mtf00020jp-4.jpg", 45 | "https://pics.dmm.co.jp/digital/video/41mtf00020/41mtf00020jp-5.jpg", 46 | "https://pics.dmm.co.jp/digital/video/41mtf00020/41mtf00020jp-6.jpg", 47 | "https://pics.dmm.co.jp/digital/video/41mtf00020/41mtf00020jp-7.jpg", 48 | "https://pics.dmm.co.jp/digital/video/41mtf00020/41mtf00020jp-8.jpg", 49 | "https://pics.dmm.co.jp/digital/video/41mtf00020/41mtf00020jp-9.jpg", 50 | "https://pics.dmm.co.jp/digital/video/41mtf00020/41mtf00020jp-10.jpg", 51 | "https://pics.dmm.co.jp/digital/video/41mtf00020/41mtf00020jp-11.jpg", 52 | "https://pics.dmm.co.jp/digital/video/41mtf00020/41mtf00020jp-12.jpg", 53 | "https://pics.dmm.co.jp/digital/video/41mtf00020/41mtf00020jp-13.jpg", 54 | "https://pics.dmm.co.jp/digital/video/41mtf00020/41mtf00020jp-14.jpg", 55 | "https://pics.dmm.co.jp/digital/video/41mtf00020/41mtf00020jp-15.jpg", 56 | "https://pics.dmm.co.jp/digital/video/41mtf00020/41mtf00020jp-16.jpg", 57 | "https://pics.dmm.co.jp/digital/video/41mtf00020/41mtf00020jp-17.jpg", 58 | "https://pics.dmm.co.jp/digital/video/41mtf00020/41mtf00020jp-18.jpg", 59 | "https://pics.dmm.co.jp/digital/video/41mtf00020/41mtf00020jp-19.jpg", 60 | "https://pics.dmm.co.jp/digital/video/41mtf00020/41mtf00020jp-20.jpg" 61 | ], 62 | "preview_video": null 63 | } -------------------------------------------------------------------------------- /unittest/data/NANP-030 (javbus).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "NANP-030", 3 | "cid": null, 4 | "url": "https://www.javbus.com/NANP-030", 5 | "plot": null, 6 | "cover": "https://www.javbus.com/pics/cover/5ev3_b.jpg", 7 | "big_cover": null, 8 | "genre": [ 9 | "業餘", 10 | "獵豔", 11 | "偷窥", 12 | "乳房", 13 | "高畫質", 14 | "立即口交" 15 | ], 16 | "genre_id": null, 17 | "genre_norm": [ 18 | "业余", 19 | "猎艳", 20 | "偷窥", 21 | "乳房", 22 | "立即口交" 23 | ], 24 | "score": null, 25 | "title": "Taking Home An Amateur After Picking Her Up! Secretly Filming The SEX And Illegally Selling The Vide", 26 | "ori_title": null, 27 | "magnet": null, 28 | "serial": null, 29 | "actress": [ 30 | "涼木みらい" 31 | ], 32 | "actress_pics": { 33 | "涼木みらい": "https://www.javbus.com/pics/actress/nau_a.jpg" 34 | }, 35 | "director": null, 36 | "duration": null, 37 | "producer": null, 38 | "publisher": null, 39 | "uncensored": false, 40 | "publish_date": "2016-03-11", 41 | "preview_pics": [ 42 | "https://pics.dmm.co.jp/digital/video/h_891nanp00030/h_891nanp00030jp-1.jpg", 43 | "https://pics.dmm.co.jp/digital/video/h_891nanp00030/h_891nanp00030jp-2.jpg", 44 | "https://pics.dmm.co.jp/digital/video/h_891nanp00030/h_891nanp00030jp-3.jpg", 45 | "https://pics.dmm.co.jp/digital/video/h_891nanp00030/h_891nanp00030jp-4.jpg", 46 | "https://pics.dmm.co.jp/digital/video/h_891nanp00030/h_891nanp00030jp-5.jpg", 47 | "https://pics.dmm.co.jp/digital/video/h_891nanp00030/h_891nanp00030jp-6.jpg", 48 | "https://pics.dmm.co.jp/digital/video/h_891nanp00030/h_891nanp00030jp-7.jpg", 49 | "https://pics.dmm.co.jp/digital/video/h_891nanp00030/h_891nanp00030jp-8.jpg", 50 | "https://pics.dmm.co.jp/digital/video/h_891nanp00030/h_891nanp00030jp-9.jpg", 51 | "https://pics.dmm.co.jp/digital/video/h_891nanp00030/h_891nanp00030jp-10.jpg", 52 | "https://pics.dmm.co.jp/digital/video/h_891nanp00030/h_891nanp00030jp-11.jpg", 53 | "https://pics.dmm.co.jp/digital/video/h_891nanp00030/h_891nanp00030jp-12.jpg", 54 | "https://pics.dmm.co.jp/digital/video/h_891nanp00030/h_891nanp00030jp-13.jpg", 55 | "https://pics.dmm.co.jp/digital/video/h_891nanp00030/h_891nanp00030jp-14.jpg", 56 | "https://pics.dmm.co.jp/digital/video/h_891nanp00030/h_891nanp00030jp-15.jpg", 57 | "https://pics.dmm.co.jp/digital/video/h_891nanp00030/h_891nanp00030jp-16.jpg", 58 | "https://pics.dmm.co.jp/digital/video/h_891nanp00030/h_891nanp00030jp-17.jpg", 59 | "https://pics.dmm.co.jp/digital/video/h_891nanp00030/h_891nanp00030jp-18.jpg", 60 | "https://pics.dmm.co.jp/digital/video/h_891nanp00030/h_891nanp00030jp-19.jpg", 61 | "https://pics.dmm.co.jp/digital/video/h_891nanp00030/h_891nanp00030jp-20.jpg" 62 | ], 63 | "preview_video": null 64 | } -------------------------------------------------------------------------------- /unittest/data/OPCYN-174 (jav321).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "OPCYN-174", 3 | "cid": "opcyn174", 4 | "url": "https://en.jav321.com/video/opcyn174", 5 | "plot": "どこからどう見ても初心でセックスのセの字も知らなそうな美少女だが…実は淫乱ドスケベ!妹系の可愛らしい皮をかぶった痴女娘が小さなカラダでデカチンを貪りまくる!!カラダに合わせた小さなミニマ●コがデカチンを飲み込んで悲鳴をあげる!男を熱狂させる小柄な美少女が聖獣と化して乱れまくる姿は必見!!", 6 | "cover": "http://pics.dmm.co.jp/digital/amateur/opcyn174/opcyn174jp-001.jpg", 7 | "big_cover": null, 8 | "genre": [], 9 | "genre_id": [], 10 | "genre_norm": null, 11 | "score": null, 12 | "title": "いちか ", 13 | "ori_title": null, 14 | "magnet": null, 15 | "serial": null, 16 | "actress": [], 17 | "actress_pics": {}, 18 | "director": null, 19 | "duration": "71", 20 | "producer": null, 21 | "publisher": null, 22 | "uncensored": null, 23 | "publish_date": "2021-07-07", 24 | "preview_pics": [ 25 | "http://pics.dmm.co.jp/digital/amateur/opcyn174/opcyn174jp-002.jpg", 26 | "http://pics.dmm.co.jp/digital/amateur/opcyn174/opcyn174jp-003.jpg", 27 | "http://pics.dmm.co.jp/digital/amateur/opcyn174/opcyn174jp-004.jpg", 28 | "http://pics.dmm.co.jp/digital/amateur/opcyn174/opcyn174jp-005.jpg" 29 | ], 30 | "preview_video": null 31 | } -------------------------------------------------------------------------------- /unittest/data/RED-096 (airav).json: -------------------------------------------------------------------------------- 1 | { 2 | "注": "此文件用来测试找不到番号时的处理,故各字段故意为空", 3 | "dvdid": "RED-096", 4 | "cid": null, 5 | "url": null, 6 | "plot": null, 7 | "cover": null, 8 | "big_cover": null, 9 | "genre": null, 10 | "genre_id": null, 11 | "genre_norm": null, 12 | "score": null, 13 | "title": null, 14 | "ori_title": null, 15 | "magnet": null, 16 | "serial": null, 17 | "actress": null, 18 | "actress_pics": null, 19 | "director": null, 20 | "duration": null, 21 | "producer": null, 22 | "publisher": null, 23 | "uncensored": null, 24 | "publish_date": null, 25 | "preview_pics": null, 26 | "preview_video": null 27 | } -------------------------------------------------------------------------------- /unittest/data/SCUTE-1177 (jav321).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "SCUTE-1177", 3 | "cid": "scute1177", 4 | "url": "https://en.jav321.com/video/scute1177", 5 | "plot": null, 6 | "cover": "http://pics.dmm.co.jp/digital/amateur/scute1177/scute1177jp.jpg", 7 | "big_cover": null, 8 | "genre": [], 9 | "genre_id": [], 10 | "genre_norm": null, 11 | "score": "10.0", 12 | "title": "いちか ", 13 | "ori_title": null, 14 | "magnet": null, 15 | "serial": null, 16 | "actress": [], 17 | "actress_pics": {}, 18 | "director": null, 19 | "duration": "53", 20 | "producer": null, 21 | "publisher": null, 22 | "uncensored": null, 23 | "publish_date": "2021-12-11", 24 | "preview_pics": [], 25 | "preview_video": null 26 | } -------------------------------------------------------------------------------- /unittest/data/SHMO-031 (arzon_iv).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "SHMO-031", 3 | "cid": null, 4 | "url": "https://www.arzon.jp/image_1176276.html", 5 | "plot": "鈴木心春ヌードイメージ第2弾!今回は温泉大好き心春ちゃんが開放感あふれる露天風呂で美しい裸身を披露。心春ちゃんの激カワショットの連続で貴方を釘付け!セクシーポーズの数々を心行くまでご堪能ください。ヌードだけではなく水着姿やコスプレ姿も収録。見所盛りだくさんの内容でお届けいたします。AVでは決して見れない心春ちゃんのもうひとつの世界が今ここに!「Spring has come」も含めて是非ワンツーコンプリートしてください。※R-15", 6 | "cover": "https://img.arzon.jp/image/7/1176/1176276L.jpg", 7 | "big_cover": null, 8 | "genre": "", 9 | "genre_id": null, 10 | "genre_norm": null, 11 | "score": null, 12 | "title": "Spring has come 2 鈴木心春", 13 | "ori_title": null, 14 | "magnet": null, 15 | "serial": null, 16 | "actress": [ 17 | "鈴木心春" 18 | ], 19 | "actress_pics": null, 20 | "director": null, 21 | "duration": "90", 22 | "producer": "オルスタックソフト販売", 23 | "publisher": null, 24 | "uncensored": null, 25 | "publish_date": "2014-03-28", 26 | "preview_pics": null, 27 | "preview_video": null 28 | } -------------------------------------------------------------------------------- /unittest/data/SIRO-4718 (mgstage).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "SIRO-4718", 3 | "cid": null, 4 | "url": "https://www.mgstage.com/product/product_detail/SIRO-4718/", 5 | "plot": "プレイ内容:インタビュー、ベロキス、背後から乳房揉み~耳舐め、乳首弄り舐め、クリ擦り、指入れ、手マン大量潮吹き、男根弄り、濃厚フェラ、裏筋舐め、男の乳首舐め~手コキ、正常位挿入、騎乗位、立ちバック、寝バック、正常位、顔射、お掃除フェラ\nあらすじ:左手●指に指輪を光らせながらインタビューに答える現役美容師「ゆりのさん、33歳。」人妻でありながら性豪であることを自称し、旦那容認でセフレもいると語る彼女。敏感ボディを責め立てるとレンズにかかるほどの潮を撒き散らかし、待ちわびていたとばかりに男根に濃厚奉仕を繰り出して..", 6 | "cover": "https://image.mgstage.com/images/shirouto/siro/4718/pb_e_siro-4718.jpg", 7 | "big_cover": null, 8 | "genre": [ 9 | "独占配信", 10 | "配信専用", 11 | "素人", 12 | "初撮り", 13 | "フルハイビジョン(FHD)", 14 | "ハメ撮り", 15 | "人妻", 16 | "美尻", 17 | "三十路" 18 | ], 19 | "genre_id": null, 20 | "genre_norm": null, 21 | "score": "7.60", 22 | "title": "【初撮り】【奇跡の33歳】【美しい桃尻】類い稀なる美貌を携えたSEX大好き人妻美容師が登場。大股開きの騎乗位で結合部を露わにしながら快楽に美顔を蕩けさせて.. ネットでAV応募→AV体験撮影 1695", 23 | "ori_title": null, 24 | "magnet": null, 25 | "serial": "【初撮り】ネットでAV応募→AV体験撮影", 26 | "actress": [ 27 | "ゆりの 33歳 美容師" 28 | ], 29 | "actress_pics": null, 30 | "director": null, 31 | "duration": "65", 32 | "producer": "シロウトTV", 33 | "publisher": null, 34 | "uncensored": false, 35 | "publish_date": "2021-12-01", 36 | "preview_pics": [ 37 | "https://image.mgstage.com/images/shirouto/siro/4718/cap_e_0_siro-4718.jpg", 38 | "https://image.mgstage.com/images/shirouto/siro/4718/cap_e_1_siro-4718.jpg", 39 | "https://image.mgstage.com/images/shirouto/siro/4718/cap_e_2_siro-4718.jpg", 40 | "https://image.mgstage.com/images/shirouto/siro/4718/cap_e_3_siro-4718.jpg", 41 | "https://image.mgstage.com/images/shirouto/siro/4718/cap_e_4_siro-4718.jpg", 42 | "https://image.mgstage.com/images/shirouto/siro/4718/cap_e_5_siro-4718.jpg", 43 | "https://image.mgstage.com/images/shirouto/siro/4718/cap_e_6_siro-4718.jpg", 44 | "https://image.mgstage.com/images/shirouto/siro/4718/cap_e_7_siro-4718.jpg", 45 | "https://image.mgstage.com/images/shirouto/siro/4718/cap_e_8_siro-4718.jpg", 46 | "https://image.mgstage.com/images/shirouto/siro/4718/cap_e_9_siro-4718.jpg", 47 | "https://image.mgstage.com/images/shirouto/siro/4718/cap_e_10_siro-4718.jpg", 48 | "https://image.mgstage.com/images/shirouto/siro/4718/cap_e_11_siro-4718.jpg" 49 | ], 50 | "preview_video": "https://sample.mgstage.com/sample/shirouto/siro/4718/siro-4718_20211118T152801.mp4" 51 | } -------------------------------------------------------------------------------- /unittest/data/SKY-247 (jav321).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "SKY-247", 3 | "cid": "SKY-247", 4 | "url": "https://en.jav321.com/video/SKY-247", 5 | "plot": "もかと見せつける開帳オナニー!快感に耐え切れず激しい潮吹き連発!", 6 | "cover": null, 7 | "big_cover": null, 8 | "genre": [], 9 | "genre_id": [], 10 | "genre_norm": null, 11 | "score": null, 12 | "title": "ゴールドエンジェル 22 中野ありさ ", 13 | "ori_title": null, 14 | "magnet": null, 15 | "serial": null, 16 | "actress": [], 17 | "actress_pics": {}, 18 | "director": null, 19 | "duration": "109", 20 | "producer": "Tokyo Hot", 21 | "publisher": null, 22 | "uncensored": null, 23 | "publish_date": "2017-04-23", 24 | "preview_pics": null, 25 | "preview_video": "http://my.cdn.tokyo-hot.com/media/samples/SKY-247.mp4" 26 | } -------------------------------------------------------------------------------- /unittest/data/SKYHD-012 (airav).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "SKYHD-012", 3 | "cid": null, 4 | "url": "https://www.airav.wiki/video/SKYHD-012", 5 | "plot": null, 6 | "cover": "https://wiki-img.airav.wiki/storage/big_pic/88-07-0064.jpg", 7 | "big_cover": null, 8 | "genre": [ 9 | "中出", 10 | "巨乳", 11 | "無碼片", 12 | "美少女", 13 | "藍光" 14 | ], 15 | "genre_id": null, 16 | "genre_norm": null, 17 | "score": null, 18 | "title": "SKY ANGEL BLUE Vol.12 (藍光版)", 19 | "ori_title": null, 20 | "magnet": null, 21 | "serial": null, 22 | "actress": [ 23 | "櫻井莉亞" 24 | ], 25 | "actress_pics": null, 26 | "director": null, 27 | "duration": null, 28 | "producer": "SKY HIGH", 29 | "publisher": null, 30 | "uncensored": null, 31 | "publish_date": "2009-03-16", 32 | "preview_pics": [], 33 | "preview_video": null 34 | } -------------------------------------------------------------------------------- /unittest/data/SONE-273 (arzon).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "SONE-273", 3 | "cid": null, 4 | "url": "https://www.arzon.jp/item_1782451.html", 5 | "plot": "プロダクション、テレビ局、出版社、友人、飲食店、本人自宅やトイレまで!?関係各所が完全協力依頼!いたるところでズボッ!ズボッ!ズボッ!!今まで見れなかった“素”の反応を激写した、総撮影日数35日間!隠しカメラ20台以上の超本気ドッキリ即ハメ大計画!!撮影の裏側から私生活まで完全盗撮一部始終!隙あらばいきなり突撃!「ええええぇええ…もう~またですか…」あれれ??プライベートの方が濡れてない?!騙して!尾行して!盗撮して!ハメる!!愛ちゃんゴメンね!騙されて、ハメられて、超感じちゃう姿はエロくて最高だったよw", 6 | "cover": "https://img.arzon.jp/image/1/1782/1782451L.jpg", 7 | "big_cover": null, 8 | "genre": [ 9 | "BD-S1 NO.1 STYLE" 10 | ], 11 | "genre_id": null, 12 | "genre_norm": null, 13 | "score": null, 14 | "title": "「えっ!ここでヤルの?」 マルチに活躍する本郷愛のプライベートに完全密着して隙あらばいきなり即ズボッ!前代未聞ドッキリAV大作戦 本郷愛", 15 | "ori_title": null, 16 | "magnet": null, 17 | "serial": null, 18 | "actress": [ 19 | "本郷愛" 20 | ], 21 | "actress_pics": null, 22 | "director": "キョウセイ", 23 | "duration": "150", 24 | "producer": "S1(エスワン ナンバーワンスタイル)", 25 | "publisher": null, 26 | "uncensored": null, 27 | "publish_date": "2024-07-23", 28 | "preview_pics": [ 29 | "https://img.arzon.jp/image/1/1782/1782451P-01.jpg", 30 | "https://img.arzon.jp/image/1/1782/1782451P-02.jpg", 31 | "https://img.arzon.jp/image/1/1782/1782451P-03.jpg", 32 | "https://img.arzon.jp/image/1/1782/1782451P-04.jpg", 33 | "https://img.arzon.jp/image/1/1782/1782451P-05.jpg", 34 | "https://img.arzon.jp/image/1/1782/1782451P-06.jpg", 35 | "https://img.arzon.jp/image/1/1782/1782451P-07.jpg", 36 | "https://img.arzon.jp/image/1/1782/1782451P-08.jpg", 37 | "https://img.arzon.jp/image/1/1782/1782451P-09.jpg", 38 | "https://img.arzon.jp/image/1/1782/1782451P-10.jpg" 39 | ], 40 | "preview_video": null 41 | } -------------------------------------------------------------------------------- /unittest/data/SQTE-148 (airav).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "SQTE-148", 3 | "cid": null, 4 | "url": "https://www.airav.wiki/video/SQTE-148", 5 | "plot": "全都是可愛正妹的成人網站S-Cute來啦、這次從人氣排行榜中挑出前30名來獻給你!快來享受美少女們害羞姿態、看到興奮爽翻天吧!", 6 | "cover": "https://wiki-img.airav.wiki/storage/big_pic/177867.jpg", 7 | "big_cover": null, 8 | "genre": [ 9 | "超過4小時", 10 | "素人", 11 | "合輯、精選", 12 | "美少女", 13 | "迷你系" 14 | ], 15 | "genre_id": null, 16 | "genre_norm": null, 17 | "score": null, 18 | "title": "S-Cute 2016年銷售排行榜前30名 - 上", 19 | "ori_title": null, 20 | "magnet": null, 21 | "serial": null, 22 | "actress": [ 23 | "星野美優", 24 | "佳苗瑠華", 25 | "彩城友理奈", 26 | "西川結衣", 27 | "司美琴", 28 | "涼川絢音", 29 | "宮崎彩", 30 | "鈴原愛蜜莉", 31 | "美咲佳奈", 32 | "美波愛星", 33 | "稀夕蘭蘭", 34 | "花音詩織", 35 | "廣瀨海", 36 | "大野美鈴", 37 | "大島美緒", 38 | "椎名空", 39 | "向井藍", 40 | "上杉玲奈", 41 | "葵玲奈", 42 | "唯川千尋", 43 | "姬川優奈", 44 | "佐倉亞衣" 45 | ], 46 | "actress_pics": null, 47 | "director": null, 48 | "duration": null, 49 | "producer": "S-Cute", 50 | "publisher": null, 51 | "uncensored": null, 52 | "publish_date": "2016-12-01", 53 | "preview_pics": [ 54 | "https://wiki-img.airav.wiki/storage/sections/177867_001.jpg", 55 | "https://wiki-img.airav.wiki/storage/sections/177867_002.jpg", 56 | "https://wiki-img.airav.wiki/storage/sections/177867_003.jpg", 57 | "https://wiki-img.airav.wiki/storage/sections/177867_004.jpg", 58 | "https://wiki-img.airav.wiki/storage/sections/177867_005.jpg", 59 | "https://wiki-img.airav.wiki/storage/sections/177867_006.jpg", 60 | "https://wiki-img.airav.wiki/storage/sections/177867_007.jpg", 61 | "https://wiki-img.airav.wiki/storage/sections/177867_008.jpg", 62 | "https://wiki-img.airav.wiki/storage/sections/177867_009.jpg", 63 | "https://wiki-img.airav.wiki/storage/sections/177867_010.jpg", 64 | "https://wiki-img.airav.wiki/storage/sections/177867_011.jpg", 65 | "https://wiki-img.airav.wiki/storage/sections/177867_012.jpg", 66 | "https://wiki-img.airav.wiki/storage/sections/177867_013.jpg", 67 | "https://wiki-img.airav.wiki/storage/sections/177867_014.jpg", 68 | "https://wiki-img.airav.wiki/storage/sections/177867_015.jpg", 69 | "https://wiki-img.airav.wiki/storage/sections/177867_016.jpg", 70 | "https://wiki-img.airav.wiki/storage/sections/177867_017.jpg", 71 | "https://wiki-img.airav.wiki/storage/sections/177867_018.jpg", 72 | "https://wiki-img.airav.wiki/storage/sections/177867_019.jpg", 73 | "https://wiki-img.airav.wiki/storage/sections/177867_020.jpg" 74 | ], 75 | "preview_video": "http://freems.airav.cc/mv_download/A1EC6B065F22F9437E7A9DA14B10E0E9/61497429/mv/A595F9149551ED4830A11BF3E6F74BB0/61497429/0/36000/208383/normal.mp4" 76 | } -------------------------------------------------------------------------------- /unittest/data/SSIS-698 (avwiki).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "SSIS-698", 3 | "cid": null, 4 | "url": "https://av-wiki.net/SSIS-698", 5 | "plot": null, 6 | "cover": "https://pics.dmm.co.jp/digital/video/ssis00698/ssis00698pl.jpg", 7 | "big_cover": null, 8 | "genre": null, 9 | "genre_id": null, 10 | "genre_norm": null, 11 | "score": null, 12 | "title": "三上悠亜と新ありなと相沢みなみ", 13 | "ori_title": null, 14 | "magnet": null, 15 | "serial": null, 16 | "actress": [ 17 | "三上悠亜", 18 | "新ありな", 19 | "相沢みなみ" 20 | ], 21 | "actress_pics": null, 22 | "director": null, 23 | "duration": null, 24 | "producer": "エスワン ナンバーワンスタイル", 25 | "publisher": null, 26 | "uncensored": false, 27 | "publish_date": "2023-05-05", 28 | "preview_pics": null, 29 | "preview_video": null 30 | } -------------------------------------------------------------------------------- /unittest/data/SSNI-589 (javlib).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "SSNI-589", 3 | "cid": null, 4 | "url": "https://www.javlibrary.com/cn/?v=javli6igzq", 5 | "plot": null, 6 | "cover": "https://pics.dmm.co.jp/mono/movie/adult/ssni589/ssni589pl.jpg", 7 | "big_cover": null, 8 | "genre": [ 9 | "单体作品", 10 | "巨乳", 11 | "女上位", 12 | "荡妇", 13 | "艺人", 14 | "薄马赛克" 15 | ], 16 | "genre_id": null, 17 | "genre_norm": null, 18 | "score": "8.10", 19 | "title": "三上悠亜の全力イクイク騎乗位マニアックス", 20 | "ori_title": null, 21 | "magnet": null, 22 | "serial": null, 23 | "actress": [ 24 | "三上悠亜" 25 | ], 26 | "actress_pics": null, 27 | "director": "紋℃", 28 | "duration": "150", 29 | "producer": "S1 NO.1 STYLE", 30 | "publisher": "S1 NO.1 STYLE", 31 | "uncensored": null, 32 | "publish_date": "2019-10-19", 33 | "preview_pics": null, 34 | "preview_video": null 35 | } -------------------------------------------------------------------------------- /unittest/data/STAR-299 (airav).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "STAR-299", 3 | "cid": null, 4 | "url": "https://www.airav.wiki/video/STAR-299", 5 | "plot": "話題網路偶像「片桐衿里香」在人氣系列作中首次登場!毫無任何劇本的演出,與男優兩人獨處的空間之間,發掘出衿里香究極的淫蕩本性!", 6 | "cover": "https://wiki-img.airav.wiki/storage/big_pic/65649.jpg", 7 | "big_cover": null, 8 | "genre": [], 9 | "genre_id": null, 10 | "genre_norm": null, 11 | "score": null, 12 | "title": null, 13 | "ori_title": null, 14 | "magnet": null, 15 | "serial": null, 16 | "actress": [ 17 | "片桐衿里香" 18 | ], 19 | "actress_pics": null, 20 | "director": null, 21 | "duration": null, 22 | "producer": "SOD", 23 | "publisher": null, 24 | "uncensored": null, 25 | "publish_date": "2011-08-06", 26 | "preview_pics": [ 27 | "https://wiki-img.airav.wiki/storage/sections/65649_001.jpg", 28 | "https://wiki-img.airav.wiki/storage/sections/65649_002.jpg", 29 | "https://wiki-img.airav.wiki/storage/sections/65649_003.jpg", 30 | "https://wiki-img.airav.wiki/storage/sections/65649_004.jpg", 31 | "https://wiki-img.airav.wiki/storage/sections/65649_005.jpg", 32 | "https://wiki-img.airav.wiki/storage/sections/65649_006.jpg", 33 | "https://wiki-img.airav.wiki/storage/sections/65649_007.jpg", 34 | "https://wiki-img.airav.wiki/storage/sections/65649_008.jpg", 35 | "https://wiki-img.airav.wiki/storage/sections/65649_009.jpg", 36 | "https://wiki-img.airav.wiki/storage/sections/65649_010.jpg", 37 | "https://wiki-img.airav.wiki/storage/sections/65649_011.jpg", 38 | "https://wiki-img.airav.wiki/storage/sections/65649_012.jpg", 39 | "https://wiki-img.airav.wiki/storage/sections/65649_013.jpg", 40 | "https://wiki-img.airav.wiki/storage/sections/65649_014.jpg", 41 | "https://wiki-img.airav.wiki/storage/sections/65649_015.jpg", 42 | "https://wiki-img.airav.wiki/storage/sections/65649_016.jpg", 43 | "https://wiki-img.airav.wiki/storage/sections/65649_017.jpg", 44 | "https://wiki-img.airav.wiki/storage/sections/65649_018.jpg", 45 | "https://wiki-img.airav.wiki/storage/sections/65649_019.jpg" 46 | ], 47 | "preview_video": "http://freemsall.airav.cc/mp4vod_path/tBHq7-7elUJT8zBNeyxYXA/1652681726/0/3600/124352/normal.mp4" 48 | } -------------------------------------------------------------------------------- /unittest/data/STAR-676 (javbus).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "STAR-676", 3 | "cid": null, 4 | "url": "https://www.javbus.com/STAR-676", 5 | "plot": null, 6 | "cover": "https://www.javsee.men/pics/cover/5hhh_b.jpg", 7 | "big_cover": null, 8 | "genre": [ 9 | "中出", 10 | "單體作品", 11 | "強姦", 12 | "監禁", 13 | "已婚婦女", 14 | "性奴", 15 | "高畫質" 16 | ], 17 | "genre_id": null, 18 | "genre_norm": [ 19 | "中出", 20 | "单体作品", 21 | "强奸", 22 | "监禁", 23 | "已婚妇女", 24 | "性奴" 25 | ], 26 | "score": null, 27 | "title": "Iori Kogawa Married Woman Abducted & Raped - Breaking IN A Rich Young Wife While Her Husband's Away", 28 | "ori_title": null, 29 | "magnet": null, 30 | "serial": null, 31 | "actress": [ 32 | "古川いおり" 33 | ], 34 | "actress_pics": { 35 | "古川いおり": "https://www.javsee.men/pics/actress/9mi_a.jpg" 36 | }, 37 | "director": null, 38 | "duration": null, 39 | "producer": "SODクリエイト", 40 | "publisher": null, 41 | "uncensored": false, 42 | "publish_date": "2016-05-12", 43 | "preview_pics": [ 44 | "https://pics.dmm.co.jp/digital/video/1star00676/1star00676jp-1.jpg", 45 | "https://pics.dmm.co.jp/digital/video/1star00676/1star00676jp-2.jpg", 46 | "https://pics.dmm.co.jp/digital/video/1star00676/1star00676jp-3.jpg", 47 | "https://pics.dmm.co.jp/digital/video/1star00676/1star00676jp-4.jpg", 48 | "https://pics.dmm.co.jp/digital/video/1star00676/1star00676jp-5.jpg", 49 | "https://pics.dmm.co.jp/digital/video/1star00676/1star00676jp-6.jpg", 50 | "https://pics.dmm.co.jp/digital/video/1star00676/1star00676jp-7.jpg", 51 | "https://pics.dmm.co.jp/digital/video/1star00676/1star00676jp-8.jpg", 52 | "https://pics.dmm.co.jp/digital/video/1star00676/1star00676jp-9.jpg", 53 | "https://pics.dmm.co.jp/digital/video/1star00676/1star00676jp-10.jpg", 54 | "https://pics.dmm.co.jp/digital/video/1star00676/1star00676jp-11.jpg", 55 | "https://pics.dmm.co.jp/digital/video/1star00676/1star00676jp-12.jpg", 56 | "https://pics.dmm.co.jp/digital/video/1star00676/1star00676jp-13.jpg", 57 | "https://pics.dmm.co.jp/digital/video/1star00676/1star00676jp-14.jpg", 58 | "https://pics.dmm.co.jp/digital/video/1star00676/1star00676jp-15.jpg", 59 | "https://pics.dmm.co.jp/digital/video/1star00676/1star00676jp-16.jpg", 60 | "https://pics.dmm.co.jp/digital/video/1star00676/1star00676jp-17.jpg", 61 | "https://pics.dmm.co.jp/digital/video/1star00676/1star00676jp-18.jpg", 62 | "https://pics.dmm.co.jp/digital/video/1star00676/1star00676jp-19.jpg", 63 | "https://pics.dmm.co.jp/digital/video/1star00676/1star00676jp-20.jpg" 64 | ], 65 | "preview_video": null 66 | } -------------------------------------------------------------------------------- /unittest/data/STARS-213 (javlib).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "STARS-213", 3 | "cid": null, 4 | "url": "https://www.javlibrary.com/cn/?v=javli63cze", 5 | "plot": null, 6 | "cover": "https://pics.dmm.co.jp/mono/movie/adult/1stars213/1stars213pl.jpg", 7 | "big_cover": null, 8 | "genre": [ 9 | "单体作品", 10 | "首次亮相", 11 | "美少女", 12 | "潮吹" 13 | ], 14 | "genre_id": null, 15 | "genre_norm": null, 16 | "score": "7.50", 17 | "title": "朝比奈ななせ AV DEBUT", 18 | "ori_title": null, 19 | "magnet": null, 20 | "serial": null, 21 | "actress": [ 22 | "朝比奈ななせ" 23 | ], 24 | "actress_pics": null, 25 | "director": "キョウセイ", 26 | "duration": "185", 27 | "producer": "SOD Create", 28 | "publisher": null, 29 | "uncensored": null, 30 | "publish_date": "2020-03-12", 31 | "preview_pics": null, 32 | "preview_video": null 33 | } -------------------------------------------------------------------------------- /unittest/data/STARS-256 (javdb).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "STARS-256", 3 | "cid": null, 4 | "url": "https://javdb.com/v/eDZBr", 5 | "plot": null, 6 | "cover": "https://c0.jdbstatic.com/covers/ed/eDZBr.jpg", 7 | "big_cover": null, 8 | "genre": [ 9 | "淫亂真實", 10 | "單體作品", 11 | "平胸", 12 | "美少女電影", 13 | "無碼破解" 14 | ], 15 | "genre_id": null, 16 | "genre_norm": [ 17 | "淫乱?真实", 18 | "單體作品", 19 | "平胸", 20 | "美少女电影", 21 | "無碼破解" 22 | ], 23 | "score": "9.14", 24 | "title": "如同剛出生小鹿般雙腳顫抖 1日中狂抽猛插性交 永野一夏", 25 | "ori_title": "生まれたての子鹿の如く崩れ落とす 1日中超ピストン性交 永野いち夏", 26 | "magnet": [ 27 | "magnet:?xt=urn:btih:4641aa8de0a29d80b98111116806d82fac687562&dn=STARS-256-U.torrent.无码破解", 28 | "magnet:?xt=urn:btih:8de6747ebd3c772c2e582c2bb97440f74a1e37c0&dn=stars-256.torrent", 29 | "magnet:?xt=urn:btih:b826f69e327b65f8e036b9b2e34566a77ead9178&dn=STARS-256.mp4", 30 | "magnet:?xt=urn:btih:f33f2f712049dffc9564d7ebc85b426d874c3c5c&dn=STARS-256", 31 | "magnet:?xt=urn:btih:6faeb4e4119f11dcee3191b2dfa5d7df8304b982&dn=stars-256", 32 | "magnet:?xt=urn:btih:9463b6eb5d08c18c915acc971efd8c59e00411de&dn=STARS-256.HD", 33 | "magnet:?xt=urn:btih:3712dc3d34f6967af9862e007b610b147268fb82&dn=HD_stars-256", 34 | "magnet:?xt=urn:btih:2620e75b5b178838596332d099292afdbab72df9&dn=STARS256", 35 | "magnet:?xt=urn:btih:654aa99fe5f7207f91ed65e2367c6d0008b4bfcc&dn=STARS-256.mp4", 36 | "magnet:?xt=urn:btih:ba820a03dddebdf0ffbc7e55d22f6247756b44f4&dn=STARS-256 1 .mp4", 37 | "magnet:?xt=urn:btih:a1d73b741204ad7b84db7ee60a7a2219494c6f05&dn=STARS-256.mp4" 38 | ], 39 | "serial": "生まれたての子鹿の如く崩れ落とす1日中超ピストン性交", 40 | "actress": [ 41 | "永野いち夏" 42 | ], 43 | "actress_pics": null, 44 | "director": "トレンディ山口", 45 | "duration": "115", 46 | "producer": "SOD Create", 47 | "publisher": null, 48 | "uncensored": false, 49 | "publish_date": "2020-07-09", 50 | "preview_pics": [ 51 | "https://c0.jdbstatic.com/samples/ed/eDZBr_l_0.jpg", 52 | "https://c0.jdbstatic.com/samples/ed/eDZBr_l_1.jpg", 53 | "https://c0.jdbstatic.com/samples/ed/eDZBr_l_2.jpg", 54 | "https://c0.jdbstatic.com/samples/ed/eDZBr_l_3.jpg", 55 | "https://c0.jdbstatic.com/samples/ed/eDZBr_l_4.jpg", 56 | "https://c0.jdbstatic.com/samples/ed/eDZBr_l_5.jpg", 57 | "https://c0.jdbstatic.com/samples/ed/eDZBr_l_6.jpg", 58 | "https://c0.jdbstatic.com/samples/ed/eDZBr_l_7.jpg", 59 | "https://c0.jdbstatic.com/samples/ed/eDZBr_l_8.jpg", 60 | "https://c0.jdbstatic.com/samples/ed/eDZBr_l_9.jpg", 61 | "https://c0.jdbstatic.com/samples/ed/eDZBr_l_10.jpg", 62 | "https://c0.jdbstatic.com/samples/ed/eDZBr_l_11.jpg", 63 | "https://c0.jdbstatic.com/samples/ed/eDZBr_l_12.jpg", 64 | "https://c0.jdbstatic.com/samples/ed/eDZBr_l_13.jpg", 65 | "https://c0.jdbstatic.com/samples/ed/eDZBr_l_14.jpg", 66 | "https://c0.jdbstatic.com/samples/ed/eDZBr_l_15.jpg", 67 | "https://c0.jdbstatic.com/samples/ed/eDZBr_l_16.jpg", 68 | "https://c0.jdbstatic.com/samples/ed/eDZBr_l_17.jpg", 69 | "https://c0.jdbstatic.com/samples/ed/eDZBr_l_18.jpg" 70 | ], 71 | "preview_video": "https://cc3001.dmm.co.jp/litevideo/freepv/1/1st/1stars00256/1stars00256_dm_w.mp4" 72 | } -------------------------------------------------------------------------------- /unittest/data/d_aisoft3356 (fanza).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": null, 3 | "cid": "d_aisoft3356", 4 | "url": "https://www.dmm.co.jp/mono/doujin/-/detail/=/cid=d_aisoft3356/", 5 | "plot": "夏の田舎、蝉の音に混じり聞こえてくる元気な掛け声。\n暇を持て余し木陰で昼寝をしていたあなたは、姪のわかばとふたばにかくれんぼに誘われました。\nそして汗だくになって遊ぶ中、わかばと一緒に今は使われていない古民家へやって来ます。\n\nそこは隠れるには絶好の秘密の場所で……\n\nこれは背伸びしたい年頃のおませな双子姉妹と、\n少し歳の離れたおじさんとのちょっぴりエッチな夏の一齣です。\n\n・基本動画50本+差分+テキスト\n・動画解像度 960×600\n\n対応OS:WindowsVista / Windows7\n製品仕様:DVD-ROM1枚 DVD型トールケース", 6 | "cover": "https://pics.dmm.co.jp/mono/doujin/d_aisoft3356/d_aisoft3356pl.jpg", 7 | "big_cover": null, 8 | "genre": [ 9 | "3DCG", 10 | "動画・アニメーション", 11 | "音声付き", 12 | "オリジナル", 13 | "貧乳・微乳", 14 | "ミニ系", 15 | "サンプル動画" 16 | ], 17 | "genre_id": [ 18 | "12", 19 | "13", 20 | "15", 21 | "20", 22 | "2005", 23 | "2008", 24 | "6102" 25 | ], 26 | "genre_norm": null, 27 | "score": "10.00", 28 | "title": "夏のひめごと。", 29 | "ori_title": null, 30 | "magnet": null, 31 | "serial": null, 32 | "actress": null, 33 | "actress_pics": null, 34 | "director": null, 35 | "duration": null, 36 | "producer": null, 37 | "publisher": null, 38 | "uncensored": false, 39 | "publish_date": "2014-11-07", 40 | "preview_pics": [ 41 | "https://pics.dmm.co.jp/mono/doujin/d_aisoft3356/d_aisoft3356js-001.jpg", 42 | "https://pics.dmm.co.jp/mono/doujin/d_aisoft3356/d_aisoft3356js-002.jpg", 43 | "https://pics.dmm.co.jp/mono/doujin/d_aisoft3356/d_aisoft3356js-003.jpg", 44 | "https://pics.dmm.co.jp/mono/doujin/d_aisoft3356/d_aisoft3356js-004.jpg" 45 | ], 46 | "preview_video": null 47 | } -------------------------------------------------------------------------------- /unittest/data/hjmo00214 (fanza).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": null, 3 | "cid": "hjmo00214", 4 | "url": "https://www.dmm.co.jp/digital/videoa/-/detail/=/cid=hjmo00214/", 5 | "plot": "はじめ企画もついに7周年と言う事で、はじめ企画の大人気シリーズ・彼チン新作の登場です!!愛する彼氏のチンコを5本の中から当てることが出来たら賞金!!外れると大好きな彼氏の目の前で残酷な罰ゲームが!!チンコだけを頼りに必死になって探す彼女達、意外と分からない様で苦戦してます!!", 6 | "cover": "https://pics.dmm.co.jp/digital/video/hjmo00214/hjmo00214pl.jpg", 7 | "big_cover": null, 8 | "genre": [ 9 | "ハイビジョン", 10 | "寝取り・寝取られ・NTR", 11 | "素人", 12 | "企画", 13 | "デジモ", 14 | "独占配信", 15 | "羞恥" 16 | ], 17 | "genre_id": [ 18 | "6533", 19 | "4111", 20 | "4024", 21 | "4007", 22 | "6004", 23 | "6548", 24 | "28" 25 | ], 26 | "genre_norm": null, 27 | "score": "8.00", 28 | "title": "彼女なら!彼氏のち○ぽ当ててみろ!! 11", 29 | "ori_title": null, 30 | "magnet": null, 31 | "serial": "彼女なら!彼氏のち○ぽ当ててみろ!!", 32 | "actress": [], 33 | "actress_pics": null, 34 | "director": "はじめ", 35 | "duration": "235", 36 | "producer": "はじめ企画", 37 | "publisher": null, 38 | "uncensored": false, 39 | "publish_date": "2011-12-08", 40 | "preview_pics": [ 41 | "https://pics.dmm.co.jp/digital/video/hjmo00214/hjmo00214-1.jpg", 42 | "https://pics.dmm.co.jp/digital/video/hjmo00214/hjmo00214-2.jpg", 43 | "https://pics.dmm.co.jp/digital/video/hjmo00214/hjmo00214-3.jpg", 44 | "https://pics.dmm.co.jp/digital/video/hjmo00214/hjmo00214-4.jpg", 45 | "https://pics.dmm.co.jp/digital/video/hjmo00214/hjmo00214-5.jpg", 46 | "https://pics.dmm.co.jp/digital/video/hjmo00214/hjmo00214-6.jpg", 47 | "https://pics.dmm.co.jp/digital/video/hjmo00214/hjmo00214-7.jpg", 48 | "https://pics.dmm.co.jp/digital/video/hjmo00214/hjmo00214-8.jpg", 49 | "https://pics.dmm.co.jp/digital/video/hjmo00214/hjmo00214-9.jpg", 50 | "https://pics.dmm.co.jp/digital/video/hjmo00214/hjmo00214-10.jpg" 51 | ], 52 | "preview_video": "https://cc3001.dmm.co.jp/litevideo/freepv/h/hjm/hjmo00214/hjmo00214_dmb_s.mp4" 53 | } -------------------------------------------------------------------------------- /unittest/data/ipx-001 (javlib).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": "IPX-001", 3 | "cid": null, 4 | "url": "https://www.javlibrary.com/cn/?v=javliktmzq", 5 | "plot": null, 6 | "cover": "https://pics.dmm.co.jp/mono/movie/adult/ipx001/ipx001pl.jpg", 7 | "big_cover": null, 8 | "genre": [ 9 | "单体作品", 10 | "高中女生", 11 | "巨乳", 12 | "第一人称摄影", 13 | "美少女", 14 | "深喉", 15 | "数位马赛克" 16 | ], 17 | "genre_id": null, 18 | "genre_norm": null, 19 | "score": "6.30", 20 | "title": "女子校生便所交際 便所でしか濡れないおじさん好きの変態マゾ美少女と思う存分便所ファック! 妃月るい", 21 | "ori_title": null, 22 | "magnet": null, 23 | "serial": null, 24 | "actress": [ 25 | "音琴るい" 26 | ], 27 | "actress_pics": null, 28 | "director": "刃修羅", 29 | "duration": "160", 30 | "producer": "IDEA POCKET", 31 | "publisher": "ティッシュ", 32 | "uncensored": null, 33 | "publish_date": "2017-08-11", 34 | "preview_pics": null, 35 | "preview_video": null 36 | } -------------------------------------------------------------------------------- /unittest/data/midv-001 (javdb).json: -------------------------------------------------------------------------------- 1 | { 2 | "注": "此文件用来测试多影片同番号的处理,各字段故意为空", 3 | "dvdid": "midv-001", 4 | "cid": null, 5 | "url": null, 6 | "plot": null, 7 | "cover": null, 8 | "big_cover": null, 9 | "genre": null, 10 | "genre_id": null, 11 | "genre_norm": null, 12 | "score": null, 13 | "title": null, 14 | "ori_title": null, 15 | "magnet": null, 16 | "serial": null, 17 | "actress": null, 18 | "actress_pics": null, 19 | "director": null, 20 | "duration": null, 21 | "producer": null, 22 | "publisher": null, 23 | "uncensored": null, 24 | "publish_date": null, 25 | "preview_pics": null, 26 | "preview_video": null 27 | } -------------------------------------------------------------------------------- /unittest/data/midv-001 (javlib).json: -------------------------------------------------------------------------------- 1 | { 2 | "注": "此文件用来测试多影片同番号的处理,各字段故意为空", 3 | "dvdid": "midv-001", 4 | "cid": null, 5 | "url": null, 6 | "plot": null, 7 | "cover": null, 8 | "big_cover": null, 9 | "genre": null, 10 | "genre_id": null, 11 | "genre_norm": null, 12 | "score": null, 13 | "title": null, 14 | "ori_title": null, 15 | "magnet": null, 16 | "serial": null, 17 | "actress": null, 18 | "actress_pics": null, 19 | "director": null, 20 | "duration": null, 21 | "producer": null, 22 | "publisher": null, 23 | "uncensored": null, 24 | "publish_date": null, 25 | "preview_pics": null, 26 | "preview_video": null 27 | } -------------------------------------------------------------------------------- /unittest/data/parathd03639 (fanza).json: -------------------------------------------------------------------------------- 1 | { 2 | "作为此类影片的代表": "https://www.dmm.co.jp/monthly/paradisetv/-/detail/=/cid=parathd03639/", 3 | "dvdid": null, 4 | "cid": "parathd03639", 5 | "url": "https://www.dmm.co.jp/digital/videoa/-/detail/=/cid=parathd03639/", 6 | "plot": "★推定Gカップの巨乳の奥さん、推定Hカップの長女、推定Hカップの次女が働いている家族経営のコンビニでバイトする俺が3人ともモノにする物語!3人の巨乳とSEXだ!◆推定Hカップの長女さくら(25)。胸の盛り上がる服に思わず手が出てしまった!バイト終わりのバックヤードで制服姿の長女とおっぱいを揺らしてSEX!◆推定Hカップの次女ねる(20)。バイト終わりに制服から私服へ着替えていた。我慢できず自分の住むアパートに誘って次女とヤる!Hカップ爆乳にパイズリしてもらい昇天寸前!◆推定Gカップの奥さん(45)。夫である店長が夜勤で不在。自宅を訪ねて奥さんともSEX!", 7 | "cover": "https://pics.dmm.co.jp/digital/video/parathd03639/parathd03639pl.jpg", 8 | "big_cover": null, 9 | "genre": [ 10 | "中出し", 11 | "ドラマ", 12 | "巨乳", 13 | "職業色々", 14 | "ハイビジョン" 15 | ], 16 | "genre_id": [ 17 | "5001", 18 | "4114", 19 | "2001", 20 | "1026", 21 | "6533" 22 | ], 23 | "genre_norm": null, 24 | "score": "10.00", 25 | "title": "【連続スケベ小説 総集編】コンビニで働く巨乳母娘3人と中●しSEXしちゃった俺", 26 | "ori_title": null, 27 | "magnet": null, 28 | "serial": "連続スケベ小説", 29 | "actress": [], 30 | "actress_pics": null, 31 | "director": null, 32 | "duration": "115", 33 | "producer": "パラダイステレビ", 34 | "publisher": null, 35 | "uncensored": false, 36 | "publish_date": "2023-01-05", 37 | "preview_pics": [ 38 | "https://pics.dmm.co.jp/digital/video/parathd03639/parathd03639-1.jpg", 39 | "https://pics.dmm.co.jp/digital/video/parathd03639/parathd03639-2.jpg", 40 | "https://pics.dmm.co.jp/digital/video/parathd03639/parathd03639-3.jpg", 41 | "https://pics.dmm.co.jp/digital/video/parathd03639/parathd03639-4.jpg", 42 | "https://pics.dmm.co.jp/digital/video/parathd03639/parathd03639-5.jpg", 43 | "https://pics.dmm.co.jp/digital/video/parathd03639/parathd03639-6.jpg", 44 | "https://pics.dmm.co.jp/digital/video/parathd03639/parathd03639-7.jpg", 45 | "https://pics.dmm.co.jp/digital/video/parathd03639/parathd03639-8.jpg", 46 | "https://pics.dmm.co.jp/digital/video/parathd03639/parathd03639-9.jpg", 47 | "https://pics.dmm.co.jp/digital/video/parathd03639/parathd03639-10.jpg", 48 | "https://pics.dmm.co.jp/digital/video/parathd03639/parathd03639-11.jpg", 49 | "https://pics.dmm.co.jp/digital/video/parathd03639/parathd03639-12.jpg", 50 | "https://pics.dmm.co.jp/digital/video/parathd03639/parathd03639-13.jpg", 51 | "https://pics.dmm.co.jp/digital/video/parathd03639/parathd03639-14.jpg", 52 | "https://pics.dmm.co.jp/digital/video/parathd03639/parathd03639-15.jpg", 53 | "https://pics.dmm.co.jp/digital/video/parathd03639/parathd03639-16.jpg", 54 | "https://pics.dmm.co.jp/digital/video/parathd03639/parathd03639-17.jpg", 55 | "https://pics.dmm.co.jp/digital/video/parathd03639/parathd03639-18.jpg", 56 | "https://pics.dmm.co.jp/digital/video/parathd03639/parathd03639-19.jpg", 57 | "https://pics.dmm.co.jp/digital/video/parathd03639/parathd03639-20.jpg" 58 | ], 59 | "preview_video": "https://cc3001.dmm.co.jp/litevideo/freepv/p/par/parathd03639/parathd03639_mhb_w.mp4" 60 | } -------------------------------------------------------------------------------- /unittest/data/sqte00300 (fanza).json: -------------------------------------------------------------------------------- 1 | { 2 | "dvdid": null, 3 | "cid": "sqte00300", 4 | "url": "https://www.dmm.co.jp/digital/videoa/-/detail/=/cid=sqte00300/", 5 | "plot": "女の子の自然なエロさを追求するアダルトサイトS-Cute。モデルみたいにスレンダーでなおかつお尻もオッパイも実に美味しそう!ちょっと恥ずかしがりな女の子だけど、脱いだらエロ美しい体をクネクネやらしくよがらせて感じまくる!ハメ潮まで吹いちゃうヘンタイっぷりに腰が止まりません!", 6 | "cover": "https://pics.dmm.co.jp/digital/video/sqte00300/sqte00300pl.jpg", 7 | "big_cover": null, 8 | "genre": [ 9 | "ハイビジョン", 10 | "美少女", 11 | "恋愛", 12 | "スレンダー", 13 | "騎乗位", 14 | "フェラ" 15 | ], 16 | "genre_id": [ 17 | "6533", 18 | "1027", 19 | "555", 20 | "2006", 21 | "4106", 22 | "5002" 23 | ], 24 | "genre_norm": null, 25 | "score": "8.00", 26 | "title": "おしゃべりは得意じゃないけど、脱いだらエロいって褒められます。", 27 | "ori_title": null, 28 | "magnet": null, 29 | "serial": null, 30 | "actress": [ 31 | "笠木いちか", 32 | "月乃ルナ" 33 | ], 34 | "actress_pics": null, 35 | "director": null, 36 | "duration": "196", 37 | "producer": "S-Cute", 38 | "publisher": null, 39 | "uncensored": false, 40 | "publish_date": "2020-05-10", 41 | "preview_pics": [ 42 | "https://pics.dmm.co.jp/digital/video/sqte00300/sqte00300-1.jpg", 43 | "https://pics.dmm.co.jp/digital/video/sqte00300/sqte00300-2.jpg", 44 | "https://pics.dmm.co.jp/digital/video/sqte00300/sqte00300-3.jpg", 45 | "https://pics.dmm.co.jp/digital/video/sqte00300/sqte00300-4.jpg", 46 | "https://pics.dmm.co.jp/digital/video/sqte00300/sqte00300-5.jpg", 47 | "https://pics.dmm.co.jp/digital/video/sqte00300/sqte00300-6.jpg", 48 | "https://pics.dmm.co.jp/digital/video/sqte00300/sqte00300-7.jpg", 49 | "https://pics.dmm.co.jp/digital/video/sqte00300/sqte00300-8.jpg", 50 | "https://pics.dmm.co.jp/digital/video/sqte00300/sqte00300-9.jpg", 51 | "https://pics.dmm.co.jp/digital/video/sqte00300/sqte00300-10.jpg", 52 | "https://pics.dmm.co.jp/digital/video/sqte00300/sqte00300-11.jpg", 53 | "https://pics.dmm.co.jp/digital/video/sqte00300/sqte00300-12.jpg", 54 | "https://pics.dmm.co.jp/digital/video/sqte00300/sqte00300-13.jpg", 55 | "https://pics.dmm.co.jp/digital/video/sqte00300/sqte00300-14.jpg", 56 | "https://pics.dmm.co.jp/digital/video/sqte00300/sqte00300-15.jpg", 57 | "https://pics.dmm.co.jp/digital/video/sqte00300/sqte00300-16.jpg", 58 | "https://pics.dmm.co.jp/digital/video/sqte00300/sqte00300-17.jpg", 59 | "https://pics.dmm.co.jp/digital/video/sqte00300/sqte00300-18.jpg", 60 | "https://pics.dmm.co.jp/digital/video/sqte00300/sqte00300-19.jpg", 61 | "https://pics.dmm.co.jp/digital/video/sqte00300/sqte00300-20.jpg" 62 | ], 63 | "preview_video": "https://cc3001.dmm.co.jp/litevideo/freepv/s/sqt/sqte00300/sqte00300_mhb_w.mp4" 64 | } -------------------------------------------------------------------------------- /unittest/test_avid.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import uuid 4 | import pytest 5 | from shutil import rmtree 6 | 7 | file_dir = os.path.dirname(__file__) 8 | sys.path.insert(0, os.path.abspath(os.path.join(file_dir, '..'))) 9 | from javsp.avid import get_id, get_cid 10 | 11 | 12 | @pytest.fixture 13 | def prepare_files(files): 14 | """按照指定的文件列表创建对应的文件,并在测试完成后删除它们 15 | 16 | Args: 17 | files (list of tuple): 文件列表,仅接受相对路径 18 | """ 19 | tmp_folder = 'tmp_' + uuid.uuid4().hex[:8] 20 | for i in files: 21 | path = os.path.join(tmp_folder, i) 22 | folder = os.path.split(path)[0] 23 | if folder and (not os.path.exists(folder)): 24 | os.makedirs(folder) 25 | with open(path, 'wt', encoding='utf-8') as f: 26 | f.write(path) 27 | yield 28 | rmtree(tmp_folder) 29 | return 30 | 31 | 32 | def test_fc2(): 33 | assert 'FC2-123456' == get_id('(2017) [FC2-123456] 【個人撮影】') 34 | assert 'FC2-123456' == get_id('fc2-ppv-123456-1.delogo.mp4') 35 | assert 'FC2-123456' == get_id('FC2-PPV-123456.mp4') 36 | assert 'FC2-123456' == get_id('FC2PPV-123456 Yuukiy') 37 | assert 'FC2-1234567' == get_id('fc2-ppv_1234567-2.mp4') 38 | 39 | 40 | def test_normal(): 41 | assert '' == get_id('Yuukiy') 42 | assert 'ABC-12' == get_id('ABC-12_01.mkv') 43 | assert 'ABC-123' == get_id('Sky Angel Vol.6 月丘うさぎ(ABC-123).avi') 44 | assert 'ABCD-123' == get_id('ABCD-123.mp4') 45 | 46 | 47 | def test_cid_valid(): 48 | assert 'ab012st' == get_cid('ab012st') 49 | assert 'ab012st' == get_cid('ab012st.mp4') 50 | assert '123_0456' == get_cid('123_0456.mp4') 51 | assert '123abc00045' == get_cid('123abc00045.mp4') 52 | assert '403abcd56789' == get_cid('403abcd56789_1') 53 | assert 'h_001abc00001' == get_cid('h_001abc00001.mp4') 54 | assert '1234wvr00001rp' == get_cid('1234wvr00001rp.mp4') 55 | assert '402abc_hello000089' == get_cid('402abc_hello000089.mp4') 56 | assert 'h_826zizd021' == get_cid('h_826zizd021.mp4') 57 | assert '403abcd56789' == get_cid('403abcd56789cd1.mp4') 58 | 59 | 60 | def test_from_file(): 61 | # 用来控制是否将转换结果覆盖原文件(便于检查所有失败的条目) 62 | write_back = False 63 | rewrite_lines = [] 64 | 65 | datafile = os.path.join(file_dir, 'testdata_avid.txt') 66 | with open(datafile, 'rt', encoding='utf-8') as f: 67 | lines = f.readlines() 68 | for line_no, line in enumerate(lines, start=1): 69 | items = line.strip('\r\n').split('\t') 70 | if len(items) == 2: 71 | (filename, avid), ignore = items, False 72 | else: 73 | filename, avid, ignore = items 74 | guess_id = get_id(filename) 75 | if write_back: 76 | rewrite_lines.append(f'{filename}\t{guess_id}\n') 77 | continue 78 | if guess_id != avid: 79 | if ignore: 80 | print(f"Ignored: {guess_id} != {avid}\t'{filename}'") 81 | else: 82 | assert guess_id == avid.upper(), f'AV ID not match at line {line_no}' 83 | if write_back: 84 | with open(datafile, 'wt', encoding='utf-8') as f: 85 | f.writelines(rewrite_lines) 86 | 87 | 88 | def test_cid_invalid(): 89 | assert '' == get_cid('hasUpperletter.mp4') 90 | assert '' == get_cid('存在非ASCII字符.mp4') 91 | assert '' == get_cid('has-dash.mp4') 92 | assert '' == get_cid('403_abcd56789_fgh') 93 | assert '' == get_cid('many_parts1234-12.mp4') 94 | assert '' == get_cid('abc12.mp4') 95 | assert '' == get_cid('ab012st/仅文件夹名称为cid.mp4') 96 | assert '' == get_cid('123_0456st.mp4') 97 | 98 | 99 | @pytest.mark.parametrize('files', [('Unknown.mp4',)]) 100 | def test_by_folder_name1(prepare_files): 101 | assert '' == get_id('Unknown.mp4') 102 | 103 | 104 | @pytest.mark.parametrize('files', [('FC2-123456/Unknown.mp4',)]) 105 | def test_by_folder_name2(prepare_files): 106 | assert 'FC2-123456' == get_id('FC2-123456/Unknown.mp4') 107 | 108 | 109 | @pytest.mark.parametrize('files', [('ABC-123/CDF-456.mp4',)]) 110 | def test_by_folder_name3(prepare_files): 111 | assert 'CDF-456' == get_id('ABC-123/CDF-456.mp4') 112 | -------------------------------------------------------------------------------- /unittest/test_crawlers.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import logging 4 | import requests 5 | from urllib.parse import urlsplit 6 | 7 | 8 | file_dir = os.path.dirname(__file__) 9 | data_dir = os.path.join(file_dir, 'data') 10 | sys.path.insert(0, os.path.abspath(os.path.join(file_dir, '..'))) 11 | 12 | from javsp.datatype import MovieInfo 13 | from javsp.web.exceptions import CrawlerError, SiteBlocked 14 | 15 | 16 | logger = logging.getLogger(__name__) 17 | 18 | 19 | def test_crawler(crawler_params): 20 | """包装函数,便于通过参数判断测试用例生成,以及负责将参数解包后进行实际调用""" 21 | # crawler_params: ('ABC-123', 'javlib', 'path_to_local_json') 22 | # TODO: 在Github actions环境中总是无法通过Cloudflare的检测,因此暂时忽略需要过验证站点的失败项 23 | try: 24 | site, params = crawler_params[1], crawler_params[:2] 25 | compare(*crawler_params) 26 | except requests.exceptions.ReadTimeout: 27 | logger.warning(f"{site} 连接超时: {params}") 28 | except Exception as e: 29 | if os.getenv('GITHUB_ACTIONS') and (site in ['javdb', 'javlib', 'airav']): 30 | logger.debug(f'检测到Github actions环境,已忽略测试失败项: {params}', exc_info=True) 31 | else: 32 | raise 33 | 34 | def compare(avid, scraper, file): 35 | """从本地的数据文件生成Movie实例,并与在线抓取到的数据进行比较""" 36 | local = MovieInfo(from_file=file) 37 | if scraper != 'fanza': 38 | online = MovieInfo(avid) 39 | else: 40 | online = MovieInfo(cid=avid) 41 | # 导入抓取器模块 42 | scraper_mod = 'javsp.web.' + scraper 43 | __import__(scraper_mod) 44 | mod = sys.modules[scraper_mod] 45 | if hasattr(mod, 'parse_clean_data'): 46 | parse_data = getattr(mod, 'parse_clean_data') 47 | else: 48 | parse_data = getattr(mod, 'parse_data') 49 | 50 | try: 51 | parse_data(online) 52 | except SiteBlocked as e: 53 | logger.warning(e) 54 | return 55 | except (CrawlerError, requests.exceptions.ReadTimeout) as e: 56 | logger.info(e) 57 | 58 | try: 59 | # 解包数据再进行比较,以便测试不通过时快速定位不相等的键值 60 | local_vars = vars(local) 61 | online_vars = vars(online) 62 | for k, v in online_vars.items(): 63 | # 部分字段可能随时间变化,因此只要这些字段不是一方有值一方无值就行 64 | if k in ['score', 'magnet']: 65 | assert bool(v) == bool(local_vars.get(k, None)) 66 | elif k == 'preview_video' and scraper in ['airav', 'javdb']: 67 | assert bool(v) == bool(local_vars.get(k, None)) 68 | # JavBus采用免代理域名时图片地址也会是免代理域名,因此只比较path部分即可 69 | elif k == 'cover' and scraper == 'javbus': 70 | assert urlsplit(v).path == urlsplit(local_vars.get(k, None)).path 71 | elif k == 'actress_pics' and scraper == 'javbus': 72 | local_tmp = online_tmp = {} 73 | local_pics = local_vars.get(k) 74 | if local_pics: 75 | local_tmp = {name: urlsplit(url).path for name, url in local_pics.items()} 76 | if v: 77 | online_tmp = {name: urlsplit(url).path for name, url in v.items()} 78 | assert local_tmp == online_tmp 79 | elif k == 'preview_pics' and scraper == 'javbus': 80 | local_pics = local_vars.get(k) 81 | if local_pics: 82 | local_tmp = [urlsplit(i).path for i in local_pics] 83 | if v: 84 | online_tmp = [urlsplit(i).path for i in v] 85 | assert local_tmp == online_tmp 86 | # 对顺序没有要求的list型字段,比较时也应该忽略顺序信息 87 | elif k in ['genre', 'genre_id', 'genre_norm', 'actress']: 88 | if isinstance(v, list): 89 | loc_v = local_vars.get(k) 90 | if loc_v is None: 91 | loc_v = [] 92 | assert sorted(v) == sorted(loc_v) 93 | else: 94 | assert v == local_vars.get(k, None) 95 | else: 96 | assert v == local_vars.get(k, None) 97 | except AssertionError: 98 | # 本地运行时更新已有的测试数据,方便利用版本控制系统检查差异项 99 | if not os.getenv('GITHUB_ACTIONS'): 100 | online.dump(file) 101 | raise 102 | except Exception as e: 103 | logger.error(e) 104 | raise 105 | -------------------------------------------------------------------------------- /unittest/test_exe.py: -------------------------------------------------------------------------------- 1 | import os 2 | import random 3 | import string 4 | import shutil 5 | import subprocess 6 | from glob import glob 7 | 8 | 9 | def test_javsp_exe(): 10 | cwd = os.getcwd() 11 | dist_dir = os.path.normpath(os.path.join(os.path.dirname(__file__) + '/../dist')) 12 | os.chdir(dist_dir) 13 | 14 | size = 300 * 2**20 15 | tmp_folder = '.TMP_' + ''.join(random.choices(string.ascii_uppercase, k=6)) 16 | FILE = '300MAAN-642.RIP.f4v' 17 | try: 18 | os.system(f"fsutil file createnew {FILE} {size}") 19 | r = subprocess.run(f"JavSP.exe --auto-exit --input . --output {tmp_folder}".split(), capture_output=True, encoding='utf-8') 20 | print(r.stdout, r.stderr.encode().decode("unicode_escape"), sep='\n') 21 | r.check_returncode() 22 | # Check generated files 23 | files = glob(tmp_folder + '/**/*.*', recursive=True) 24 | print('\n'.join(files)) 25 | # assert all('横宮七海' in i for i in files), "Actress name not found" 26 | assert any(i.endswith('fanart.jpg') for i in files), "fanart not found" 27 | assert any(i.endswith('poster.jpg') for i in files), "poster not found" 28 | assert any(i.endswith('.f4v') for i in files), "video file not found" 29 | assert any(i.endswith('.nfo') for i in files), "nfo file not found" 30 | finally: 31 | if os.path.exists(FILE): 32 | os.remove(FILE) 33 | if os.path.exists(tmp_folder): 34 | shutil.rmtree(tmp_folder) 35 | os.chdir(cwd) 36 | -------------------------------------------------------------------------------- /unittest/test_func.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import random 4 | 5 | sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) 6 | from javsp.func import * 7 | 8 | 9 | def test_remove_trail_actor_in_title(): 10 | run = remove_trail_actor_in_title 11 | delimiters = list('-xX &·,; &・,;') 12 | title1 = '东风夜放花千树,更吹落、星如雨。' 13 | title2 = '辛弃疾 ' + title1 14 | names = ['辛弃疾', '牛顿', '爱因斯坦', '阿基米德', '伽利略'] 15 | 16 | def combine(items): 17 | sep = random.choice(delimiters) 18 | new_str = sep.join(items) 19 | print(new_str) 20 | return new_str 21 | 22 | # 定义测试用例 23 | assert title1 == run(combine([title1, '辛弃疾']), names) 24 | assert title1 == run(combine([title1] + names), names) 25 | assert title1 == run(combine([title1, '辛弃疾']), names) 26 | assert title2 == run(combine([title2] + names), names) 27 | -------------------------------------------------------------------------------- /unittest/test_lib.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) 5 | from javsp.lib import * 6 | 7 | 8 | def test_detect_special_attr(): 9 | run = detect_special_attr 10 | 11 | # 定义测试用例 12 | assert run('STARS-225_UNCENSORED_LEAKED.mp4') == 'U' 13 | assert run('STARS-225_UNCENSORED_LEAKED-C.mp4') == 'UC' 14 | assert run('STARS-225_无码.mp4') == '' 15 | assert run('STARS-225_无码流出.mp4') == 'U' 16 | assert run('STARS-225_无码破解.mp4') == 'U' 17 | assert run('STARS-225_UNCEN.mp4') == 'U' 18 | assert run('STARS-225_UNCEN-C.mp4') == 'UC' 19 | assert run('STARS-225u.mp4', 'STARS-225') == 'U' 20 | assert run('STARS-225C.mp4', 'STARS-225') == 'C' 21 | assert run('STARS-225uC.mp4', 'STARS-225') == 'UC' 22 | assert run('STARS225u.mp4', 'STARS-225') == 'U' 23 | assert run('STARS225C.mp4', 'STARS-225') == 'C' 24 | assert run('STARS225uC.mp4', 'STARS-225') == 'UC' 25 | assert run('STARS-225CD1.mp4', 'STARS-225') == '' 26 | assert run('stars225cd2.mp4', 'STARS-225') == '' 27 | -------------------------------------------------------------------------------- /unittest/test_proxyfree.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) 5 | from javsp.web.proxyfree import * 6 | 7 | 8 | def test_get_url(): 9 | assert get_proxy_free_url('javlib') != '' 10 | assert get_proxy_free_url('javdb') != '' 11 | 12 | 13 | def test_get_url_with_prefer(): 14 | prefer_url = 'https://www.baidu.com' 15 | assert prefer_url == get_proxy_free_url('javlib', prefer_url) 16 | 17 | if __name__ == "__main__": 18 | print(get_proxy_free_url('javlib')) 19 | --------------------------------------------------------------------------------